@cassius0924/dsh-usage-dashboard 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -26,6 +26,7 @@ __export(index_exports, {
26
26
  name: () => name
27
27
  });
28
28
  module.exports = __toCommonJS(index_exports);
29
+ var import_react7 = require("react");
29
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",
@@ -578,7 +586,7 @@ var en = {
578
586
  "delta.noBaseline": "No baseline",
579
587
  "delta.flat": "No change",
580
588
  "period.volume": "{tokens} tokens \xB7 {calls}",
581
- "budget.unset": "Set a monthly budget to see whether current spending is on track.",
589
+ "budget.unset": "No monthly budget set.",
582
590
  "budget.configure": "Set budget",
583
591
  "budget.over": "Over budget by \xA5{amount}",
584
592
  "budget.forecastOver": "At this pace, \xA5{amount} over by month-end",
@@ -601,7 +609,6 @@ var en = {
601
609
  "pricing.unit": "Unit: {currency} / million tokens. ",
602
610
  "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
611
  "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
612
  "pricing.unknown": "Unknown models use deepseek-v4-pro rates.",
606
613
  "coverage.noRange": "No valid usage timestamps",
607
614
  "coverage.title": "Coverage",
@@ -622,23 +629,19 @@ var en = {
622
629
  "export.modelCsv": "Model CSV",
623
630
  "export.fullJson": "Full JSON",
624
631
  "export.jsonHint": "Charts and rankings included",
625
- "cache.empty": "No prompt tokens are available for cache analysis yet.",
632
+ "cache.empty": "No cache data yet.",
626
633
  "cache.rate": "Cache hit rate",
627
634
  "cache.saved": "Estimated savings",
628
635
  "cache.allMiss": "Cost with no hits",
629
636
  "cache.aria": "{hit}% cache hits, {miss}% misses",
630
637
  "cache.hitLegend": "Cache hits {tokens}",
631
638
  "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.",
639
+ "peak.empty": "No usage yet.",
635
640
  "peak.aria": "{peak}% peak, {offPeak}% off-peak",
636
641
  "peak.peakLegend": "Peak {share}% \xB7 {tokens} tokens \xB7 {calls}",
637
642
  "peak.offPeakLegend": "Off-peak {share}% \xB7 {tokens} tokens \xB7 {calls}",
638
643
  "peak.shift": "If all peak usage moved off-peak",
639
644
  "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
645
  "vision.empty": "No image input in {window}.",
643
646
  "vision.title": "Image / multimodal usage",
644
647
  "vision.images": "Images",
@@ -646,7 +649,7 @@ var en = {
646
649
  "vision.cost": "Estimated cost",
647
650
  "vision.tokensShare": "{percent}% of input",
648
651
  "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.",
652
+ "vision.note": "Estimated with the official rule; provider usage numbers are the source of truth.",
650
653
  "vision.sessionsTitle": "Sessions with the most images",
651
654
  "vision.sessionRow": "{images} images \xB7 \xA5{cost}",
652
655
  "vision.dailyTitle": "{window} \xB7 images per day",
@@ -655,7 +658,7 @@ var en = {
655
658
  "sessions.empty": "No sessions have incurred cost yet.",
656
659
  "sessions.detail": "{tokens} tokens \xB7 {calls} \xB7 {ago}",
657
660
  "sessions.share": "{percent}% of total",
658
- "sessions.more": "{count} sessions have usage; the {shown} most expensive are shown above.",
661
+ "sessions.more": "{count} sessions; showing the {shown} most expensive.",
659
662
  "sessions.fallbackTitle": "Session {id}",
660
663
  "models.empty": "No model usage can be classified yet.",
661
664
  "models.shareAria": "{name} accounts for {percent}% of total cost",
@@ -682,9 +685,9 @@ var en = {
682
685
  "chart.dimensionLabel": "Chart interval",
683
686
  "chart.daily": "Daily",
684
687
  "chart.hourly": "Hourly",
685
- "chart.noWindowData": "No usage in {window}. Try a longer range.",
688
+ "chart.noWindowData": "No usage in {window}.",
686
689
  "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.",
690
+ "chart.empty": "No usage yet.",
688
691
  "chart.hourPoint": "{hour}:00",
689
692
  "chart.tooltip": "{head} \xB7 {tokens} tokens \xB7 \xA5{cost} \xB7 {calls}",
690
693
  "chart.less": "Less",
@@ -750,7 +753,7 @@ var en = {
750
753
  "balance.overYear": "> 365 days",
751
754
  "balance.noRunway": "No usage in the last {days} days, so runway cannot be estimated",
752
755
  "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.",
756
+ "balance.empty": "Balance is unavailable. Check your sign-in or API key, then refresh.",
754
757
  "sessionUsage.title": "Current session spend",
755
758
  "common.estimated": "Estimated cost",
756
759
  "sessionUsage.cost": "Cost",
@@ -758,7 +761,7 @@ var en = {
758
761
  "sessionUsage.calls": "Calls",
759
762
  "sessionUsage.range": "Active {range}",
760
763
  "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.",
764
+ "sessionUsage.empty": "No usage in this session yet.",
762
765
  "overview.title": "Spend overview",
763
766
  "overview.today": "Today",
764
767
  "overview.month": "This month",
@@ -780,9 +783,9 @@ var en = {
780
783
  "links.status": "Service status",
781
784
  "settings.title": "Settings",
782
785
  "settings.widget": "Show the floating balance widget on other views",
783
- "settings.widgetHint": "The widget always hides on Usage; choose its other views below.",
786
+ "settings.widgetHint": "Hidden on Usage; shown on the views selected below.",
784
787
  "settings.widgetTabs": "Show widget on",
785
- "settings.widgetTabsHint": "This live list comes from DSH\u2019s tab registry, including tabs added by other plugins.",
788
+ "settings.widgetTabsHint": "Updates automatically as views are installed.",
786
789
  "settings.widgetTabsEmpty": "There are no configurable conversation views right now.",
787
790
  "settings.lowBalance": "Balance warning",
788
791
  "settings.lowBalanceOn": "This page and the widget turn amber below {amount} {currency}. Enter 0 to disable.",
@@ -790,6 +793,18 @@ var en = {
790
793
  "settings.monthlyBudget": "Monthly budget",
791
794
  "settings.monthlyBudgetOn": "Track a {amount} CNY budget by Beijing calendar month and forecast month-end spend. Enter 0 to disable.",
792
795
  "settings.monthlyBudgetOff": "Enter an amount above 0 to show progress and a month-end forecast.",
796
+ "settings.sidebarEntry": "Sidebar Usage entry",
797
+ "settings.sidebarEntryHint": "Add a Usage entry at the top or bottom of the sidebar; it opens the account-wide Usage page.",
798
+ "settings.sidebarEntryOff": "Off",
799
+ "settings.sidebarEntryTop": "Top",
800
+ "settings.sidebarEntryBottom": "Bottom",
801
+ "settings.tabPosition": "Usage tab position",
802
+ "settings.tabPositionHint": "Drag Usage to change its tab position.",
803
+ "settings.tabDragAria": "Drag Usage to change its tab position",
804
+ "settings.tabMoveEarlier": "Move earlier",
805
+ "settings.tabMoveLater": "Move later",
806
+ "panel.caption": "Account-wide \xB7 across all sessions",
807
+ "panel.openAria": "Open Usage",
793
808
  "widget.updatedNow": "Updated just now",
794
809
  "widget.stale": "Data from {minutes}m ago; press \u21BB to update",
795
810
  "widget.loading": "Loading\u2026",
@@ -1353,6 +1368,27 @@ var quotaViewActiveStore = {
1353
1368
  }
1354
1369
  };
1355
1370
 
1371
+ // src/client/layout-prefs.ts
1372
+ var QUOTA_PANEL_ID = "quota";
1373
+ var isPlacement = (value) => value === "off" || value === "top" || value === "bottom";
1374
+ var sidebarEntryStore = createStore("layout.sidebarEntry", isPlacement, "off");
1375
+ var isTabIndex = (value) => typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= 50;
1376
+ var quotaTabIndexStore = createStore("layout.tabIndex", isTabIndex, 2);
1377
+ function clampTabIndex(index, count) {
1378
+ return Math.max(0, Math.min(index, Math.max(count, 0)));
1379
+ }
1380
+ function viewOrderForIndex(entries, index, selfId = "balance", fallback = 20) {
1381
+ const others = entries.filter((entry) => entry.options.id !== selfId);
1382
+ if (others.length === 0) return fallback;
1383
+ const clamped = clampTabIndex(index, others.length);
1384
+ const orderAt = (position) => others[position]?.options.order ?? 0;
1385
+ if (clamped === 0) return orderAt(0) - 10;
1386
+ if (clamped === others.length) return orderAt(others.length - 1) + 10;
1387
+ const previous = orderAt(clamped - 1);
1388
+ const next = orderAt(clamped);
1389
+ return previous >= next ? previous + 0.5 : (previous + next) / 2;
1390
+ }
1391
+
1356
1392
  // src/client/dashboard.tsx
1357
1393
  var import_jsx_runtime4 = require("react/jsx-runtime");
1358
1394
  var COMPOSER_FALLBACK_PX = 126;
@@ -1518,7 +1554,6 @@ function PricingNote(props) {
1518
1554
  t("pricing.unit", { currency: pricing.currency }),
1519
1555
  t("pricing.splitNote", { windows: pricing.peakWindows.join(t("common.listSeparator")) }),
1520
1556
  t("pricing.visionNote"),
1521
- t("pricing.modelNote"),
1522
1557
  t("pricing.unknown")
1523
1558
  ] })
1524
1559
  ] })
@@ -1780,8 +1815,7 @@ function CacheCard(props) {
1780
1815
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-legend-swatch dq-legend-swatch--miss" }),
1781
1816
  t("cache.missLegend", { tokens: fmtCompact(totals.input, locale) })
1782
1817
  ] })
1783
- ] }),
1784
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-cache-foot", children: low ? t("cache.lowHint") : t("cache.goodHint") })
1818
+ ] })
1785
1819
  ] });
