@cassius0924/dsh-usage-dashboard 0.7.0 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -26,6 +26,7 @@ __export(index_exports, {
26
26
  name: () => name
27
27
  });
28
28
  module.exports = __toCommonJS(index_exports);
29
+ var import_react8 = require("react");
29
30
 
30
31
  // src/client/boundary.tsx
31
32
  var import_react = require("react");
@@ -317,7 +318,7 @@ var zh = {
317
318
  "delta.noBaseline": "\u65E0\u5BF9\u6BD4",
318
319
  "delta.flat": "\u6301\u5E73",
319
320
  "period.volume": "{tokens} tokens \xB7 {calls}",
320
- "budget.unset": "\u8FD8\u6CA1\u8BBE\u7F6E\u6708\u5EA6\u9884\u7B97\uFF0C\u65E0\u6CD5\u63D0\u524D\u5224\u65AD\u662F\u5426\u4F1A\u8D85\u652F\u3002",
321
+ "budget.unset": "\u672A\u8BBE\u7F6E\u6708\u5EA6\u9884\u7B97\u3002",
321
322
  "budget.configure": "\u8BBE\u7F6E\u9884\u7B97",
322
323
  "budget.over": "\u5DF2\u8D85\u9884\u7B97 \xA5{amount}",
323
324
  "budget.forecastOver": "\u7167\u5F53\u524D\u901F\u5EA6\uFF0C\u9884\u8BA1\u8D85\u51FA \xA5{amount}",
@@ -340,7 +341,6 @@ var zh = {
340
341
  "pricing.unit": "\u5355\u4F4D\uFF1A{currency} / \u767E\u4E07 tokens\u3002",
341
342
  "pricing.splitNote": "\u9AD8\u5CF0\u65F6\u6BB5\u4E3A\u5317\u4EAC\u65F6\u95F4 {windows}\uFF08\u5468\u4E00\u81F3\u5468\u4E94\uFF09\uFF0C\u5176\u4F59\u4E3A\u95F2\u65F6\uFF08\u4EF7\u683C\u51CF\u534A\uFF09\uFF1B\u6BCF\u6761\u7528\u91CF\u6309\u53D1\u751F\u65F6\u523B\u751F\u6548\u7684\u5B98\u65B9\u4EF7\u683C\u4F30\u7B97\u3002",
342
343
  "pricing.visionNote": "\u56FE\u7247\u6309\u5C3A\u5BF8\u6298\u7B97 token\uFF0C\u4E0E\u6587\u672C\u4E00\u5E76\u8BA1\u8D39\u3002",
343
- "pricing.modelNote": "deepseek-v4-flash\u3001deepseek-v4-flash-vision-exp \u662F\u65E7\u6A21\u578B\u540D\uFF0C\u73B0\u7531 deepseek-flash \u627F\u63A5\u5E76\u6309 Flash \u4EF7\u8BA1\u8D39\uFF1B2026-09-14 12:00 \u8D77 deepseek-v4-pro \u7684\u8BF7\u6C42\u4E5F\u4F1A\u8DEF\u7531\u5230 V4.1-Flash\uFF0C\u6309 Flash \u4EF7\u8BA1\u8D39\u3002",
344
344
  "pricing.unknown": "\u672A\u77E5\u6A21\u578B\u6309 deepseek-v4-pro \u8BA1\u4EF7\u3002",
345
345
  "coverage.noRange": "\u6682\u65E0\u6709\u6548\u7528\u91CF\u65F6\u95F4",
346
346
  "coverage.title": "\u7EDF\u8BA1\u8303\u56F4",
@@ -361,23 +361,19 @@ var zh = {
361
361
  "export.modelCsv": "\u9010\u6A21\u578B CSV",
362
362
  "export.fullJson": "\u5B8C\u6574 JSON",
363
363
  "export.jsonHint": "\u542B\u56FE\u8868\u4E0E\u6392\u884C",
364
- "cache.empty": "\u8FD8\u6CA1\u6709 prompt token \u53EF\u7EDF\u8BA1\u7F13\u5B58\u547D\u4E2D\u3002",
364
+ "cache.empty": "\u6682\u65E0\u7F13\u5B58\u547D\u4E2D\u6570\u636E\u3002",
365
365
  "cache.rate": "\u7F13\u5B58\u547D\u4E2D\u7387",
366
366
  "cache.saved": "\u5DF2\u8282\u7701\u8D39\u7528\uFF08\u4F30\u7B97\uFF09",
367
367
  "cache.allMiss": "\u82E5\u5168\u90E8\u672A\u547D\u4E2D",
368
368
  "cache.aria": "\u7F13\u5B58\u547D\u4E2D {hit}%\uFF0C\u672A\u547D\u4E2D {miss}%",
369
369
  "cache.hitLegend": "\u7F13\u5B58\u547D\u4E2D {tokens}",
370
370
  "cache.missLegend": "\u672A\u547D\u4E2D {tokens}\uFF08\u6309\u672A\u547D\u4E2D\u4EF7\u8BA1\u8D39\uFF09",
371
- "cache.lowHint": "\u547D\u4E2D\u7387\u504F\u4F4E\uFF1A\u9891\u7E41\u6539\u52A8 system prompt / \u5DE5\u5177\u5B9A\u4E49\u4F1A\u8BA9\u524D\u7F00\u7F13\u5B58\u5931\u6548\uFF0C\u628A\u7A33\u5B9A\u5185\u5BB9\u653E\u5728\u5BF9\u8BDD\u6700\u524D\u9762\u80FD\u63D0\u9AD8\u547D\u4E2D\u7387\u3002",
372
- "cache.goodHint": "\u524D\u7F00\u7F13\u5B58\u628A\u91CD\u590D\u7684 prompt \u524D\u7F00\u6309\u547D\u4E2D\u4EF7\u8BA1\u8D39\uFF0C\u662F\u8FD9\u4EFD\u8D26\u5355\u4E0A\u6700\u5927\u7684\u7701\u94B1\u6760\u6746\u3002",
373
- "peak.empty": "\u8FD8\u6CA1\u6709\u53EF\u6309\u65F6\u6BB5\u5F52\u7C7B\u7684\u7528\u91CF\u3002",
371
+ "peak.empty": "\u6682\u65E0\u7528\u91CF\u3002",
374
372
  "peak.aria": "\u9AD8\u5CF0\u65F6\u6BB5 {peak}%\uFF0C\u95F2\u65F6 {offPeak}%",
375
373
  "peak.peakLegend": "\u9AD8\u5CF0 {share}% \xB7 {tokens} tokens \xB7 {calls}",
376
374
  "peak.offPeakLegend": "\u95F2\u65F6 {share}% \xB7 {tokens} tokens \xB7 {calls}",
377
375
  "peak.shift": "\u82E5\u9AD8\u5CF0\u7528\u91CF\u90FD\u632A\u5230\u95F2\u65F6",
378
376
  "peak.saving": "\u53EF\u7701 \xA5{amount}",
379
- "peak.schedule": "\u9AD8\u5CF0\u65F6\u6BB5\u4E3A\u5317\u4EAC\u65F6\u95F4 {windows}\uFF08\u5468\u4E00\u81F3\u5468\u4E94\uFF09\uFF0C\u95F2\u65F6\u4EF7\u683C\u51CF\u534A\u3002",
380
- "peak.activeHint": "\u628A\u6279\u91CF\u3001\u53EF\u5EF6\u540E\u7684\u4EFB\u52A1\u653E\u5230\u95F2\u65F6\u8DD1\uFF0C\u540C\u6837\u7684 token \u53EA\u8981\u4E00\u534A\u7684\u94B1\u3002",
381
377
  "vision.empty": "{window}\u6CA1\u6709\u56FE\u7247\u8F93\u5165\u3002",
382
378
  "vision.title": "\u56FE\u7247 / \u591A\u6A21\u6001\u7528\u91CF",
383
379
  "vision.images": "\u56FE\u7247\u5F20\u6570",
@@ -385,7 +381,7 @@ var zh = {
385
381
  "vision.cost": "\u4F30\u7B97\u8D39\u7528",
386
382
  "vision.tokensShare": "\u5360\u8F93\u5165 {percent}%",
387
383
  "vision.costShare": "\u5360\u8D39\u7528 {percent}%",
388
- "vision.note": "\u56FE\u7247\u6309\u5B98\u65B9\u6362\u7B97\u89C4\u5219\u4F30\u7B97\uFF1A\u5148\u6309\u6BD4\u4F8B\u7F29\u653E\uFF08\u4E0A\u9650 ~800\xD7800\u3001\u4E0B\u9650 384\xD7384\uFF09\uFF0Ctoken \u4E0E\u7F29\u653E\u540E\u9762\u79EF\u6210\u6B63\u6BD4\u3001\u6BCF\u5F20\u4E0A\u9650 384\uFF1B\u56FE\u7247\u5F52\u5C5E\u5230\u968F\u540E\u4E00\u6B21\u6A21\u578B\u8C03\u7528\uFF0C\u6309\u8BE5\u8C03\u7528\u7684\u65F6\u6BB5\u4E0E\u6A21\u578B\u5355\u4EF7\u4F30\u7B97\uFF0C\u5B9E\u9645\u4EE5\u63A5\u53E3\u8FD4\u56DE\u7684\u7528\u91CF\u4E3A\u51C6\u3002",
384
+ "vision.note": "\u6309\u5B98\u65B9\u89C4\u5219\u4F30\u7B97\uFF0C\u5B9E\u9645\u4EE5\u63A5\u53E3\u8FD4\u56DE\u7684\u7528\u91CF\u4E3A\u51C6\u3002",
389
385
  "vision.sessionsTitle": "\u56FE\u7247\u6700\u591A\u7684\u4F1A\u8BDD",
390
386
  "vision.sessionRow": "{images} \u5F20 \xB7 \xA5{cost}",
391
387
  "vision.dailyTitle": "{window} \xB7 \u6BCF\u5929\u56FE\u7247\u6570",
@@ -394,7 +390,7 @@ var zh = {
394
390
  "sessions.empty": "\u8FD8\u6CA1\u6709\u4EA7\u751F\u8D39\u7528\u7684\u4F1A\u8BDD\u3002",
395
391
  "sessions.detail": "{tokens} tokens \xB7 {calls} \xB7 {ago}",
396
392
  "sessions.share": "\u5360 {percent}%",
397
- "sessions.more": "\u5171 {count} \u4E2A\u4F1A\u8BDD\u6709\u7528\u91CF\uFF0C\u4E0A\u9762\u662F\u6700\u8D35\u7684 {shown} \u4E2A\u3002",
393
+ "sessions.more": "\u5171 {count} \u4E2A\u4F1A\u8BDD\uFF0C\u663E\u793A\u6700\u8D35\u7684 {shown} \u4E2A\u3002",
398
394
  "sessions.fallbackTitle": "\u4F1A\u8BDD {id}",
399
395
  "models.empty": "\u8FD8\u6CA1\u6709\u53EF\u5F52\u7C7B\u7684\u6A21\u578B\u7528\u91CF\u3002",
400
396
  "models.shareAria": "{name} \u5360\u603B\u8D39\u7528 {percent}%",
@@ -421,9 +417,9 @@ var zh = {
421
417
  "chart.dimensionLabel": "\u5207\u6362\u56FE\u8868\u7EF4\u5EA6",
422
418
  "chart.daily": "\u9010\u5929",
423
419
  "chart.hourly": "\u9010\u5C0F\u65F6",
424
- "chart.noWindowData": "{window}\u6CA1\u6709\u7528\u91CF\u8BB0\u5F55\uFF0C\u6362\u4E2A\u66F4\u957F\u5468\u671F\u770B\u770B\u3002",
420
+ "chart.noWindowData": "{window}\u6CA1\u6709\u7528\u91CF\u8BB0\u5F55\u3002",
425
421
  "chart.heatmapTitle": "\u8FD1\u4E00\u5E74 \xB7 \u6BCF\u65E5",
426
- "chart.empty": "\u8FD8\u6CA1\u6709\u7528\u91CF\u8BB0\u5F55\u3002\u5728 DSH \u91CC\u8DD1\u4E00\u8F6E\u5BF9\u8BDD\uFF0C\u8FD9\u91CC\u4F1A\u51FA\u73B0\u9010\u5929 / \u9010\u5C0F\u65F6\u7EDF\u8BA1\u4E0E\u8D39\u7528\u4F30\u7B97\u3002",
422
+ "chart.empty": "\u8FD8\u6CA1\u6709\u7528\u91CF\u8BB0\u5F55\u3002",
427
423
  "chart.hourPoint": "{hour} \u70B9",
428
424
  "chart.tooltip": "{head} \xB7 {tokens} tokens \xB7 \xA5{cost} \xB7 {calls}",
429
425
  "chart.less": "\u5C11",
@@ -489,7 +485,7 @@ var zh = {
489
485
  "balance.overYear": "> 365 \u5929",
490
486
  "balance.noRunway": "\u8FD1 {days} \u5929\u6CA1\u6709\u7528\u91CF\uFF0C\u65E0\u6CD5\u4F30\u7B97",
491
487
  "balance.runwayTitle": "\u6309\u8FD1 {days} \u5929\u65E5\u5747 \xA5{cost} \u4F30\u7B97\uFF08\u542B\u65E0\u7528\u91CF\u7684\u65E5\u5B50\uFF1B\u4ECA\u5929\u5C1A\u672A\u8FC7\u5B8C\uFF09",
492
- "balance.empty": "\u8BFB\u4E0D\u5230\u4F59\u989D\u3002\u8BF7\u786E\u8BA4\u8D26\u53F7\u5DF2\u767B\u5F55\uFF0C\u6216\u5728\u300C\u8BBE\u7F6E \u2192 \u6A21\u578B\u300D\u4E2D\u586B\u5199 DEEPSEEK_API_KEY\uFF0C\u7136\u540E\u70B9\u53F3\u4E0A\u89D2\u5237\u65B0\u3002",
488
+ "balance.empty": "\u8BFB\u4E0D\u5230\u4F59\u989D\uFF0C\u8BF7\u68C0\u67E5\u8D26\u53F7\u767B\u5F55\u6216 API Key \u540E\u5237\u65B0\u3002",
493
489
  "sessionUsage.title": "\u5F53\u524D\u4F1A\u8BDD\u6D88\u8017",
494
490
  "common.estimated": "\u8D39\u7528\u4E3A\u4F30\u7B97",
495
491
  "sessionUsage.cost": "\u8D39\u7528",
@@ -497,7 +493,7 @@ var zh = {
497
493
  "sessionUsage.calls": "\u8C03\u7528\u6B21\u6570",
498
494
  "sessionUsage.range": "\u6D3B\u8DC3\u65F6\u95F4 {range}",
499
495
  "sessionUsage.single": "{model} \xB7 \u8F93\u5165 {input} / \u8F93\u51FA {output} / \u7F13\u5B58 {cache}",
500
- "sessionUsage.empty": "\u8FD9\u4E2A\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u4EA7\u751F\u7528\u91CF\u3002\u53D1\u4E00\u6761\u6D88\u606F\u540E\uFF0C\u8FD9\u91CC\u4F1A\u663E\u793A\u8FD9\u4E2A\u4F1A\u8BDD\u4E13\u5C5E\u7684\u8D39\u7528\u4E0E\u6A21\u578B\u660E\u7EC6\u3002",
496
+ "sessionUsage.empty": "\u8FD9\u4E2A\u4F1A\u8BDD\u8FD8\u6CA1\u6709\u7528\u91CF\u3002",
501
497
  "overview.title": "\u6D88\u8017\u6982\u89C8",
502
498
  "overview.today": "\u4ECA\u65E5",
503
499
  "overview.month": "\u672C\u6708",
@@ -519,9 +515,9 @@ var zh = {
519
515
  "links.status": "\u670D\u52A1\u72B6\u6001",
520
516
  "settings.title": "\u8BBE\u7F6E",
521
517
  "settings.widget": "\u5728\u5176\u4ED6\u9875\u9762\u663E\u793A\u53F3\u4E0B\u89D2\u60AC\u6D6E\u989D\u5EA6\u7A97\u53E3",
522
- "settings.widgetHint": "\u989D\u5EA6\u9875\u4F1A\u81EA\u52A8\u9690\u85CF\u6D6E\u7A97\uFF1B\u5176\u4ED6\u9875\u9762\u662F\u5426\u5C55\u793A\u7531\u4E0B\u65B9\u9009\u62E9\u51B3\u5B9A\u3002",
518
+ "settings.widgetHint": "\u989D\u5EA6\u9875\u81EA\u52A8\u9690\u85CF\uFF1B\u5176\u4ED6\u9875\u9762\u6309\u4E0B\u65B9\u9009\u62E9\u663E\u793A\u3002",
523
519
  "settings.widgetTabs": "\u5C55\u793A\u6D6E\u7A97\u7684\u9875\u9762",
524
- "settings.widgetTabsHint": "\u9875\u9762\u5217\u8868\u6765\u81EA DSH \u7684\u5B9E\u65F6 Tab \u6CE8\u518C\u8868\uFF1B\u5176\u4ED6\u63D2\u4EF6\u65B0\u589E\u7684 Tab \u4E5F\u4F1A\u81EA\u52A8\u51FA\u73B0\u5728\u8FD9\u91CC\u3002",
520
+ "settings.widgetTabsHint": "\u5217\u8868\u4F1A\u968F\u5DF2\u5B89\u88C5\u7684\u9875\u9762\u81EA\u52A8\u66F4\u65B0\u3002",
525
521
  "settings.widgetTabsEmpty": "\u5F53\u524D\u6CA1\u6709\u53EF\u914D\u7F6E\u7684\u4F1A\u8BDD\u9875\u9762\u3002",
526
522
  "settings.lowBalance": "\u4F59\u989D\u9884\u8B66\u7EBF",
527
523
  "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",
@@ -529,6 +525,18 @@ var zh = {
529
525
  "settings.monthlyBudget": "\u6708\u5EA6\u9884\u7B97",
530
526
  "settings.monthlyBudgetOn": "\u6309\u5317\u4EAC\u65F6\u95F4\u81EA\u7136\u6708\u8DDF\u8E2A {amount} CNY \u9884\u7B97\uFF0C\u5E76\u5728\u6D88\u8017\u6982\u89C8\u9884\u6D4B\u6708\u5E95\u82B1\u8D39\u3002\u586B 0 \u5173\u95ED\u3002",
531
527
  "settings.monthlyBudgetOff": "\u586B\u4E00\u4E2A\u5927\u4E8E 0 \u7684\u91D1\u989D\uFF0C\u6D88\u8017\u6982\u89C8\u4F1A\u663E\u793A\u8FDB\u5EA6\u4E0E\u6708\u5E95\u9884\u6D4B\u3002",
528
+ "settings.sidebarEntry": "\u4FA7\u8FB9\u680F\u989D\u5EA6\u5165\u53E3",
529
+ "settings.sidebarEntryHint": "\u5728\u4FA7\u8FB9\u680F\u9876\u90E8\u6216\u5E95\u90E8\u653E\u4E00\u4E2A\u300C\u989D\u5EA6\u300D\u5165\u53E3\uFF0C\u6253\u5F00\u8D26\u6237\u7EA7\u989D\u5EA6\u9875\u3002",
530
+ "settings.sidebarEntryOff": "\u5173\u95ED",
531
+ "settings.sidebarEntryTop": "\u9876\u90E8",
532
+ "settings.sidebarEntryBottom": "\u5E95\u90E8",
533
+ "settings.tabPosition": "\u300C\u989D\u5EA6\u300D\u6807\u7B7E\u4F4D\u7F6E",
534
+ "settings.tabPositionHint": "\u62D6\u52A8\u300C\u989D\u5EA6\u300D\u8C03\u6574\u6807\u7B7E\u4F4D\u7F6E\u3002",
535
+ "settings.tabDragAria": "\u62D6\u52A8\u300C\u989D\u5EA6\u300D\u8C03\u6574\u6807\u7B7E\u4F4D\u7F6E",
536
+ "settings.tabMoveEarlier": "\u524D\u79FB",
537
+ "settings.tabMoveLater": "\u540E\u79FB",
538
+ "panel.caption": "\u8D26\u6237\u7EA7 \xB7 \u8DE8\u5168\u90E8\u4F1A\u8BDD",
539
+ "panel.openAria": "\u6253\u5F00\u989D\u5EA6\u9875",
532
540
  "widget.updatedNow": "\u521A\u521A\u66F4\u65B0",
533
541
  "widget.stale": "{minutes} \u5206\u949F\u524D\u7684\u6570\u636E\uFF0C\u70B9 \u21BB \u66F4\u65B0",
534
542
  "widget.loading": "\u67E5\u8BE2\u4E2D\u2026",
@@ -546,6 +554,9 @@ var zh = {
546
554
  "widget.expandAria": "\u5C55\u5F00\u989D\u5EA6\u7A97\u53E3",
547
555
  "widget.collapseAria": "\u6536\u8D77\u989D\u5EA6\u7A97\u53E3",
548
556
  "messageCost.label": "\u672C\u8F6E\u8D39\u7528",
557
+ "sessionCost.title": "\u4F1A\u8BDD\u8D39\u7528",
558
+ "sessionCost.aria": "\u672C\u4F1A\u8BDD\u4F30\u7B97\u8D39\u7528 {amount}",
559
+ "sessionCost.active": "\u6D3B\u8DC3\u65F6\u95F4",
549
560
  "messageCost.tooltip": "\u7531 dsh-usage-dashboard \u63D0\u4F9B \xB7 \u672C\u8F6E\u5BF9\u8BDD\u8D39\u7528\u4E3A\u4F30\u7B97\u503C",
550
561
  "messageCost.aria": "{amount}\uFF0C\u672C\u8F6E\u5BF9\u8BDD\u4F30\u7B97\u8D39\u7528\uFF0C\u7531 dsh-usage-dashboard \u63D0\u4F9B",
551
562
  "boundary.title": "\u989D\u5EA6\u9762\u677F\u51FA\u9519\u4E86",
@@ -578,7 +589,7 @@ var en = {
578
589
  "delta.noBaseline": "No baseline",
579
590
  "delta.flat": "No change",
580
591
  "period.volume": "{tokens} tokens \xB7 {calls}",
581
- "budget.unset": "Set a monthly budget to see whether current spending is on track.",
592
+ "budget.unset": "No monthly budget set.",
582
593
  "budget.configure": "Set budget",
583
594
  "budget.over": "Over budget by \xA5{amount}",
584
595
  "budget.forecastOver": "At this pace, \xA5{amount} over by month-end",
@@ -601,7 +612,6 @@ var en = {
601
612
  "pricing.unit": "Unit: {currency} / million tokens. ",
602
613
  "pricing.splitNote": "Peak periods are {windows} Beijing time, Monday to Friday; off-peak rates are half price. Each record is estimated at the official rates in force when it was written. ",
603
614
  "pricing.visionNote": "Images are converted to tokens by size and charged together with text. ",
604
- "pricing.modelNote": "deepseek-v4-flash and deepseek-v4-flash-vision-exp are legacy names, now served by deepseek-flash at Flash rates; from 2026-09-14 12:00 deepseek-v4-pro requests are routed to V4.1-Flash and billed at Flash rates. ",
605
615
  "pricing.unknown": "Unknown models use deepseek-v4-pro rates.",
606
616
  "coverage.noRange": "No valid usage timestamps",
607
617
  "coverage.title": "Coverage",
@@ -622,23 +632,19 @@ var en = {
622
632
  "export.modelCsv": "Model CSV",
623
633
  "export.fullJson": "Full JSON",
624
634
  "export.jsonHint": "Charts and rankings included",
625
- "cache.empty": "No prompt tokens are available for cache analysis yet.",
635
+ "cache.empty": "No cache data yet.",
626
636
  "cache.rate": "Cache hit rate",
627
637
  "cache.saved": "Estimated savings",
628
638
  "cache.allMiss": "Cost with no hits",
629
639
  "cache.aria": "{hit}% cache hits, {miss}% misses",
630
640
  "cache.hitLegend": "Cache hits {tokens}",
631
641
  "cache.missLegend": "Misses {tokens} (charged at miss rate)",
632
- "cache.lowHint": "Low hit rate: frequent changes to the system prompt or tool definitions invalidate prefix caching. Put stable content first to improve reuse.",
633
- "cache.goodHint": "Prefix caching charges repeated prompt prefixes at the hit rate, making it the largest saving lever on this bill.",
634
- "peak.empty": "No usage can be classified by pricing period yet.",
642
+ "peak.empty": "No usage yet.",
635
643
  "peak.aria": "{peak}% peak, {offPeak}% off-peak",
636
644
  "peak.peakLegend": "Peak {share}% \xB7 {tokens} tokens \xB7 {calls}",
637
645
  "peak.offPeakLegend": "Off-peak {share}% \xB7 {tokens} tokens \xB7 {calls}",
638
646
  "peak.shift": "If all peak usage moved off-peak",
639
647
  "peak.saving": "Save \xA5{amount}",
640
- "peak.schedule": "Peak periods are {windows} Beijing time, Monday to Friday; off-peak rates are half price. ",
641
- "peak.activeHint": "Run batch or delay-tolerant work off-peak to pay half as much for the same tokens.",
642
648
  "vision.empty": "No image input in {window}.",
643
649
  "vision.title": "Image / multimodal usage",
644
650
  "vision.images": "Images",
@@ -646,7 +652,7 @@ var en = {
646
652
  "vision.cost": "Estimated cost",
647
653
  "vision.tokensShare": "{percent}% of input",
648
654
  "vision.costShare": "{percent}% of cost",
649
- "vision.note": "Images are estimated with the official rule: resized keeping aspect ratio (cap ~800\xD7800, floor 384\xD7384), tokens proportional to the resized area with a 384-token cap per image. Each image is attributed to the next model call and priced at that call's period and model rate; the provider usage numbers remain the source of truth.",
655
+ "vision.note": "Estimated with the official rule; provider usage numbers are the source of truth.",
650
656
  "vision.sessionsTitle": "Sessions with the most images",
651
657
  "vision.sessionRow": "{images} images \xB7 \xA5{cost}",
652
658
  "vision.dailyTitle": "{window} \xB7 images per day",
@@ -655,7 +661,7 @@ var en = {
655
661
  "sessions.empty": "No sessions have incurred cost yet.",
656
662
  "sessions.detail": "{tokens} tokens \xB7 {calls} \xB7 {ago}",
657
663
  "sessions.share": "{percent}% of total",
658
- "sessions.more": "{count} sessions have usage; the {shown} most expensive are shown above.",
664
+ "sessions.more": "{count} sessions; showing the {shown} most expensive.",
659
665
  "sessions.fallbackTitle": "Session {id}",
660
666
  "models.empty": "No model usage can be classified yet.",
661
667
  "models.shareAria": "{name} accounts for {percent}% of total cost",
@@ -682,9 +688,9 @@ var en = {
682
688
  "chart.dimensionLabel": "Chart interval",
683
689
  "chart.daily": "Daily",
684
690
  "chart.hourly": "Hourly",
685
- "chart.noWindowData": "No usage in {window}. Try a longer range.",
691
+ "chart.noWindowData": "No usage in {window}.",
686
692
  "chart.heatmapTitle": "Last 12 months \xB7 daily",
687
- "chart.empty": "No usage yet. Run a conversation in DSH to see daily and hourly totals with cost estimates.",
693
+ "chart.empty": "No usage yet.",
688
694
  "chart.hourPoint": "{hour}:00",
689
695
  "chart.tooltip": "{head} \xB7 {tokens} tokens \xB7 \xA5{cost} \xB7 {calls}",
690
696
  "chart.less": "Less",
@@ -750,7 +756,7 @@ var en = {
750
756
  "balance.overYear": "> 365 days",
751
757
  "balance.noRunway": "No usage in the last {days} days, so runway cannot be estimated",
752
758
  "balance.runwayTitle": "Based on \xA5{cost} average daily spend over the last {days} days (including idle days; today is incomplete)",
753
- "balance.empty": "Balance is unavailable. Sign in to your account or add DEEPSEEK_API_KEY under Settings \u2192 Models, then refresh.",
759
+ "balance.empty": "Balance is unavailable. Check your sign-in or API key, then refresh.",
754
760
  "sessionUsage.title": "Current session spend",
755
761
  "common.estimated": "Estimated cost",
756
762
  "sessionUsage.cost": "Cost",
@@ -758,7 +764,7 @@ var en = {
758
764
  "sessionUsage.calls": "Calls",
759
765
  "sessionUsage.range": "Active {range}",
760
766
  "sessionUsage.single": "{model} \xB7 input {input} / output {output} / cache {cache}",
761
- "sessionUsage.empty": "No usage in this session yet. Send a message and this session\u2019s own cost and model breakdown will show up here.",
767
+ "sessionUsage.empty": "No usage in this session yet.",
762
768
  "overview.title": "Spend overview",
763
769
  "overview.today": "Today",
764
770
  "overview.month": "This month",
@@ -780,9 +786,9 @@ var en = {
780
786
  "links.status": "Service status",
781
787
  "settings.title": "Settings",
782
788
  "settings.widget": "Show the floating balance widget on other views",
783
- "settings.widgetHint": "The widget always hides on Usage; choose its other views below.",
789
+ "settings.widgetHint": "Hidden on Usage; shown on the views selected below.",
784
790
  "settings.widgetTabs": "Show widget on",
785
- "settings.widgetTabsHint": "This live list comes from DSH\u2019s tab registry, including tabs added by other plugins.",
791
+ "settings.widgetTabsHint": "Updates automatically as views are installed.",
786
792
  "settings.widgetTabsEmpty": "There are no configurable conversation views right now.",
787
793
  "settings.lowBalance": "Balance warning",
788
794
  "settings.lowBalanceOn": "This page and the widget turn amber below {amount} {currency}. Enter 0 to disable.",
@@ -790,6 +796,18 @@ var en = {
790
796
  "settings.monthlyBudget": "Monthly budget",
791
797
  "settings.monthlyBudgetOn": "Track a {amount} CNY budget by Beijing calendar month and forecast month-end spend. Enter 0 to disable.",
792
798
  "settings.monthlyBudgetOff": "Enter an amount above 0 to show progress and a month-end forecast.",
799
+ "settings.sidebarEntry": "Sidebar Usage entry",
800
+ "settings.sidebarEntryHint": "Add a Usage entry at the top or bottom of the sidebar; it opens the account-wide Usage page.",
801
+ "settings.sidebarEntryOff": "Off",
802
+ "settings.sidebarEntryTop": "Top",
803
+ "settings.sidebarEntryBottom": "Bottom",
804
+ "settings.tabPosition": "Usage tab position",
805
+ "settings.tabPositionHint": "Drag Usage to change its tab position.",
806
+ "settings.tabDragAria": "Drag Usage to change its tab position",
807
+ "settings.tabMoveEarlier": "Move earlier",
808
+ "settings.tabMoveLater": "Move later",
809
+ "panel.caption": "Account-wide \xB7 across all sessions",
810
+ "panel.openAria": "Open Usage",
793
811
  "widget.updatedNow": "Updated just now",
794
812
  "widget.stale": "Data from {minutes}m ago; press \u21BB to update",
795
813
  "widget.loading": "Loading\u2026",
@@ -807,6 +825,9 @@ var en = {
807
825
  "widget.expandAria": "Expand balance widget",
808
826
  "widget.collapseAria": "Collapse balance widget",
809
827
  "messageCost.label": "Turn cost",
828
+ "sessionCost.title": "Session cost",
829
+ "sessionCost.aria": "Estimated session cost {amount}",
830
+ "sessionCost.active": "Active time",
810
831
  "messageCost.tooltip": "Provided by dsh-usage-dashboard \xB7 estimated cost for this turn",
811
832
  "messageCost.aria": "{amount}, estimated cost for this turn, provided by dsh-usage-dashboard",
812
833
  "boundary.title": "The usage dashboard crashed",
@@ -1353,6 +1374,27 @@ var quotaViewActiveStore = {
1353
1374
  }
1354
1375
  };
1355
1376
 
1377
+ // src/client/layout-prefs.ts
1378
+ var QUOTA_PANEL_ID = "quota";
1379
+ var isPlacement = (value) => value === "off" || value === "top" || value === "bottom";
1380
+ var sidebarEntryStore = createStore("layout.sidebarEntry", isPlacement, "off");
1381
+ var isTabIndex = (value) => typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 50;
1382
+ var quotaTabIndexStore = createStore("layout.tabIndex", isTabIndex, 2);
1383
+ function clampTabIndex(index, count) {
1384
+ return Math.max(0, Math.min(index, Math.max(count, 0)));
1385
+ }
1386
+ function viewOrderForIndex(entries, index, selfId = "balance", fallback = 20) {
1387
+ const others = entries.filter((entry) => entry.options.id !== selfId);
1388
+ if (others.length === 0) return fallback;
1389
+ const clamped = clampTabIndex(index, others.length);
1390
+ const orderAt = (position) => others[position]?.options.order ?? 0;
1391
+ if (clamped === 0) return orderAt(0) - 10;
1392
+ if (clamped === others.length) return orderAt(others.length - 1) + 10;
1393
+ const previous = orderAt(clamped - 1);
1394
+ const next = orderAt(clamped);
1395
+ return previous >= next ? previous + 0.5 : (previous + next) / 2;
1396
+ }
1397
+
1356
1398
  // src/client/dashboard.tsx
1357
1399
  var import_jsx_runtime4 = require("react/jsx-runtime");
1358
1400
  var COMPOSER_FALLBACK_PX = 126;
@@ -1518,7 +1560,6 @@ function PricingNote(props) {
1518
1560
  t("pricing.unit", { currency: pricing.currency }),
1519
1561
  t("pricing.splitNote", { windows: pricing.peakWindows.join(t("common.listSeparator")) }),
1520
1562
  t("pricing.visionNote"),
1521
- t("pricing.modelNote"),
1522
1563
  t("pricing.unknown")
1523
1564
  ] })
1524
1565
  ] })
@@ -1780,8 +1821,7 @@ function CacheCard(props) {
1780
1821
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-legend-swatch dq-legend-swatch--miss" }),
1781
1822
  t("cache.missLegend", { tokens: fmtCompact(totals.input, locale) })
1782
1823
  ] })
1783
- ] }),
1784
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-cache-foot", children: low ? t("cache.lowHint") : t("cache.goodHint") })
1824
+ ] })
1785
1825
  ] });
1786
1826
  }
1787
1827
  function VisionCard(props) {
@@ -1863,11 +1903,7 @@ function PeakCard(props) {
1863
1903
  fmt(split.offPeakEraCost),
1864
1904
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-peak-delta dq-peak-delta--save", children: t("peak.saving", { amount: fmt(shiftSaving) }) })
1865
1905
  ] })
1866
- ] }) }),
1867
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "dq-peak-foot", children: [
1868
- t("peak.schedule", { windows: pricing.peakWindows.join(t("common.listSeparator")) }),
1869
- t("peak.activeHint")
1870
- ] })
1906
+ ] }) })
1871
1907
  ] });
