@cassius0924/dsh-usage-dashboard 0.5.0 → 0.6.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/README.md +29 -10
- package/lib/client.js +176 -55
- package/lib/client.js.map +3 -3
- package/lib/index.js +181 -25
- package/lib/index.js.map +3 -3
- package/lib/types/client/dom.d.ts +18 -1
- package/lib/types/client/locales.d.ts +17 -8
- package/lib/types/client/styles.d.ts +1 -1
- package/lib/types/context.d.ts +37 -1
- package/lib/types/contract.d.ts +50 -10
- package/lib/types/pricing.d.ts +46 -15
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
[](./LICENSE)
|
|
5
5
|
|
|
6
6
|
在 [DSH](https://github.com/deepseek-ai)(DeepSeek Harness)的 Web GUI 里,随时看得见 DeepSeek 的钱花在哪:
|
|
7
|
-
|
|
7
|
+
**余额还能撑几天、今天花了多少、哪个模型最贵、缓存替你省了多少、钱里有多少来自图片**,以及峰谷定价生效后把任务挪到闲时能省多少。
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
| 今天花了多少?这个月呢? | 只有「累计」,没有时间感 |
|
|
24
24
|
| 钱花在哪个模型上? | 不知道 |
|
|
25
25
|
| 缓存到底帮我省了多少? | 完全看不到 |
|
|
26
|
-
|
|
|
26
|
+
| 钱里有多少来自图片? | 多模态模型上线后更是看不见 |
|
|
27
|
+
| 高峰时段多付了多少? | 只能自己算 |
|
|
27
28
|
|
|
28
29
|
这个插件把这些问题逐个翻译成一眼能看懂的数字。**费用全部为估算**,算法与单价公开可查(见[费用是怎么算的](#费用是怎么算的))。
|
|
29
30
|
|
|
@@ -59,13 +60,13 @@
|
|
|
59
60
|
|
|
60
61
|
展开**计价说明**能看到费用估算实际套用的单价表——估算不该是个黑箱。
|
|
61
62
|
|
|
62
|
-
###
|
|
63
|
+
### 高峰时段多付了多少
|
|
63
64
|
|
|
64
65
|
<img src="docs/peak.png" width="860" alt="高峰 / 闲时分布">
|
|
65
66
|
|
|
66
67
|
DeepSeek 从 2026-08-17 00:00 起改峰谷定价,高峰(北京时间 09:00–12:00、14:00–18:00)价格是闲时的两倍。
|
|
67
68
|
|
|
68
|
-
|
|
69
|
+
这张卡告诉你用量落在峰谷两侧的比例,以及**如果高峰用量都挪到闲时能省多少**——批量、可延后的任务放到闲时跑,同样的 token 只要一半的钱。
|
|
69
70
|
|
|
70
71
|
### 缓存替你省了多少
|
|
71
72
|
|
|
@@ -75,6 +76,16 @@ DeepSeek 从 2026-08-17 00:00 起改峰谷定价,高峰(北京时间 09:00
|
|
|
75
76
|
|
|
76
77
|
命中率掉到 60% 以下时,文案会换成怎么把它救回来的建议。
|
|
77
78
|
|
|
79
|
+
### 钱里有多少来自图片
|
|
80
|
+
|
|
81
|
+
> 截图待补拍:当前机器没有真实图片用量时,卡片显示的是空态,以下先以文字说明功能。
|
|
82
|
+
|
|
83
|
+
`deepseek-v4-flash-vision-exp` 上线后,图片按尺寸折算成 token 与文本一并计费,但「看了一堆截图」和「纯文本」在账单里长得一模一样。
|
|
84
|
+
|
|
85
|
+
这张卡重放会话日志时顺手数出图片:用户消息里的附件和 tool result 里的截图都会统计(不用读附件本体,日志里就有宽高)。每组数字给出**图片张数、按官方换算规则估算的 token 与费用,以及占窗口输入 / 费用的比例**,旁边是每天图片数的趋势和「图片最多的会话」排行——一个不停刷截图的循环会话,在这里一眼就能认出来。
|
|
86
|
+
|
|
87
|
+
图片归属到随后一次模型调用,按该调用的时段与模型单价估算;实际 token 数以接口返回的用量为准。
|
|
88
|
+
|
|
78
89
|
### 钱花在哪个模型上
|
|
79
90
|
|
|
80
91
|
<img src="docs/models.png" width="860" alt="模型成本排行">
|
|
@@ -129,23 +140,30 @@ dsh plugin --profile web add ./path/to/dsh-usage-dashboard
|
|
|
129
140
|
|
|
130
141
|
装好后打开 GUI,**进入任意一个会话**(「额度」tab 挂在 `conversation.view` 上,New Session 首页没有 tab 栏),顶部就能看到 `Chat / Trajectory / 额度`。
|
|
131
142
|
|
|
143
|
+
> 兼容性:同时支持两代宿主接口——旧版 `readFrom(id, 0)` 与新版 handle 化 `open(id, 'read')` / `read` / `close`。
|
|
144
|
+
> 宿主升级到移除 `readFrom` 的版本后,插件无需更换版本,会话日志照常重放。
|
|
145
|
+
|
|
132
146
|
## 费用是怎么算的
|
|
133
147
|
|
|
134
148
|
费用是**估算**,不是账单。规则都在 [`src/pricing.ts`](src/pricing.ts) 一个文件里:
|
|
135
149
|
|
|
136
|
-
- 逐条用量记录按「**模型** +
|
|
137
|
-
- 2026-08-17 00:00
|
|
150
|
+
- 逐条用量记录按「**模型** + **是否落在高峰时段** + **当时的价目表**」计价,而不是全局一套价。
|
|
151
|
+
- 2026-08-17 00:00(北京时间)之前按旧的固定价;之后按峰谷价,闲时为高峰的一半。
|
|
152
|
+
- 2026-09-10 12:00 起 Flash 系列降价(V4.1-Flash),高峰时段同时收紧为**周一至周五**。
|
|
153
|
+
- 2026-09-14 12:00 起 `deepseek-v4-pro` 的请求由 V4.1-Flash 承接,按 Flash 价计费。
|
|
138
154
|
- 高峰时段按**北京时间**判定,不随机器时区漂移。
|
|
139
155
|
- 未识别的模型按 `deepseek-v4-pro`(较贵的一侧)计价。
|
|
140
156
|
|
|
141
|
-
单价(CNY / 百万 tokens
|
|
157
|
+
单价(CNY / 百万 tokens,与官方价目表的两档一致):
|
|
142
158
|
|
|
143
159
|
| 模型 | 时段 | 输入·缓存命中 | 输入·未命中 | 输出 |
|
|
144
160
|
|---|---|---|---|---|
|
|
145
|
-
| deepseek-
|
|
161
|
+
| deepseek-flash | 高峰 / 闲时 | 0.04 / 0.02 | 2 / 1 | 8 / 4 |
|
|
146
162
|
| deepseek-v4-pro | 高峰 / 闲时 | 0.3 / 0.15 | 9 / 4.5 | 27 / 13.5 |
|
|
147
|
-
|
|
148
|
-
|
|
163
|
+
|
|
164
|
+
`deepseek-v4-flash`、`deepseek-v4-flash-vision-exp` 是旧模型名,现由 `deepseek-flash` 承接、按 Flash 价计费;2026-09-14 12:00 起 `deepseek-v4-pro` 的请求也路由到 V4.1-Flash,按 Flash 价计费。历史各时期单价:08-17 前 pro 0.025/3/6、flash 0.02/1/2;08-17 – 09-10 12:00 的 flash 高峰 0.1/3/9、闲时 0.05/1.5/4.5。
|
|
165
|
+
|
|
166
|
+
图片不另外收费,而是按尺寸折算成 token 与文本一并计费:官方规则是先把图片按比例缩放到 ~800×800(小于 ~384×384 的放大),token 与缩放后面积成正比、每张上限 384 tokens。多模态卡的估算就套这条规则。
|
|
149
167
|
|
|
150
168
|
DeepSeek 再调价时,只改这张表。
|
|
151
169
|
|
|
@@ -192,6 +210,7 @@ src/
|
|
|
192
210
|
|
|
193
211
|
- 用量数据来自**本机** DSH 会话日志,不含其它机器/账户的用量。
|
|
194
212
|
- 费用是估算:不含 DeepSeek 侧的折扣、赠金消耗顺序等因素,以官方账单为准。
|
|
213
|
+
- 图片 token 按官方换算规则估算,且把图片归属到随后一次模型调用;若缺少图片宽高或图片后没有成功的调用,则不纳入估算(无调用即无计费)。
|
|
195
214
|
- 首次加载用量需要重放全部会话日志(本机实测约 5 秒),因此有 5 分钟的服务端记忆化;界面在此期间显示骨架屏。
|
|
196
215
|
|
|
197
216
|
## License
|
package/lib/client.js
CHANGED
|
@@ -131,7 +131,7 @@ async function getJson(path, cache) {
|
|
|
131
131
|
var usageIsUsable = (res) => {
|
|
132
132
|
if (res.ok !== true) return false;
|
|
133
133
|
const data = res.data;
|
|
134
|
-
return data !== void 0 && Array.isArray(data.daily) && Array.isArray(data.hourly) && Array.isArray(data.heatmap) && Array.isArray(data.models) && Array.isArray(data.sessions) && typeof data.sessionCount === "number" && data.coverage?.scope === "local-dsh-session-logs" && typeof data.coverage.scannedSessions === "number" && typeof data.coverage.skippedRecords === "number" && Array.isArray(data.windows) && data.windows.length === USAGE_WINDOW_DAYS.length && USAGE_WINDOW_DAYS.every((days) => data.windows.some((window2) => window2.days === days)) && data.windows.every((window2) => Array.isArray(window2.daily) && Array.isArray(window2.hourly) && Array.isArray(window2.models) && Array.isArray(window2.sessions)) && data.totals !== void 0 && typeof data.totals.cacheSavings === "number" && data.summary?.today !== void 0 && Array.isArray(data.pricing?.tiers) && data.peakSplit?.peak !== void 0;
|
|
134
|
+
return data !== void 0 && Array.isArray(data.daily) && Array.isArray(data.hourly) && Array.isArray(data.heatmap) && Array.isArray(data.models) && Array.isArray(data.sessions) && typeof data.sessionCount === "number" && data.coverage?.scope === "local-dsh-session-logs" && typeof data.coverage.scannedSessions === "number" && typeof data.coverage.skippedRecords === "number" && Array.isArray(data.windows) && data.windows.length === USAGE_WINDOW_DAYS.length && USAGE_WINDOW_DAYS.every((days) => data.windows.some((window2) => window2.days === days)) && data.windows.every((window2) => Array.isArray(window2.daily) && Array.isArray(window2.hourly) && Array.isArray(window2.models) && Array.isArray(window2.sessions)) && data.vision !== void 0 && typeof data.vision.images === "number" && typeof data.vision.imageTokens === "number" && typeof data.vision.cost === "number" && Array.isArray(data.visionDaily) && Array.isArray(data.visionSessions) && data.windows.every((window2) => window2.vision !== void 0 && Array.isArray(window2.visionDaily) && Array.isArray(window2.visionSessions)) && data.totals !== void 0 && typeof data.totals.cacheSavings === "number" && data.summary?.today !== void 0 && Array.isArray(data.pricing?.tiers) && data.peakSplit?.peak !== void 0;
|
|
135
135
|
};
|
|
136
136
|
var balanceIsUsable = (res) => res.ok === true && Array.isArray(res.data?.balances);
|
|
137
137
|
var balanceCache = createCache(BALANCE_TTL_MS, "dsh-usage-dashboard:balance", balanceIsUsable);
|
|
@@ -174,8 +174,8 @@ async function fetchUsage(force = false) {
|
|
|
174
174
|
if (usageInflight !== null) return usageInflight;
|
|
175
175
|
}
|
|
176
176
|
const suffix = force ? "?refresh=1" : "";
|
|
177
|
-
const task = getJson(`/api/dsh-usage-dashboard/usage${suffix}`,
|
|
178
|
-
if (res.ok) {
|
|
177
|
+
const task = getJson(`/api/dsh-usage-dashboard/usage${suffix}`, "no-store").then((res) => {
|
|
178
|
+
if (res.ok && usageIsUsable(res)) {
|
|
179
179
|
usageCache.put(res);
|
|
180
180
|
for (const listener of [...usageListeners]) listener();
|
|
181
181
|
}
|
|
@@ -332,16 +332,15 @@ var zh = {
|
|
|
332
332
|
"pricing.currentOffPeak": "\u5F53\u524D \u95F2\u65F6",
|
|
333
333
|
"pricing.model": "\u6A21\u578B",
|
|
334
334
|
"pricing.period": "\u65F6\u6BB5",
|
|
335
|
-
"pricing.unitPrice": "\u5355\u4EF7",
|
|
336
335
|
"pricing.inputCacheHit": "\u8F93\u5165\xB7\u7F13\u5B58\u547D\u4E2D",
|
|
337
336
|
"pricing.inputCacheMiss": "\u8F93\u5165\xB7\u672A\u547D\u4E2D",
|
|
338
337
|
"pricing.output": "\u8F93\u51FA",
|
|
339
338
|
"pricing.peak": "\u9AD8\u5CF0",
|
|
340
|
-
"pricing.fixed": "\u56FA\u5B9A",
|
|
341
339
|
"pricing.offPeak": "\u95F2\u65F6",
|
|
342
340
|
"pricing.unit": "\u5355\u4F4D\uFF1A{currency} / \u767E\u4E07 tokens\u3002",
|
|
343
|
-
"pricing.splitNote": "\u9AD8\u5CF0\u65F6\u6BB5\u4E3A\u5317\u4EAC\u65F6\u95F4 {windows}\uFF0C\u5176\u4F59\u4E3A\u95F2\u65F6\uFF08\u4EF7\u683C\u51CF\u534A\uFF09\uFF1B
|
|
344
|
-
"pricing.
|
|
341
|
+
"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
|
+
"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",
|
|
345
344
|
"pricing.unknown": "\u672A\u77E5\u6A21\u578B\u6309 deepseek-v4-pro \u8BA1\u4EF7\u3002",
|
|
346
345
|
"coverage.noRange": "\u6682\u65E0\u6709\u6548\u7528\u91CF\u65F6\u95F4",
|
|
347
346
|
"coverage.title": "\u7EDF\u8BA1\u8303\u56F4",
|
|
@@ -375,13 +374,23 @@ var zh = {
|
|
|
375
374
|
"peak.aria": "\u9AD8\u5CF0\u65F6\u6BB5 {peak}%\uFF0C\u95F2\u65F6 {offPeak}%",
|
|
376
375
|
"peak.peakLegend": "\u9AD8\u5CF0 {share}% \xB7 {tokens} tokens \xB7 {calls}",
|
|
377
376
|
"peak.offPeakLegend": "\u95F2\u65F6 {share}% \xB7 {tokens} tokens \xB7 {calls}",
|
|
378
|
-
"peak.newPrice": "\u540C\u6837\u7528\u91CF\u5728\u65B0\u4EF7\u4E0B",
|
|
379
|
-
"peak.increase": "\u8F83\u73B0\u4EF7 +{percent}%",
|
|
380
377
|
"peak.shift": "\u82E5\u9AD8\u5CF0\u7528\u91CF\u90FD\u632A\u5230\u95F2\u65F6",
|
|
381
378
|
"peak.saving": "\u53EF\u7701 \xA5{amount}",
|
|
382
|
-
"peak.schedule": "\u9AD8\u5CF0\u65F6\u6BB5\u4E3A\u5317\u4EAC\u65F6\u95F4 {windows}\uFF0C\u95F2\u65F6\u4EF7\u683C\u51CF\u534A\u3002",
|
|
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",
|
|
383
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",
|
|
384
|
-
"
|
|
381
|
+
"vision.empty": "{window}\u6CA1\u6709\u56FE\u7247\u8F93\u5165\u3002",
|
|
382
|
+
"vision.title": "\u56FE\u7247 / \u591A\u6A21\u6001\u7528\u91CF",
|
|
383
|
+
"vision.images": "\u56FE\u7247\u5F20\u6570",
|
|
384
|
+
"vision.tokens": "\u4F30\u7B97\u56FE\u7247 tokens",
|
|
385
|
+
"vision.cost": "\u4F30\u7B97\u8D39\u7528",
|
|
386
|
+
"vision.tokensShare": "\u5360\u8F93\u5165 {percent}%",
|
|
387
|
+
"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",
|
|
389
|
+
"vision.sessionsTitle": "\u56FE\u7247\u6700\u591A\u7684\u4F1A\u8BDD",
|
|
390
|
+
"vision.sessionRow": "{images} \u5F20 \xB7 \xA5{cost}",
|
|
391
|
+
"vision.dailyTitle": "{window} \xB7 \u6BCF\u5929\u56FE\u7247\u6570",
|
|
392
|
+
"vision.barAria": "{date} \xB7 {images} \u5F20\u56FE\u7247 \xB7 \u4F30\u7B97 {tokens} tokens",
|
|
393
|
+
"vision.barTitle": "{date} \xB7 {images} \u5F20\u56FE\u7247 \xB7 \u4F30\u7B97 {tokens} tokens \xB7 \xA5{cost}",
|
|
385
394
|
"sessions.empty": "\u8FD8\u6CA1\u6709\u4EA7\u751F\u8D39\u7528\u7684\u4F1A\u8BDD\u3002",
|
|
386
395
|
"sessions.detail": "{tokens} tokens \xB7 {calls} \xB7 {ago}",
|
|
387
396
|
"sessions.share": "\u5360 {percent}%",
|
|
@@ -581,16 +590,15 @@ var en = {
|
|
|
581
590
|
"pricing.currentOffPeak": "Off-peak now",
|
|
582
591
|
"pricing.model": "Model",
|
|
583
592
|
"pricing.period": "Period",
|
|
584
|
-
"pricing.unitPrice": "Rate",
|
|
585
593
|
"pricing.inputCacheHit": "Input \xB7 cache hit",
|
|
586
594
|
"pricing.inputCacheMiss": "Input \xB7 cache miss",
|
|
587
595
|
"pricing.output": "Output",
|
|
588
596
|
"pricing.peak": "Peak",
|
|
589
|
-
"pricing.fixed": "Fixed",
|
|
590
597
|
"pricing.offPeak": "Off-peak",
|
|
591
598
|
"pricing.unit": "Unit: {currency} / million tokens. ",
|
|
592
|
-
"pricing.splitNote": "Peak periods are {windows} Beijing time; off-peak rates are half price.
|
|
593
|
-
"pricing.
|
|
599
|
+
"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. ",
|
|
600
|
+
"pricing.visionNote": "Images are converted to tokens by size and charged together with text. ",
|
|
601
|
+
"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. ",
|
|
594
602
|
"pricing.unknown": "Unknown models use deepseek-v4-pro rates.",
|
|
595
603
|
"coverage.noRange": "No valid usage timestamps",
|
|
596
604
|
"coverage.title": "Coverage",
|
|
@@ -624,13 +632,23 @@ var en = {
|
|
|
624
632
|
"peak.aria": "{peak}% peak, {offPeak}% off-peak",
|
|
625
633
|
"peak.peakLegend": "Peak {share}% \xB7 {tokens} tokens \xB7 {calls}",
|
|
626
634
|
"peak.offPeakLegend": "Off-peak {share}% \xB7 {tokens} tokens \xB7 {calls}",
|
|
627
|
-
"peak.newPrice": "Same usage at new rates",
|
|
628
|
-
"peak.increase": "+{percent}% vs current rates",
|
|
629
635
|
"peak.shift": "If all peak usage moved off-peak",
|
|
630
636
|
"peak.saving": "Save \xA5{amount}",
|
|
631
|
-
"peak.schedule": "Peak periods are {windows} Beijing time; off-peak rates are half price. ",
|
|
637
|
+
"peak.schedule": "Peak periods are {windows} Beijing time, Monday to Friday; off-peak rates are half price. ",
|
|
632
638
|
"peak.activeHint": "Run batch or delay-tolerant work off-peak to pay half as much for the same tokens.",
|
|
633
|
-
"
|
|
639
|
+
"vision.empty": "No image input in {window}.",
|
|
640
|
+
"vision.title": "Image / multimodal usage",
|
|
641
|
+
"vision.images": "Images",
|
|
642
|
+
"vision.tokens": "Estimated image tokens",
|
|
643
|
+
"vision.cost": "Estimated cost",
|
|
644
|
+
"vision.tokensShare": "{percent}% of input",
|
|
645
|
+
"vision.costShare": "{percent}% of cost",
|
|
646
|
+
"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.",
|
|
647
|
+
"vision.sessionsTitle": "Sessions with the most images",
|
|
648
|
+
"vision.sessionRow": "{images} images \xB7 \xA5{cost}",
|
|
649
|
+
"vision.dailyTitle": "{window} \xB7 images per day",
|
|
650
|
+
"vision.barAria": "{date} \xB7 {images} images \xB7 est. {tokens} tokens",
|
|
651
|
+
"vision.barTitle": "{date} \xB7 {images} images \xB7 est. {tokens} tokens \xB7 \xA5{cost}",
|
|
634
652
|
"sessions.empty": "No sessions have incurred cost yet.",
|
|
635
653
|
"sessions.detail": "{tokens} tokens \xB7 {calls} \xB7 {ago}",
|
|
636
654
|
"sessions.share": "{percent}% of total",
|
|
@@ -1137,9 +1155,25 @@ function getShellFrame(node) {
|
|
|
1137
1155
|
return overlay?.parentElement ?? null;
|
|
1138
1156
|
}
|
|
1139
1157
|
function getComposerElement(frame) {
|
|
1140
|
-
if (frame === null)
|
|
1158
|
+
if (frame === null) {
|
|
1159
|
+
return slotElement(document.documentElement, "conversation.composer.dock", "ancestor");
|
|
1160
|
+
}
|
|
1141
1161
|
return slotElement(frame, "conversation.composer.dock", "ancestor");
|
|
1142
1162
|
}
|
|
1163
|
+
function getComposerInputElement(frame) {
|
|
1164
|
+
const root = frame ?? document.documentElement;
|
|
1165
|
+
const textarea = root.querySelector(
|
|
1166
|
+
'[data-slot="conversation.composer"] textarea, [data-slot="conversation.composer.bar"] textarea, textarea'
|
|
1167
|
+
);
|
|
1168
|
+
if (textarea === null) return null;
|
|
1169
|
+
let el = textarea.parentElement;
|
|
1170
|
+
for (let depth = 0; depth < 6 && el !== null; depth++) {
|
|
1171
|
+
const cs = getComputedStyle(el);
|
|
1172
|
+
if (cs.borderRadius !== "0px" && cs.backgroundColor !== "rgba(0, 0, 0, 0)") return el;
|
|
1173
|
+
el = el.parentElement;
|
|
1174
|
+
}
|
|
1175
|
+
return null;
|
|
1176
|
+
}
|
|
1143
1177
|
function getActiveConversationViewId(frame, viewIds) {
|
|
1144
1178
|
if (viewIds.length === 0) return null;
|
|
1145
1179
|
const header = frame === null ? null : slotElement(frame, "conversation.session.header", "child");
|
|
@@ -1443,30 +1477,29 @@ var rate = (value) => `\xA5${value.toLocaleString(void 0, { maximumFractionDigit
|
|
|
1443
1477
|
function PricingNote(props) {
|
|
1444
1478
|
const { t } = useI18n();
|
|
1445
1479
|
const { pricing } = props;
|
|
1446
|
-
const split = pricing.splitActive;
|
|
1447
1480
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("details", { className: "dq-pricing", children: [
|
|
1448
1481
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("summary", { className: "dq-pricing-summary", children: [
|
|
1449
1482
|
t("pricing.title"),
|
|
1450
|
-
|
|
1483
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: `dq-pricing-now${pricing.inPeakNow ? " dq-pricing-now--peak" : ""}`, children: pricing.inPeakNow ? t("pricing.currentPeak") : t("pricing.currentOffPeak") })
|
|
1451
1484
|
] }),
|
|
1452
1485
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-pricing-body", children: [
|
|
1453
1486
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("table", { className: "dq-pricing-table", children: [
|
|
1454
1487
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1455
1488
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: t("pricing.model") }),
|
|
1456
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children:
|
|
1489
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: t("pricing.period") }),
|
|
1457
1490
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: t("pricing.inputCacheHit") }),
|
|
1458
1491
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: t("pricing.inputCacheMiss") }),
|
|
1459
1492
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("th", { children: t("pricing.output") })
|
|
1460
1493
|
] }) }),
|
|
1461
1494
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("tbody", { children: pricing.tiers.map((tier) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react4.Fragment, { children: [
|
|
1462
1495
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1463
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { rowSpan:
|
|
1464
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children:
|
|
1496
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { rowSpan: 2, children: tier.model }),
|
|
1497
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: t("pricing.peak") }),
|
|
1465
1498
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: rate(tier.peak.cacheHit) }),
|
|
1466
1499
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: rate(tier.peak.input) }),
|
|
1467
1500
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: rate(tier.peak.output) })
|
|
1468
1501
|
] }),
|
|
1469
|
-
|
|
1502
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("tr", { children: [
|
|
1470
1503
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: t("pricing.offPeak") }),
|
|
1471
1504
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: rate(tier.offPeak.cacheHit) }),
|
|
1472
1505
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("td", { children: rate(tier.offPeak.input) }),
|
|
@@ -1476,7 +1509,9 @@ function PricingNote(props) {
|
|
|
1476
1509
|
] }),
|
|
1477
1510
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "dq-pricing-foot", children: [
|
|
1478
1511
|
t("pricing.unit", { currency: pricing.currency }),
|
|
1479
|
-
|
|
1512
|
+
t("pricing.splitNote", { windows: pricing.peakWindows.join(t("common.listSeparator")) }),
|
|
1513
|
+
t("pricing.visionNote"),
|
|
1514
|
+
t("pricing.modelNote"),
|
|
1480
1515
|
t("pricing.unknown")
|
|
1481
1516
|
] })
|
|
1482
1517
|
] })
|
|
@@ -1728,15 +1763,50 @@ function CacheCard(props) {
|
|
|
1728
1763
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-cache-foot", children: low ? t("cache.lowHint") : t("cache.goodHint") })
|
|
1729
1764
|
] });
|
|
1730
1765
|
}
|
|
1766
|
+
function VisionCard(props) {
|
|
1767
|
+
const { t, locale } = useI18n();
|
|
1768
|
+
const { vision, daily, sessions, windowLabel, inputTokens, costTotal } = props;
|
|
1769
|
+
if (vision.images === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("vision.empty", { window: windowLabel }) });
|
|
1770
|
+
const tokensShare = inputTokens > 0 ? vision.imageTokens / inputTokens * 100 : 0;
|
|
1771
|
+
const costShare = costTotal > 0 ? vision.cost / costTotal * 100 : 0;
|
|
1772
|
+
const labelEvery = daily.length <= 7 ? 1 : daily.length <= 30 ? 5 : daily.length <= 90 ? 15 : 30;
|
|
1773
|
+
const bars = daily.map((d) => ({
|
|
1774
|
+
label: d.date.slice(8, 10),
|
|
1775
|
+
value: d.images,
|
|
1776
|
+
title: t("vision.barTitle", { date: d.date, images: fmtInt(d.images), tokens: fmtCompact(d.imageTokens, locale), cost: fmt(d.cost) })
|
|
1777
|
+
}));
|
|
1778
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1779
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-usage-totals", children: [
|
|
1780
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Stat, { label: t("vision.images"), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-value", children: fmtInt(vision.images) }) }),
|
|
1781
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Stat, { label: t("vision.tokens"), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat-value", children: [
|
|
1782
|
+
fmtCompact(vision.imageTokens, locale),
|
|
1783
|
+
tokensShare > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-vision-share", children: t("vision.tokensShare", { percent: tokensShare.toFixed(1) }) })
|
|
1784
|
+
] }) }),
|
|
1785
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Stat, { label: t("vision.cost"), children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat-value", children: [
|
|
1786
|
+
"\xA5 ",
|
|
1787
|
+
fmt(vision.cost),
|
|
1788
|
+
costShare > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-vision-share", children: t("vision.costShare", { percent: costShare.toFixed(1) }) })
|
|
1789
|
+
] }) })
|
|
1790
|
+
] }),
|
|
1791
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-chart-title", children: t("vision.dailyTitle", { window: windowLabel }) }),
|
|
1792
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Bars, { data: bars, height: 80, labelEvery }),
|
|
1793
|
+
sessions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-vision-sessions", children: [
|
|
1794
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-chart-title", children: t("vision.sessionsTitle") }),
|
|
1795
|
+
sessions.map((session) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-session", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-session-head", children: [
|
|
1796
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-session-title", title: session.id, children: session.title }),
|
|
1797
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-session-cost", children: t("vision.sessionRow", { images: fmtInt(session.images), cost: fmt(session.cost) }) })
|
|
1798
|
+
] }) }, session.id))
|
|
1799
|
+
] }),
|
|
1800
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-cache-foot", children: t("vision.note") })
|
|
1801
|
+
] });
|
|
1802
|
+
}
|
|
1731
1803
|
function PeakCard(props) {
|
|
1732
1804
|
const { t, locale } = useI18n();
|
|
1733
|
-
const { split, pricing
|
|
1805
|
+
const { split, pricing } = props;
|
|
1734
1806
|
const total = split.peak.total + split.offPeak.total;
|
|
1735
1807
|
if (total === 0) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("peak.empty") });
|
|
1736
1808
|
const peakShare = split.peak.total / total;
|
|
1737
1809
|
const shiftSaving = split.peakEraCost - split.offPeakEraCost;
|
|
1738
|
-
const increase = split.peakEraCost - currentCost;
|
|
1739
|
-
const increasePercent = currentCost > 0 ? increase / currentCost * 100 : 0;
|
|
1740
1810
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-peak", children: [
|
|
1741
1811
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1742
1812
|
"div",
|
|
@@ -1765,27 +1835,17 @@ function PeakCard(props) {
|
|
|
1765
1835
|
})
|
|
1766
1836
|
] })
|
|
1767
1837
|
] }),
|
|
1768
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-peak-delta", children: t("peak.increase", { percent: increasePercent.toFixed(0) }) })
|
|
1775
|
-
] })
|
|
1776
|
-
] }),
|
|
1777
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
1778
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: t("peak.shift") }),
|
|
1779
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat-value", children: [
|
|
1780
|
-
"\xA5 ",
|
|
1781
|
-
fmt(split.offPeakEraCost),
|
|
1782
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-peak-delta dq-peak-delta--save", children: t("peak.saving", { amount: fmt(shiftSaving) }) })
|
|
1783
|
-
] })
|
|
1838
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-peak-figures", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat", children: [
|
|
1839
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-stat-label", children: t("peak.shift") }),
|
|
1840
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-stat-value", children: [
|
|
1841
|
+
"\xA5 ",
|
|
1842
|
+
fmt(split.offPeakEraCost),
|
|
1843
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "dq-peak-delta dq-peak-delta--save", children: t("peak.saving", { amount: fmt(shiftSaving) }) })
|
|
1784
1844
|
] })
|
|
1785
|
-
] }),
|
|
1845
|
+
] }) }),
|
|
1786
1846
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "dq-peak-foot", children: [
|
|
1787
1847
|
t("peak.schedule", { windows: pricing.peakWindows.join(t("common.listSeparator")) }),
|
|
1788
|
-
|
|
1848
|
+
t("peak.activeHint")
|
|
1789
1849
|
] })
|
|
1790
1850
|
] });
|
|
1791
1851
|
}
|
|
@@ -2044,6 +2104,7 @@ function BalanceDashboard(props) {
|
|
|
2044
2104
|
const budgetInputRef = (0, import_react4.useRef)(null);
|
|
2045
2105
|
const rootRef = (0, import_react4.useRef)(null);
|
|
2046
2106
|
const [composerHeight, setComposerHeight] = (0, import_react4.useState)(null);
|
|
2107
|
+
const [composerWidth, setComposerWidth] = (0, import_react4.useState)(null);
|
|
2047
2108
|
const [sessionUsage, setSessionUsage] = (0, import_react4.useState)(null);
|
|
2048
2109
|
const [loadingSessionUsage, setLoadingSessionUsage] = (0, import_react4.useState)(props.sessionId !== void 0 && props.sessionId !== "");
|
|
2049
2110
|
const [sessionUsageError, setSessionUsageError] = (0, import_react4.useState)(null);
|
|
@@ -2055,16 +2116,22 @@ function BalanceDashboard(props) {
|
|
|
2055
2116
|
const frame2 = getShellFrame(node);
|
|
2056
2117
|
const composer2 = getComposerElement(frame2);
|
|
2057
2118
|
const rect = composer2?.getBoundingClientRect() ?? null;
|
|
2058
|
-
const
|
|
2059
|
-
setComposerHeight((prev) => prev ===
|
|
2119
|
+
const nextHeight = rect !== null && rect.height > 0 ? Math.round(rect.height) : null;
|
|
2120
|
+
setComposerHeight((prev) => prev === nextHeight ? prev : nextHeight);
|
|
2121
|
+
const input2 = getComposerInputElement(frame2);
|
|
2122
|
+
const inputRect = input2?.getBoundingClientRect() ?? null;
|
|
2123
|
+
const nextWidth = inputRect !== null && inputRect.width > 0 ? Math.round(inputRect.width) : null;
|
|
2124
|
+
setComposerWidth((prev) => prev === nextWidth ? prev : nextWidth);
|
|
2060
2125
|
};
|
|
2061
2126
|
measure();
|
|
2062
2127
|
const frame = getShellFrame(node);
|
|
2063
2128
|
const composer = getComposerElement(frame);
|
|
2129
|
+
const input = getComposerInputElement(frame);
|
|
2064
2130
|
let observer = null;
|
|
2065
2131
|
if (typeof ResizeObserver === "function") {
|
|
2066
2132
|
observer = new ResizeObserver(measure);
|
|
2067
2133
|
if (composer !== null) observer.observe(composer);
|
|
2134
|
+
if (input !== null) observer.observe(input);
|
|
2068
2135
|
if (frame !== null) observer.observe(frame);
|
|
2069
2136
|
}
|
|
2070
2137
|
window.addEventListener("resize", measure);
|
|
@@ -2073,6 +2140,12 @@ function BalanceDashboard(props) {
|
|
|
2073
2140
|
window.removeEventListener("resize", measure);
|
|
2074
2141
|
};
|
|
2075
2142
|
}, []);
|
|
2143
|
+
(0, import_react4.useLayoutEffect)(() => {
|
|
2144
|
+
const node = rootRef.current;
|
|
2145
|
+
if (node === null) return;
|
|
2146
|
+
const scrollport = node.closest("[data-conversation-scroll]");
|
|
2147
|
+
if (scrollport instanceof HTMLElement) scrollport.scrollTop = 0;
|
|
2148
|
+
}, [props.sessionId]);
|
|
2076
2149
|
(0, import_react4.useLayoutEffect)(() => {
|
|
2077
2150
|
quotaViewActiveStore.set(true);
|
|
2078
2151
|
return () => quotaViewActiveStore.set(false);
|
|
@@ -2282,7 +2355,11 @@ function BalanceDashboard(props) {
|
|
|
2282
2355
|
);
|
|
2283
2356
|
const errorText = error === null ? null : localizeApiError(error, t, "error.query");
|
|
2284
2357
|
const composerSafeAreaStyle = {
|
|
2285
|
-
"--dq-composer-h": `${composerHeight ?? COMPOSER_FALLBACK_PX}px
|
|
2358
|
+
"--dq-composer-h": `${composerHeight ?? COMPOSER_FALLBACK_PX}px`,
|
|
2359
|
+
// Only set once measured; .dq-balance's max-width falls back to 860px
|
|
2360
|
+
// without it (pre-measurement render keeps the settled wide-viewport
|
|
2361
|
+
// column, then narrows in place — no flash of the old width).
|
|
2362
|
+
...composerWidth !== null ? { "--dq-composer-w": `${composerWidth}px` } : {}
|
|
2286
2363
|
};
|
|
2287
2364
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-balance", ref: rootRef, style: composerSafeAreaStyle, children: [
|
|
2288
2365
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-status-row", children: [
|
|
@@ -2426,6 +2503,31 @@ function BalanceDashboard(props) {
|
|
|
2426
2503
|
),
|
|
2427
2504
|
usage !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PricingNote, { pricing: usage.pricing })
|
|
2428
2505
|
] }),
|
|
2506
|
+
usage !== null && activeWindow !== null && activeWindow.vision !== void 0 && Array.isArray(activeWindow.visionDaily) && Array.isArray(activeWindow.visionSessions) ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
|
|
2507
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-card-title", children: t("vision.title") }),
|
|
2508
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
2509
|
+
VisionCard,
|
|
2510
|
+
{
|
|
2511
|
+
vision: activeWindow.vision,
|
|
2512
|
+
daily: activeWindow.visionDaily,
|
|
2513
|
+
sessions: activeWindow.visionSessions,
|
|
2514
|
+
windowLabel: activeWindowName,
|
|
2515
|
+
inputTokens: activeWindow.totals.input,
|
|
2516
|
+
costTotal: activeWindow.totals.cost
|
|
2517
|
+
}
|
|
2518
|
+
)
|
|
2519
|
+
] }) : usage === null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
|
|
2520
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-card-title", children: t("vision.title") }),
|
|
2521
|
+
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: [
|
|
2522
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w, h: 11 }),
|
|
2523
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 72, h: 20 })
|
|
2524
|
+
] }, i)) }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-empty", children: t("vision.empty", { window: activeWindowName }) })
|
|
2525
|
+
] }) : (
|
|
2526
|
+
// Host serving a pre-vision payload (e.g. not restarted yet): hide the
|
|
2527
|
+
// whole card instead of leaving a titled shell — it reappears as soon
|
|
2528
|
+
// as the host half serves the new shape.
|
|
2529
|
+
null
|
|
2530
|
+
),
|
|
2429
2531
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card dq-card--primary", children: [
|
|
2430
2532
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card-head", children: [
|
|
2431
2533
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-card-title", children: t("usage.trendTitle") }),
|
|
@@ -2502,7 +2604,7 @@ function BalanceDashboard(props) {
|
|
|
2502
2604
|
] }),
|
|
2503
2605
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-card", children: [
|
|
2504
2606
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-card-title", children: t("peak.title") }),
|
|
2505
|
-
usage !== null ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PeakCard, { split: usage.peakSplit, pricing: usage.pricing
|
|
2607
|
+
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: [
|
|
2506
2608
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "dq-peak-track" }),
|
|
2507
2609
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "dq-peak-figures", children: [
|
|
2508
2610
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Skel, { w: 120, h: 11 }),
|
|
@@ -2601,7 +2703,8 @@ function BalanceDashboard(props) {
|
|
|
2601
2703
|
/* @__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") })
|
|
2602
2704
|
] })
|
|
2603
2705
|
] })
|
|
2604
|
-
] })
|
|
2706
|
+
] }),
|
|
2707
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "dq-footer", children: "@cassius0924/dsh-usage-dashboard v0.6.0" })
|
|
2605
2708
|
] });
|
|
2606
2709
|
}
|
|
2607
2710
|
|
|
@@ -2692,9 +2795,14 @@ var css = `
|
|
|
2692
2795
|
/* \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
|
|
2693
2796
|
\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
|
|
2694
2797
|
padding\uFF0816px/20px\uFF0C\u5361\u7247\u5185\u90E8\u547C\u5438\u611F\uFF0C\u8F6E\u6B21 36 \u5DF2\u5B9A\uFF09\u6DF7\u540C\u3002 */
|
|
2695
|
-
|
|
2798
|
+
/* Width parity with the host composer: the dashboard column reserves the
|
|
2799
|
+
same max-width as the conversation reading column (860px, \u8F6E\u6B21 42 kept it
|
|
2800
|
+
deliberately), but on narrower viewports the fixed 860px would overhang
|
|
2801
|
+
the input box \u2014 measured in dashboard.tsx as --dq-composer-w, capped at
|
|
2802
|
+
the reading-column width so wide viewports are unchanged. */
|
|
2803
|
+
.dq-balance{display:flex;flex-direction:column;gap:24px;padding:20px 24px 48px;max-width:min(860px,var(--dq-composer-w,860px));margin:0 auto;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:13px;line-height:1.5;color:var(--dsw-alias-label-primary,#1f2328)}
|
|
2696
2804
|
@media (max-width:620px){.dq-balance{box-sizing:border-box;max-width:calc(100vw - 56px);padding:16px 16px calc(var(--dq-composer-h,126px) + 16px)}}
|
|
2697
|
-
.dq-card{background:var(--dsw-alias-bg-layer-1,#ffffff);border:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08));border-radius:12px;padding:16px}
|
|
2805
|
+
.dq-card{background:var(--dsw-alias-bg-layer-1,#ffffff);border:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08));border-radius:12px;padding:16px;min-width:0;max-width:100%}
|
|
2698
2806
|
.dq-card-title{font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary,#59636e);margin:0 0 12px;letter-spacing:.02em}
|
|
2699
2807
|
.dq-card-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px}
|
|
2700
2808
|
.dq-card-head .dq-card-title{margin:0}
|
|
@@ -2910,6 +3018,9 @@ p.dq-session-sub{margin:8px 0}
|
|
|
2910
3018
|
.dq-setting-label{font-size:13px;color:var(--dsw-alias-label-primary,#1f2328);padding-top:6px;flex:none}
|
|
2911
3019
|
.dq-setting-control{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0}
|
|
2912
3020
|
.dq-setting-hint{font-size:12px;color:var(--dsw-alias-label-secondary,#59636e);line-height:1.5}
|
|
3021
|
+
/* Page footer: the package name + version, injected at build time from
|
|
3022
|
+
package.json (see build.mjs) so it can never drift from the release. */
|
|
3023
|
+
.dq-footer{margin:18px 0 0;font-size:12px;text-align:center;color:var(--dsw-alias-label-tertiary,#59636e)}
|
|
2913
3024
|
.dq-number{width:82px;padding:5px 8px;font-size:13px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.16));background:var(--dsw-alias-bg-layer-1,#fff);color:var(--dsw-alias-label-primary,#1f2328);font-variant-numeric:tabular-nums}
|
|
2914
3025
|
.dq-number:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#0969da);outline-offset:1px;border-color:transparent}
|
|
2915
3026
|
.dq-links-title{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary,#1f2328);margin:0 0 8px}
|
|
@@ -2936,6 +3047,16 @@ p.dq-session-sub{margin:8px 0}
|
|
|
2936
3047
|
case is the font-size drop below, which keeps every measured stress value comfortably under the
|
|
2937
3048
|
83px column width without ever hitting the ellipsis fallback (see round 38 PROGRESS entry) */
|
|
2938
3049
|
.dq-usage-totals .dq-stat-value{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
3050
|
+
.dq-vision-share{display:inline-block;margin-left:6px;font-size:11px;font-weight:550;color:var(--dsw-alias-label-tertiary,#59636e)}
|
|
3051
|
+
.dq-vision-sessions{margin-top:12px;display:flex;flex-direction:column;gap:8px;min-width:0}
|
|
3052
|
+
/* The vision card's session rows reuse .dq-session-head, but \u2014 unlike the
|
|
3053
|
+
ranking card's rows \u2014 they sit directly under .dq-session with no
|
|
3054
|
+
.dq-session-summary-body wrapper to carry min-width:0. Without the chain
|
|
3055
|
+
below, a long session title (nowrap) sets the row's min-content width and
|
|
3056
|
+
can stretch the card past its siblings / the container. */
|
|
3057
|
+
.dq-vision-sessions .dq-session{min-width:0}
|
|
3058
|
+
.dq-vision-sessions .dq-session-head{min-width:0}
|
|
3059
|
+
.dq-vision-sessions .dq-session-title{flex:1 1 auto;min-width:0}
|
|
2939
3060
|
@media (max-width:620px){.dq-usage-totals{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px 24px}.dq-usage-totals .dq-stat{min-width:0}.dq-usage-totals .dq-stat-value{font-size:13px}}
|
|
2940
3061
|
.dq-chart-peak{margin-left:10px;font-size:11px;font-weight:400;color:var(--dsw-alias-label-tertiary,#59636e);font-variant-numeric:tabular-nums}
|
|
2941
3062
|
.dq-chart-title{font-size:12px;font-weight:600;color:var(--dsw-alias-label-secondary,#59636e);margin:16px 0 8px}
|