1786
1820
  }
1787
1821
  function VisionCard(props) {
@@ -1863,11 +1897,7 @@ function PeakCard(props) {
1863
1897
  fmt(split.offPeakEraCost),
1864
1898
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-peak-delta dq-peak-delta--save", children: t("peak.saving", { amount: fmt(shiftSaving) }) })
1865
1899
  ] })
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
- ] })
1900
+ ] }) })
1871
1901
  ] });
1872
1902
  }
1873
1903
  function agoText(ms, t) {
@@ -2118,6 +2148,11 @@ function BalanceDashboard(props) {
2118
2148
  const [selectedModels, setSelectedModels] = (0, import_react4.useState)([]);
2119
2149
  const [windowDays, setWindowDays] = (0, import_react4.useState)(usageWindowStore.get());
2120
2150
  const [chartMetric, setChartMetric] = (0, import_react4.useState)(chartMetricStore.get());
2151
+ const [sidebarEntry, setSidebarEntry] = (0, import_react4.useState)(sidebarEntryStore.get());
2152
+ (0, import_react4.useEffect)(() => sidebarEntryStore.subscribe(() => setSidebarEntry(sidebarEntryStore.get())), []);
2153
+ const [tabDragging, setTabDragging] = (0, import_react4.useState)(false);
2154
+ const [tabDropTarget, setTabDropTarget] = (0, import_react4.useState)(null);
2155
+ const [retainedVision, setRetainedVision] = (0, import_react4.useState)(null);
2121
2156
  const [runwayHovering, setRunwayHovering] = (0, import_react4.useState)(false);
2122
2157
  const [runwayManuallyOpened, setRunwayManuallyOpened] = (0, import_react4.useState)(false);
2123
2158
  const [remainingHovering, setRemainingHovering] = (0, import_react4.useState)(false);
@@ -2290,6 +2325,71 @@ function BalanceDashboard(props) {
2290
2325
  setChartMetric(value);
2291
2326
  chartMetricStore.set(value);
2292
2327
  };
2328
+ const changeSidebarEntry = (value) => {
2329
+ setSidebarEntry(value);
2330
+ sidebarEntryStore.set(value);
2331
+ };
2332
+ const balanceTabIndex = viewTabs.findIndex((tab) => tab.id === "balance");
2333
+ const finalTabIndex = (target, after) => {
2334
+ const gap = after ? target + 1 : target;
2335
+ const dropped = balanceTabIndex >= 0 && gap > balanceTabIndex ? gap - 1 : gap;
2336
+ return Math.max(0, Math.min(dropped, Math.max(viewTabs.length - 1, 0)));
2337
+ };
2338
+ const endTabDrag = () => {
2339
+ setTabDragging(false);
2340
+ setTabDropTarget(null);
2341
+ };
2342
+ const previewTabDropTarget = (index) => {
2343
+ setTabDropTarget((prev) => prev === index ? prev : index);
2344
+ };
2345
+ const applyQuotaTabIndex = (index) => {
2346
+ const next = Math.max(0, Math.min(index, Math.max(viewTabs.length - 1, 0)));
2347
+ if (next === balanceTabIndex) return;
2348
+ quotaTabIndexStore.set(next);
2349
+ window.setTimeout(() => {
2350
+ const chip = document.querySelector(".dq-tab-chip--current");
2351
+ chip?.focus();
2352
+ }, 80);
2353
+ };
2354
+ const dropQuotaTab = (target, after) => {
2355
+ applyQuotaTabIndex(finalTabIndex(target, after));
2356
+ endTabDrag();
2357
+ };
2358
+ const onQuotaTabDragStart = (event) => {
2359
+ setTabDragging(true);
2360
+ event.dataTransfer.effectAllowed = "move";
2361
+ event.dataTransfer.setData("text/plain", "balance");
2362
+ };
2363
+ const onChipDragOver = (event, index) => {
2364
+ if (!tabDragging) return;
2365
+ event.preventDefault();
2366
+ event.stopPropagation();
2367
+ previewTabDropTarget(index);
2368
+ };
2369
+ const onChipDrop = (event, index) => {
2370
+ if (!tabDragging) return;
2371
+ event.preventDefault();
2372
+ event.stopPropagation();
2373
+ const rect = event.currentTarget.getBoundingClientRect();
2374
+ dropQuotaTab(index, event.clientX > rect.left + rect.width / 2);
2375
+ };
2376
+ const onOrderDragOver = (event) => {
2377
+ if (!tabDragging) return;
2378
+ event.preventDefault();
2379
+ if (event.target === event.currentTarget) previewTabDropTarget(viewTabs.length - 1);
2380
+ };
2381
+ const onOrderDrop = (event) => {
2382
+ if (!tabDragging) return;
2383
+ event.preventDefault();
2384
+ if (event.target !== event.currentTarget) return;
2385
+ applyQuotaTabIndex(viewTabs.length - 1);
2386
+ endTabDrag();
2387
+ };
2388
+ const onQuotaTabKeyDown = (event) => {
2389
+ if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
2390
+ event.preventDefault();
2391
+ applyQuotaTabIndex(balanceTabIndex + (event.key === "ArrowLeft" ? -1 : 1));
2392
+ };
2293
2393
  const primary = balance !== null && balance.balances.length > 0 ? balance.balances[0] : null;
2294
2394
  const recentDays = (usage?.daily ?? []).slice(-7);
2295
2395
  const avgDailyCost = recentDays.length > 0 ? recentDays.reduce((sum, d) => sum + d.cost, 0) / recentDays.length : 0;
@@ -2300,6 +2400,26 @@ function BalanceDashboard(props) {
2300
2400
  const runwayTitle = daysLeft === null ? t("balance.noRunway", { days: recentDays.length || 7 }) : t("balance.runwayTitle", { days: recentDays.length, cost: fmt(avgDailyCost) });
2301
2401
  const activeWindow = usage?.windows.find((window2) => window2.days === windowDays) ?? null;
2302
2402
  const activeWindowName = windowName(windowDays, t);
2403
+ const visionStats = activeWindow?.vision;
2404
+ const visionOpen = usage !== null && activeWindow !== null && visionStats !== void 0 && visionStats.images > 0 && Array.isArray(activeWindow.visionDaily) && Array.isArray(activeWindow.visionSessions);
2405
+ const visionPayload = (window2, stats, label) => ({
2406
+ vision: stats,
2407
+ daily: window2.visionDaily,
2408
+ sessions: window2.visionSessions,
2409
+ windowLabel: label,
2410
+ inputTokens: window2.totals.input,
2411
+ costTotal: window2.totals.cost
2412
+ });
2413
+ const liveVision = visionOpen && activeWindow !== null && visionStats !== void 0 ? visionPayload(activeWindow, visionStats, activeWindowName) : null;
2414
+ (0, import_react4.useEffect)(() => {
2415
+ if (visionOpen && activeWindow !== null && visionStats !== void 0) {
2416
+ setRetainedVision(visionPayload(activeWindow, visionStats, activeWindowName));
2417
+ return;
2418
+ }
2419
+ const timer = window.setTimeout(() => setRetainedVision(null), 320);
2420
+ return () => window.clearTimeout(timer);
2421
+ }, [visionOpen, activeWindow, activeWindowName]);
2422
+ const shownVision = visionOpen ? liveVision : retainedVision;
2303
2423
  const dailyBars = (activeWindow?.daily ?? []).map((d) => ({
2304
2424
  label: windowDays === 365 ? d.date.slice(5).replace("-", "/") : d.date.slice(8, 10),
2305
2425
  value: chartMetricValue(chartMetric, d),
@@ -2524,34 +2644,6 @@ function BalanceDashboard(props) {
2524
2644
  ),
2525
2645
  usage !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PricingNote, { pricing: usage.pricing })
2526
2646
  ] }),
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
2647
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card dq-card--primary", children: [
2556
2648
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card-head", children: [
2557
2649
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { children: t("usage.trendTitle") }),
@@ -2615,31 +2707,31 @@ function BalanceDashboard(props) {
2615
2707
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-legend-swatch", style: { background: s.color } }),
2616
2708
  s.name
2617
2709
  ] }, 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") })