1872
1908
  }
1873
1909
  function agoText(ms, t) {
@@ -2118,6 +2154,11 @@ function BalanceDashboard(props) {
2118
2154
  const [selectedModels, setSelectedModels] = (0, import_react4.useState)([]);
2119
2155
  const [windowDays, setWindowDays] = (0, import_react4.useState)(usageWindowStore.get());
2120
2156
  const [chartMetric, setChartMetric] = (0, import_react4.useState)(chartMetricStore.get());
2157
+ const [sidebarEntry, setSidebarEntry] = (0, import_react4.useState)(sidebarEntryStore.get());
2158
+ (0, import_react4.useEffect)(() => sidebarEntryStore.subscribe(() => setSidebarEntry(sidebarEntryStore.get())), []);
2159
+ const [tabDragging, setTabDragging] = (0, import_react4.useState)(false);
2160
+ const [tabDropTarget, setTabDropTarget] = (0, import_react4.useState)(null);
2161
+ const [retainedVision, setRetainedVision] = (0, import_react4.useState)(null);
2121
2162
  const [runwayHovering, setRunwayHovering] = (0, import_react4.useState)(false);
2122
2163
  const [runwayManuallyOpened, setRunwayManuallyOpened] = (0, import_react4.useState)(false);
2123
2164
  const [remainingHovering, setRemainingHovering] = (0, import_react4.useState)(false);
@@ -2290,6 +2331,71 @@ function BalanceDashboard(props) {
2290
2331
  setChartMetric(value);
2291
2332
  chartMetricStore.set(value);
2292
2333
  };
2334
+ const changeSidebarEntry = (value) => {
2335
+ setSidebarEntry(value);
2336
+ sidebarEntryStore.set(value);
2337
+ };
2338
+ const balanceTabIndex = viewTabs.findIndex((tab) => tab.id === "balance");
2339
+ const finalTabIndex = (target, after) => {
2340
+ const gap = after ? target + 1 : target;
2341
+ const dropped = balanceTabIndex >= 0 && gap > balanceTabIndex ? gap - 1 : gap;
2342
+ return Math.max(0, Math.min(dropped, Math.max(viewTabs.length - 1, 0)));
2343
+ };
2344
+ const endTabDrag = () => {
2345
+ setTabDragging(false);
2346
+ setTabDropTarget(null);
2347
+ };
2348
+ const previewTabDropTarget = (index) => {
2349
+ setTabDropTarget((prev) => prev === index ? prev : index);
2350
+ };
2351
+ const applyQuotaTabIndex = (index) => {
2352
+ const next = Math.max(0, Math.min(index, Math.max(viewTabs.length - 1, 0)));
2353
+ if (next === balanceTabIndex) return;
2354
+ quotaTabIndexStore.set(next);
2355
+ window.setTimeout(() => {
2356
+ const chip = document.querySelector(".dq-tab-chip--current");
2357
+ chip?.focus();
2358
+ }, 80);
2359
+ };
2360
+ const dropQuotaTab = (target, after) => {
2361
+ applyQuotaTabIndex(finalTabIndex(target, after));
2362
+ endTabDrag();
2363
+ };
2364
+ const onQuotaTabDragStart = (event) => {
2365
+ setTabDragging(true);
2366
+ event.dataTransfer.effectAllowed = "move";
2367
+ event.dataTransfer.setData("text/plain", "balance");
2368
+ };
2369
+ const onChipDragOver = (event, index) => {
2370
+ if (!tabDragging) return;
2371
+ event.preventDefault();
2372
+ event.stopPropagation();
2373
+ previewTabDropTarget(index);
2374
+ };
2375
+ const onChipDrop = (event, index) => {
2376
+ if (!tabDragging) return;
2377
+ event.preventDefault();
2378
+ event.stopPropagation();
2379
+ const rect = event.currentTarget.getBoundingClientRect();
2380
+ dropQuotaTab(index, event.clientX > rect.left + rect.width / 2);
2381
+ };
2382
+ const onOrderDragOver = (event) => {
2383
+ if (!tabDragging) return;
2384
+ event.preventDefault();
2385
+ if (event.target === event.currentTarget) previewTabDropTarget(viewTabs.length - 1);
2386
+ };
2387
+ const onOrderDrop = (event) => {
2388
+ if (!tabDragging) return;
2389
+ event.preventDefault();
2390
+ if (event.target !== event.currentTarget) return;
2391
+ applyQuotaTabIndex(viewTabs.length - 1);
2392
+ endTabDrag();
2393
+ };
2394
+ const onQuotaTabKeyDown = (event) => {
2395
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
2396
+ event.preventDefault();
2397
+ applyQuotaTabIndex(balanceTabIndex + (event.key === "ArrowLeft" ? -1 : 1));
2398
+ };
2293
2399
  const primary = balance !== null && balance.balances.length > 0 ? balance.balances[0] : null;
2294
2400
  const recentDays = (usage?.daily ?? []).slice(-7);
2295
2401
  const avgDailyCost = recentDays.length > 0 ? recentDays.reduce((sum, d) => sum + d.cost, 0) / recentDays.length : 0;
@@ -2300,6 +2406,26 @@ function BalanceDashboard(props) {
2300
2406
  const runwayTitle = daysLeft === null ? t("balance.noRunway", { days: recentDays.length || 7 }) : t("balance.runwayTitle", { days: recentDays.length, cost: fmt(avgDailyCost) });
2301
2407
  const activeWindow = usage?.windows.find((window2) => window2.days === windowDays) ?? null;
2302
2408
  const activeWindowName = windowName(windowDays, t);
2409
+ const visionStats = activeWindow?.vision;
2410
+ const visionOpen = usage !== null && activeWindow !== null && visionStats !== void 0 && visionStats.images > 0 && Array.isArray(activeWindow.visionDaily) && Array.isArray(activeWindow.visionSessions);
2411
+ const visionPayload = (window2, stats, label) => ({
2412
+ vision: stats,
2413
+ daily: window2.visionDaily,
2414
+ sessions: window2.visionSessions,
2415
+ windowLabel: label,
2416
+ inputTokens: window2.totals.input,
2417
+ costTotal: window2.totals.cost
2418
+ });
2419
+ const liveVision = visionOpen && activeWindow !== null && visionStats !== void 0 ? visionPayload(activeWindow, visionStats, activeWindowName) : null;
2420
+ (0, import_react4.useEffect)(() => {
2421
+ if (visionOpen && activeWindow !== null && visionStats !== void 0) {
2422
+ setRetainedVision(visionPayload(activeWindow, visionStats, activeWindowName));
2423
+ return;
2424
+ }
2425
+ const timer = window.setTimeout(() => setRetainedVision(null), 320);
2426
+ return () => window.clearTimeout(timer);
2427
+ }, [visionOpen, activeWindow, activeWindowName]);
2428
+ const shownVision = visionOpen ? liveVision : retainedVision;
2303
2429
  const dailyBars = (activeWindow?.daily ?? []).map((d) => ({
2304
2430
  label: windowDays === 365 ? d.date.slice(5).replace("-", "/") : d.date.slice(8, 10),
2305
2431
  value: chartMetricValue(chartMetric, d),
@@ -2524,34 +2650,6 @@ function BalanceDashboard(props) {
2524
2650
  ),
2525
2651
  usage !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PricingNote, { pricing: usage.pricing })
2526
2652
  ] }),
2527
- usage !== null && activeWindow !== null && activeWindow.vision !== void 0 && Array.isArray(activeWindow.visionDaily) && Array.isArray(activeWindow.visionSessions) ? (
2528
- // 窗口内没有图片时整张卡不出现:一行「没有图片输入」不值得一个小节的位置。
2529
- activeWindow.vision.images === 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
2530
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { caption: activeWindowName, children: t("vision.title") }),
2531
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2532
- VisionCard,
2533
- {
2534
- vision: activeWindow.vision,
2535
- daily: activeWindow.visionDaily,
2536
- sessions: activeWindow.visionSessions,
2537
- windowLabel: activeWindowName,
2538
- inputTokens: activeWindow.totals.input,
2539
- costTotal: activeWindow.totals.cost
2540
- }
2541
- )
2542
- ] })
2543
- ) : usage === null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
2544
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { caption: activeWindowName, children: t("vision.title") }),
2545
- loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-usage-totals", children: [64, 88, 72].map((w, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
2546
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w, h: 11 }),
2547
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 72, h: 20 })
2548
- ] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("vision.empty", { window: activeWindowName }) })
2549
- ] }) : (
2550
- // Host serving a pre-vision payload (e.g. not restarted yet): hide the
2551
- // whole card instead of leaving a titled shell — it reappears as soon
2552
- // as the host half serves the new shape.
2553
- null
2554
- ),
2555
2653
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card dq-card--primary", children: [
2556
2654
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card-head", children: [
2557
2655
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { children: t("usage.trendTitle") }),
@@ -2615,31 +2713,31 @@ function BalanceDashboard(props) {
2615
2713
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-legend-swatch", style: { background: s.color } }),
2616
2714
  s.name
2617
2715
  ] }, s.key)) })
2618
- ] }) : barMode === "daily" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Bars, { data: dailyBars, height: 160, labelEvery: dailyLabelEvery, minWidth: dailyMinWidth }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Bars, { data: hourlyBars, height: 140, labelEvery: 3 }),
2619
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CoverageDiagnostics, { coverage: usage.coverage })
2620
- ] }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UsageSkeleton, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("chart.empty") })
2716
+ ] }) : barMode === "daily" ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Bars, { data: dailyBars, height: 160, labelEvery: dailyLabelEvery, minWidth: dailyMinWidth }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Bars, { data: hourlyBars, height: 140, labelEvery: 3 })
2717
+ ] }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UsageSkeleton, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("chart.empty") }),
2718
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-vision-inline", "data-open": visionOpen ? "true" : "false", "aria-hidden": !visionOpen, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-vision-inline-body", children: shownVision !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
2719
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-chart-title", children: [
2720
+ t("vision.title"),
2721
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-chart-peak", children: shownVision.windowLabel })
2722
+ ] }),
2723
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2724
+ VisionCard,
2725
+ {
2726
+ vision: shownVision.vision,
2727
+ daily: shownVision.daily,
2728
+ sessions: shownVision.sessions,
2729
+ windowLabel: shownVision.windowLabel,
2730
+ inputTokens: shownVision.inputTokens,
2731
+ costTotal: shownVision.costTotal
2732
+ }
2733
+ )
2734
+ ] }) }) }),
2735
+ usage !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CoverageDiagnostics, { coverage: usage.coverage })
2621
2736
  ] }),