2710
+ ] }) : 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 })
2711
+ ] }) : loadingUsage ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UsageSkeleton, {}) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("chart.empty") }),
2712
+ /* @__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: [
2713
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-chart-title", children: [
2714
+ t("vision.title"),
2715
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-chart-peak", children: shownVision.windowLabel })
2716
+ ] }),
2717
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2718
+ VisionCard,
2719
+ {
2720
+ vision: shownVision.vision,
2721
+ daily: shownVision.daily,
2722
+ sessions: shownVision.sessions,
2723
+ windowLabel: shownVision.windowLabel,
2724
+ inputTokens: shownVision.inputTokens,
2725
+ costTotal: shownVision.costTotal
2726
+ }
2727
+ )
2728
+ ] }) }) }),
2729
+ usage !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CoverageDiagnostics, { coverage: usage.coverage })
2621
2730
  ] }),
2622
2731
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
2623
2732
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { caption: t("chart.heatmapTitle"), children: t("heatmap.title") }),
2624
2733
  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
2734
  ] }),
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
2735
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
2644
2736
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { caption: activeWindowName, children: t("models.title") }),
2645
2737
  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 +2748,25 @@ function BalanceDashboard(props) {
2656
2748
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 250, h: 11 })
2657
2749
  ] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("sessions.empty") })
2658
2750
  ] }),
2751
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card-pair", children: [
2752
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
2753
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { children: t("peak.title") }),
2754
+ 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: [
2755
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-peak-track" }),
2756
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-peak-figures", children: [
2757
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 120, h: 11 }),
2758
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 96, h: 20 })
2759
+ ] })
2760
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("peak.empty") })
2761
+ ] }),
2762
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
2763
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { children: t("cache.title") }),
2764
+ 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: [
2765
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w, h: 11 }),
2766
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 72, h: 20 })
2767
+ ] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("cache.empty") })
2768
+ ] })
2769
+ ] }),
2659
2770
  /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
2660
2771
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(CardTitle, { children: t("settings.title") }),
2661
2772
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-links-title", children: t("links.title") }),
@@ -2723,9 +2834,67 @@ function BalanceDashboard(props) {
2723
2834
  ),
2724
2835
  /* @__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
2836
  ] })
2837
+ ] }),
2838
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
2839
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("label", { className: "dq-setting-label", htmlFor: "dq-sidebar-entry", children: t("settings.sidebarEntry") }),
2840
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
2841
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
2842
+ "select",
2843
+ {
2844
+ id: "dq-sidebar-entry",
2845
+ className: "dq-select",
2846
+ value: sidebarEntry,
2847
+ onChange: (e) => changeSidebarEntry(e.target.value),
2848
+ children: [
2849
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "off", children: t("settings.sidebarEntryOff") }),
2850
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "top", children: t("settings.sidebarEntryTop") }),
2851
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("option", { value: "bottom", children: t("settings.sidebarEntryBottom") })
2852
+ ]
2853
+ }
2854
+ ),
2855
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-setting-hint", children: t("settings.sidebarEntryHint") })
2856
+ ] })
2857
+ ] }),
2858
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting", children: [
2859
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-setting-label", id: "dq-tab-order-label", children: t("settings.tabPosition") }),
2860
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-setting-control", children: [
2861
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2862
+ "div",
2863
+ {
2864
+ className: "dq-tab-order",
2865
+ role: "group",
2866
+ "aria-labelledby": "dq-tab-order-label",
2867
+ "aria-describedby": "dq-tab-order-hint",
2868
+ onDragOver: onOrderDragOver,
2869
+ onDrop: onOrderDrop,
2870
+ children: viewTabs.map((tab, index) => {
2871
+ const current = tab.id === "balance";
2872
+ const dropTarget = tabDragging && !current && tabDropTarget === index;
2873
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
2874
+ "span",
2875
+ {
2876
+ className: `dq-tab-chip${current ? " dq-tab-chip--current" : ""}${current && tabDragging ? " dq-tab-chip--dragging" : ""}${dropTarget ? " dq-tab-chip--drop-target" : ""}`,
2877
+ draggable: current,
2878
+ tabIndex: current ? 0 : void 0,
2879
+ role: current ? "button" : void 0,
2880
+ "aria-label": current ? t("settings.tabDragAria") : void 0,
2881
+ onDragStart: current ? onQuotaTabDragStart : void 0,
2882
+ onDragEnd: current ? endTabDrag : void 0,
2883
+ onDragOver: (event) => onChipDragOver(event, index),
2884
+ onDrop: (event) => onChipDrop(event, index),
2885
+ onKeyDown: current ? onQuotaTabKeyDown : void 0,
2886
+ children: tab.label
2887
+ },
2888
+ tab.id
2889
+ );
2890
+ })
2891
+ }
2892
+ ),
2893
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { id: "dq-tab-order-hint", className: "dq-setting-hint", children: t("settings.tabPositionHint") })
2894
+ ] })
2726
2895
  ] })
2727
2896
  ] }),
2728
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-footer", children: "@cassius0924/dsh-usage-dashboard v0.7.0" })
2897
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-footer", children: "@cassius0924/dsh-usage-dashboard v0.8.0" })
2729
2898
  ] });
2730
2899
  }
2731
2900
 
@@ -2822,10 +2991,10 @@ var css = `
2822
2991
  the input box \u2014 measured in dashboard.tsx as --dq-composer-w, capped at
2823
2992
  the reading-column width so wide viewports are unchanged. */