2622
2737
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
2623
2738
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { caption: t("chart.heatmapTitle"), children: t("heatmap.title") }),
2624
2739
  usage !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Heatmap, { data: usage.heatmap }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HeatmapSkeleton, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("chart.empty") })
2625
2740
  ] }),
2626
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
2627
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { children: t("peak.title") }),
2628
- usage !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PeakCard, { split: usage.peakSplit, pricing: usage.pricing }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
2629
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-peak-track" }),
2630
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-peak-figures", children: [
2631
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 120, h: 11 }),
2632
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 96, h: 20 })
2633
- ] })
2634
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("peak.empty") })
2635
- ] }),
2636
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
2637
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { children: t("cache.title") }),
2638
- usage !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CacheCard, { totals: usage.totals }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-cache-figures", children: [60, 96, 72].map((w, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
2639
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w, h: 11 }),
2640
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 72, h: 20 })
2641
- ] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("cache.empty") })
2642
- ] }),
2643
2741
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
2644
2742
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { caption: activeWindowName, children: t("models.title") }),
2645
2743
  activeWindow !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ModelRanking, { models: modelList, totalCost: modelCostTotal, colorOf }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-rank", children: [0, 1].map((i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-rank-row", children: [
@@ -2656,6 +2754,25 @@ function BalanceDashboard(props) {
2656
2754
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 250, h: 11 })
2657
2755
  ] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("sessions.empty") })
2658
2756
  ] }),
2757
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card-pair", children: [
2758
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
2759
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { children: t("peak.title") }),
2760
+ usage !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PeakCard, { split: usage.peakSplit, pricing: usage.pricing }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
2761
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-peak-track" }),
2762
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-peak-figures", children: [
2763
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 120, h: 11 }),
2764
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 96, h: 20 })
2765
+ ] })
2766
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("peak.empty") })
2767
+ ] }),
2768
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
2769
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { children: t("cache.title") }),
2770
+ usage !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CacheCard, { totals: usage.totals }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-cache-figures", children: [60, 96, 72].map((w, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
2771
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w, h: 11 }),
2772
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 72, h: 20 })
2773
+ ] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("cache.empty") })
2774
+ ] })
2775
+ ] }),
2659
2776
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
2660
2777
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { children: t("settings.title") }),
2661
2778
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-links-title", children: t("links.title") }),
@@ -2723,9 +2840,67 @@ function BalanceDashboard(props) {
2723
2840
  ),
2724
2841
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { id: "dq-monthly-budget-hint", className: "dq-setting-hint", children: monthlyBudget > 0 ? t("settings.monthlyBudgetOn", { amount: fmt(monthlyBudget) }) : t("settings.monthlyBudgetOff") })
2725
2842
  ] })
2843
+ ] }),
2844
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
2845
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-setting-label", htmlFor: "dq-sidebar-entry", children: t("settings.sidebarEntry") }),
2846
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
2847
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2848
+ "select",
2849
+ {
2850
+ id: "dq-sidebar-entry",
2851
+ className: "dq-select",
2852
+ value: sidebarEntry,
2853
+ onChange: (e) => changeSidebarEntry(e.target.value),
2854
+ children: [
2855
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "off", children: t("settings.sidebarEntryOff") }),
2856
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "top", children: t("settings.sidebarEntryTop") }),
2857
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "bottom", children: t("settings.sidebarEntryBottom") })
2858
+ ]
2859
+ }
2860
+ ),
2861
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-setting-hint", children: t("settings.sidebarEntryHint") })
2862
+ ] })
2863
+ ] }),
2864
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
2865
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-label", id: "dq-tab-order-label", children: t("settings.tabPosition") }),
2866
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
2867
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2868
+ "div",
2869
+ {
2870
+ className: "dq-tab-order",
2871
+ role: "group",
2872
+ "aria-labelledby": "dq-tab-order-label",
2873
+ "aria-describedby": "dq-tab-order-hint",
2874
+ onDragOver: onOrderDragOver,
2875
+ onDrop: onOrderDrop,
2876
+ children: viewTabs.map((tab, index) => {
2877
+ const current = tab.id === "balance";
2878
+ const dropTarget = tabDragging && !current && tabDropTarget === index;
2879
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2880
+ "span",
2881
+ {
2882
+ className: `dq-tab-chip${current ? " dq-tab-chip--current" : ""}${current && tabDragging ? " dq-tab-chip--dragging" : ""}${dropTarget ? " dq-tab-chip--drop-target" : ""}`,
2883
+ draggable: current,
2884
+ tabIndex: current ? 0 : void 0,
2885
+ role: current ? "button" : void 0,
2886
+ "aria-label": current ? t("settings.tabDragAria") : void 0,
2887
+ onDragStart: current ? onQuotaTabDragStart : void 0,
2888
+ onDragEnd: current ? endTabDrag : void 0,
2889
+ onDragOver: (event) => onChipDragOver(event, index),
2890
+ onDrop: (event) => onChipDrop(event, index),
2891
+ onKeyDown: current ? onQuotaTabKeyDown : void 0,
2892
+ children: tab.label
2893
+ },
2894
+ tab.id
2895
+ );
2896
+ })
2897
+ }
2898
+ ),
2899
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { id: "dq-tab-order-hint", className: "dq-setting-hint", children: t("settings.tabPositionHint") })
2900
+ ] })
2726
2901
  ] })
2727
2902
  ] }),
2728
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-footer", children: "@cassius0924/dsh-usage-dashboard v0.7.0" })
2903
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-footer", children: "@cassius0924/dsh-usage-dashboard v0.8.1" })
2729
2904
  ] });
2730
2905
  }
2731
2906
 
@@ -2769,6 +2944,254 @@ function MessageCost({ sessionId, messageId }) {
2769
2944
  );
2770
2945
  }
2771
2946
 