2824
2993
  .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}}
2994
+ @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
2995
  /* \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
2996
  \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}
2997
+ .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
2998
  .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
2999
  .dq-card-caption{font-size:12px;font-weight:400;color:var(--dsw-alias-label-secondary,#59636e)}
2831
3000
  .dq-card-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px}
@@ -2839,9 +3008,10 @@ var css = `
2839
3008
  \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
3009
  \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
3010
  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}
3011
+ .dq-card--primary{padding:20px;border-color:var(--dsw-alias-border-l3,rgba(0,0,0,.12))}
2843
3012
  /* \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}
3013
+ .dq-card--hero{padding:0 20px;background:transparent;border:0;border-radius:0}
3014
+ .dq-card-pair{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(340px,100%),1fr));gap:24px;min-width:0}
2845
3015
  .dq-card--hero .dq-stat-label{font-size:13px;margin-bottom:2px}
2846
3016
  .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
3017
  .dq-card--hero .dq-balance-grid{align-items:stretch;gap:12px 48px}
@@ -2849,6 +3019,7 @@ var css = `
2849
3019
  /* 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
3020
  .dq-card--hero details>summary{text-decoration:none;margin:-2px -6px;padding:2px 6px;border-radius:8px;transition:background .15s ease}
2851
3021
  .dq-card--hero details>summary:hover,.dq-card--hero details[open]>summary{background:var(--dsw-alias-bg-layer-2,rgba(0,0,0,.05))}
3022
+ @media (max-width:620px){.dq-card{padding:16px}.dq-card--hero{padding:0 16px}}
2852
3023
  .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
3024
  .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
3025
  .dq-window-btn:hover{color:var(--dsw-alias-label-primary,#1f2328)}
@@ -2993,7 +3164,6 @@ var css = `
2993
3164
  .dq-peak-figures{display:flex;flex-wrap:wrap;gap:12px 32px;margin-top:16px}
2994
3165
  .dq-peak-delta{font-size:12px;font-weight:500;margin-left:8px;color:var(--dq-text-warn)}
2995
3166
  .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
3167
  .dq-cache-figures{display:flex;flex-wrap:wrap;gap:12px 32px;margin-bottom:12px}
2998
3168
  @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
3169
  .dq-muted-value{color:var(--dsw-alias-label-secondary,#59636e)!important;font-weight:500}
@@ -3198,6 +3368,77 @@ var css = `
3198
3368
  .dq-refresh-icon{display:inline-block;line-height:1}
3199
3369
  .dq-refresh-icon--spin{animation:dq-spin .9s linear infinite}
3200
3370
  @keyframes dq-spin{to{transform:rotate(360deg)}}
3371
+ /* ============================================================================
3372
+ Host chrome: the \u300C\u989D\u5EA6\u300Dconversation view does not use the chat composer.
3373
+ The attribute is set by index.tsx only when the dashboard is mounted inside
3374
+ the conversation scrollport, so the account-wide panel never hides it.
3375
+ '!important' because the composer's slot anchor is a "display:contents"
3376
+ wrapper whose display we must beat regardless of host rule order.
3377
+ ============================================================================ */
3378
+ html[data-dq-quota-view] [data-slot="conversation.composer"]{display:none!important}
3379
+ /* The narrow-viewport rule pads for the fixed composer; with it hidden there
3380
+ is nothing to clear, so fall back to the page's own bottom breathing room. */
3381
+ html[data-dq-quota-view] .dq-balance{padding-bottom:48px}
3382
+ /* ============================================================================
3383
+ Account-wide \u300C\u989D\u5EA6\u300Dpanel (the optional sidebar entry opens this main
3384
+ keyed panel). The dashboard keeps its own width/padding; the panel only
3385
+ supplies the page title row and the scrolling box the main column lacks.
3386
+ ============================================================================ */
3387
+ /* Account-wide page frame: mirrors DSH's first-level pages (Plugins /
3388
+ Scheduled tasks) \u2014 full-height scroll box, 28px top inset on the heading,
3389
+ a 960px centred column, clamp(24px,4vw,48px) inline + 48px bottom insets and
3390
+ a 20px/28px/500 page title, so the three sibling entries line up. */
3391
+ .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}
3392
+ .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}
3393
+ .dq-panel-title{margin:0;font-size:20px;font-weight:500;line-height:28px;color:var(--dsw-alias-label-primary,#1f2328)}
3394
+ .dq-panel-caption{font-size:13px;line-height:20px;color:var(--dsw-alias-label-secondary,#59636e)}
3395
+ .dq-panel .dq-balance{box-sizing:border-box;width:100%;max-width:960px;padding:0}
3396
+ /* macOS hiddenInset titlebar: the frame's top clearance keeps the heading
3397
+ below the window's top strip, matching the host's sibling pages. */
3398
+ [data-platform="darwin"] .dq-panel-head{padding-top:calc(28px + var(--dsh-frame-top-clearance,0px))}
3399
+ @media (max-width:620px){
3400
+ .dq-panel-head{padding-top:24px;margin-bottom:24px}
3401
+ .dq-panel .dq-balance{max-width:100%;padding:0}
3402
+ }
3403
+ /* Optional sidebar entry. Top placement joins the host's global-panel icon
3404
+ row (only the glyph is ours); bottom placement renders a row beside
3405
+ Settings, collapsing to a square rail button with the sidebar. */
3406
+ /* The glyph is an SVG matching the host's 16px stroke icons; color inherits
3407
+ from the sidebar row so selected/hover states stay the host's business. */
3408
+ .dq-side-icon{flex:none;display:block}
3409
+ .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}
3410
+ .dq-side-entry:hover{background:var(--dsw-alias-bg-layer-2,rgba(120,130,150,.10));color:var(--dsw-alias-label-primary,#1f2328)}
3411
+ .dq-side-entry:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px}
3412
+ .dq-side-entry[aria-pressed="true"]{color:var(--dsw-alias-label-primary,#1f2328)}
3413
+ .dq-side-entry--rail{justify-content:center;width:32px;height:32px;margin:0 auto;padding:0}
3414
+ .dq-side-entry-icon{flex:none;display:inline-flex;align-items:center;justify-content:center}
3415
+ .dq-side-entry-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
3416
+ /* ============================================================================
3417
+ Settings controls for the two layout preferences: a compact select and the
3418
+ draggable tab-order strip.
3419
+ ============================================================================ */
3420
+ .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}
3421
+ .dq-select:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px}
3422
+ .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))}
3423
+ .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}
3424
+ .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}
3425
+ .dq-tab-chip--current:active{cursor:grabbing}
3426
+ .dq-tab-chip--current:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px}
3427
+ .dq-tab-chip--dragging{opacity:.55}
3428
+ .dq-tab-chip--drop-target{border-style:dashed;border-color:var(--dsw-alias-state-business-primary,#0969da)}
3429
+ /* ============================================================================
3430
+ \u300C\u56FE\u7247 / \u591A\u6A21\u6001\u7528\u91CF\u300Dlives inside the usage-trend card. The grid-rows 0fr\u21921fr
3431
+ trick animates the section's real height as a window range makes it appear
3432
+ or disappear; opacity/translate make the change read as a reveal rather
3433
+ than a reflow. The body keeps 14px of top room in both states (clipped by
3434
+ the 0fr row when closed) so opening never jumps.
3435
+ ============================================================================ */
3436
+ .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)}
3437
+ .dq-vision-inline[data-open="true"]{grid-template-rows:1fr;margin-top:24px;opacity:1;transform:none}
3438
+ .dq-vision-inline-body{min-height:0;overflow:hidden}
3439
+ .dq-vision-inline-body>.dq-chart-title{margin-top:0}
3440
+ .dq-vision-inline-body>.dq-chart-title{margin-top:0}
3441
+
3201
3442
  @media (prefers-reduced-motion:reduce){
3202
3443
  .dsh-quota-root,.dsh-quota-root *,
3203
3444
  .dsh-quota-root *::before,.dsh-quota-root *::after,
@@ -3619,7 +3860,8 @@ function conversationViewsSource(slots) {
3619
3860
  // src/client/index.tsx
3620
3861
  var import_jsx_runtime7 = require("react/jsx-runtime");
3621
3862
  var name = "dsh-usage-dashboard";
3622
- var inject = ["slots", "locale"];
3863
+ var inject = ["slots", "locale", "layout"];
3864
+ var openQuotaTab = null;
3623
3865
  function apply(ctx) {
3624
3866
  ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-usage-dashboard: browser dictionaries");
3625
3867
  const t = ctx.locale.bind(NS);
@@ -3649,17 +3891,153 @@ function apply(ctx) {
3649
3891
  },
3650
3892
  ({ t: slotT, sessionId, messageId }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ErrorBoundary, { t: slotT, silent: true, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MessageCost, { sessionId, messageId }) }) })