2947
+ // src/client/session-cost-pill.tsx
2948
+ var import_react6 = require("react");
2949
+ var import_react_dom = require("react-dom");
2950
+
2951
+ // src/pricing.ts
2952
+ var PEAK_PRICING_FROM_MS = Date.UTC(2026, 7, 16, 16, 0, 0);
2953
+ var V41_FLASH_PRICING_FROM_MS = Date.UTC(2026, 8, 10, 4, 0, 0);
2954
+ var PRO_ROUTED_TO_FLASH_FROM_MS = Date.UTC(2026, 8, 14, 4, 0, 0);
2955
+ var PEAK_WINDOWS = [[9, 12], [14, 18]];
2956
+ var BEIJING_OFFSET_MS3 = 8 * 36e5;
2957
+ var FLAT_RATES = {
2958
+ pro: { cacheHit: 0.025, input: 3, output: 6 },
2959
+ flash: { cacheHit: 0.02, input: 1, output: 2 },
2960
+ // The vision model billed at flash rates from day one, so its pre-switch
2961
+ // history is costed at the same flat prices as flash.
2962
+ vision: { cacheHit: 0.02, input: 1, output: 2 }
2963
+ };
2964
+ var PEAK_RATES_08_17 = {
2965
+ pro: { cacheHit: 0.3, input: 9, output: 27 },
2966
+ flash: { cacheHit: 0.1, input: 3, output: 9 },
2967
+ // Official policy: the vision model is priced identically to flash; images
2968
+ // cost tokens, not their own multiplier.
2969
+ vision: { cacheHit: 0.1, input: 3, output: 9 }
2970
+ };
2971
+ var PEAK_RATES_09_10 = {
2972
+ pro: { cacheHit: 0.3, input: 9, output: 27 },
2973
+ flash: { cacheHit: 0.04, input: 2, output: 8 },
2974
+ vision: { cacheHit: 0.04, input: 2, output: 8 }
2975
+ };
2976
+ var PRICE_ERAS = [
2977
+ { fromMs: PEAK_PRICING_FROM_MS, peak: PEAK_RATES_08_17, weekdaysOnly: false },
2978
+ { fromMs: V41_FLASH_PRICING_FROM_MS, peak: PEAK_RATES_09_10, weekdaysOnly: true }
2979
+ ];
2980
+ function eraAt(timeMs) {
2981
+ let era = PRICE_ERAS[0];
2982
+ for (const candidate of PRICE_ERAS) if (timeMs >= candidate.fromMs) era = candidate;
2983
+ return era;
2984
+ }
2985
+ var halved = (rates) => ({
2986
+ cacheHit: rates.cacheHit / 2,
2987
+ input: rates.input / 2,
2988
+ output: rates.output / 2
2989
+ });
2990
+ function tierOf(model) {
2991
+ const name2 = model.toLowerCase();
2992
+ if (name2.includes("vision")) return "vision";
2993
+ if (name2.includes("flash")) return "flash";
2994
+ return "pro";
2995
+ }
2996
+ function billedTierOf(timeMs, model) {
2997
+ const tier = tierOf(model);
2998
+ return tier === "pro" && timeMs >= PRO_ROUTED_TO_FLASH_FROM_MS ? "flash" : tier;
2999
+ }
3000
+ function isBeijingWeekday(timeMs) {
3001
+ const day = new Date(timeMs + BEIJING_OFFSET_MS3).getUTCDay();
3002
+ return day >= 1 && day <= 5;
3003
+ }
3004
+ function isPeak(timeMs) {
3005
+ if (eraAt(timeMs).weekdaysOnly && !isBeijingWeekday(timeMs)) return false;
3006
+ const hour = Math.floor(((timeMs + BEIJING_OFFSET_MS3) % 864e5 + 864e5) % 864e5 / 36e5);
3007
+ return PEAK_WINDOWS.some(([from, to]) => hour >= from && hour < to);
3008
+ }
3009
+ function ratesAt(timeMs, model) {
3010
+ if (timeMs < PEAK_PRICING_FROM_MS) return FLAT_RATES[tierOf(model)];
3011
+ const peak = eraAt(timeMs).peak[billedTierOf(timeMs, model)];
3012
+ return isPeak(timeMs) ? peak : halved(peak);
3013
+ }
3014
+
3015
+ // src/client/session-cost-pill.tsx
3016
+ var import_jsx_runtime6 = require("react/jsx-runtime");
3017
+ var PANEL_MARGIN = 12;
3018
+ var PANEL_GAP = 8;
3019
+ var cachedData2 = (sessionId) => {
3020
+ const response = getCachedSessionUsage(sessionId);
3021
+ return response?.ok === true ? response.data ?? null : null;
3022
+ };
3023
+ function CostIcon() {
3024
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3025
+ "svg",
3026
+ {
3027
+ width: "14",
3028
+ height: "14",
3029
+ viewBox: "0 0 16 16",
3030
+ fill: "none",
3031
+ stroke: "currentColor",
3032
+ strokeWidth: "1",
3033
+ strokeLinecap: "round",
3034
+ strokeLinejoin: "round",
3035
+ "aria-hidden": "true",
3036
+ focusable: "false",
3037
+ children: [
3038
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("circle", { cx: "8", cy: "8", r: "6" }),
3039
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5.5 5.3 8 8.4l2.5-3.1" }),
3040
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M8 8.4v2.9" }),
3041
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5.8 9.5h4.4" }),
3042
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("path", { d: "M5.8 10.9h4.4" })
3043
+ ]
3044
+ }
3045
+ );
3046
+ }
3047
+ function SessionCostPill({ sessionId }) {
3048
+ const { t, locale } = useI18n();
3049
+ const [data, setData] = (0, import_react6.useState)(
3050
+ () => sessionId === void 0 || sessionId === "" ? null : cachedData2(sessionId)
3051
+ );
3052
+ const [open, setOpen] = (0, import_react6.useState)(false);
3053
+ const anchorRef = (0, import_react6.useRef)(null);
3054
+ const panelRef = (0, import_react6.useRef)(null);
3055
+ const [pos, setPos] = (0, import_react6.useState)(null);
3056
+ (0, import_react6.useEffect)(() => {
3057
+ if (sessionId === void 0 || sessionId === "") return;
3058
+ setData(cachedData2(sessionId));
3059
+ const unsubscribe = subscribeSessionUsage(sessionId, () => setData(cachedData2(sessionId)));
3060
+ if (getCachedSessionUsage(sessionId) === null) void fetchSessionUsage(sessionId);
3061
+ return unsubscribe;
3062
+ }, [sessionId]);
3063
+ (0, import_react6.useLayoutEffect)(() => {
3064
+ if (!open) {
3065
+ setPos(null);
3066
+ return;
3067
+ }
3068
+ const anchor = anchorRef.current;
3069
+ const panel = panelRef.current;
3070
+ if (anchor === null || panel === null) return;
3071
+ const pill = anchor.getBoundingClientRect();
3072
+ const width = panel.getBoundingClientRect().width;
3073
+ setPos({
3074
+ left: Math.min(Math.max(pill.left, PANEL_MARGIN), Math.max(PANEL_MARGIN, window.innerWidth - width - PANEL_MARGIN)),
3075
+ bottom: window.innerHeight - pill.top + PANEL_GAP
3076
+ });
3077
+ }, [open, data]);
3078
+ (0, import_react6.useEffect)(() => {
3079
+ if (!open) return;
3080
+ const close = () => setOpen(false);
3081
+ const onPointerDown = (event) => {
3082
+ const target = event.target;
3083
+ if (!(target instanceof Node)) return;
3084
+ if (anchorRef.current?.contains(target) === true || panelRef.current?.contains(target) === true) return;
3085
+ close();
3086
+ };
3087
+ const onKeyDown = (event) => {
3088
+ if (event.key === "Escape") close();
3089
+ };
3090
+ document.addEventListener("pointerdown", onPointerDown, true);
3091
+ document.addEventListener("keydown", onKeyDown);
3092
+ window.addEventListener("resize", close);
3093
+ document.addEventListener("scroll", close, true);
3094
+ return () => {
3095
+ document.removeEventListener("pointerdown", onPointerDown, true);
3096
+ document.removeEventListener("keydown", onKeyDown);
3097
+ window.removeEventListener("resize", close);
3098
+ document.removeEventListener("scroll", close, true);
3099
+ };
3100
+ }, [open]);
3101
+ if (sessionId === void 0 || sessionId === "" || data === null || data.calls === 0) return null;
3102
+ const amount = `\xA5${fmtTurnCost(data.cost)}`;
3103
+ const priceAt = data.lastActive ?? Date.now();
3104
+ const split = data.models.reduce(
3105
+ (sum, model) => {
3106
+ const rates = ratesAt(priceAt, model.model);
3107
+ const weighted = {
3108
+ input: model.input * rates.input,
3109
+ cache: model.cache * rates.cacheHit,
3110
+ output: model.output * rates.output
3111
+ };
3112
+ const weight = weighted.input + weighted.cache + weighted.output;
3113
+ if (weight <= 0) return sum;
3114
+ sum.input += model.cost * (weighted.input / weight);
3115
+ sum.cache += model.cost * (weighted.cache / weight);
3116
+ sum.output += model.cost * (weighted.output / weight);
3117
+ return sum;
3118
+ },
3119
+ { input: 0, cache: 0, output: 0 }
3120
+ );
3121
+ const rangeTime = new Intl.DateTimeFormat(locale === "en" ? "en-US" : "zh-CN", {
3122
+ timeZone: "Asia/Shanghai",
3123
+ month: "numeric",
3124
+ day: "numeric",
3125
+ hour: "2-digit",
3126
+ minute: "2-digit",
3127
+ hour12: false
3128
+ });
3129
+ const range = data.firstActive !== null && data.lastActive !== null ? `${rangeTime.format(data.firstActive)} \u2013 ${rangeTime.format(data.lastActive)}` : null;
3130
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
3131
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { ref: anchorRef, className: "dq-cost-anchor", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3132
+ "button",
3133
+ {
3134
+ type: "button",
3135
+ className: "dq-cost-pill",
3136
+ "aria-haspopup": "dialog",
3137
+ "aria-expanded": open,
3138
+ "aria-label": t("sessionCost.aria", { amount }),
3139
+ onClick: () => setOpen((value) => !value),
3140
+ children: [
3141
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CostIcon, {}),
3142
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dq-cost-pill-label", children: amount })
3143
+ ]
3144
+ }
3145
+ ) }),
3146
+ open && (0, import_react_dom.createPortal)(
3147
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
3148
+ "div",
3149
+ {
3150
+ ref: panelRef,
3151
+ className: "dq-cost-panel",
3152
+ role: "dialog",
3153
+ "aria-label": t("sessionCost.title"),
3154
+ style: pos === null ? { visibility: "hidden", left: 0, top: 0 } : { visibility: "visible", left: pos.left, bottom: pos.bottom, top: "auto" },
3155
+ children: [
3156
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dq-cost-panel-title", children: [
3157
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "dq-cost-panel-title-label", children: [
3158
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(CostIcon, {}),
3159
+ t("sessionCost.title")
3160
+ ] }),
3161
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dq-cost-panel-value", children: amount })
3162
+ ] }),
3163
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dq-cost-panel-rule", "aria-hidden": true }),
3164
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dl", { className: "dq-cost-details", children: [
3165
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: t("common.input") }),
3166
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dd", { children: [
3167
+ "\xA5",
3168
+ fmtTurnCost(split.input)
3169
+ ] }),
3170
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: t("common.cacheHit") }),
3171
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dd", { children: [
3172
+ "\xA5",
3173
+ fmtTurnCost(split.cache)
3174
+ ] }),
3175
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: t("common.output") }),
3176
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("dd", { children: [
3177
+ "\xA5",
3178
+ fmtTurnCost(split.output)
3179
+ ] }),
3180
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: t("sessionUsage.calls") }),
3181
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { children: fmtInt(data.calls) }),
3182
+ range !== null && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
3183
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dt", { children: t("sessionCost.active") }),
3184
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("dd", { children: range })
3185
+ ] })
3186
+ ] })
3187
+ ]
3188
+ }
3189
+ ),
3190
+ document.body
3191
+ )
3192
+ ] });
3193
+ }
3194
+
2772
3195
  // src/client/styles.ts
2773
3196
  var css = `
2774
3197
  .dsh-quota-root{-webkit-text-size-adjust:100%;text-size-adjust:100%;position:absolute;right:16px;bottom:16px;z-index:2147483000;pointer-events:auto;opacity:1;visibility:visible;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:13px;line-height:1.45;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary,#1f2328);--dq-text-success:#1a7f37;--dq-text-success:color-mix(in oklab,var(--dsw-alias-state-success-primary,#2da44e) 52%,var(--dsw-alias-label-primary,#1f2328) 48%);--dq-text-warn:#9a6700;--dq-text-warn:color-mix(in oklab,var(--dsw-alias-state-warn-primary,#bf8700) 52%,var(--dsw-alias-label-primary,#1f2328) 48%);transition:left .28s cubic-bezier(.22,1,.36,1),top .28s cubic-bezier(.22,1,.36,1),opacity .16s ease-out,visibility 0s linear}
@@ -2807,12 +3230,31 @@ var css = `
2807
3230
  .dq-message-cost-amount{text-decoration-line:underline;text-decoration-style:dotted;text-decoration-color:currentColor;text-underline-offset:4px}
2808
3231
  .dq-message-cost:hover{color:var(--dsw-alias-label-secondary,#3f4852)}
2809
3232
  .dq-message-cost:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px;border-radius:4px}
2810
- .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}
3233
+ .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}
2811
3234
  .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}
2812
3235
  @media (hover:hover){
2813
3236
  [data-time-hover-root] .dq-message-cost{opacity:0;transition:opacity 80ms ease}
2814
3237
  [data-time-hover-root]:hover .dq-message-cost,[data-time-hover-root]:focus-within .dq-message-cost{opacity:1}
2815
3238
  }