3651
3893
  ));
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 }) }) })
3662
- ));
3894
+ const QuotaView = ({ t: slotT, sessionId, openView }) => {
3895
+ (0, import_react7.useEffect)(() => {
3896
+ if (openView !== void 0) openQuotaTab = openView;
3897
+ }, [openView]);
3898
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ErrorBoundary, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BalanceDashboard, { sessionId, views }) }) });
3899
+ };
3900
+ ctx.effect(() => {
3901
+ let disposeView = null;
3902
+ const mount = () => {
3903
+ disposeView?.();
3904
+ disposeView = ctx.slots.inject("conversation.view", () => ctx.slots.register(
3905
+ {
3906
+ name: "conversation.view",
3907
+ id: "balance",
3908
+ order: viewOrderForIndex(ctx.slots.entries("conversation.view"), quotaTabIndexStore.get()),
3909
+ locale: NS,
3910
+ label: () => t("nav.quota")
3911
+ },
3912
+ // `conversation.view` is a session-scoped slot, so the host injects its
3913
+ // standard session props (including `sessionId`) alongside `t`; only the
3914
+ // fields this plugin needs are destructured, per the existing
3915
+ // "hand-write the minimal structural type" convention (see context.ts).
3916
+ // `sessionId` stays optional: BalanceDashboard renders the current-
3917
+ // session card only when it actually has one, never throws either way.
3918
+ QuotaView
3919
+ ));
3920
+ };
3921
+ mount();
3922
+ const unsubscribe = quotaTabIndexStore.subscribe(() => {
3923
+ const restore = document !== void 0 && document.documentElement.hasAttribute("data-dq-quota-view");
3924
+ disposeView?.();
3925
+ disposeView = null;
3926
+ mount();
3927
+ if (restore) queueMicrotask(() => openQuotaTab?.("balance", "dsh-usage-dashboard:tab-order"));
3928
+ });
3929
+ return () => {
3930
+ unsubscribe();
3931
+ disposeView?.();
3932
+ };
3933
+ }, "dsh-usage-dashboard: quota tab registration");
3934
+ const QuotaPanel = ({ t: slotT }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dq-panel", children: [
3935
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "dq-panel-head", "data-window-drag": true, children: [
3936
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h1", { className: "dq-panel-title", children: slotT("nav.quota") }),
3937
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dq-panel-caption", children: slotT("panel.caption") })
3938
+ ] }),
3939
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(LocaleProvider, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ErrorBoundary, { t: slotT, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(BalanceDashboard, { views }) }) })
3940
+ ] });
3941
+ const QuotaGlyph = ({ size, active }) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3942
+ "svg",
3943
+ {
3944
+ className: "dq-side-icon",
3945
+ "data-active": active === true ? "true" : "false",
3946
+ width: size,
3947
+ height: size,
3948
+ viewBox: "0 0 16 16",
3949
+ fill: "none",
3950
+ stroke: "currentColor",
3951
+ strokeWidth: 1.1,
3952
+ strokeLinecap: "round",
3953
+ strokeLinejoin: "round",
3954
+ "aria-hidden": "true",
3955
+ focusable: "false",
3956
+ children: [
3957
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M3.5 3.3 8 8.7l4.5-5.4" }),
3958
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M8 8.7V13.2" }),
3959
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M3.5 10.2h9" }),
3960
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("path", { d: "M3.5 12.1h9" })
3961
+ ]
3962
+ }
3963
+ );
3964
+ const QuotaFooterButton = ({ wide, t: slotT }) => {
3965
+ const [active, setActive] = (0, import_react7.useState)(() => ctx.layout.panelInfo.getSnapshot().activePanelId === QUOTA_PANEL_ID);
3966
+ (0, import_react7.useEffect)(() => {
3967
+ const sync = () => {
3968
+ setActive(ctx.layout.panelInfo.getSnapshot().activePanelId === QUOTA_PANEL_ID);
3969
+ };
3970
+ sync();
3971
+ return ctx.layout.panelInfo.subscribe(sync);
3972
+ }, []);
3973
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
3974
+ "button",
3975
+ {
3976
+ type: "button",
3977
+ className: `dq-side-entry${wide ? "" : " dq-side-entry--rail"}`,
3978
+ title: slotT("panel.openAria"),
3979
+ "aria-label": slotT("nav.quota"),
3980
+ "aria-pressed": active,
3981
+ onClick: () => {
3982
+ ctx.layout.selectPanel(active ? null : QUOTA_PANEL_ID);
3983
+ },
3984
+ children: [
3985
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dq-side-entry-icon", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(QuotaGlyph, { size: 16 }) }),
3986
+ wide && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "dq-side-entry-label", children: slotT("nav.quota") })
3987
+ ]
3988
+ }
3989
+ );
3990
+ };
3991
+ ctx.effect(() => ctx.slots.inject("main", () => ctx.slots.register(
3992
+ { name: "main", key: QUOTA_PANEL_ID, locale: NS },
3993
+ QuotaPanel
3994
+ )), "dsh-usage-dashboard: quota panel");
3995
+ ctx.effect(() => {
3996
+ let disposeEntry = null;
3997
+ const sync = () => {
3998
+ disposeEntry?.();
3999
+ disposeEntry = null;
4000
+ const placement = sidebarEntryStore.get();
4001
+ if (placement === "top") {
4002
+ disposeEntry = ctx.slots.inject("sidebar.panellist", () => ctx.slots.register(
4003
+ { name: "sidebar.panellist", id: QUOTA_PANEL_ID, order: 1e3, label: () => t("nav.quota"), locale: NS },
4004
+ QuotaGlyph
4005
+ ));
4006
+ return;
4007
+ }
4008
+ if (placement === "bottom") {
4009
+ disposeEntry = ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register(
4010
+ { name: "sidebar.footer.action", id: "quota", order: 10, locale: NS },
4011
+ QuotaFooterButton
4012
+ ));
4013
+ return;
4014
+ }
4015
+ if (ctx.layout.panelInfo.getSnapshot().activePanelId === QUOTA_PANEL_ID) ctx.layout.selectPanel(null);
4016
+ };
4017
+ sync();
4018
+ const unsubscribe = sidebarEntryStore.subscribe(sync);
4019
+ return () => {
4020
+ unsubscribe();
4021
+ disposeEntry?.();
4022
+ };
4023
+ }, "dsh-usage-dashboard: sidebar entry");
4024
+ ctx.effect(() => {
4025
+ let applied = false;
4026
+ const sync = () => {
4027
+ const root = document.querySelector(".dq-balance");
4028
+ const should = quotaViewActiveStore.get() && root !== null && root.closest("[data-conversation-scroll]") !== null;
4029
+ if (should === applied) return;
4030
+ applied = should;
4031
+ if (should) document.documentElement.setAttribute("data-dq-quota-view", "");
4032
+ else document.documentElement.removeAttribute("data-dq-quota-view");
4033
+ };
4034
+ sync();
4035
+ const unsubscribe = quotaViewActiveStore.subscribe(sync);
4036
+ return () => {
4037
+ unsubscribe();
4038
+ if (applied) document.documentElement.removeAttribute("data-dq-quota-view");
4039
+ };
4040
+ }, "dsh-usage-dashboard: composer visibility");
3663
4041
  }
3664
4042
  return module.exports; } });
3665
4043
  //# sourceMappingURL=client.js.map