3239
+ /* Composer-dock session cost pill: one more reading beside the host's own
3240
+ \u300C\u4F1A\u8BDD\u7EDF\u8BA1\u300D/\u300CToken \u7528\u91CF\u300Dpills (same slot, same compact skin). */
3241
+ .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))}
3242
+ .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}
3243
+ .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)}
3244
+ .dq-cost-pill:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:2px}
3245
+ .dq-cost-pill svg{flex:none}
3246
+ .dq-cost-pill-label{min-width:0;overflow:hidden;text-overflow:ellipsis}
3247
+ /* Click-open breakdown: the host stat-dialog skin (menu surface, prominent
3248
+ elevation, 12/18 rows), portaled to the body and placed above the pill. */
3249
+ .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}
3250
+ .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}
3251
+ .dq-cost-panel-title-label{display:inline-flex;align-items:center;gap:6px;min-width:0}
3252
+ .dq-cost-panel-title-label svg{flex:none}
3253
+ .dq-cost-panel-value{font-variant-numeric:tabular-nums}
3254
+ .dq-cost-panel-rule{margin-bottom:10px;border-top:.5px solid var(--dsw-alias-border-l2,rgba(0,0,0,.1))}
3255
+ .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)}
3256
+ .dq-cost-details dt,.dq-cost-details dd{min-width:0;margin:0}
3257
+ .dq-cost-details dd{color:var(--dsw-alias-label-secondary,#59636e);font-variant-numeric:tabular-nums;text-align:right}
2816
3258
  /* \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
2817
3259
  \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
2818
3260
  padding\uFF0816px/20px\uFF0C\u5361\u7247\u5185\u90E8\u547C\u5438\u611F\uFF0C\u8F6E\u6B21 36 \u5DF2\u5B9A\uFF09\u6DF7\u540C\u3002 */
@@ -2822,10 +3264,10 @@ var css = `
2822
3264
  the input box \u2014 measured in dashboard.tsx as --dq-composer-w, capped at
2823
3265
  the reading-column width so wide viewports are unchanged. */
2824
3266
  .dq-balance{-webkit-text-size-adjust:100%;text-size-adjust:100%;box-sizing:border-box;width:100%;min-width:0;display:flex;flex-direction:column;gap:32px;padding:20px 24px 48px;max-width:min(860px,var(--dq-composer-w,860px));margin:0 auto;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:13px;line-height:1.5;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary,#1f2328);--dq-text-success:#1a7f37;--dq-text-success:color-mix(in oklab,var(--dsw-alias-state-success-primary,#2da44e) 52%,var(--dsw-alias-label-primary,#1f2328) 48%);--dq-text-warn:#9a6700;--dq-text-warn:color-mix(in oklab,var(--dsw-alias-state-warn-primary,#bf8700) 52%,var(--dsw-alias-label-primary,#1f2328) 48%);}
2825
- @media (max-width:620px){.dq-balance{max-width:100%;gap:24px;padding:16px 12px calc(var(--dq-composer-h,126px) + 16px)}.dq-card,.dq-card--hero,.dq-status-row{padding:0 16px}.dq-card--primary{padding:16px}}
3267
+ @media (max-width:620px){.dq-balance{max-width:100%;gap:24px;padding:16px 12px calc(var(--dq-composer-h,126px) + 16px)}.dq-card{padding:16px}.dq-card--hero{padding:0 16px}.dq-status-row{padding:0 16px}}
2826
3268
  /* \u8F6E\u6B21 55\uFF1A\u53EA\u6709\u4E3B\u5361\u7247\u5E26\u6846\u3002\u6B21\u7EA7\u5206\u6790\u4E0E\u8BBE\u7F6E\u662F\u65E0\u6846\u5C0F\u8282\uFF0C\u9760 32px \u6BB5\u8DDD\u548C 13px \u4E3B\u8272\u6807\u9898\u5206\u7EC4\uFF1B
2827
3269
  \u5DE6\u53F3 20px \u5185\u8FB9\u8DDD\u8BA9\u5B83\u4EEC\u7684\u6587\u5B57\u8FB9\u4E0E\u4E3B\u5361\u7247\u5185\u6587\u5BF9\u9F50\uFF0C\u6574\u9875\u53EA\u6709\u4E00\u6761\u6587\u5B57\u5DE6\u8FB9\u7EBF\u3002 */
2828
- .dq-card{box-sizing:border-box;min-width:0;max-width:100%;padding:0 20px}
3270
+ .dq-card{box-sizing:border-box;min-width:0;max-width:100%;padding:20px;background:var(--dsw-alias-bg-layer-1,#ffffff);border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.10));border-radius:12px}
2829
3271
  .dq-card-title{display:flex;align-items:baseline;flex-wrap:wrap;gap:4px 8px;font-size:15px;font-weight:600;line-height:1.4;color:var(--dsw-alias-label-primary,#1f2328);margin:0 0 16px;letter-spacing:0;text-wrap:balance}
2830
3272
  .dq-card-caption{font-size:12px;font-weight:400;color:var(--dsw-alias-label-secondary,#59636e)}
2831
3273
  .dq-card-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px}
@@ -2839,9 +3281,10 @@ var css = `
2839
3281
  \u6253\u5F00\u8FD9\u4E2A\u300C\u989D\u5EA6\u300Dtab \u65F6\u4E0A\u4E0B\u6587\u6700\u76F4\u63A5\u76F8\u5173\u7684\u6570\u5B57\uFF08\u8FD9\u4E2A tab \u6302\u5728\u5177\u4F53\u4F1A\u8BDD\u5185\u90E8\uFF0C\u89C1 DESIGN_NOTES\u300C\u4FE1\u606F\u5C42\u7EA7\u300D\uFF09\uFF0C
2840
3282
  \u4E0D\u5C5E\u4E8E\u539F\u6709\u8D26\u6237\u5168\u5C40\u5C42\u7EA7 1-3 \u7684\u4EFB\u4F55\u4E00\u7EA7\uFF0C\u800C\u662F\u4E0E\u4E4B\u6B63\u4EA4\u7684\u53E6\u4E00\u4E2A\u7EF4\u5EA6\uFF0C\u4F46\u540C\u6837\u662F\u9996\u5C4F\u5FC5\u8BFB\uFF0C\u56E0\u6B64\u540C\u6837\u7ED9
2841
3283
  primary \u6743\u91CD\uFF0C\u4E0D\u65B0\u5F00\u7B2C\u4E09\u6863\u3002 */
2842
- .dq-card--primary{padding:20px;background:var(--dsw-alias-bg-layer-1,#ffffff);border:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.1));border-radius:12px}
3284
+ .dq-card--primary{padding:20px;border-color:var(--dsw-alias-border-l3,rgba(0,0,0,.12))}
2843
3285
  /* \u4F59\u989D\u662F\u6574\u9875\u552F\u4E00\u7684 hero\uFF1A\u4E0D\u5957\u6846\uFF0C32px \u6570\u5B57\u72EC\u5360\u4E00\u6863\uFF0C\u8BA9\u5B83\u5728\u906E\u4F4F\u6587\u5B57\u65F6\u4ECD\u662F\u7B2C\u4E00\u773C\u3002 */
2844
- .dq-card--hero{padding:0 20px}
3286
+ .dq-card--hero{padding:0 20px;background:transparent;border:0;border-radius:0}
3287
+ .dq-card-pair{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(340px,100%),1fr));gap:24px;min-width:0}
2845
3288
  .dq-card--hero .dq-stat-label{font-size:13px;margin-bottom:2px}
2846
3289
  .dq-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
2847
3290
  .dq-card--hero .dq-balance-grid{align-items:stretch;gap:12px 48px}
@@ -2849,6 +3292,7 @@ var css = `
2849
3292
  /* hero \u6570\u5B57\u4E0D\u753B\u70B9\u72B6\u4E0B\u5212\u7EBF\uFF0840px \u4E0B\u592A\u91CD\uFF09\uFF1B\u53EF\u70B9\u6027\u9760\u60AC\u505C\u5E95\u8272\u548C title \u63D0\u793A\u3002 */
2850
3293
  .dq-card--hero details>summary{text-decoration:none;margin:-2px -6px;padding:2px 6px;border-radius:8px;transition:background .15s ease}
2851
3294
  .dq-card--hero details>summary:hover,.dq-card--hero details[open]>summary{background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,.05))}
3295
+ @media (max-width:620px){.dq-card{padding:16px}.dq-card--hero{padding:0 16px}}
2852
3296
  .dq-window-switch{display:inline-flex;align-items:center;gap:2px;padding:2px;border-radius:8px;background:var(--dsw-alias-bg-layer-2,rgba(120,130,150,.08))}
2853
3297
  .dq-window-btn{border:0;border-radius:6px;padding:4px 8px;background:transparent;color:var(--dsw-alias-label-secondary,#59636e);font-size:12px;white-space:nowrap;cursor:pointer;transition:background .15s ease,color .15s ease,box-shadow .15s ease}
2854
3298
  .dq-window-btn:hover{color:var(--dsw-alias-label-primary,#1f2328)}
@@ -2993,7 +3437,6 @@ var css = `
2993
3437
  .dq-peak-figures{display:flex;flex-wrap:wrap;gap:12px 32px;margin-top:16px}
2994
3438
  .dq-peak-delta{font-size:12px;font-weight:500;margin-left:8px;color:var(--dq-text-warn)}
2995
3439
  .dq-peak-delta--save{color:var(--dq-text-success)}
2996
- .dq-peak-foot{margin:12px 0 0;font-size:12px;line-height:1.6;color:var(--dsw-alias-label-secondary,#59636e);text-wrap:pretty}
2997
3440
  .dq-cache-figures{display:flex;flex-wrap:wrap;gap:12px 32px;margin-bottom:12px}
2998
3441
  @media (max-width:620px){.dq-cache-figures{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px 16px}.dq-cache-figures .dq-stat{min-width:0}}
2999
3442
  .dq-muted-value{color:var(--dsw-alias-label-secondary,#59636e)!important;font-weight:500}
@@ -3198,6 +3641,77 @@ var css = `
3198
3641
  .dq-refresh-icon{display:inline-block;line-height:1}
3199
3642
  .dq-refresh-icon--spin{animation:dq-spin .9s linear infinite}
3200
3643
  @keyframes dq-spin{to{transform:rotate(360deg)}}
3644
+ /* ============================================================================
3645
+ Host chrome: the \u300C\u989D\u5EA6\u300Dconversation view does not use the chat composer.
3646
+ The attribute is set by index.tsx only when the dashboard is mounted inside
3647
+ the conversation scrollport, so the account-wide panel never hides it.
3648
+ '!important' because the composer's slot anchor is a "display:contents"
3649
+ wrapper whose display we must beat regardless of host rule order.
3650
+ ============================================================================ */
3651
+ html[data-dq-quota-view] [data-slot="conversation.composer"]{display:none!important}
3652
+ /* The narrow-viewport rule pads for the fixed composer; with it hidden there
3653
+ is nothing to clear, so fall back to the page's own bottom breathing room. */
3654
+ html[data-dq-quota-view] .dq-balance{padding-bottom:48px}
3655
+ /* ============================================================================
3656
+ Account-wide \u300C\u989D\u5EA6\u300Dpanel (the optional sidebar entry opens this main
3657
+ keyed panel). The dashboard keeps its own width/padding; the panel only
3658
+ supplies the page title row and the scrolling box the main column lacks.
3659
+ ============================================================================ */
3660
+ /* Account-wide page frame: mirrors DSH's first-level pages (Plugins /
3661
+ Scheduled tasks) \u2014 full-height scroll box, 28px top inset on the heading,
3662
+ a 960px centred column, clamp(24px,4vw,48px) inline + 48px bottom insets and
3663
+ a 20px/28px/500 page title, so the three sibling entries line up. */
3664
+ .dq-panel{box-sizing:border-box;height:100%;min-height:0;overflow-y:auto;overflow-x:hidden;padding:0 clamp(24px,4vw,48px) 48px;background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-primary,#1f2328);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:13px;line-height:1.5}
3665
+ .dq-panel-head{box-sizing:border-box;display:flex;flex-direction:column;align-items:flex-start;gap:4px;width:100%;max-width:960px;margin:0 auto 32px;padding-top:28px}
3666
+ .dq-panel-title{margin:0;font-size:20px;font-weight:500;line-height:28px;color:var(--dsw-alias-label-primary,#1f2328)}
3667
+ .dq-panel-caption{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary,#59636e)}
3668
+ .dq-panel .dq-balance{box-sizing:border-box;width:100%;max-width:960px;padding:0}
3669
+ /* macOS hiddenInset titlebar: the frame's top clearance keeps the heading
3670
+ below the window's top strip, matching the host's sibling pages. */
3671
+ [data-platform="darwin"] .dq-panel-head{padding-top:calc(28px + var(--dsh-frame-top-clearance,0px))}
3672
+ @media (max-width:620px){
3673
+ .dq-panel-head{padding-top:24px;margin-bottom:24px}
3674
+ .dq-panel .dq-balance{max-width:100%;padding:0}
3675
+ }
3676
+ /* Optional sidebar entry. Top placement joins the host's global-panel icon
3677
+ row (only the glyph is ours); bottom placement renders a row beside
3678
+ Settings, collapsing to a square rail button with the sidebar. */
3679
+ /* The glyph is an SVG matching the host's 16px stroke icons; color inherits
3680
+ from the sidebar row so selected/hover states stay the host's business. */
3681
+ .dq-side-icon{flex:none;display:block}
3682
+ .dq-side-entry{display:flex;align-items:center;gap:8px;box-sizing:border-box;width:100%;border:0;border-radius:8px;padding:7px 8px;background:transparent;color:var(--dsw-alias-label-secondary,#59636e);font:inherit;font-size:13px;line-height:1.3;text-align:left;cursor:pointer}
3683
+ .dq-side-entry:hover{background:var(--dsw-alias-bg-layer-2,rgba(120,130,150,.10));color:var(--dsw-alias-label-primary,#1f2328)}
3684
+ .dq-side-entry:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px}
3685
+ .dq-side-entry[aria-pressed="true"]{color:var(--dsw-alias-label-primary,#1f2328)}
3686
+ .dq-side-entry--rail{justify-content:center;width:32px;height:32px;margin:0 auto;padding:0}
3687
+ .dq-side-entry-icon{flex:none;display:inline-flex;align-items:center;justify-content:center}
3688
+ .dq-side-entry-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
3689
+ /* ============================================================================
3690
+ Settings controls for the two layout preferences: a compact select and the
3691
+ draggable tab-order strip.
3692
+ ============================================================================ */
3693
+ .dq-select{font:inherit;font-size:13px;line-height:1.3;color:var(--dsw-alias-label-primary,#1f2328);background:var(--dsw-alias-bg-layer-1,#fff);border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.16));border-radius:8px;padding:5px 8px;min-width:96px;cursor:pointer}
3694
+ .dq-select:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px}
3695
+ .dq-tab-order{display:flex;flex-wrap:wrap;align-items:center;gap:6px;box-sizing:border-box;max-width:100%;min-height:32px;padding:4px;border:1px dashed var(--dsw-alias-border-l2,rgba(0,0,0,.16));border-radius:8px;background:var(--dsw-alias-bg-layer-2,rgba(120,130,150,.06))}
3696
+ .dq-tab-chip{display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08));border-radius:999px;background:var(--dsw-alias-bg-overlay,#fff);color:var(--dsw-alias-label-secondary,#59636e);font-size:12px;line-height:1.4;white-space:nowrap}
3697
+ .dq-tab-chip--current{cursor:grab;color:var(--dsw-alias-label-primary,#1f2328);border-color:var(--dsw-alias-state-business-primary,#0969da);font-weight:600;user-select:none;-webkit-user-select:none;touch-action:none}
3698
+ .dq-tab-chip--current:active{cursor:grabbing}
3699
+ .dq-tab-chip--current:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px}
3700
+ .dq-tab-chip--dragging{opacity:.55}
3701
+ .dq-tab-chip--drop-target{border-style:dashed;border-color:var(--dsw-alias-state-business-primary,#0969da)}
3702
+ /* ============================================================================
3703
+ \u300C\u56FE\u7247 / \u591A\u6A21\u6001\u7528\u91CF\u300Dlives inside the usage-trend card. The grid-rows 0fr\u21921fr
3704
+ trick animates the section's real height as a window range makes it appear
3705
+ or disappear; opacity/translate make the change read as a reveal rather
3706
+ than a reflow. The body keeps 14px of top room in both states (clipped by
3707
+ the 0fr row when closed) so opening never jumps.
3708
+ ============================================================================ */
3709
+ .dq-vision-inline{display:grid;grid-template-rows:0fr;margin-top:0;opacity:0;transform:translateY(-3px);overflow:hidden;transition:grid-template-rows .32s cubic-bezier(.22,1,.36,1),margin-top .32s cubic-bezier(.22,1,.36,1),opacity .2s ease,transform .32s cubic-bezier(.22,1,.36,1)}
3710
+ .dq-vision-inline[data-open="true"]{grid-template-rows:1fr;margin-top:24px;opacity:1;transform:none}
3711
+ .dq-vision-inline-body{min-height:0;overflow:hidden}
3712
+ .dq-vision-inline-body>.dq-chart-title{margin-top:0}
3713
+ .dq-vision-inline-body>.dq-chart-title{margin-top:0}
3714
+
3201
3715
  @media (prefers-reduced-motion:reduce){
3202
3716
  .dsh-quota-root,.dsh-quota-root *,
3203
3717
  .dsh-quota-root *::before,.dsh-quota-root *::after,
@@ -3208,7 +3722,7 @@ var css = `
3208
3722
  `;
3209
3723
 
3210
3724
  // src/client/widget.tsx
3211
- var import_react6 = require("react");
3725
+ var import_react7 = require("react");
3212
3726
 
3213
3727
  // src/client/sessions.ts
3214
3728
  function currentSessionIdOf(state) {
@@ -3217,7 +3731,7 @@ function currentSessionIdOf(state) {
3217
3731
  }
3218
3732
 
3219
3733
  // src/client/widget.tsx
3220
- var import_jsx_runtime6 = require("react/jsx-runtime");
3734
+ var import_jsx_runtime7 = require("react/jsx-runtime");
3221
3735
  var MARGIN = 16;
3222
3736
  var CORNER_KEY = "widget.corner";
3223
3737
  var COLLAPSED_KEY = "widget.collapsed";
@@ -3268,47 +3782,47 @@ function cornerPos(corner, node, bounds) {
3268
3782
  function QuotaWidget(props) {
3269
3783
  const { t } = useI18n();
3270
3784
  const sessionId = props.useSessions(currentSessionIdOf);
3271
- const [visible, setVisible] = (0, import_react6.useState)(widgetVisibleStore.get());
3272
- (0, import_react6.useEffect)(() => widgetVisibleStore.subscribe(() => setVisible(widgetVisibleStore.get())), []);
3273
- const [quotaViewActive2, setQuotaViewActive] = (0, import_react6.useState)(quotaViewActiveStore.get());
3274
- (0, import_react6.useEffect)(() => quotaViewActiveStore.subscribe(() => setQuotaViewActive(quotaViewActiveStore.get())), []);
3275
- const [lowBalance, setLowBalance] = (0, import_react6.useState)(lowBalanceStore.get());
3276
- (0, import_react6.useEffect)(() => lowBalanceStore.subscribe(() => setLowBalance(lowBalanceStore.get())), []);
3277
- const [selectedTabIds, setSelectedTabIds] = (0, import_react6.useState)(widgetTabIdsStore.get());
3278
- (0, import_react6.useEffect)(() => widgetTabIdsStore.subscribe(() => setSelectedTabIds(widgetTabIdsStore.get())), []);
3279
- const [viewTabs, setViewTabs] = (0, import_react6.useState)(() => props.views.list());
3280
- (0, import_react6.useEffect)(() => props.views.subscribe(() => setViewTabs(props.views.list())), [props.views]);
3281
- const [activeViewId, setActiveViewId] = (0, import_react6.useState)(null);
3785
+ const [visible, setVisible] = (0, import_react7.useState)(widgetVisibleStore.get());
3786
+ (0, import_react7.useEffect)(() => widgetVisibleStore.subscribe(() => setVisible(widgetVisibleStore.get())), []);
3787
+ const [quotaViewActive2, setQuotaViewActive] = (0, import_react7.useState)(quotaViewActiveStore.get());
3788
+ (0, import_react7.useEffect)(() => quotaViewActiveStore.subscribe(() => setQuotaViewActive(quotaViewActiveStore.get())), []);
3789
+ const [lowBalance, setLowBalance] = (0, import_react7.useState)(lowBalanceStore.get());
3790
+ (0, import_react7.useEffect)(() => lowBalanceStore.subscribe(() => setLowBalance(lowBalanceStore.get())), []);
3791
+ const [selectedTabIds, setSelectedTabIds] = (0, import_react7.useState)(widgetTabIdsStore.get());
3792
+ (0, import_react7.useEffect)(() => widgetTabIdsStore.subscribe(() => setSelectedTabIds(widgetTabIdsStore.get())), []);
3793
+ const [viewTabs, setViewTabs] = (0, import_react7.useState)(() => props.views.list());
3794
+ (0, import_react7.useEffect)(() => props.views.subscribe(() => setViewTabs(props.views.list())), [props.views]);
3795
+ const [activeViewId, setActiveViewId] = (0, import_react7.useState)(null);
3282
3796
  const cachedBalance = getCachedBalance();
3283
- const [data, setData] = (0, import_react6.useState)(cachedBalance?.data ?? null);
3284
- const [error, setError] = (0, import_react6.useState)(null);
3285
- const [loading, setLoading] = (0, import_react6.useState)(cachedBalance === null);
3286
- const [collapsed, setCollapsed] = (0, import_react6.useState)(() => loadPref(COLLAPSED_KEY, isBoolean, false));
3287
- const [pos, setPos] = (0, import_react6.useState)(null);
3288
- const [corner, setCorner] = (0, import_react6.useState)(() => loadPref(CORNER_KEY, isCorner, "br"));
3289
- const [dragging, setDragging] = (0, import_react6.useState)(false);
3290
- const rootRef = (0, import_react6.useRef)(null);
3291
- const dragRef = (0, import_react6.useRef)(null);
3797
+ const [data, setData] = (0, import_react7.useState)(cachedBalance?.data ?? null);
3798
+ const [error, setError] = (0, import_react7.useState)(null);
3799
+ const [loading, setLoading] = (0, import_react7.useState)(cachedBalance === null);
3800
+ const [collapsed, setCollapsed] = (0, import_react7.useState)(() => loadPref(COLLAPSED_KEY, isBoolean, false));
3801
+ const [pos, setPos] = (0, import_react7.useState)(null);
3802
+ const [corner, setCorner] = (0, import_react7.useState)(() => loadPref(CORNER_KEY, isCorner, "br"));
3803
+ const [dragging, setDragging] = (0, import_react7.useState)(false);
3804
+ const rootRef = (0, import_react7.useRef)(null);
3805
+ const dragRef = (0, import_react7.useRef)(null);
3292
3806
  const readUsage = () => {
3293
3807
  const cached = getCachedUsage();
3294
3808
  return { data: cached?.ok === true ? cached.data ?? null : null, at: getCachedUsageAt() };
3295
3809
  };
3296
- const [usage, setUsage] = (0, import_react6.useState)(readUsage);
3297
- (0, import_react6.useEffect)(() => subscribeUsage(() => setUsage(readUsage())), []);
3810
+ const [usage, setUsage] = (0, import_react7.useState)(readUsage);
3811
+ (0, import_react7.useEffect)(() => subscribeUsage(() => setUsage(readUsage())), []);
3298
3812
  const readSessionUsage = (id) => {
3299
3813
  if (id === void 0) return null;
3300
3814
  const response = getCachedSessionUsage(id);
3301
3815
  return response?.ok === true ? response.data ?? null : null;
3302
3816
  };
3303
- const [sessionUsage, setSessionUsage] = (0, import_react6.useState)(() => readSessionUsage(sessionId));
3304
- (0, import_react6.useEffect)(() => {
3817
+ const [sessionUsage, setSessionUsage] = (0, import_react7.useState)(() => readSessionUsage(sessionId));
3818
+ (0, import_react7.useEffect)(() => {
3305
3819
  setSessionUsage(readSessionUsage(sessionId));
3306
3820
  if (sessionId === void 0) return;
3307
3821
  const unsubscribe = subscribeSessionUsage(sessionId, () => setSessionUsage(readSessionUsage(sessionId)));
3308
3822
  void fetchSessionUsage(sessionId);
3309
3823
  return unsubscribe;
3310
3824
  }, [sessionId]);
3311
- (0, import_react6.useLayoutEffect)(() => {
3825
+ (0, import_react7.useLayoutEffect)(() => {
3312
3826
  const viewIds = viewTabs.map((tab) => tab.id);
3313
3827
  const read = () => {
3314
3828
  const next = getActiveConversationViewId(getShellFrame(rootRef.current), viewIds);
@@ -3345,14 +3859,14 @@ function QuotaWidget(props) {
3345
3859
  setLoading(false);
3346
3860
  }
3347
3861
  };
3348
- (0, import_react6.useEffect)(() => {
3862
+ (0, import_react7.useEffect)(() => {
3349
3863
  void load(true);
3350
3864
  const id = setInterval(() => {
3351
3865
  void load(false);
3352
3866
  }, 6e4);
3353
3867
  return () => clearInterval(id);
3354
3868
  }, []);
3355
- (0, import_react6.useLayoutEffect)(() => {
3869
+ (0, import_react7.useLayoutEffect)(() => {
3356
3870
  if (!visible || quotaViewActive2) return;
3357
3871
  const node = rootRef.current;
3358
3872
  if (node === null) return;
@@ -3480,9 +3994,9 @@ function QuotaWidget(props) {
3480
3994
  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";
3481
3995
  const ageMinutes = usage.at === null ? null : Math.floor((Date.now() - usage.at) / 6e4);
3482
3996
  const usageAgeText = ageMinutes === null || ageMinutes < 1 ? t("widget.updatedNow") : t("widget.stale", { minutes: ageMinutes });
3483
- const sessionCostRow = sessionUsage === null ? null : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-row", title: t("widget.sessionCostTitle"), children: [
3484
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dsh-quota-label", children: t("widget.sessionCost") }),
3485
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "dsh-quota-value", children: [
3997
+ const sessionCostRow = sessionUsage === null ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-row", title: t("widget.sessionCostTitle"), children: [
3998
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-label", children: t("widget.sessionCost") }),
3999
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "dsh-quota-value", children: [
3486
4000
  "\xA5",
3487
4001
  fmtTurnCost(sessionUsage.cost)
3488
4002
  ] })
@@ -3490,50 +4004,50 @@ function QuotaWidget(props) {
3490
4004
  let body = null;
3491
4005
  if (!collapsed) {
3492
4006
  if (loading && data === null && error === null) {
3493
- body = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-body", children: [
3494
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { children: t("widget.loading") }),
4007
+ body = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-body", children: [
4008
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { children: t("widget.loading") }),
3495
4009
  sessionCostRow
3496
4010
  ] });
3497
4011
  } else if (error !== null && data === null) {
3498
- body = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-body", children: [
3499
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dsh-quota-error", children: localizeApiError(error, t, "error.query") }),
4012
+ body = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-body", children: [
4013
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-quota-error", children: localizeApiError(error, t, "error.query") }),
3500
4014
  sessionCostRow
3501
4015
  ] });
3502
4016
  } else if (primary !== null) {
3503
- body = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-body", children: [
3504
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dsh-quota-remaining-label", children: t("balance.remaining") }),
3505
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { title: low ? t("widget.lowTitle", { amount: fmt(lowBalance) }) : void 0, children: [
3506
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: `dsh-quota-total${low ? " dsh-quota-total--low" : ""}`, children: fmt(primary.total) }),
3507
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dsh-quota-currency", children: primary.currency })
4017
+ body = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-body", children: [
4018
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-quota-remaining-label", children: t("balance.remaining") }),
4019
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { title: low ? t("widget.lowTitle", { amount: fmt(lowBalance) }) : void 0, children: [
4020
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: `dsh-quota-total${low ? " dsh-quota-total--low" : ""}`, children: fmt(primary.total) }),
4021
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-currency", children: primary.currency })
3508
4022
  ] }),
3509
- usage.data !== null && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-row", title: t("widget.todayCostTitle", { age: usageAgeText }), children: [
3510
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dsh-quota-label", children: t("widget.todayCost") }),
3511
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { className: "dsh-quota-value", children: [
4023
+ usage.data !== null && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-row", title: t("widget.todayCostTitle", { age: usageAgeText }), children: [
4024
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-label", children: t("widget.todayCost") }),
4025
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: "dsh-quota-value", children: [
3512
4026
  "\xA5",
3513
4027
  fmt(usage.data.summary.today.cost)
3514
4028
  ] })
3515
4029
  ] }),
3516
4030
  sessionCostRow,
3517
- data?.isAvailable === false && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-row", children: [
3518
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dsh-quota-label", children: t("common.status") }),
3519
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dsh-quota-value dsh-quota-error", children: t("common.unavailable") })
4031
+ data?.isAvailable === false && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-row", children: [
4032
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-label", children: t("common.status") }),
4033
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-value dsh-quota-error", children: t("common.unavailable") })
3520
4034
  ] })
3521
4035
  ] });
3522
4036
  } else {
3523
- body = /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-body", children: [
3524
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "dsh-quota-error", children: t("widget.noBalance") }),
4037
+ body = /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-body", children: [
4038
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-quota-error", children: t("widget.noBalance") }),
3525
4039
  sessionCostRow
3526
4040
  ] });
3527
4041
  }
3528
4042
  }
3529
4043
  const style = pos !== null ? { left: `${pos.x}px`, top: `${pos.y}px`, right: "auto", bottom: "auto" } : void 0;
3530
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4044
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3531
4045
  "div",
3532
4046
  {
3533
4047
  className: `dsh-quota-root${dragging ? " dsh-quota-dragging" : ""}${hidden ? " dsh-quota-root--hidden" : ""}`,
3534
4048
  style,
3535
4049
  "aria-hidden": hidden,
3536
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
4050
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3537
4051
  "div",
3538
4052
  {
3539
4053
  ref: rootRef,
@@ -3545,14 +4059,14 @@ function QuotaWidget(props) {
3545
4059
  onPointerCancel: () => endDrag(false),
3546
4060
  onLostPointerCapture: () => endDrag(false),
3547
4061
  children: [
3548
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-header", children: [
3549
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-title", children: [
3550
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dsh-quota-grip", title: t("widget.dragGrip"), children: "\u2807" }),
3551
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: dotClass }),
3552
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "dsh-quota-name", children: t("widget.title") })
4062
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-header", children: [
4063
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-title", children: [
4064
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-grip", title: t("widget.dragGrip"), children: "\u2807" }),
4065
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: dotClass }),
4066
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dsh-quota-name", children: t("widget.title") })
3553
4067
  ] }),
3554
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "dsh-quota-actions", children: [
3555
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4068
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dsh-quota-actions", children: [
4069
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3556
4070
  "button",
3557
4071
  {
3558
4072
  className: "dsh-quota-btn",
@@ -3568,7 +4082,7 @@ function QuotaWidget(props) {
3568
4082
  children: "\u21BB"
3569
4083
  }
3570
4084
  ),
3571
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4085
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
3572
4086
  "button",
3573
4087
  {
3574
4088
  className: "dsh-quota-btn",
@@ -3586,7 +4100,7 @@ function QuotaWidget(props) {
3586
4100
  )
3587
4101
  ] })
3588
4102
  ] }),
3589
- 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: [
4103
+ collapsed && primary !== null && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "dsh-quota-collapsed-total-row", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { className: `dsh-quota-collapsed-total${low ? " dsh-quota-total--low" : ""}`, children: [
3590
4104
  fmt(primary.total),
3591
4105
  " ",
3592
4106
  primary.currency
@@ -3617,9 +4131,10 @@ function conversationViewsSource(slots) {
3617
4131
  }
3618
4132
 
3619
4133
  // src/client/index.tsx
3620
- var import_jsx_runtime7 = require("react/jsx-runtime");
4134
+ var import_jsx_runtime8 = require("react/jsx-runtime");
3621
4135
  var name = "dsh-usage-dashboard";
3622
- var inject = ["slots", "locale"];
4136
+ var inject = ["slots", "locale", "layout"];
4137
+ var openQuotaTab = null;
3623
4138
  function apply(ctx) {
3624
4139
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-usage-dashboard: browser dictionaries");
3625
4140
  const t = ctx.locale.bind(NS);
@@ -3637,7 +4152,11 @@ function apply(ctx) {
3637
4152
  });
3638
4153
  ctx.slots.inject("shell.overlay", () => ctx.slots.register(
3639
4154
  { name: "shell.overlay", id: "deepseek-quota", order: 1e3, locale: NS, label: () => t("widget.title") },
3640
- ({ 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 }) }) })
4155
+ ({ t: slotT, useSessions }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorBoundary, { t: slotT, silent: true, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(QuotaWidget, { useSessions, views }) }) })
4156
+ ));
4157
+ ctx.slots.inject("conversation.composer.dock", () => ctx.slots.register(
4158
+ { name: "conversation.composer.dock", id: "quota-cost", order: 2, locale: NS },
4159
+ ({ t: slotT, sessionId }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorBoundary, { t: slotT, silent: true, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(SessionCostPill, { sessionId }) }) })
3641
4160
  ));
3642
4161
  ctx.slots.inject("conversation.chat.assistant-actions", () => ctx.slots.register(
3643
4162
  {
@@ -3647,19 +4166,155 @@ function apply(ctx) {
3647
4166
  locale: NS,
3648
4167
  label: () => t("messageCost.label")
3649
4168
  },
3650
- ({ 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 }) }) })
3651
- ));
3652
- ctx.slots.inject("conversation.view", () => ctx.slots.register(
3653
- { name: "conversation.view", id: "balance", order: 20, locale: NS, label: () => t("nav.quota") },
3654
- // `conversation.view` is a session-scoped slot, so the host injects its
3655
- // standard session props (including `sessionId`) alongside `t` — this
3656
- // plugin only destructures the two fields it needs, per the existing
3657
- // "hand-write the minimal structural type" convention (see context.ts).
3658
- // `sessionId` is typed optional defensively even though the host is
3659
- // expected to always supply it: BalanceDashboard renders the current-
3660
- // session card only when it actually has one, never throws either way.
3661
- ({ t: slotT, sessionId }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ErrorBoundary, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BalanceDashboard, { sessionId, views }) }) })
4169
+ ({ t: slotT, sessionId, messageId }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorBoundary, { t: slotT, silent: true, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageCost, { sessionId, messageId }) }) })
3662
4170
  ));
4171
+ const QuotaView = ({ t: slotT, sessionId, openView }) => {
4172
+ (0, import_react8.useEffect)(() => {
4173
+ if (openView !== void 0) openQuotaTab = openView;
4174
+ }, [openView]);
4175
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorBoundary, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(BalanceDashboard, { sessionId, views }) }) });
4176
+ };
4177
+ ctx.effect(() => {
4178
+ let disposeView = null;
4179
+ const mount = () => {
4180
+ disposeView?.();
4181
+ disposeView = ctx.slots.inject("conversation.view", () => ctx.slots.register(
4182
+ {
4183
+ name: "conversation.view",
4184
+ id: "balance",
4185
+ order: viewOrderForIndex(ctx.slots.entries("conversation.view"), quotaTabIndexStore.get()),
4186
+ locale: NS,
4187
+ label: () => t("nav.quota")
4188
+ },
4189
+ // `conversation.view` is a session-scoped slot, so the host injects its
4190
+ // standard session props (including `sessionId`) alongside `t`; only the
4191
+ // fields this plugin needs are destructured, per the existing
4192
+ // "hand-write the minimal structural type" convention (see context.ts).
4193
+ // `sessionId` stays optional: BalanceDashboard renders the current-
4194
+ // session card only when it actually has one, never throws either way.
4195
+ QuotaView
4196
+ ));
4197
+ };
4198
+ mount();
4199
+ const unsubscribe = quotaTabIndexStore.subscribe(() => {
4200
+ const restore = document !== void 0 && document.documentElement.hasAttribute("data-dq-quota-view");
4201
+ disposeView?.();
4202
+ disposeView = null;
4203
+ mount();
4204
+ if (restore) queueMicrotask(() => openQuotaTab?.("balance", "dsh-usage-dashboard:tab-order"));
4205
+ });
4206
+ return () => {
4207
+ unsubscribe();
4208
+ disposeView?.();
4209
+ };
4210
+ }, "dsh-usage-dashboard: quota tab registration");
4211
+ const QuotaPanel = ({ t: slotT }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dq-panel", children: [
4212
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "dq-panel-head", "data-window-drag": true, children: [
4213
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h1", { className: "dq-panel-title", children: slotT("nav.quota") }),
4214
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dq-panel-caption", children: slotT("panel.caption") })
4215
+ ] }),
4216
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorBoundary, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(BalanceDashboard, { views }) }) })
4217
+ ] });
4218
+ const QuotaGlyph = ({ size, active }) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4219
+ "svg",
4220
+ {
4221
+ className: "dq-side-icon",
4222
+ "data-active": active === true ? "true" : "false",
4223
+ width: size,
4224
+ height: size,
4225
+ viewBox: "0 0 16 16",
4226
+ fill: "none",
4227
+ stroke: "currentColor",
4228
+ strokeWidth: 1.1,
4229
+ strokeLinecap: "round",
4230
+ strokeLinejoin: "round",
4231
+ "aria-hidden": "true",
4232
+ focusable: "false",
4233
+ children: [
4234
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M3.5 3.3 8 8.7l4.5-5.4" }),
4235
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M8 8.7V13.2" }),
4236
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M3.5 10.2h9" }),
4237
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { d: "M3.5 12.1h9" })
4238
+ ]
4239
+ }
4240
+ );
4241
+ const QuotaFooterButton = ({ wide, t: slotT }) => {
4242
+ const [active, setActive] = (0, import_react8.useState)(() => ctx.layout.panelInfo.getSnapshot().activePanelId === QUOTA_PANEL_ID);
4243
+ (0, import_react8.useEffect)(() => {
4244
+ const sync = () => {
4245
+ setActive(ctx.layout.panelInfo.getSnapshot().activePanelId === QUOTA_PANEL_ID);
4246
+ };
4247
+ sync();
4248
+ return ctx.layout.panelInfo.subscribe(sync);
4249
+ }, []);
4250
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
4251
+ "button",
4252
+ {
4253
+ type: "button",
4254
+ className: `dq-side-entry${wide ? "" : " dq-side-entry--rail"}`,
4255
+ title: slotT("panel.openAria"),
4256
+ "aria-label": slotT("nav.quota"),
4257
+ "aria-pressed": active,
4258
+ onClick: () => {
4259
+ ctx.layout.selectPanel(active ? null : QUOTA_PANEL_ID);
4260
+ },
4261
+ children: [
4262
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dq-side-entry-icon", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(QuotaGlyph, { size: 16 }) }),
4263
+ wide && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "dq-side-entry-label", children: slotT("nav.quota") })
4264
+ ]
4265
+ }
4266
+ );
4267
+ };
4268
+ ctx.effect(() => ctx.slots.inject("main", () => ctx.slots.register(
4269
+ { name: "main", key: QUOTA_PANEL_ID, locale: NS },
4270
+ QuotaPanel
4271
+ )), "dsh-usage-dashboard: quota panel");
4272
+ ctx.effect(() => {
4273
+ let disposeEntry = null;
4274
+ const sync = () => {
4275
+ disposeEntry?.();
4276
+ disposeEntry = null;
4277
+ const placement = sidebarEntryStore.get();
4278
+ if (placement === "top") {
4279
+ disposeEntry = ctx.slots.inject("sidebar.panellist", () => ctx.slots.register(
4280
+ { name: "sidebar.panellist", id: QUOTA_PANEL_ID, order: 1e3, label: () => t("nav.quota"), locale: NS },
4281
+ QuotaGlyph
4282
+ ));
4283
+ return;
4284
+ }
4285
+ if (placement === "bottom") {
4286
+ disposeEntry = ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register(
4287
+ { name: "sidebar.footer.action", id: "quota", order: 10, locale: NS },
4288
+ QuotaFooterButton
4289
+ ));
4290
+ return;
4291
+ }
4292
+ if (ctx.layout.panelInfo.getSnapshot().activePanelId === QUOTA_PANEL_ID) ctx.layout.selectPanel(null);
4293
+ };
4294
+ sync();
4295
+ const unsubscribe = sidebarEntryStore.subscribe(sync);
4296
+ return () => {
4297
+ unsubscribe();
4298
+ disposeEntry?.();
4299
+ };
4300
+ }, "dsh-usage-dashboard: sidebar entry");
4301
+ ctx.effect(() => {
4302
+ let applied = false;
4303
+ const sync = () => {
4304
+ const root = document.querySelector(".dq-balance");
4305
+ const should = quotaViewActiveStore.get() && root !== null && root.closest("[data-conversation-scroll]") !== null;
4306
+ if (should === applied) return;
4307
+ applied = should;
4308
+ if (should) document.documentElement.setAttribute("data-dq-quota-view", "");
4309
+ else document.documentElement.removeAttribute("data-dq-quota-view");
4310
+ };
4311
+ sync();
4312
+ const unsubscribe = quotaViewActiveStore.subscribe(sync);
4313
+ return () => {
4314
+ unsubscribe();
4315
+ if (applied) document.documentElement.removeAttribute("data-dq-quota-view");
4316
+ };
4317
+ }, "dsh-usage-dashboard: composer visibility");
3663
4318
  }
3664
4319
  return module.exports; } });
3665
4320
  //# sourceMappingURL=client.js.map