dsh-key-usage 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2280 @@
1
+ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
2
+
3
+
4
+ var module = { exports: {} };
5
+ var exports = module.exports;
6
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
+ let react = require("react");
8
+ let react_jsx_runtime = require("react/jsx-runtime");
9
+ //#region src/client/UsageIcon.tsx
10
+ function UsageIcon({ size = 18 }) {
11
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
12
+ width: size,
13
+ height: size,
14
+ viewBox: "0 0 16 16",
15
+ fill: "none",
16
+ "aria-hidden": "true",
17
+ focusable: "false",
18
+ style: { display: "block" },
19
+ children: [
20
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
21
+ x: "2.6",
22
+ y: "8.8",
23
+ width: "2.6",
24
+ height: "4.2",
25
+ rx: "0.7",
26
+ fill: "currentColor"
27
+ }),
28
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
29
+ x: "6.7",
30
+ y: "5.6",
31
+ width: "2.6",
32
+ height: "7.4",
33
+ rx: "0.7",
34
+ fill: "currentColor"
35
+ }),
36
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
37
+ x: "10.8",
38
+ y: "2.8",
39
+ width: "2.6",
40
+ height: "10.2",
41
+ rx: "0.7",
42
+ fill: "currentColor"
43
+ })
44
+ ]
45
+ });
46
+ }
47
+ //#endregion
48
+ //#region src/client/api.ts
49
+ async function fetchJson(path) {
50
+ try {
51
+ const response = await fetch(path, { headers: { Accept: "application/json" } });
52
+ if (!response.ok) return null;
53
+ return await response.json();
54
+ } catch {
55
+ return null;
56
+ }
57
+ }
58
+ async function loadChannels() {
59
+ const result = await fetchJson("/dsh-key-usage/channels");
60
+ return result === null ? null : result.channels;
61
+ }
62
+ async function loadUsage(from, to, provider) {
63
+ const params = new URLSearchParams({
64
+ from,
65
+ to
66
+ });
67
+ if (provider !== "ALL") params.set("provider", provider);
68
+ return fetchJson("/dsh-key-usage/usage?" + params.toString());
69
+ }
70
+ async function loadHeatmap() {
71
+ const result = await fetchJson("/dsh-key-usage/heatmap");
72
+ return result === null ? null : result.days;
73
+ }
74
+ /**
75
+ * 余额与订阅窗口:服务端实时优先、失败回落本机快照。
76
+ * @param fresh - 用户手动刷新时传 true,强制重新查一次官方接口
77
+ */
78
+ async function loadQuota(fresh = false) {
79
+ const result = await fetchJson("/dsh-key-usage/quota" + (fresh ? "?fresh=1" : ""));
80
+ return result === null ? null : result.providers;
81
+ }
82
+ function loadSummary() {
83
+ return fetchJson("/dsh-key-usage/summary");
84
+ }
85
+ function loadSessions(limit = 300) {
86
+ return fetchJson("/dsh-key-usage/sessions?limit=" + String(limit));
87
+ }
88
+ function loadProjects(limit = 200) {
89
+ return fetchJson("/dsh-key-usage/projects?limit=" + String(limit));
90
+ }
91
+ //#endregion
92
+ //#region src/client/format.ts
93
+ /** 展示层格式化:全部与统计口径无关,只负责把数字变成界面文本。 */
94
+ /** 本地日历 `YYYY-MM-DD`。 */
95
+ function isoDate(date) {
96
+ const month = String(date.getMonth() + 1).padStart(2, "0");
97
+ const day = String(date.getDate()).padStart(2, "0");
98
+ return `${date.getFullYear()}-${month}-${day}`;
99
+ }
100
+ function shiftedIsoDate(offsetDays) {
101
+ return isoDate(new Date(Date.now() + offsetDays * 864e5));
102
+ }
103
+ function fmtTokens(count) {
104
+ if (count >= 1e9) return (count / 1e9).toFixed(2) + "B";
105
+ if (count >= 1e6) return (count / 1e6).toFixed(1) + "M";
106
+ if (count >= 1e3) return (count / 1e3).toFixed(0) + "K";
107
+ return String(count);
108
+ }
109
+ function fmtCost(amount) {
110
+ return "¥" + amount.toFixed(2);
111
+ }
112
+ function fmtCalls(count) {
113
+ return count.toLocaleString("en-US").replace(/,/g, " ");
114
+ }
115
+ /** `MM-DD`(图表坐标轴与范围文案用)。 */
116
+ function fmtShortDate(dateKey) {
117
+ return dateKey.slice(5);
118
+ }
119
+ /** 毫秒时间戳 → `YYYY-MM-DD HH:mm`(本地时区)。 */
120
+ function fmtDateTime(timeMs) {
121
+ if (timeMs === null) return "—";
122
+ const date = new Date(timeMs);
123
+ const hours = String(date.getHours()).padStart(2, "0");
124
+ const minutes = String(date.getMinutes()).padStart(2, "0");
125
+ return `${isoDate(date)} ${hours}:${minutes}`;
126
+ }
127
+ /** 毫秒时间戳 → `HH:mm`。 */
128
+ function fmtTime(timeMs) {
129
+ if (timeMs === null) return "—";
130
+ const date = new Date(timeMs);
131
+ return `${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
132
+ }
133
+ /** 时长(毫秒)→ `2h 15m` / `43m` / `—`。 */
134
+ function fmtDuration(milliseconds) {
135
+ if (milliseconds === null || milliseconds <= 0) return "—";
136
+ const minutes = Math.max(1, Math.round(milliseconds / 6e4));
137
+ if (minutes < 60) return `${minutes}m`;
138
+ return `${Math.floor(minutes / 60)}h ${minutes % 60}m`;
139
+ }
140
+ /** 最近活跃:刚刚 / N 分钟前 / N 小时前 / N 天前。 */
141
+ function fmtRelative(timeMs, nowMs) {
142
+ if (timeMs === null) return "—";
143
+ const minutes = Math.round((nowMs - timeMs) / 6e4);
144
+ if (minutes < 1) return "刚刚";
145
+ if (minutes < 60) return `${minutes} 分钟前`;
146
+ if (minutes < 1440) return `${Math.round(minutes / 60)} 小时前`;
147
+ return `${Math.round(minutes / 1440)} 天前`;
148
+ }
149
+ //#endregion
150
+ //#region \0dsh-css:D:\EasyJob\.plugin\dsh-key-usage\src\client\styles\shared.module.css.mjs
151
+ const css$5 = ".LhdpGW_section{color:var(--dsw-alias-label-primary,#e8e8ec);font-size:13.5px;line-height:1.55}.LhdpGW_title{justify-content:space-between;align-items:center;gap:10px;margin-bottom:12px;font-size:18px;font-weight:650;display:flex}.LhdpGW_titleActions{align-items:center;gap:8px;display:inline-flex}.LhdpGW_refreshBtn{border:1px solid var(--dsw-alias-border-l2,#2c2c34);color:var(--dsw-alias-label-secondary,#9a9aa6);cursor:pointer;background:0 0;border-radius:999px;padding:3px 12px;font-family:inherit;font-size:11.5px}.LhdpGW_refreshBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#80808c1a);color:var(--dsw-alias-label-primary,#e8e8ec)}.LhdpGW_refreshBtn:disabled{opacity:.6;cursor:default}.LhdpGW_tabBar{border-bottom:1px solid var(--dsw-alias-border-l1,#2c2c34);flex-wrap:wrap;gap:2px;margin-bottom:18px;display:flex}.LhdpGW_tabBar button{color:var(--dsw-alias-label-secondary,#9a9aa6);font:inherit;cursor:pointer;background:0 0;border:0;border-bottom:2px solid #0000;margin-bottom:-1px;padding:8px 14px;font-size:13px}.LhdpGW_tabBar button:hover{color:var(--dsw-alias-label-primary,#e8e8ec)}.LhdpGW_tabBar button.LhdpGW_active{color:var(--dsw-alias-state-accent-primary,#5b8cff);border-bottom-color:var(--dsw-alias-state-accent-primary,#5b8cff);font-weight:600}.LhdpGW_card{background:var(--dsw-alias-bg-layer-2,#1d1d22);border:1px solid var(--dsw-alias-border-l1,#2c2c34);border-radius:12px;padding:14px 16px}.LhdpGW_cardGrid3{grid-template-columns:repeat(3,minmax(0,1fr));gap:10px;margin-bottom:12px;display:grid}.LhdpGW_cardGrid4{grid-template-columns:repeat(4,minmax(0,1fr));gap:10px;margin-bottom:18px;display:grid}.LhdpGW_cardGridAuto{grid-template-columns:repeat(auto-fit,minmax(170px,1fr));gap:10px;margin-bottom:18px;display:grid}@media (max-width:720px){.LhdpGW_cardGrid4{grid-template-columns:repeat(2,minmax(0,1fr))}}.LhdpGW_statLabel{color:var(--dsw-alias-label-tertiary,#67676f);margin-bottom:6px;font-size:11.5px}.LhdpGW_statValue{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary,#e8e8ec);font-size:20px;font-weight:650}.LhdpGW_statExtra{color:var(--dsw-alias-label-tertiary,#67676f);margin-top:4px;font-size:11.5px}.LhdpGW_groupLabel{color:var(--dsw-alias-label-secondary,#9a9aa6);margin:4px 0 10px;font-size:12.5px;font-weight:600}.LhdpGW_blockLabel{color:var(--dsw-alias-label-secondary,#9a9aa6);margin:18px 0 4px;font-size:11.5px}.LhdpGW_table{border-collapse:collapse;width:100%;margin-top:10px}.LhdpGW_table th{text-align:right;color:var(--dsw-alias-label-tertiary,#67676f);border-bottom:1px solid var(--dsw-alias-border-l1,#2c2c34);white-space:nowrap;padding:5px 8px;font-size:10.5px;font-weight:500}.LhdpGW_table td{text-align:right;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary,#9a9aa6);vertical-align:middle;border-bottom:1px solid var(--dsw-alias-border-l1,#2c2c34);white-space:normal;overflow-wrap:anywhere;padding:7px 8px;font-size:12px}.LhdpGW_table th:first-child,.LhdpGW_table td:first-child{text-align:left;padding-left:0}.LhdpGW_table td:first-child{color:var(--dsw-alias-label-primary,#e8e8ec)}.LhdpGW_table th.LhdpGW_sortable{cursor:pointer;-webkit-user-select:none;user-select:none}.LhdpGW_table th.LhdpGW_sortable:hover{color:var(--dsw-alias-label-secondary,#9a9aa6)}.LhdpGW_table th.LhdpGW_sortActive{color:var(--dsw-alias-state-accent-primary,#5b8cff)}.LhdpGW_sortArrow{opacity:.8;margin-left:3px;font-size:8px}.LhdpGW_table tr.LhdpGW_clickable{cursor:pointer}.LhdpGW_table tr.LhdpGW_clickable:hover{background:var(--dsw-alias-interactive-bg-hover,#80808c12)}.LhdpGW_table tr.LhdpGW_total td{color:var(--dsw-alias-label-primary,#e8e8ec);border-bottom:0;border-top:1px solid var(--dsw-alias-border-l2,#2c2c34);font-weight:600}.LhdpGW_table tr.LhdpGW_disabled{opacity:.55}.LhdpGW_chipRow{flex-wrap:wrap;gap:8px;margin-bottom:14px;display:flex}.LhdpGW_chip{border:1px solid var(--dsw-alias-border-l2,#2c2c34);color:var(--dsw-alias-label-secondary,#9a9aa6);font:inherit;cursor:pointer;background:0 0;border-radius:999px;align-items:center;gap:6px;padding:5px 13px;font-size:12px;display:inline-flex}.LhdpGW_chip:hover{color:var(--dsw-alias-label-primary,#e8e8ec);border-color:var(--dsw-alias-label-tertiary,#67676f)}.LhdpGW_chip.LhdpGW_active{background:var(--dsw-alias-interactive-bg-active,#5b8cff24);color:var(--dsw-alias-state-accent-primary,#5b8cff);border-color:#5b8cff66;font-weight:600}.LhdpGW_statusDot{border-radius:50%;flex-shrink:0;width:8px;height:8px;display:inline-block}.LhdpGW_dim{color:var(--dsw-alias-label-tertiary,#67676f)}.LhdpGW_mono{color:var(--dsw-alias-label-secondary,#9a9aa6);font-family:ui-monospace,Consolas,monospace;font-size:11px}.LhdpGW_empty{color:var(--dsw-alias-label-tertiary,#67676f);padding:18px 0}.LhdpGW_warning{color:var(--dsw-alias-state-warning-primary,#e0a13c);margin-bottom:10px;font-size:11.5px}.LhdpGW_legend{color:var(--dsw-alias-label-tertiary,#67676f);flex-wrap:wrap;align-items:center;gap:10px;font-size:10.5px;display:inline-flex}.LhdpGW_legend i{border-radius:2px;width:10px;height:10px;margin-right:3px;display:inline-block}.LhdpGW_legend i.LhdpGW_ghost{background:#80808c1a;border:1px solid #80808c47}.LhdpGW_footnote{border-top:1px solid var(--dsw-alias-border-l1,#2c2c34);color:var(--dsw-alias-label-tertiary,#67676f);margin-top:26px;padding-top:14px;font-size:11.5px;line-height:1.8}.LhdpGW_footnote b{color:var(--dsw-alias-label-secondary,#9a9aa6);font-weight:500}.LhdpGW_footnoteList{margin:4px 0 0;padding-left:18px}.LhdpGW_footnoteList li{margin:1px 0}.LhdpGW_tooltip{z-index:9999;pointer-events:none;background:var(--dsw-alias-bg-layer-3,#232329);border:1px solid var(--dsw-alias-border-l2,#2c2c34);box-shadow:var(--dsw-shadow-lv2,0 8px 24px #00000059);min-width:210px;max-width:300px;color:var(--dsw-alias-label-primary,#e8e8ec);border-radius:10px;padding:10px 12px;font-family:inherit;font-size:12px;line-height:1.5;position:fixed}.LhdpGW_tipTitle{margin-bottom:2px;font-size:12px;font-weight:650}.LhdpGW_tipSub{color:var(--dsw-alias-label-secondary,#9a9aa6);margin-bottom:8px;font-size:11.5px}.LhdpGW_tipRow{color:var(--dsw-alias-label-secondary,#9a9aa6);align-items:center;gap:6px;padding:2px 0;font-size:11px;display:flex}.LhdpGW_tipRow i{border-radius:2px;flex-shrink:0;width:7px;height:7px}.LhdpGW_tipName{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.LhdpGW_tipAmount{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-tertiary,#67676f);text-align:right;width:58px}.LhdpGW_tipRate{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary,#e8e8ec);font-weight:600}.LhdpGW_tipBar{background:var(--dsw-alias-fill-l1,#80808c2e);border-radius:2px;height:3px;margin:1px 0 5px;overflow:hidden}.LhdpGW_tipBar>i{border-radius:2px;height:100%;display:block}.LhdpGW_tipDivider{border-top:1px solid var(--dsw-alias-border-l1,#2c2c34);color:var(--dsw-alias-label-secondary,#9a9aa6);justify-content:space-between;gap:12px;margin-top:6px;padding-top:6px;font-size:11px;display:flex}";
152
+ const tagId$5 = "dsh-key-usage/shared.module.css";
153
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$5) + "]") === null) {
154
+ const tag = document.createElement("style");
155
+ tag.dataset.plugin = "dsh-key-usage";
156
+ tag.dataset.pluginCss = tagId$5;
157
+ tag.textContent = css$5;
158
+ document.head.appendChild(tag);
159
+ }
160
+ const cssRegistry$5 = globalThis.__DSH_PLUGIN_CSS__ ??= [];
161
+ if (!cssRegistry$5.some((e) => e.tagId === tagId$5)) cssRegistry$5.push({
162
+ tagId: tagId$5,
163
+ css: css$5
164
+ });
165
+ var shared_module_css_default = {
166
+ "tipSub": "LhdpGW_tipSub",
167
+ "sortable": "LhdpGW_sortable",
168
+ "clickable": "LhdpGW_clickable",
169
+ "cardGridAuto": "LhdpGW_cardGridAuto",
170
+ "card": "LhdpGW_card",
171
+ "tipDivider": "LhdpGW_tipDivider",
172
+ "tipName": "LhdpGW_tipName",
173
+ "sortActive": "LhdpGW_sortActive",
174
+ "footnote": "LhdpGW_footnote",
175
+ "tipBar": "LhdpGW_tipBar",
176
+ "tipRow": "LhdpGW_tipRow",
177
+ "tipRate": "LhdpGW_tipRate",
178
+ "tabBar": "LhdpGW_tabBar",
179
+ "blockLabel": "LhdpGW_blockLabel",
180
+ "empty": "LhdpGW_empty",
181
+ "total": "LhdpGW_total",
182
+ "statLabel": "LhdpGW_statLabel",
183
+ "sortArrow": "LhdpGW_sortArrow",
184
+ "statusDot": "LhdpGW_statusDot",
185
+ "mono": "LhdpGW_mono",
186
+ "statExtra": "LhdpGW_statExtra",
187
+ "refreshBtn": "LhdpGW_refreshBtn",
188
+ "legend": "LhdpGW_legend",
189
+ "footnoteList": "LhdpGW_footnoteList",
190
+ "cardGrid4": "LhdpGW_cardGrid4",
191
+ "dim": "LhdpGW_dim",
192
+ "section": "LhdpGW_section",
193
+ "cardGrid3": "LhdpGW_cardGrid3",
194
+ "tipTitle": "LhdpGW_tipTitle",
195
+ "statValue": "LhdpGW_statValue",
196
+ "titleActions": "LhdpGW_titleActions",
197
+ "title": "LhdpGW_title",
198
+ "active": "LhdpGW_active",
199
+ "disabled": "LhdpGW_disabled",
200
+ "groupLabel": "LhdpGW_groupLabel",
201
+ "chipRow": "LhdpGW_chipRow",
202
+ "chip": "LhdpGW_chip",
203
+ "tooltip": "LhdpGW_tooltip",
204
+ "tipAmount": "LhdpGW_tipAmount",
205
+ "table": "LhdpGW_table",
206
+ "warning": "LhdpGW_warning",
207
+ "ghost": "LhdpGW_ghost"
208
+ };
209
+ //#endregion
210
+ //#region src/client/tooltip.tsx
211
+ /** 共享浮层:跟随鼠标的 fixed 层,内容用 React 节点渲染(不再拼接 innerHTML)。 */
212
+ function useTooltip() {
213
+ const [tooltip, setTooltip] = (0, react.useState)(null);
214
+ return {
215
+ tooltip,
216
+ show: (0, react.useCallback)((event, node) => {
217
+ setTooltip({
218
+ x: event.clientX,
219
+ y: event.clientY,
220
+ node
221
+ });
222
+ }, []),
223
+ hide: (0, react.useCallback)(() => setTooltip(null), [])
224
+ };
225
+ }
226
+ function TooltipView({ tooltip }) {
227
+ const elementRef = (0, react.useRef)(null);
228
+ (0, react.useLayoutEffect)(() => {
229
+ const element = elementRef.current;
230
+ if (element === null || tooltip === null) return;
231
+ const maxX = window.innerWidth - element.offsetWidth - 12;
232
+ const maxY = window.innerHeight - element.offsetHeight - 12;
233
+ element.style.left = Math.max(8, Math.min(tooltip.x + 14, maxX)) + "px";
234
+ element.style.top = Math.max(8, Math.min(tooltip.y + 14, maxY)) + "px";
235
+ }, [tooltip]);
236
+ if (tooltip === null) return null;
237
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
238
+ ref: elementRef,
239
+ className: shared_module_css_default.tooltip,
240
+ children: tooltip.node
241
+ });
242
+ }
243
+ function TipTitle({ children }) {
244
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
245
+ className: shared_module_css_default.tipTitle,
246
+ children
247
+ });
248
+ }
249
+ function TipSub({ children }) {
250
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
251
+ className: shared_module_css_default.tipSub,
252
+ children
253
+ });
254
+ }
255
+ function TipRow(props) {
256
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
257
+ className: shared_module_css_default.tipRow,
258
+ children: [
259
+ props.color !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: { background: props.color } }),
260
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
261
+ className: shared_module_css_default.tipName,
262
+ children: props.label
263
+ }),
264
+ props.amount !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
265
+ className: shared_module_css_default.tipAmount,
266
+ children: props.amount
267
+ }),
268
+ props.rate !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
269
+ className: shared_module_css_default.tipRate,
270
+ children: props.rate
271
+ })
272
+ ]
273
+ });
274
+ }
275
+ function TipBar({ percent, color }) {
276
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
277
+ className: shared_module_css_default.tipBar,
278
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: {
279
+ width: Math.max(2, percent) + "%",
280
+ background: color
281
+ } })
282
+ });
283
+ }
284
+ function TipDivider({ children }) {
285
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
286
+ className: shared_module_css_default.tipDivider,
287
+ children
288
+ });
289
+ }
290
+ //#endregion
291
+ //#region src/client/channels.ts
292
+ const CHANNEL_COLORS = [
293
+ "#5b8cff",
294
+ "#9a7bff",
295
+ "#3fb96f",
296
+ "#e0a13c",
297
+ "#e05a5a",
298
+ "#38bdc8"
299
+ ];
300
+ function channelIndexOf(channels, provider) {
301
+ return channels.findIndex((channel) => channel.provider === provider);
302
+ }
303
+ function channelColorOf(channels, provider) {
304
+ const index = channelIndexOf(channels, provider);
305
+ return CHANNEL_COLORS[(index >= 0 ? index : 0) % CHANNEL_COLORS.length];
306
+ }
307
+ function displayNameOf(channels, provider) {
308
+ return channels.find((channel) => channel.provider === provider)?.displayName ?? provider;
309
+ }
310
+ function channelOf(channels, provider) {
311
+ if (provider === null) return null;
312
+ return channels.find((channel) => channel.provider === provider) ?? null;
313
+ }
314
+ /** 成本单元格:按计费模式给出文案,无法判定时回落金额或 —。 */
315
+ function costCellText(channel, cost) {
316
+ if (channel === null) return cost > 0 ? {
317
+ text: "¥" + cost.toFixed(2),
318
+ dim: false
319
+ } : {
320
+ text: "—",
321
+ dim: true
322
+ };
323
+ if (channel.costMode === "sub") return {
324
+ text: "订阅内",
325
+ dim: true
326
+ };
327
+ if (channel.costMode === "free") return {
328
+ text: "¥0.00",
329
+ dim: true
330
+ };
331
+ if (channel.costMode === "none") return {
332
+ text: "无计费",
333
+ dim: true
334
+ };
335
+ return cost > 0 ? {
336
+ text: "¥" + cost.toFixed(2),
337
+ dim: false
338
+ } : {
339
+ text: "—",
340
+ dim: true
341
+ };
342
+ }
343
+ /** 状态点的颜色(走宿主告警/成功令牌)。 */
344
+ function statusColorOf(status) {
345
+ if (status === "ok") return "var(--dsw-alias-state-success-primary, #3fb96f)";
346
+ if (status === "warn") return "var(--dsw-alias-state-warning-primary, #e0a13c)";
347
+ if (status === "danger") return "var(--dsw-alias-state-danger-primary, #e05a5a)";
348
+ return "var(--dsw-alias-label-tertiary, #67676f)";
349
+ }
350
+ /** 窗口占用进度条颜色:≥90 危险、≥60 警告,其余正常。 */
351
+ function quotaBarColor(percent) {
352
+ if (percent >= 90) return "var(--dsw-alias-state-danger-primary, #e05a5a)";
353
+ if (percent >= 60) return "var(--dsw-alias-state-warning-primary, #e0a13c)";
354
+ return "var(--dsw-alias-state-success-primary, #3fb96f)";
355
+ }
356
+ //#endregion
357
+ //#region \0dsh-css:D:\EasyJob\.plugin\dsh-key-usage\src\client\styles\overview.module.css.mjs
358
+ const css$4 = ".J7Idbq_keyTable{border-collapse:collapse;width:100%}.J7Idbq_keyTable th{text-align:right;color:var(--dsw-alias-label-tertiary,#67676f);border-bottom:1px solid var(--dsw-alias-border-l1,#2c2c34);white-space:nowrap;padding:6px 10px;font-size:10.5px;font-weight:500}.J7Idbq_keyTable th:first-child{text-align:left;padding-left:0}.J7Idbq_keyTable td{text-align:right;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary,#9a9aa6);border-bottom:1px solid var(--dsw-alias-fill-l1,#80808c1f);white-space:nowrap;padding:10px;font-size:12.5px}.J7Idbq_keyTable td:first-child{text-align:left;padding-left:0}.J7Idbq_keyTable tr:last-child td{border-bottom:0}.J7Idbq_keyTable tbody tr.J7Idbq_clickable{cursor:pointer}.J7Idbq_keyTable tbody tr.J7Idbq_clickable:hover{background:var(--dsw-alias-interactive-bg-hover,#80808c12)}.J7Idbq_keyTable tr.J7Idbq_disabledRow{opacity:.55}.J7Idbq_cellKey{align-items:center;gap:10px;display:flex}.J7Idbq_keyDisplay{color:var(--dsw-alias-label-primary,#e8e8ec);font-size:13px;font-weight:600}.J7Idbq_keyEnv{color:var(--dsw-alias-label-tertiary,#67676f);margin-top:1px;font-family:ui-monospace,Consolas,monospace;font-size:10px}.J7Idbq_tag{border:1px solid var(--dsw-alias-border-l2,#2c2c34);color:var(--dsw-alias-label-secondary,#9a9aa6);background:var(--dsw-alias-bg-layer-2,#1d1d22);border-radius:5px;padding:1px 7px;font-size:10.5px;display:inline-block}.J7Idbq_tag.J7Idbq_free{color:var(--dsw-alias-state-success-primary,#3fb96f);border-color:#3fb96f59}.J7Idbq_tag.J7Idbq_sub{color:var(--dsw-alias-state-accent-primary,#5b8cff);border-color:#5b8cff59}.J7Idbq_tag.J7Idbq_off{color:var(--dsw-alias-label-tertiary,#67676f)}.J7Idbq_rowLink{color:var(--dsw-alias-label-tertiary,#67676f);font-size:11px}.J7Idbq_keyTable tbody tr.J7Idbq_clickable:hover .J7Idbq_rowLink{color:var(--dsw-alias-state-accent-primary,#5b8cff)}.J7Idbq_quotaCell{align-items:center;gap:8px;display:inline-flex}.J7Idbq_miniBar{background:var(--dsw-alias-fill-l2,#80808c2e);border-radius:3px;flex-shrink:0;width:64px;height:5px;overflow:hidden}.J7Idbq_miniBar>i{border-radius:3px;height:100%;display:block}.J7Idbq_quotaText{text-align:left;min-width:74px}.J7Idbq_quotaText.J7Idbq_warn{color:var(--dsw-alias-state-warning-primary,#e0a13c);font-weight:600}.J7Idbq_quotaText.J7Idbq_danger{color:var(--dsw-alias-state-danger-primary,#e05a5a);font-weight:600}.J7Idbq_quotaText.J7Idbq_dim{color:var(--dsw-alias-label-tertiary,#67676f)}.J7Idbq_heatmapCard{background:var(--dsw-alias-bg-layer-2,#1d1d22);border:1px solid var(--dsw-alias-border-l1,#2c2c34);border-radius:12px;margin-top:24px;padding:14px 16px}.J7Idbq_heatmapHead{flex-wrap:wrap;justify-content:space-between;align-items:baseline;gap:6px;margin-bottom:10px;display:flex}.J7Idbq_heatmapTitle{color:var(--dsw-alias-label-secondary,#9a9aa6);font-size:11.5px}.J7Idbq_heatmapScroll{padding-bottom:4px;overflow-x:auto}.J7Idbq_heatmapBody{gap:6px;width:max-content;display:flex}.J7Idbq_heatWeekdays{flex-direction:column;flex-shrink:0;gap:3px;display:flex}.J7Idbq_heatWeekdays span{color:var(--dsw-alias-label-tertiary,#67676f);text-align:right;width:13px;height:28px;font-size:9.5px;line-height:28px}.J7Idbq_heatGridCol{flex-direction:column;display:flex}.J7Idbq_heatGrid{gap:3px;display:flex}.J7Idbq_heatWeek{flex-direction:column;gap:3px;display:flex}.J7Idbq_heatCell{box-sizing:border-box;background:var(--dsw-alias-fill-l1,#80808c2e);cursor:default;border-radius:5px;flex-shrink:0;width:28px;height:28px}.J7Idbq_heatCell.J7Idbq_idle{background:#80808c38}.J7Idbq_heatCell.J7Idbq_empty{background:#80808c1a;box-shadow:inset 0 0 0 1px #80808c47}.J7Idbq_heatMonths{gap:3px;margin-top:6px;display:flex}.J7Idbq_heatMonths span{width:28px;color:var(--dsw-alias-label-tertiary,#67676f);text-align:left;white-space:nowrap;flex-shrink:0;font-size:10px}";
359
+ const tagId$4 = "dsh-key-usage/overview.module.css";
360
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
361
+ const tag = document.createElement("style");
362
+ tag.dataset.plugin = "dsh-key-usage";
363
+ tag.dataset.pluginCss = tagId$4;
364
+ tag.textContent = css$4;
365
+ document.head.appendChild(tag);
366
+ }
367
+ const cssRegistry$4 = globalThis.__DSH_PLUGIN_CSS__ ??= [];
368
+ if (!cssRegistry$4.some((e) => e.tagId === tagId$4)) cssRegistry$4.push({
369
+ tagId: tagId$4,
370
+ css: css$4
371
+ });
372
+ var overview_module_css_default = {
373
+ "keyEnv": "J7Idbq_keyEnv",
374
+ "quotaText": "J7Idbq_quotaText",
375
+ "quotaCell": "J7Idbq_quotaCell",
376
+ "heatWeekdays": "J7Idbq_heatWeekdays",
377
+ "heatGrid": "J7Idbq_heatGrid",
378
+ "warn": "J7Idbq_warn",
379
+ "heatmapHead": "J7Idbq_heatmapHead",
380
+ "heatmapBody": "J7Idbq_heatmapBody",
381
+ "off": "J7Idbq_off",
382
+ "idle": "J7Idbq_idle",
383
+ "keyDisplay": "J7Idbq_keyDisplay",
384
+ "disabledRow": "J7Idbq_disabledRow",
385
+ "sub": "J7Idbq_sub",
386
+ "heatGridCol": "J7Idbq_heatGridCol",
387
+ "clickable": "J7Idbq_clickable",
388
+ "danger": "J7Idbq_danger",
389
+ "heatmapTitle": "J7Idbq_heatmapTitle",
390
+ "free": "J7Idbq_free",
391
+ "miniBar": "J7Idbq_miniBar",
392
+ "heatmapCard": "J7Idbq_heatmapCard",
393
+ "tag": "J7Idbq_tag",
394
+ "rowLink": "J7Idbq_rowLink",
395
+ "empty": "J7Idbq_empty",
396
+ "heatWeek": "J7Idbq_heatWeek",
397
+ "heatMonths": "J7Idbq_heatMonths",
398
+ "keyTable": "J7Idbq_keyTable",
399
+ "dim": "J7Idbq_dim",
400
+ "cellKey": "J7Idbq_cellKey",
401
+ "heatmapScroll": "J7Idbq_heatmapScroll",
402
+ "heatCell": "J7Idbq_heatCell"
403
+ };
404
+ //#endregion
405
+ //#region src/client/components/Heatmap.tsx
406
+ /**
407
+ * 活跃热力图:最近约 9 个月的滚动窗口(含未来格占位)。
408
+ * 强度分档取活跃日的三分位(p33 / p67),阈值随可见区间自动变化,
409
+ * 具体数值并入页面底部的口径说明,不再单独占一行。
410
+ * 格子用固定方形尺寸 + 横向滚动,保证「有数据 / 无数据 / 无用量」三类格子完全等大。
411
+ */
412
+ const DAY_MS = 864e5;
413
+ function levelThresholds(days) {
414
+ const active = days.map((day) => day.totalTokens).filter((tokens) => tokens > 0).sort((first, second) => first - second);
415
+ if (active.length < 3) return [5e7, 2e8];
416
+ const pick = (fraction) => active[Math.min(active.length - 1, Math.floor(active.length * fraction))];
417
+ return [pick(.33), pick(.67)];
418
+ }
419
+ function levelOf(tokens, thresholds) {
420
+ if (tokens <= 0) return 0;
421
+ if (tokens < thresholds[0]) return 1;
422
+ if (tokens < thresholds[1]) return 2;
423
+ return 3;
424
+ }
425
+ const LEVEL_COLORS = [
426
+ "rgba(128,128,140,.22)",
427
+ "rgba(91,140,255,.25)",
428
+ "rgba(91,140,255,.55)",
429
+ "#5b8cff"
430
+ ];
431
+ function Heatmap(props) {
432
+ const { days, channels, tooltip } = props;
433
+ const heatmapByDate = (0, react.useMemo)(() => new Map(days.map((day) => [day.date, day])), [days]);
434
+ const thresholds = (0, react.useMemo)(() => levelThresholds(days), [days]);
435
+ const { columns, monthLabels } = (0, react.useMemo)(() => {
436
+ const today = /* @__PURE__ */ new Date();
437
+ today.setHours(0, 0, 0, 0);
438
+ const endSaturday = new Date(today.getTime() + (6 - today.getDay()) * DAY_MS);
439
+ const start = /* @__PURE__ */ new Date(endSaturday.getTime() - 272 * DAY_MS);
440
+ const dataStart = days.length > 0 ? days[0].date : null;
441
+ const cells = [];
442
+ for (let offset = 0; offset < 273; offset += 1) {
443
+ const date = new Date(start.getTime() + offset * DAY_MS);
444
+ const dateKey = isoDate(date);
445
+ if (date.getTime() > today.getTime()) {
446
+ cells.push({
447
+ date: dateKey,
448
+ state: "future",
449
+ level: 0
450
+ });
451
+ continue;
452
+ }
453
+ if (dataStart === null || dateKey < dataStart) {
454
+ cells.push({
455
+ date: dateKey,
456
+ state: "empty",
457
+ level: 0
458
+ });
459
+ continue;
460
+ }
461
+ const day = heatmapByDate.get(dateKey);
462
+ if (day === void 0 || day.totalTokens <= 0) {
463
+ cells.push({
464
+ date: dateKey,
465
+ state: "idle",
466
+ level: 0
467
+ });
468
+ continue;
469
+ }
470
+ cells.push({
471
+ date: dateKey,
472
+ state: "active",
473
+ level: levelOf(day.totalTokens, thresholds)
474
+ });
475
+ }
476
+ const weeks = [];
477
+ for (let index = 0; index < cells.length; index += 7) weeks.push(cells.slice(index, index + 7));
478
+ const labels = [];
479
+ let previousMonth = -1;
480
+ for (const week of weeks) {
481
+ const month = Number(week[0].date.slice(5, 7));
482
+ labels.push(month !== previousMonth ? month + "月" : "");
483
+ previousMonth = month;
484
+ }
485
+ return {
486
+ columns: weeks,
487
+ monthLabels: labels
488
+ };
489
+ }, [days, heatmapByDate]);
490
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
491
+ className: overview_module_css_default.heatmapCard,
492
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
493
+ className: overview_module_css_default.heatmapHead,
494
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
495
+ className: overview_module_css_default.heatmapTitle,
496
+ children: "活跃热力图 · 近 9 个月 · 按当日 Tokens 总量 · 悬停查看当日各模型占比"
497
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
498
+ className: shared_module_css_default.legend,
499
+ children: [
500
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { className: "ghost" }), "无数据"] }),
501
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: { background: LEVEL_COLORS[0] } }), "无用量"] }),
502
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: { background: LEVEL_COLORS[1] } }), "少"] }),
503
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: { background: LEVEL_COLORS[2] } }), "中"] }),
504
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: { background: LEVEL_COLORS[3] } }), "高"] })
505
+ ]
506
+ })]
507
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
508
+ className: overview_module_css_default.heatmapScroll,
509
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
510
+ className: overview_module_css_default.heatmapBody,
511
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
512
+ className: overview_module_css_default.heatWeekdays,
513
+ children: [
514
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "日" }),
515
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "一" }),
516
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "二" }),
517
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "三" }),
518
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "四" }),
519
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "五" }),
520
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "六" })
521
+ ]
522
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
523
+ className: overview_module_css_default.heatGridCol,
524
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
525
+ className: overview_module_css_default.heatGrid,
526
+ children: columns.map((week, weekIndex) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
527
+ className: overview_module_css_default.heatWeek,
528
+ children: week.map((cell) => {
529
+ const day = cell.state === "active" ? heatmapByDate.get(cell.date) : void 0;
530
+ const className = cell.state === "active" ? overview_module_css_default.heatCell : cell.state === "idle" ? overview_module_css_default.heatCell + " " + overview_module_css_default.idle : overview_module_css_default.heatCell + " " + overview_module_css_default.empty;
531
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
532
+ className,
533
+ style: cell.state === "active" ? { background: LEVEL_COLORS[cell.level] } : void 0,
534
+ onMouseMove: (event) => tooltip.show(event, tipOf(cell, day, channels)),
535
+ onMouseLeave: tooltip.hide
536
+ }, cell.date);
537
+ })
538
+ }, weekIndex))
539
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
540
+ className: overview_module_css_default.heatMonths,
541
+ children: monthLabels.map((label, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }, index))
542
+ })]
543
+ })]
544
+ })
545
+ })]
546
+ });
547
+ }
548
+ function tipOf(cell, day, channels) {
549
+ if (cell.state !== "active" || day === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipTitle, { children: cell.date }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipSub, { children: cell.state === "idle" ? "当日无用量记录" : "台账尚未覆盖" })] });
550
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
551
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipTitle, { children: cell.date }),
552
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(TipSub, { children: [
553
+ "合计 ",
554
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: fmtTokens(day.totalTokens) }),
555
+ " tokens"
556
+ ] }),
557
+ day.models.map((model) => {
558
+ const percent = day.totalTokens > 0 ? Math.round(model.totalTokens / day.totalTokens * 100) : 0;
559
+ const color = channelColorOf(channels, model.provider);
560
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipRow, {
561
+ color,
562
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [model.model, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
563
+ className: shared_module_css_default.dim,
564
+ children: [" · ", displayNameOf(channels, model.provider)]
565
+ })] }),
566
+ amount: fmtTokens(model.totalTokens),
567
+ rate: percent + "%"
568
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipBar, {
569
+ percent,
570
+ color
571
+ })] }, model.provider + "|" + model.model);
572
+ })
573
+ ] });
574
+ }
575
+ //#endregion
576
+ //#region src/client/tabs/OverviewTab.tsx
577
+ function OverviewTab(props) {
578
+ const { channels, summary } = props;
579
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
580
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
581
+ className: shared_module_css_default.groupLabel,
582
+ children: "Key 清单"
583
+ }),
584
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
585
+ className: overview_module_css_default.keyTable,
586
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
587
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "通道" }),
588
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "类型" }),
589
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "今日" }),
590
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "近 30 天" }),
591
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "余额 / 窗口" }),
592
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "30 天成本" }),
593
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", {})
594
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tbody", { children: [channels.map((channel) => {
595
+ const row = summary?.channels.find((item) => item.provider === channel.provider) ?? null;
596
+ const todayTokens = row === null ? 0 : row.today.tokens;
597
+ const monthTokens = row === null ? 0 : row.month.tokens;
598
+ const costCell = costCellText(channel, row === null ? 0 : row.month.cost);
599
+ const disabled = channel.status === "off";
600
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
601
+ className: disabled ? overview_module_css_default.disabledRow : overview_module_css_default.clickable,
602
+ onClick: disabled ? void 0 : () => props.onOpenDetail(channel.provider),
603
+ children: [
604
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
605
+ className: overview_module_css_default.cellKey,
606
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
607
+ className: shared_module_css_default.statusDot,
608
+ style: { background: statusColorOf(channel.status) }
609
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
610
+ className: overview_module_css_default.keyDisplay,
611
+ children: channel.displayName
612
+ })]
613
+ }) }),
614
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
615
+ className: tagClassOf(channel),
616
+ children: channel.status === "off" ? "未配置" : channel.costLabel
617
+ }) }),
618
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: todayTokens > 0 ? fmtTokens(todayTokens) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
619
+ className: shared_module_css_default.dim,
620
+ children: "—"
621
+ }) }),
622
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: monthTokens > 0 ? fmtTokens(monthTokens) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
623
+ className: shared_module_css_default.dim,
624
+ children: "—"
625
+ }) }),
626
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: quotaCellOf(channel) }),
627
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: costCell.dim ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
628
+ className: shared_module_css_default.dim,
629
+ children: costCell.text
630
+ }) : costCell.text }),
631
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: disabled ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
632
+ className: overview_module_css_default.rowLink,
633
+ children: "明细 →"
634
+ }) })
635
+ ]
636
+ }, channel.provider);
637
+ }), channels.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
638
+ colSpan: 7,
639
+ className: shared_module_css_default.empty,
640
+ children: "未读取到通道配置"
641
+ }) })] })]
642
+ }),
643
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Heatmap, {
644
+ days: props.heatmap,
645
+ channels,
646
+ tooltip: props.tooltip
647
+ }),
648
+ summary === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
649
+ className: shared_module_css_default.empty,
650
+ children: "台账读取中或不可用,今日 / 近 30 天列为空"
651
+ }),
652
+ props.quotas.length === 0 && channels.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
653
+ className: shared_module_css_default.empty,
654
+ children: "未读取到余额快照,余额 / 窗口列显示为 —"
655
+ })
656
+ ] });
657
+ }
658
+ function tagClassOf(channel) {
659
+ const base = overview_module_css_default.tag;
660
+ if (channel.status === "off") return base + " " + overview_module_css_default.off;
661
+ if (channel.costMode === "free") return base + " " + overview_module_css_default.free;
662
+ if (channel.costMode === "sub") return base + " " + overview_module_css_default.sub;
663
+ return base;
664
+ }
665
+ function quotaCellOf(channel) {
666
+ if (channel.costMode === "sub" && channel.quotaPercent !== null) {
667
+ const percent = channel.quotaPercent;
668
+ const levelClass = percent >= 90 ? overview_module_css_default.danger : percent >= 60 ? overview_module_css_default.warn : "";
669
+ const windowLabel = channel.quotaWindowLabel ?? channel.quotaWindowKey ?? "";
670
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
671
+ className: overview_module_css_default.quotaCell,
672
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
673
+ className: overview_module_css_default.miniBar,
674
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: {
675
+ width: Math.max(2, percent) + "%",
676
+ background: percent >= 60 ? "var(--dsw-alias-state-warning-primary, #e0a13c)" : "var(--dsw-alias-state-success-primary, #3fb96f)"
677
+ } })
678
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
679
+ className: [overview_module_css_default.quotaText, levelClass].join(" ").trim(),
680
+ children: [
681
+ windowLabel,
682
+ " ",
683
+ percent,
684
+ "%"
685
+ ]
686
+ })]
687
+ });
688
+ }
689
+ if (channel.balanceText !== null) {
690
+ const levelClass = channel.status === "danger" ? overview_module_css_default.danger : channel.status === "warn" ? overview_module_css_default.warn : "";
691
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
692
+ className: overview_module_css_default.quotaCell,
693
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
694
+ className: [overview_module_css_default.quotaText, levelClass].join(" ").trim(),
695
+ children: channel.balanceText
696
+ })
697
+ });
698
+ }
699
+ const fallback = channel.costMode === "none" ? "无计费" : channel.costMode === "free" ? "—" : "未查询";
700
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
701
+ className: [overview_module_css_default.quotaText, overview_module_css_default.dim].join(" "),
702
+ children: fallback
703
+ });
704
+ }
705
+ //#endregion
706
+ //#region \0dsh-css:D:\EasyJob\.plugin\dsh-key-usage\src\client\styles\detail.module.css.mjs
707
+ const css$3 = ".V9QVRa_rangeBar{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:10px;margin-bottom:12px;display:flex}.V9QVRa_rangeLeft{flex-wrap:wrap;align-items:center;gap:10px;display:flex}.V9QVRa_rangeTabs{border:1px solid var(--dsw-alias-border-l2,#2c2c34);border-radius:9px;gap:2px;padding:2px;display:inline-flex}.V9QVRa_rangeTabs button{color:var(--dsw-alias-label-secondary,#9a9aa6);font:inherit;cursor:pointer;background:0 0;border:0;border-radius:7px;padding:4px 13px;font-size:12px}.V9QVRa_rangeTabs button.V9QVRa_active{background:var(--dsw-alias-interactive-bg-active,#5b8cff24);color:var(--dsw-alias-state-accent-primary,#5b8cff);font-weight:600}.V9QVRa_rangeInput{border:1px solid var(--dsw-alias-border-l2,#2c2c34);color:var(--dsw-alias-label-primary,#e8e8ec);font:inherit;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;background:0 0;border-radius:7px;padding:3px 8px;font-size:12px}@media (prefers-color-scheme:dark){.V9QVRa_rangeInput{--lightningcss-light: ;--lightningcss-dark:initial}}.V9QVRa_rangeCaption{color:var(--dsw-alias-label-tertiary,#67676f);margin-left:auto;font-size:11.5px}.V9QVRa_shareBar{border-radius:6px;gap:2px;height:12px;margin:8px 0 2px;display:flex;overflow:hidden}.V9QVRa_shareBar>i{cursor:default;height:100%;display:block}.V9QVRa_shareBar>i:hover{filter:brightness(1.15)}.V9QVRa_chartTitle{flex-wrap:wrap;justify-content:space-between;align-items:baseline;gap:10px;display:flex}.V9QVRa_bars{align-items:flex-end;gap:3px;height:110px;margin-top:8px;display:flex}.V9QVRa_barsDense{gap:2px}.V9QVRa_barColumn{cursor:default;flex-direction:column;flex:1;justify-content:flex-end;gap:1px;height:100%;display:flex}.V9QVRa_barColumn:hover{filter:brightness(1.2)}.V9QVRa_segCache{background:var(--dsw-alias-state-accent-tertiary,#5b8cff47);border-radius:2px 2px 0 0}.V9QVRa_segIn{background:var(--dsw-alias-state-accent-primary,#5b8cff)}.V9QVRa_segOut{background:var(--dsw-alias-state-business-primary,#9a7bff);border-radius:0 0 2px 2px}.V9QVRa_barLabels{margin-top:6px;display:flex}.V9QVRa_barLabels span{text-align:center;color:var(--dsw-alias-label-tertiary,#67676f);white-space:nowrap;flex:1;font-size:9.5px}.V9QVRa_subRowCell{background:var(--dsw-alias-fill-l1,#80808c0d);text-align:left!important;padding:2px 10px 10px 30px!important}.V9QVRa_subHead,.V9QVRa_subRow{grid-template-columns:minmax(150px,1.5fr) minmax(70px,1fr) minmax(70px,1fr) minmax(70px,1fr) minmax(80px,1.1fr) minmax(70px,1fr);align-items:center;gap:8px;display:grid}.V9QVRa_subHead{color:var(--dsw-alias-label-tertiary,#67676f);border-bottom:1px solid var(--dsw-alias-fill-l2,#80808c33);padding:4px 0;font-size:10px}.V9QVRa_subRow{border-bottom:1px dashed var(--dsw-alias-fill-l1,#80808c24);padding:5px 0;font-size:11.5px}.V9QVRa_subRow:last-child{border-bottom:0}.V9QVRa_subName{flex-direction:column;gap:1px;min-width:0;display:flex}.V9QVRa_subModel{color:var(--dsw-alias-label-primary,#e8e8ec);text-overflow:ellipsis;white-space:nowrap;font-family:ui-monospace,Consolas,monospace;font-size:11px;overflow:hidden}.V9QVRa_subChannel{color:var(--dsw-alias-label-tertiary,#67676f);text-overflow:ellipsis;white-space:nowrap;font-family:inherit;font-size:10px;overflow:hidden}.V9QVRa_subNumber{text-align:right;font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-secondary,#9a9aa6)}.V9QVRa_toggleArrow{width:13px;color:var(--dsw-alias-label-tertiary,#67676f);font-size:9px;display:inline-block}";
708
+ const tagId$3 = "dsh-key-usage/detail.module.css";
709
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
710
+ const tag = document.createElement("style");
711
+ tag.dataset.plugin = "dsh-key-usage";
712
+ tag.dataset.pluginCss = tagId$3;
713
+ tag.textContent = css$3;
714
+ document.head.appendChild(tag);
715
+ }
716
+ const cssRegistry$3 = globalThis.__DSH_PLUGIN_CSS__ ??= [];
717
+ if (!cssRegistry$3.some((e) => e.tagId === tagId$3)) cssRegistry$3.push({
718
+ tagId: tagId$3,
719
+ css: css$3
720
+ });
721
+ var detail_module_css_default = {
722
+ "rangeLeft": "V9QVRa_rangeLeft",
723
+ "shareBar": "V9QVRa_shareBar",
724
+ "chartTitle": "V9QVRa_chartTitle",
725
+ "bars": "V9QVRa_bars",
726
+ "barsDense": "V9QVRa_barsDense",
727
+ "subRowCell": "V9QVRa_subRowCell",
728
+ "barColumn": "V9QVRa_barColumn",
729
+ "subHead": "V9QVRa_subHead",
730
+ "segOut": "V9QVRa_segOut",
731
+ "rangeCaption": "V9QVRa_rangeCaption",
732
+ "barLabels": "V9QVRa_barLabels",
733
+ "subModel": "V9QVRa_subModel",
734
+ "segCache": "V9QVRa_segCache",
735
+ "subName": "V9QVRa_subName",
736
+ "segIn": "V9QVRa_segIn",
737
+ "subRow": "V9QVRa_subRow",
738
+ "subNumber": "V9QVRa_subNumber",
739
+ "active": "V9QVRa_active",
740
+ "rangeTabs": "V9QVRa_rangeTabs",
741
+ "rangeBar": "V9QVRa_rangeBar",
742
+ "rangeInput": "V9QVRa_rangeInput",
743
+ "subChannel": "V9QVRa_subChannel",
744
+ "toggleArrow": "V9QVRa_toggleArrow"
745
+ };
746
+ //#endregion
747
+ //#region src/client/components/ShareBar.tsx
748
+ function ShareBar(props) {
749
+ const { usage, totalTokens, channels } = props;
750
+ if (usage === null || totalTokens <= 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
751
+ className: shared_module_css_default.card,
752
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
753
+ className: shared_module_css_default.statLabel,
754
+ children: "占比 · 按 Tokens"
755
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
756
+ className: shared_module_css_default.empty,
757
+ style: { padding: "8px 0" },
758
+ children: "所选范围内无用量记录"
759
+ })]
760
+ });
761
+ let rows = [];
762
+ let title = "通道占比 · 按 Tokens";
763
+ if (props.channelFilter === "ALL") {
764
+ const byProvider = /* @__PURE__ */ new Map();
765
+ for (const day of usage.days) for (const model of day.models) byProvider.set(model.provider, (byProvider.get(model.provider) ?? 0) + model.inputTokens + model.outputTokens + model.cacheReadTokens);
766
+ rows = [...byProvider.entries()].map(([provider, tokens]) => ({
767
+ key: provider,
768
+ label: displayNameOf(channels, provider),
769
+ tokens,
770
+ color: channelColorOf(channels, provider)
771
+ }));
772
+ } else {
773
+ title = "模型占比 · 按 Tokens";
774
+ const byModel = /* @__PURE__ */ new Map();
775
+ for (const day of usage.days) for (const model of day.models) {
776
+ const bucket = byModel.get(model.model);
777
+ const tokens = model.inputTokens + model.outputTokens + model.cacheReadTokens;
778
+ if (bucket === void 0) byModel.set(model.model, {
779
+ tokens,
780
+ provider: model.provider
781
+ });
782
+ else bucket.tokens += tokens;
783
+ }
784
+ rows = [...byModel.entries()].map(([model, value]) => ({
785
+ key: model,
786
+ label: model,
787
+ tokens: value.tokens,
788
+ color: channelColorOf(channels, value.provider)
789
+ }));
790
+ }
791
+ rows.sort((first, second) => second.tokens - first.tokens);
792
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
793
+ className: shared_module_css_default.card,
794
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
795
+ className: shared_module_css_default.statLabel,
796
+ children: title
797
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
798
+ className: detail_module_css_default.shareBar,
799
+ children: rows.map((row) => {
800
+ const percent = totalTokens > 0 ? Math.round(row.tokens / totalTokens * 100) : 0;
801
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", {
802
+ style: {
803
+ width: row.tokens / totalTokens * 100 + "%",
804
+ background: row.color
805
+ },
806
+ onMouseMove: (event) => props.tooltip.show(event, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
807
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipTitle, { children: row.label }),
808
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(TipSub, { children: ["占比 ", /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("b", { children: [percent, "%"] })] }),
809
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipRow, {
810
+ color: row.color,
811
+ label: "Tokens",
812
+ amount: fmtTokens(row.tokens),
813
+ rate: percent + "%"
814
+ })
815
+ ] })),
816
+ onMouseLeave: props.tooltip.hide
817
+ }, row.key);
818
+ })
819
+ })]
820
+ });
821
+ }
822
+ //#endregion
823
+ //#region src/client/components/StatCard.tsx
824
+ function StatCard(props) {
825
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
826
+ className: shared_module_css_default.card,
827
+ children: [
828
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
829
+ className: shared_module_css_default.statLabel,
830
+ children: props.label
831
+ }),
832
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
833
+ className: shared_module_css_default.statValue,
834
+ children: props.value
835
+ }),
836
+ props.extra !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
837
+ className: shared_module_css_default.statExtra,
838
+ children: props.extra
839
+ })
840
+ ]
841
+ });
842
+ }
843
+ //#endregion
844
+ //#region src/client/components/TokenBars.tsx
845
+ function TokenBars(props) {
846
+ const { rows, tooltip } = props;
847
+ if (props.hidden === true || rows.length === 0) return null;
848
+ const peak = Math.max(1, ...rows.map((row) => row.cacheReadTokens + row.inputTokens + row.outputTokens));
849
+ const heightOf = (value) => (peak > 0 ? Math.max(1, Math.round(value / peak * 100)) : 1) + "%";
850
+ const labelEvery = Math.max(1, Math.ceil(rows.length / 14));
851
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
852
+ className: rows.length > 20 ? detail_module_css_default.bars + " " + detail_module_css_default.barsDense : detail_module_css_default.bars,
853
+ children: rows.map((row) => {
854
+ const rowTotal = row.cacheReadTokens + row.inputTokens + row.outputTokens;
855
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
856
+ className: detail_module_css_default.barColumn,
857
+ onMouseMove: (event) => tooltip.show(event, row.tip),
858
+ onMouseLeave: tooltip.hide,
859
+ children: rowTotal > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
860
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
861
+ className: detail_module_css_default.segCache,
862
+ style: { height: heightOf(row.cacheReadTokens) }
863
+ }),
864
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
865
+ className: detail_module_css_default.segIn,
866
+ style: { height: heightOf(row.inputTokens) }
867
+ }),
868
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
869
+ className: detail_module_css_default.segOut,
870
+ style: { height: heightOf(row.outputTokens) }
871
+ })
872
+ ] })
873
+ }, row.key);
874
+ })
875
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
876
+ className: detail_module_css_default.barLabels,
877
+ children: rows.map((row, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: index % labelEvery === 0 || index === rows.length - 1 ? row.axisLabel : "" }, row.key))
878
+ })] });
879
+ }
880
+ //#endregion
881
+ //#region src/client/components/DailyDetailTable.tsx
882
+ /**
883
+ * 每日细目:以「天」为折叠单位,展开行用原型子表格展示模型维度
884
+ * (模型 + 调用 / 输入 / 输出 / 缓存读 / 命中率,不占主表列)。
885
+ */
886
+ function hitRateOf$1(cacheReadTokens, inputTokens, outputTokens) {
887
+ const total = inputTokens + outputTokens + cacheReadTokens;
888
+ if (total <= 0) return "—";
889
+ return (cacheReadTokens / total * 100).toFixed(1) + "%";
890
+ }
891
+ const COMPARERS = {
892
+ period: (first, second) => first.period < second.period ? -1 : first.period > second.period ? 1 : 0,
893
+ total: (first, second) => first.totalTokens - second.totalTokens,
894
+ calls: (first, second) => first.calls - second.calls,
895
+ input: (first, second) => first.inputTokens - second.inputTokens,
896
+ output: (first, second) => first.outputTokens - second.outputTokens,
897
+ cacheRead: (first, second) => first.cacheReadTokens - second.cacheReadTokens,
898
+ hit: (first, second) => {
899
+ const firstTotal = first.inputTokens + first.outputTokens + first.cacheReadTokens;
900
+ const secondTotal = second.inputTokens + second.outputTokens + second.cacheReadTokens;
901
+ return (firstTotal > 0 ? first.cacheReadTokens / firstTotal : -1) - (secondTotal > 0 ? second.cacheReadTokens / secondTotal : -1);
902
+ },
903
+ cost: (first, second) => first.cost - second.cost
904
+ };
905
+ function DailyDetailTable(props) {
906
+ const { usage, channels, channelFilter, expandedPeriods, onTogglePeriod, sort, onSort } = props;
907
+ const modelRows = [];
908
+ for (const day of usage.days) for (const model of day.models) {
909
+ const totalTokens = model.inputTokens + model.outputTokens + model.cacheReadTokens;
910
+ modelRows.push({
911
+ period: day.date,
912
+ model: model.model,
913
+ provider: model.provider,
914
+ calls: model.calls,
915
+ inputTokens: model.inputTokens,
916
+ outputTokens: model.outputTokens,
917
+ cacheReadTokens: model.cacheReadTokens,
918
+ cost: model.cost,
919
+ totalTokens
920
+ });
921
+ }
922
+ const aggregates = /* @__PURE__ */ new Map();
923
+ for (const row of modelRows) {
924
+ const bucket = aggregates.get(row.period);
925
+ if (bucket === void 0) aggregates.set(row.period, {
926
+ period: row.period,
927
+ calls: row.calls,
928
+ inputTokens: row.inputTokens,
929
+ outputTokens: row.outputTokens,
930
+ cacheReadTokens: row.cacheReadTokens,
931
+ cost: row.cost,
932
+ totalTokens: row.totalTokens
933
+ });
934
+ else {
935
+ bucket.calls += row.calls;
936
+ bucket.inputTokens += row.inputTokens;
937
+ bucket.outputTokens += row.outputTokens;
938
+ bucket.cacheReadTokens += row.cacheReadTokens;
939
+ bucket.cost += row.cost;
940
+ bucket.totalTokens += row.totalTokens;
941
+ }
942
+ }
943
+ const comparer = COMPARERS[sort.key] ?? COMPARERS.period;
944
+ const ordered = [...aggregates.values()].sort((first, second) => {
945
+ const compared = comparer(first, second);
946
+ return sort.dir === "asc" ? compared : -compared;
947
+ });
948
+ const header = (key, label) => {
949
+ const isActive = sort.key === key;
950
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("th", {
951
+ className: isActive ? shared_module_css_default.sortActive : shared_module_css_default.sortable,
952
+ onClick: () => onSort(isActive ? {
953
+ key,
954
+ dir: sort.dir === "asc" ? "desc" : "asc"
955
+ } : {
956
+ key,
957
+ dir: "desc"
958
+ }),
959
+ children: [label, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
960
+ className: shared_module_css_default.sortArrow,
961
+ children: isActive ? sort.dir === "asc" ? "▲" : "▼" : "↕"
962
+ })]
963
+ });
964
+ };
965
+ const totals = usage.totals;
966
+ const totalTokens = totals === null ? 0 : totals.inputTokens + totals.outputTokens + totals.cacheReadTokens;
967
+ if (ordered.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
968
+ className: shared_module_css_default.blockLabel,
969
+ children: "每日细目"
970
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
971
+ className: shared_module_css_default.empty,
972
+ children: "所选范围内无用量记录"
973
+ })] });
974
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
975
+ className: shared_module_css_default.blockLabel,
976
+ children: "每日细目"
977
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
978
+ className: shared_module_css_default.table,
979
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
980
+ header("period", "日期"),
981
+ header("total", "总计"),
982
+ header("calls", "调用"),
983
+ header("input", "输入"),
984
+ header("output", "输出"),
985
+ header("cacheRead", "缓存读"),
986
+ header("hit", "命中率")
987
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tbody", { children: [ordered.map((period) => {
988
+ const modelsInPeriod = modelRows.filter((row) => row.period === period.period).sort((first, second) => second.totalTokens - first.totalTokens);
989
+ const expandable = modelsInPeriod.length > 1;
990
+ const isOpen = expandable && expandedPeriods[period.period] === true;
991
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
992
+ className: expandable ? shared_module_css_default.clickable : "",
993
+ onClick: expandable ? () => onTogglePeriod(period.period) : void 0,
994
+ children: [
995
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [expandable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
996
+ className: detail_module_css_default.toggleArrow,
997
+ children: isOpen ? "▼" : "▶"
998
+ }), period.period] }),
999
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(period.totalTokens) }),
1000
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtCalls(period.calls) }),
1001
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(period.inputTokens) }),
1002
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(period.outputTokens) }),
1003
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(period.cacheReadTokens) }),
1004
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: hitRateOf$1(period.cacheReadTokens, period.inputTokens, period.outputTokens) })
1005
+ ]
1006
+ }), isOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
1007
+ className: detail_module_css_default.subRowCell,
1008
+ colSpan: 7,
1009
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1010
+ className: detail_module_css_default.subHead,
1011
+ children: [
1012
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}),
1013
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1014
+ className: detail_module_css_default.subNumber,
1015
+ children: "调用"
1016
+ }),
1017
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1018
+ className: detail_module_css_default.subNumber,
1019
+ children: "输入"
1020
+ }),
1021
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1022
+ className: detail_module_css_default.subNumber,
1023
+ children: "输出"
1024
+ }),
1025
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1026
+ className: detail_module_css_default.subNumber,
1027
+ children: "缓存读"
1028
+ }),
1029
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1030
+ className: detail_module_css_default.subNumber,
1031
+ children: "命中率"
1032
+ })
1033
+ ]
1034
+ }), modelsInPeriod.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1035
+ className: detail_module_css_default.subRow,
1036
+ children: [
1037
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1038
+ className: detail_module_css_default.subName,
1039
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1040
+ className: detail_module_css_default.subModel,
1041
+ children: row.model
1042
+ }), channelFilter === "ALL" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1043
+ className: detail_module_css_default.subChannel,
1044
+ children: displayNameOf(channels, row.provider)
1045
+ })]
1046
+ }),
1047
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1048
+ className: detail_module_css_default.subNumber,
1049
+ children: fmtCalls(row.calls)
1050
+ }),
1051
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1052
+ className: detail_module_css_default.subNumber,
1053
+ children: fmtTokens(row.inputTokens)
1054
+ }),
1055
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1056
+ className: detail_module_css_default.subNumber,
1057
+ children: fmtTokens(row.outputTokens)
1058
+ }),
1059
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1060
+ className: detail_module_css_default.subNumber,
1061
+ children: fmtTokens(row.cacheReadTokens)
1062
+ }),
1063
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1064
+ className: detail_module_css_default.subNumber,
1065
+ children: hitRateOf$1(row.cacheReadTokens, row.inputTokens, row.outputTokens)
1066
+ })
1067
+ ]
1068
+ }, row.period + "|" + row.model + "|" + row.provider))]
1069
+ }) })] }, period.period);
1070
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
1071
+ className: shared_module_css_default.total,
1072
+ children: [
1073
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: "合计" }),
1074
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(totalTokens) }),
1075
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtCalls(totals?.calls ?? 0) }),
1076
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(totals?.inputTokens ?? 0) }),
1077
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(totals?.outputTokens ?? 0) }),
1078
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(totals?.cacheReadTokens ?? 0) }),
1079
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: hitRateOf$1(totals?.cacheReadTokens ?? 0, totals?.inputTokens ?? 0, totals?.outputTokens ?? 0) })
1080
+ ]
1081
+ })] })]
1082
+ })] });
1083
+ }
1084
+ //#endregion
1085
+ //#region src/client/tabs/DetailTab.tsx
1086
+ const RANGE_TABS = [
1087
+ ["today", "今日"],
1088
+ ["week", "周"],
1089
+ ["month", "月"],
1090
+ ["total", "总计"],
1091
+ ["custom", "自定义"]
1092
+ ];
1093
+ /** 图表最多铺开的列数:超过这个跨度的范围按天全画会挤成一团。 */
1094
+ const MAX_AXIS_DAYS = 366;
1095
+ function hitRateOf(cacheReadTokens, inputTokens, outputTokens) {
1096
+ const total = inputTokens + outputTokens + cacheReadTokens;
1097
+ return total > 0 ? (cacheReadTokens / total * 100).toFixed(1) + "%" : "—";
1098
+ }
1099
+ function daysBetween(from, to) {
1100
+ const fromTime = (/* @__PURE__ */ new Date(from + "T00:00:00")).getTime();
1101
+ const toTime = (/* @__PURE__ */ new Date(to + "T00:00:00")).getTime();
1102
+ return Math.max(1, Math.round((toTime - fromTime) / 864e5) + 1);
1103
+ }
1104
+ /** 范围内每一天的日期键(无数据的日子也要出现在横轴上)。 */
1105
+ function dateKeysOfRange(from, to) {
1106
+ const start = (/* @__PURE__ */ new Date(from + "T00:00:00")).getTime();
1107
+ const total = Math.min(daysBetween(from, to), MAX_AXIS_DAYS);
1108
+ const keys = [];
1109
+ for (let offset = 0; offset < total; offset += 1) keys.push(isoDate(new Date(start + offset * 864e5)));
1110
+ return keys;
1111
+ }
1112
+ function DetailTab(props) {
1113
+ const { usage, channels, bounds, tooltip, channelFilter } = props;
1114
+ if (usage === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1115
+ className: shared_module_css_default.empty,
1116
+ children: "台账读取中或不可用…"
1117
+ });
1118
+ const safeTotals = usage.totals ?? {
1119
+ date: "",
1120
+ calls: 0,
1121
+ inputTokens: 0,
1122
+ outputTokens: 0,
1123
+ cacheReadTokens: 0,
1124
+ reasoningTokens: 0,
1125
+ cost: 0
1126
+ };
1127
+ const totalTokens = safeTotals.inputTokens + safeTotals.outputTokens + safeTotals.cacheReadTokens;
1128
+ const hitRate = hitRateOf(safeTotals.cacheReadTokens, safeTotals.inputTokens, safeTotals.outputTokens);
1129
+ const singleChannel = channelFilter === "ALL" ? null : channelOf(channels, channelFilter);
1130
+ const costCell = costCellText(singleChannel, safeTotals.cost);
1131
+ const singleDay = bounds.from === bounds.to;
1132
+ const rangeDays = Math.min(daysBetween(bounds.from, bounds.to), MAX_AXIS_DAYS);
1133
+ const activeProviders = /* @__PURE__ */ new Set();
1134
+ for (const day of usage.days) for (const model of day.models) if (model.inputTokens + model.outputTokens + model.cacheReadTokens > 0) activeProviders.add(model.provider);
1135
+ const hourlyRows = [];
1136
+ if (singleDay && usage.hours !== null) usage.hours.forEach((slot, hour) => {
1137
+ const slotTokens = slot.buckets.inputTokens + slot.buckets.outputTokens + slot.buckets.cacheReadTokens;
1138
+ hourlyRows.push({
1139
+ key: String(hour),
1140
+ axisLabel: String(hour).padStart(2, "0") + ":00",
1141
+ cacheReadTokens: slot.buckets.cacheReadTokens,
1142
+ inputTokens: slot.buckets.inputTokens,
1143
+ outputTokens: slot.buckets.outputTokens,
1144
+ tip: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipTitle, { children: bounds.from + " " + String(hour).padStart(2, "0") + ":00" }), slot.calls === 0 && slotTokens === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipSub, { children: "该小时无用量记录" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1145
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(TipSub, { children: [
1146
+ "调用 ",
1147
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: fmtCalls(slot.calls) }),
1148
+ " · Tokens ",
1149
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: fmtTokens(slotTokens) })
1150
+ ] }),
1151
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipRow, {
1152
+ color: "var(--dsw-alias-state-accent-primary, #5b8cff)",
1153
+ label: "输入",
1154
+ amount: fmtTokens(slot.buckets.inputTokens)
1155
+ }),
1156
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipRow, {
1157
+ color: "var(--dsw-alias-state-business-primary, #9a7bff)",
1158
+ label: "输出",
1159
+ amount: fmtTokens(slot.buckets.outputTokens)
1160
+ }),
1161
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipRow, {
1162
+ color: "var(--dsw-alias-state-accent-tertiary, rgba(91,140,255,.28))",
1163
+ label: "缓存读",
1164
+ amount: fmtTokens(slot.buckets.cacheReadTokens),
1165
+ rate: hitRateOf(slot.buckets.cacheReadTokens, slot.buckets.inputTokens, slot.buckets.outputTokens)
1166
+ })
1167
+ ] })] })
1168
+ });
1169
+ });
1170
+ const sessionDates = new Set(usage.sessionDates ?? []);
1171
+ const dayByDate = new Map(usage.days.map((day) => [day.date, day]));
1172
+ const dailyRows = dateKeysOfRange(bounds.from, bounds.to).map((dateKey) => {
1173
+ const day = dayByDate.get(dateKey);
1174
+ if (day === void 0) return {
1175
+ key: dateKey,
1176
+ axisLabel: fmtShortDate(dateKey),
1177
+ cacheReadTokens: 0,
1178
+ inputTokens: 0,
1179
+ outputTokens: 0,
1180
+ tip: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipTitle, { children: dateKey }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipSub, { children: "当日无用量记录" })] })
1181
+ };
1182
+ const dayTotal = day.inputTokens + day.outputTokens + day.cacheReadTokens;
1183
+ const fromSession = sessionDates.has(day.date);
1184
+ const topModels = [...day.models].sort((first, second) => second.inputTokens + second.outputTokens + second.cacheReadTokens - (first.inputTokens + first.outputTokens + first.cacheReadTokens)).slice(0, 5);
1185
+ return {
1186
+ key: day.date,
1187
+ axisLabel: fmtShortDate(day.date),
1188
+ cacheReadTokens: day.cacheReadTokens,
1189
+ inputTokens: day.inputTokens,
1190
+ outputTokens: day.outputTokens,
1191
+ tip: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1192
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipTitle, { children: day.date }),
1193
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(TipSub, { children: [
1194
+ "调用 ",
1195
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: fmtCalls(day.calls) }),
1196
+ " · Tokens ",
1197
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: fmtTokens(dayTotal) })
1198
+ ] }),
1199
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipRow, {
1200
+ color: "var(--dsw-alias-state-accent-primary, #5b8cff)",
1201
+ label: "输入",
1202
+ amount: fmtTokens(day.inputTokens)
1203
+ }),
1204
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipRow, {
1205
+ color: "var(--dsw-alias-state-business-primary, #9a7bff)",
1206
+ label: "输出",
1207
+ amount: fmtTokens(day.outputTokens)
1208
+ }),
1209
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipRow, {
1210
+ color: "var(--dsw-alias-state-accent-tertiary, rgba(91,140,255,.28))",
1211
+ label: "缓存读",
1212
+ amount: fmtTokens(day.cacheReadTokens),
1213
+ rate: hitRateOf(day.cacheReadTokens, day.inputTokens, day.outputTokens)
1214
+ }),
1215
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(TipDivider, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "成本" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: day.cost > 0 ? fmtCost(day.cost) : "—" })] }),
1216
+ fromSession && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipSub, { children: "来源:会话事件(该日台账无记录,不含官方成本)" }),
1217
+ topModels.map((model) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TipRow, {
1218
+ label: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [model.model, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1219
+ className: shared_module_css_default.dim,
1220
+ children: [" · ", displayNameOf(channels, model.provider)]
1221
+ })] }),
1222
+ amount: fmtTokens(model.inputTokens + model.outputTokens + model.cacheReadTokens)
1223
+ }, model.provider + "|" + model.model))
1224
+ ] })
1225
+ };
1226
+ });
1227
+ const useHourAxis = singleDay && usage.hours !== null;
1228
+ const chartRows = useHourAxis ? hourlyRows : dailyRows;
1229
+ const chartTitle = useHourAxis ? "按小时 Tokens(" + bounds.from + ")" : "按天 Tokens(" + rangeDays + " 天)";
1230
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
1231
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1232
+ className: detail_module_css_default.rangeBar,
1233
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1234
+ className: detail_module_css_default.rangeLeft,
1235
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1236
+ className: detail_module_css_default.rangeTabs,
1237
+ children: RANGE_TABS.map(([key, label]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1238
+ className: props.range === key ? detail_module_css_default.active : "",
1239
+ onClick: () => props.onRange(key),
1240
+ children: label
1241
+ }, key))
1242
+ }), props.range === "custom" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
1243
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1244
+ type: "date",
1245
+ className: detail_module_css_default.rangeInput,
1246
+ value: props.customFrom,
1247
+ onChange: (event) => props.onCustomFrom(event.target.value)
1248
+ }),
1249
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1250
+ className: shared_module_css_default.dim,
1251
+ children: "至"
1252
+ }),
1253
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1254
+ type: "date",
1255
+ className: detail_module_css_default.rangeInput,
1256
+ value: props.customTo,
1257
+ onChange: (event) => props.onCustomTo(event.target.value)
1258
+ })
1259
+ ] })]
1260
+ })
1261
+ }),
1262
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1263
+ className: shared_module_css_default.cardGrid3,
1264
+ children: [
1265
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
1266
+ label: "调用次数",
1267
+ value: fmtCalls(safeTotals.calls),
1268
+ extra: activeProviders.size + " 个通道有用量"
1269
+ }),
1270
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
1271
+ label: "Tokens",
1272
+ value: fmtTokens(totalTokens),
1273
+ extra: "缓存读 " + fmtTokens(safeTotals.cacheReadTokens) + " · 命中率 " + hitRate
1274
+ }),
1275
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatCard, {
1276
+ label: "成本",
1277
+ value: costCell.dim ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1278
+ className: shared_module_css_default.dim,
1279
+ children: costCell.text
1280
+ }) : costCell.text,
1281
+ extra: "按所选范围与筛选聚合"
1282
+ })
1283
+ ]
1284
+ }),
1285
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShareBar, {
1286
+ usage,
1287
+ totalTokens,
1288
+ channelFilter,
1289
+ channels,
1290
+ tooltip
1291
+ }),
1292
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1293
+ className: shared_module_css_default.chipRow,
1294
+ style: { marginTop: 14 },
1295
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1296
+ className: channelFilter === "ALL" ? shared_module_css_default.chip + " " + shared_module_css_default.active : shared_module_css_default.chip,
1297
+ onClick: () => props.onChannelFilter("ALL"),
1298
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1299
+ className: shared_module_css_default.statusDot,
1300
+ style: { background: "var(--dsw-alias-state-accent-primary, #5b8cff)" }
1301
+ }), "全部通道"]
1302
+ }), channels.map((channel) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1303
+ className: channelFilter === channel.provider ? shared_module_css_default.chip + " " + shared_module_css_default.active : shared_module_css_default.chip,
1304
+ onClick: () => props.onChannelFilter(channel.provider),
1305
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1306
+ className: shared_module_css_default.statusDot,
1307
+ style: { background: statusColorOf(channel.status) }
1308
+ }), channel.displayName]
1309
+ }, channel.provider))]
1310
+ }),
1311
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1312
+ className: shared_module_css_default.card,
1313
+ children: [
1314
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1315
+ className: detail_module_css_default.chartTitle,
1316
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1317
+ style: {
1318
+ display: "inline-flex",
1319
+ alignItems: "center",
1320
+ gap: 8
1321
+ },
1322
+ children: singleChannel === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: "全部通道" }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1323
+ className: shared_module_css_default.statusDot,
1324
+ style: { background: statusColorOf(singleChannel.status) }
1325
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: singleChannel.displayName })] })
1326
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1327
+ className: shared_module_css_default.dim,
1328
+ style: { fontSize: 11.5 },
1329
+ children: [
1330
+ bounds.from,
1331
+ " ~ ",
1332
+ bounds.to
1333
+ ]
1334
+ })]
1335
+ }),
1336
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1337
+ className: detail_module_css_default.chartTitle,
1338
+ style: { marginTop: 10 },
1339
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1340
+ className: shared_module_css_default.statLabel,
1341
+ children: chartTitle
1342
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1343
+ className: shared_module_css_default.legend,
1344
+ children: [
1345
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: { background: "rgba(91,140,255,.28)" } }), "缓存读"] }),
1346
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: { background: "var(--dsw-alias-state-accent-primary, #5b8cff)" } }), "输入"] }),
1347
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: { background: "var(--dsw-alias-state-business-primary, #9a7bff)" } }), "输出"] })
1348
+ ]
1349
+ })]
1350
+ }),
1351
+ chartRows.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1352
+ className: shared_module_css_default.empty,
1353
+ children: "所选范围内无用量记录"
1354
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TokenBars, {
1355
+ rows: chartRows,
1356
+ tooltip
1357
+ }),
1358
+ useHourAxis && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1359
+ className: shared_module_css_default.statExtra,
1360
+ children: "按小时数据来自本地会话事件,不含成本"
1361
+ })
1362
+ ]
1363
+ }),
1364
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DailyDetailTable, {
1365
+ usage,
1366
+ channels,
1367
+ channelFilter,
1368
+ expandedPeriods: props.expandedPeriods,
1369
+ onTogglePeriod: props.onTogglePeriod,
1370
+ sort: props.sort,
1371
+ onSort: props.onSort
1372
+ })
1373
+ ] });
1374
+ }
1375
+ //#endregion
1376
+ //#region \0dsh-css:D:\EasyJob\.plugin\dsh-key-usage\src\client\styles\lists.module.css.mjs
1377
+ const css$2 = ".QpFKma_filterBar{flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:14px;display:flex}.QpFKma_searchInput{border:1px solid var(--dsw-alias-border-l2,#2c2c34);min-width:180px;color:var(--dsw-alias-label-primary,#e8e8ec);font:inherit;background:0 0;border-radius:9px;flex:1;padding:6px 12px;font-size:12.5px}.QpFKma_searchInput::placeholder{color:var(--dsw-alias-label-tertiary,#67676f)}.QpFKma_filterSelect{background:var(--dsw-alias-bg-layer-2,#1d1d22);border:1px solid var(--dsw-alias-border-l2,#2c2c34);color:var(--dsw-alias-label-secondary,#9a9aa6);font:inherit;cursor:pointer;border-radius:9px;padding:6px 10px;font-size:12px}.QpFKma_listTitle{align-items:center;gap:10px;min-width:0;display:flex}.QpFKma_listMain{color:var(--dsw-alias-label-primary,#e8e8ec);text-overflow:ellipsis;white-space:nowrap;font-size:12.5px;font-weight:600;overflow:hidden}.QpFKma_listSub{color:var(--dsw-alias-label-tertiary,#67676f);text-overflow:ellipsis;white-space:nowrap;margin-top:1px;font-size:10.5px;overflow:hidden}.QpFKma_listBar{background:var(--dsw-alias-fill-l2,#80808c2e);vertical-align:middle;border-radius:3px;width:70px;height:5px;display:inline-block;overflow:hidden}.QpFKma_listBar>i{border-radius:3px;height:100%;display:block}.QpFKma_sessionDay td{background:var(--dsw-alias-bg-layer-2,#1d1d22);color:var(--dsw-alias-label-primary,#e8e8ec);border-bottom:1px solid var(--dsw-alias-border-l1,#2c2c34);font-size:12px;font-weight:600;text-align:left!important;padding:7px 10px!important}.QpFKma_dayCount{color:var(--dsw-alias-label-tertiary,#67676f);margin-left:8px;font-size:11px;font-weight:500}";
1378
+ const tagId$2 = "dsh-key-usage/lists.module.css";
1379
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
1380
+ const tag = document.createElement("style");
1381
+ tag.dataset.plugin = "dsh-key-usage";
1382
+ tag.dataset.pluginCss = tagId$2;
1383
+ tag.textContent = css$2;
1384
+ document.head.appendChild(tag);
1385
+ }
1386
+ const cssRegistry$2 = globalThis.__DSH_PLUGIN_CSS__ ??= [];
1387
+ if (!cssRegistry$2.some((e) => e.tagId === tagId$2)) cssRegistry$2.push({
1388
+ tagId: tagId$2,
1389
+ css: css$2
1390
+ });
1391
+ var lists_module_css_default = {
1392
+ "listTitle": "QpFKma_listTitle",
1393
+ "listBar": "QpFKma_listBar",
1394
+ "filterSelect": "QpFKma_filterSelect",
1395
+ "dayCount": "QpFKma_dayCount",
1396
+ "searchInput": "QpFKma_searchInput",
1397
+ "listSub": "QpFKma_listSub",
1398
+ "listMain": "QpFKma_listMain",
1399
+ "sessionDay": "QpFKma_sessionDay",
1400
+ "filterBar": "QpFKma_filterBar"
1401
+ };
1402
+ //#endregion
1403
+ //#region src/client/tabs/ProjectsTab.tsx
1404
+ /** 项目用量:按会话工作目录聚合(数据来自宿主会话事件折叠)。 */
1405
+ function ProjectsTab(props) {
1406
+ const [query, setQuery] = (0, react.useState)("");
1407
+ const [channelFilter, setChannelFilter] = (0, react.useState)("ALL");
1408
+ const [sortKey, setSortKey] = (0, react.useState)("recent");
1409
+ const [nowMs] = (0, react.useState)(() => Date.now());
1410
+ const rows = props.data?.rows ?? [];
1411
+ const visible = (0, react.useMemo)(() => {
1412
+ const keyword = query.trim().toLowerCase();
1413
+ const filtered = rows.filter((row) => {
1414
+ const matchesQuery = keyword === "" || row.workspace.toLowerCase().includes(keyword);
1415
+ const matchesChannel = channelFilter === "ALL" || row.providers[channelFilter] !== void 0;
1416
+ return matchesQuery && matchesChannel;
1417
+ });
1418
+ const sorters = {
1419
+ recent: (first, second) => (second.lastTime ?? 0) - (first.lastTime ?? 0),
1420
+ tokensDesc: (first, second) => second.tokens - first.tokens,
1421
+ tokensAsc: (first, second) => first.tokens - second.tokens,
1422
+ callsDesc: (first, second) => second.calls - first.calls
1423
+ };
1424
+ return [...filtered].sort(sorters[sortKey]);
1425
+ }, [
1426
+ rows,
1427
+ query,
1428
+ channelFilter,
1429
+ sortKey
1430
+ ]);
1431
+ const totalTokens = rows.reduce((total, row) => total + row.tokens, 0);
1432
+ if (props.data === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1433
+ className: shared_module_css_default.empty,
1434
+ children: "会话数据读取中或不可用…"
1435
+ });
1436
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
1437
+ props.data.coverage.status !== "complete" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1438
+ className: shared_module_css_default.warning,
1439
+ children: coverageText(props.data.coverage.status, props.data.coverage.skippedSessions)
1440
+ }),
1441
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1442
+ className: lists_module_css_default.filterBar,
1443
+ children: [
1444
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1445
+ className: lists_module_css_default.searchInput,
1446
+ placeholder: "搜索项目路径…",
1447
+ value: query,
1448
+ onChange: (event) => setQuery(event.target.value)
1449
+ }),
1450
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1451
+ className: lists_module_css_default.filterSelect,
1452
+ value: channelFilter,
1453
+ onChange: (event) => setChannelFilter(event.target.value),
1454
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1455
+ value: "ALL",
1456
+ children: "全部通道"
1457
+ }), props.channels.map((channel) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1458
+ value: channel.provider,
1459
+ children: channel.displayName
1460
+ }, channel.provider))]
1461
+ }),
1462
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1463
+ className: lists_module_css_default.filterSelect,
1464
+ value: sortKey,
1465
+ onChange: (event) => setSortKey(event.target.value),
1466
+ children: [
1467
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1468
+ value: "recent",
1469
+ children: "最近活跃"
1470
+ }),
1471
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1472
+ value: "tokensDesc",
1473
+ children: "Tokens 最多"
1474
+ }),
1475
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1476
+ value: "tokensAsc",
1477
+ children: "Tokens 最少"
1478
+ }),
1479
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1480
+ value: "callsDesc",
1481
+ children: "调用最多"
1482
+ })
1483
+ ]
1484
+ })
1485
+ ]
1486
+ }),
1487
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1488
+ className: shared_module_css_default.table,
1489
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1490
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "项目" }),
1491
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "调用" }),
1492
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Tokens" }),
1493
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "占比" }),
1494
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "成本" }),
1495
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "最近活跃" })
1496
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tbody", { children: [visible.map((row) => {
1497
+ const percent = totalTokens > 0 ? Math.round(row.tokens / totalTokens * 100) : 0;
1498
+ const dominant = row.dominantProvider;
1499
+ const channel = channelOf(props.channels, dominant);
1500
+ const costCell = costCellText(channel, row.cost ?? 0);
1501
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1502
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1503
+ className: lists_module_css_default.listTitle,
1504
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1505
+ className: lists_module_css_default.listMain,
1506
+ children: baseNameOf(row.workspace)
1507
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1508
+ className: lists_module_css_default.listSub,
1509
+ children: [
1510
+ row.workspace,
1511
+ " · ",
1512
+ row.sessionCount,
1513
+ " 个会话"
1514
+ ]
1515
+ })] })
1516
+ }) }),
1517
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtCalls(row.calls) }),
1518
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(row.tokens) }),
1519
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [
1520
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1521
+ className: lists_module_css_default.listBar,
1522
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: {
1523
+ width: Math.max(2, percent) + "%",
1524
+ background: channelColorOf(props.channels, dominant ?? "")
1525
+ } })
1526
+ }),
1527
+ " ",
1528
+ percent,
1529
+ "%"
1530
+ ] }),
1531
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.cost === null || channel?.costMode === "sub" || channel?.costMode === "free" || channel?.costMode === "none" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1532
+ className: shared_module_css_default.dim,
1533
+ children: costCell.text
1534
+ }) : costCell.text }),
1535
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtRelative(row.lastTime, nowMs) })
1536
+ ] }, row.workspace);
1537
+ }), visible.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1538
+ colSpan: 6,
1539
+ className: shared_module_css_default.empty,
1540
+ children: "没有符合条件的项目"
1541
+ }) })] })]
1542
+ }),
1543
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1544
+ className: shared_module_css_default.statExtra,
1545
+ children: [
1546
+ "成本按台账模型中位均价估算",
1547
+ props.data.priced ? "" : "(台账无成本数据,暂不可估)",
1548
+ ";命中率 = 缓存读 /(输入 + 输出 + 缓存读)"
1549
+ ]
1550
+ })
1551
+ ] });
1552
+ }
1553
+ function coverageText(status, skipped) {
1554
+ if (status === "unavailable") return "会话数据不可用(宿主未提供会话服务或全部读取失败)";
1555
+ return "会话数据不完整:" + String(skipped) + " 个会话读取失败,未计入统计";
1556
+ }
1557
+ function baseNameOf(path) {
1558
+ const parts = path.replace(/[\\/]+$/, "").split(/[\\/]/);
1559
+ return parts[parts.length - 1] === "" ? path : parts[parts.length - 1];
1560
+ }
1561
+ //#endregion
1562
+ //#region \0dsh-css:D:\EasyJob\.plugin\dsh-key-usage\src\client\styles\quota.module.css.mjs
1563
+ const css$1 = ".F7iUHa_quotaList{flex-direction:column;gap:12px;display:flex}.F7iUHa_quotaCard{background:var(--dsw-alias-bg-layer-2,#1d1d22);border:1px solid var(--dsw-alias-border-l1,#2c2c34);border-radius:12px;padding:16px 18px}.F7iUHa_quotaHead{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:14px;display:flex}.F7iUHa_quotaTitle{color:var(--dsw-alias-label-primary,#e8e8ec);font-size:13px;font-weight:600}.F7iUHa_quotaEnv{color:var(--dsw-alias-label-tertiary,#67676f);margin-left:8px;font-family:ui-monospace,Consolas,monospace;font-size:10px}.F7iUHa_quotaProvider{color:var(--dsw-alias-label-tertiary,#67676f);margin-left:auto;font-size:11.5px}.F7iUHa_liveChip{border:1px solid var(--dsw-alias-state-success-primary,#3fb950);color:var(--dsw-alias-state-success-primary,#3fb950);border-radius:999px;padding:1px 7px;font-size:10px;line-height:1.6}.F7iUHa_balanceStrip{flex-wrap:wrap;align-items:baseline;gap:10px 44px;display:flex}.F7iUHa_balanceItem .F7iUHa_bsLabel{color:var(--dsw-alias-label-tertiary,#67676f);margin-bottom:2px;font-size:11px}.F7iUHa_balanceItem .F7iUHa_bsValue{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary,#e8e8ec);font-size:16px;font-weight:650}.F7iUHa_windowRow{flex-wrap:wrap;gap:18px;display:flex}.F7iUHa_windowItem{flex:1;min-width:150px}.F7iUHa_windowHead{justify-content:space-between;align-items:baseline;margin-bottom:5px;display:flex}.F7iUHa_windowName{color:var(--dsw-alias-label-secondary,#9a9aa6);font-size:11.5px}.F7iUHa_windowValue{font-variant-numeric:tabular-nums;color:var(--dsw-alias-label-primary,#e8e8ec);font-size:12px;font-weight:600}.F7iUHa_windowReset{color:var(--dsw-alias-label-tertiary,#67676f);margin-top:3px;font-size:10.5px}.F7iUHa_windowBar{background:var(--dsw-alias-fill-l2,#80808c2e);border-radius:4px;height:6px;overflow:hidden}.F7iUHa_windowBar>i{border-radius:4px;height:100%;display:block}.F7iUHa_snapshotHint{color:var(--dsw-alias-label-tertiary,#67676f);margin-top:10px;font-size:10.5px}.F7iUHa_snapshotStale{color:var(--dsw-alias-state-warning-primary,#e0a13c);margin-top:10px;font-size:10.5px}";
1564
+ const tagId$1 = "dsh-key-usage/quota.module.css";
1565
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
1566
+ const tag = document.createElement("style");
1567
+ tag.dataset.plugin = "dsh-key-usage";
1568
+ tag.dataset.pluginCss = tagId$1;
1569
+ tag.textContent = css$1;
1570
+ document.head.appendChild(tag);
1571
+ }
1572
+ const cssRegistry$1 = globalThis.__DSH_PLUGIN_CSS__ ??= [];
1573
+ if (!cssRegistry$1.some((e) => e.tagId === tagId$1)) cssRegistry$1.push({
1574
+ tagId: tagId$1,
1575
+ css: css$1
1576
+ });
1577
+ var quota_module_css_default = {
1578
+ "windowItem": "F7iUHa_windowItem",
1579
+ "windowBar": "F7iUHa_windowBar",
1580
+ "snapshotHint": "F7iUHa_snapshotHint",
1581
+ "liveChip": "F7iUHa_liveChip",
1582
+ "quotaEnv": "F7iUHa_quotaEnv",
1583
+ "balanceItem": "F7iUHa_balanceItem",
1584
+ "balanceStrip": "F7iUHa_balanceStrip",
1585
+ "quotaTitle": "F7iUHa_quotaTitle",
1586
+ "snapshotStale": "F7iUHa_snapshotStale",
1587
+ "quotaList": "F7iUHa_quotaList",
1588
+ "windowValue": "F7iUHa_windowValue",
1589
+ "windowName": "F7iUHa_windowName",
1590
+ "bsValue": "F7iUHa_bsValue",
1591
+ "quotaCard": "F7iUHa_quotaCard",
1592
+ "quotaHead": "F7iUHa_quotaHead",
1593
+ "quotaProvider": "F7iUHa_quotaProvider",
1594
+ "windowHead": "F7iUHa_windowHead",
1595
+ "windowRow": "F7iUHa_windowRow",
1596
+ "bsLabel": "F7iUHa_bsLabel",
1597
+ "windowReset": "F7iUHa_windowReset"
1598
+ };
1599
+ //#endregion
1600
+ //#region src/client/tabs/QuotaTab.tsx
1601
+ /** 余额字段展示顺序(原型顺序);未收录字段按原键名追加在后。 */
1602
+ const BALANCE_ORDER = [
1603
+ "totalBalance",
1604
+ "topUpBalance",
1605
+ "giftBalance",
1606
+ "balance",
1607
+ "currency"
1608
+ ];
1609
+ const BALANCE_LABELS = {
1610
+ totalBalance: "总余额",
1611
+ topUpBalance: "充值余额",
1612
+ giftBalance: "赠送余额",
1613
+ balance: "余额",
1614
+ currency: "币种"
1615
+ };
1616
+ /** 订阅窗口固定展示顺序。 */
1617
+ const WINDOW_ORDER = [
1618
+ "5h",
1619
+ "week",
1620
+ "month",
1621
+ "day"
1622
+ ];
1623
+ const WINDOW_LABELS = {
1624
+ "5h": "5 小时窗口",
1625
+ week: "本周窗口",
1626
+ month: "本月窗口",
1627
+ day: "当日窗口"
1628
+ };
1629
+ function resetTextOf(value) {
1630
+ if (typeof value !== "string" || value === "") return "重置时间 —";
1631
+ const time = new Date(value);
1632
+ if (Number.isNaN(time.getTime())) return "重置于 " + value;
1633
+ return "重置于 " + fmtDateTime(time.getTime());
1634
+ }
1635
+ function QuotaTab(props) {
1636
+ const snapshotOf = (provider) => props.quotas.find((item) => item.provider === provider) ?? null;
1637
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1638
+ className: quota_module_css_default.quotaList,
1639
+ children: [props.channels.map((channel) => {
1640
+ const snapshot = snapshotOf(channel.provider);
1641
+ const balance = snapshot?.balance ?? null;
1642
+ const plan = snapshot?.plan ?? null;
1643
+ const isLive = snapshot?.source === "live";
1644
+ const windows = plan !== null && Array.isArray(plan.windows) ? plan.windows : null;
1645
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1646
+ className: quota_module_css_default.quotaCard,
1647
+ children: [
1648
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1649
+ className: quota_module_css_default.quotaHead,
1650
+ children: [
1651
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1652
+ className: shared_module_css_default.statusDot,
1653
+ style: { background: statusColorOf(channel.status) }
1654
+ }),
1655
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1656
+ className: quota_module_css_default.quotaTitle,
1657
+ children: channel.displayName
1658
+ }),
1659
+ isLive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1660
+ className: quota_module_css_default.liveChip,
1661
+ children: "实时"
1662
+ }),
1663
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1664
+ className: quota_module_css_default.quotaProvider,
1665
+ children: [
1666
+ channel.provider,
1667
+ " · ",
1668
+ channel.costLabel
1669
+ ]
1670
+ })
1671
+ ]
1672
+ }),
1673
+ balance !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1674
+ className: quota_module_css_default.balanceStrip,
1675
+ children: orderedBalanceEntries(balance).map(([key, value]) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1676
+ className: quota_module_css_default.balanceItem,
1677
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1678
+ className: quota_module_css_default.bsLabel,
1679
+ children: BALANCE_LABELS[key] ?? key
1680
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1681
+ className: quota_module_css_default.bsValue,
1682
+ children: String(value)
1683
+ })]
1684
+ }, key))
1685
+ }),
1686
+ balance === null && windows !== null && windows.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1687
+ className: quota_module_css_default.windowRow,
1688
+ children: orderedWindows(windows).map((window) => {
1689
+ const percent = typeof window.percent === "number" ? window.percent : 0;
1690
+ const key = typeof window.key === "string" ? window.key : "window";
1691
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1692
+ className: quota_module_css_default.windowItem,
1693
+ children: [
1694
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1695
+ className: quota_module_css_default.windowHead,
1696
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1697
+ className: quota_module_css_default.windowName,
1698
+ children: WINDOW_LABELS[key] ?? key
1699
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1700
+ className: quota_module_css_default.windowValue,
1701
+ children: [percent, "%"]
1702
+ })]
1703
+ }),
1704
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1705
+ className: quota_module_css_default.windowBar,
1706
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: {
1707
+ width: Math.max(percent, 0) + "%",
1708
+ background: quotaBarColor(percent)
1709
+ } })
1710
+ }),
1711
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1712
+ className: quota_module_css_default.windowReset,
1713
+ children: resetTextOf(window.resetsAt)
1714
+ })
1715
+ ]
1716
+ }, key);
1717
+ })
1718
+ }),
1719
+ balance === null && (windows === null || windows.length === 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1720
+ className: shared_module_css_default.dim,
1721
+ children: emptyReasonOf(channel, snapshot)
1722
+ }),
1723
+ isLive && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1724
+ className: quota_module_css_default.snapshotHint,
1725
+ children: [
1726
+ "实时查询于 ",
1727
+ freshTextOf(snapshot?.fetchedAt ?? null),
1728
+ "(来自官方接口,最多缓存 1 分钟)"
1729
+ ]
1730
+ }),
1731
+ snapshot !== null && !isLive && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1732
+ className: staleDaysOf(snapshot.updatedAt) >= 1 ? quota_module_css_default.snapshotStale : quota_module_css_default.snapshotHint,
1733
+ children: [
1734
+ "实时查询未成功(",
1735
+ errorTextOf(snapshot.error),
1736
+ "),显示本机快照",
1737
+ snapshot.updatedAt === null ? "" : " · 快照时间 " + fmtDateTime(snapshot.updatedAt) + (staleDaysOf(snapshot.updatedAt) >= 1 ? ",已 " + staleDaysOf(snapshot.updatedAt) + " 天未更新" : "")
1738
+ ]
1739
+ })
1740
+ ]
1741
+ }, channel.provider);
1742
+ }), props.channels.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1743
+ className: shared_module_css_default.empty,
1744
+ children: "未读取到通道配置"
1745
+ })]
1746
+ });
1747
+ }
1748
+ /** 实时结果的相对时间文案。 */
1749
+ function freshTextOf(fetchedAt) {
1750
+ if (fetchedAt === null) return "刚刚";
1751
+ const seconds = Math.max(0, Math.floor((Date.now() - fetchedAt) / 1e3));
1752
+ if (seconds < 60) return "刚刚";
1753
+ const minutes = Math.floor(seconds / 60);
1754
+ if (minutes < 60) return minutes + " 分钟前";
1755
+ return Math.floor(minutes / 60) + " 小时前";
1756
+ }
1757
+ /** 实时查询失败原因的展示文案。 */
1758
+ function errorTextOf(error) {
1759
+ if (error === null) return "未启用实时查询";
1760
+ if (error === "no-key") return "该通道未配置 API Key";
1761
+ if (error === "no-snapshot") return "没有快照可用";
1762
+ if (error === "network") return "网络不可达";
1763
+ if (error === "timeout") return "请求超时";
1764
+ if (error === "unauthorized") return "凭据被拒绝(401/403)";
1765
+ if (error === "bad-json") return "接口响应无法解析";
1766
+ if (error === "too-large") return "接口响应异常大";
1767
+ if (error.startsWith("http-")) return "接口返回 HTTP " + error.slice(5);
1768
+ return error;
1769
+ }
1770
+ /** 快照距今天数(向下取整);用于把「宿主很久没刷新」显式标出来。 */
1771
+ function staleDaysOf(updatedAt) {
1772
+ if (updatedAt === null) return 0;
1773
+ return Math.floor((Date.now() - updatedAt) / 864e5);
1774
+ }
1775
+ function orderedBalanceEntries(balance) {
1776
+ const entries = Object.entries(balance).filter(([key]) => key !== "updatedAt");
1777
+ const ordered = [];
1778
+ for (const key of BALANCE_ORDER) {
1779
+ const found = entries.find(([entryKey]) => entryKey === key);
1780
+ if (found !== void 0) ordered.push(found);
1781
+ }
1782
+ for (const entry of entries) if (!BALANCE_ORDER.includes(entry[0])) ordered.push(entry);
1783
+ return ordered;
1784
+ }
1785
+ function orderedWindows(windows) {
1786
+ return [...windows].sort((first, second) => {
1787
+ const firstIndex = WINDOW_ORDER.indexOf(typeof first.key === "string" ? first.key : "");
1788
+ const secondIndex = WINDOW_ORDER.indexOf(typeof second.key === "string" ? second.key : "");
1789
+ return (firstIndex < 0 ? 99 : firstIndex) - (secondIndex < 0 ? 99 : secondIndex);
1790
+ });
1791
+ }
1792
+ /** 无数据时的分档说明:区分未配置凭据 / 免费额度 / 本地端点 / 无查询能力。 */
1793
+ function emptyReasonOf(channel, snapshot) {
1794
+ if (snapshot?.source === "snapshot" && snapshot.error === "no-key") return "该通道未配置 API Key,无法查询余额";
1795
+ if (snapshot?.source === "snapshot" && snapshot.error === "unauthorized") return "凭据被通道拒绝,无法查询余额";
1796
+ if (channel.status === "off") return "凭据引用存在,Key 未填写";
1797
+ if (channel.costMode === "none") return "本地端点,无余额查询接口";
1798
+ if (channel.costMode === "free") return "该通道无余额查询接口(免费额度)";
1799
+ return "该通道暂无可查询的余额 / 配额接口";
1800
+ }
1801
+ //#endregion
1802
+ //#region src/client/tabs/SessionsTab.tsx
1803
+ /** 会话用量:按会话列出 tokens / 命中率 / 时长,按最近活跃时按日分组。 */
1804
+ function SessionsTab(props) {
1805
+ const [query, setQuery] = (0, react.useState)("");
1806
+ const [channelFilter, setChannelFilter] = (0, react.useState)("ALL");
1807
+ const [daysFilter, setDaysFilter] = (0, react.useState)("30");
1808
+ const [sortKey, setSortKey] = (0, react.useState)("recent");
1809
+ const [nowMs] = (0, react.useState)(() => Date.now());
1810
+ const rows = props.data?.rows ?? [];
1811
+ const visible = (0, react.useMemo)(() => {
1812
+ const keyword = query.trim().toLowerCase();
1813
+ const maxAgeDays = Number(daysFilter);
1814
+ const filtered = rows.filter((row) => {
1815
+ const title = (row.title ?? "") + " " + row.id + " " + (row.workspace ?? "");
1816
+ const matchesQuery = keyword === "" || title.toLowerCase().includes(keyword);
1817
+ const matchesChannel = channelFilter === "ALL" || row.provider === channelFilter;
1818
+ const matchesAge = maxAgeDays === 0 || row.lastTime === null || nowMs - row.lastTime <= maxAgeDays * 864e5;
1819
+ return matchesQuery && matchesChannel && matchesAge;
1820
+ });
1821
+ const sorters = {
1822
+ recent: (first, second) => (second.lastTime ?? 0) - (first.lastTime ?? 0),
1823
+ tokensDesc: (first, second) => second.tokens - first.tokens,
1824
+ tokensAsc: (first, second) => first.tokens - second.tokens,
1825
+ durationDesc: (first, second) => durationOf(second) - durationOf(first)
1826
+ };
1827
+ return [...filtered].sort(sorters[sortKey]);
1828
+ }, [
1829
+ rows,
1830
+ query,
1831
+ channelFilter,
1832
+ daysFilter,
1833
+ sortKey,
1834
+ nowMs
1835
+ ]);
1836
+ if (props.data === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1837
+ className: shared_module_css_default.empty,
1838
+ children: "会话数据读取中或不可用…"
1839
+ });
1840
+ const groups = [];
1841
+ if (sortKey === "recent") for (const row of visible) {
1842
+ const label = dayLabelOf(row.lastTime, nowMs);
1843
+ const last = groups[groups.length - 1];
1844
+ if (last === void 0 || last.label !== label) groups.push({
1845
+ label,
1846
+ rows: [row]
1847
+ });
1848
+ else last.rows.push(row);
1849
+ }
1850
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
1851
+ props.data.coverage.status !== "complete" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1852
+ className: shared_module_css_default.warning,
1853
+ children: props.data.coverage.status === "unavailable" ? "会话数据不可用(宿主未提供会话服务或全部读取失败)" : "会话数据不完整:" + String(props.data.coverage.skippedSessions) + " 个会话读取失败,未计入统计"
1854
+ }),
1855
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1856
+ className: lists_module_css_default.filterBar,
1857
+ children: [
1858
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1859
+ className: lists_module_css_default.searchInput,
1860
+ placeholder: "搜索会话标题、session id、工作区…",
1861
+ value: query,
1862
+ onChange: (event) => setQuery(event.target.value)
1863
+ }),
1864
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1865
+ className: lists_module_css_default.filterSelect,
1866
+ value: channelFilter,
1867
+ onChange: (event) => setChannelFilter(event.target.value),
1868
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1869
+ value: "ALL",
1870
+ children: "全部通道"
1871
+ }), props.channels.map((channel) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1872
+ value: channel.provider,
1873
+ children: channel.displayName
1874
+ }, channel.provider))]
1875
+ }),
1876
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1877
+ className: lists_module_css_default.filterSelect,
1878
+ value: daysFilter,
1879
+ onChange: (event) => setDaysFilter(event.target.value),
1880
+ children: [
1881
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1882
+ value: "7",
1883
+ children: "近 7 天"
1884
+ }),
1885
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1886
+ value: "30",
1887
+ children: "近 30 天"
1888
+ }),
1889
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1890
+ value: "0",
1891
+ children: "全部"
1892
+ })
1893
+ ]
1894
+ }),
1895
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1896
+ className: lists_module_css_default.filterSelect,
1897
+ value: sortKey,
1898
+ onChange: (event) => setSortKey(event.target.value),
1899
+ children: [
1900
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1901
+ value: "recent",
1902
+ children: "最新会话"
1903
+ }),
1904
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1905
+ value: "tokensDesc",
1906
+ children: "Tokens 最多"
1907
+ }),
1908
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1909
+ value: "tokensAsc",
1910
+ children: "Tokens 最少"
1911
+ }),
1912
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1913
+ value: "durationDesc",
1914
+ children: "时长最长"
1915
+ })
1916
+ ]
1917
+ })
1918
+ ]
1919
+ }),
1920
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1921
+ className: shared_module_css_default.table,
1922
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1923
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "会话" }),
1924
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "通道" }),
1925
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "时间 / 时长" }),
1926
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Tokens" }),
1927
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "命中率" }),
1928
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "成本" })
1929
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tbody", { children: [
1930
+ groups.map((group) => {
1931
+ const groupTokens = group.rows.reduce((total, row) => total + row.tokens, 0);
1932
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", {
1933
+ className: lists_module_css_default.sessionDay,
1934
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
1935
+ colSpan: 6,
1936
+ children: [group.label, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1937
+ className: lists_module_css_default.dayCount,
1938
+ children: [
1939
+ group.rows.length,
1940
+ " 个会话 · ",
1941
+ fmtTokens(groupTokens)
1942
+ ]
1943
+ })]
1944
+ })
1945
+ }), group.rows.map((row) => sessionRowOf(row, props.channels))] }, "group-" + group.label);
1946
+ }),
1947
+ sortKey !== "recent" && visible.map((row) => sessionRowOf(row, props.channels)),
1948
+ visible.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1949
+ colSpan: 6,
1950
+ className: shared_module_css_default.empty,
1951
+ children: "没有符合条件的会话"
1952
+ }) })
1953
+ ] })]
1954
+ }),
1955
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1956
+ className: shared_module_css_default.statExtra,
1957
+ children: ["会话用量按宿主会话事件折叠(每个结算样本计一次调用);成本按台账模型均价估算", props.data.priced ? "" : "(台账无成本数据,暂不可估)"]
1958
+ })
1959
+ ] });
1960
+ }
1961
+ function sessionRowOf(row, channels) {
1962
+ const channel = channelOf(channels, row.provider);
1963
+ const costCell = costCellText(channel, row.cost ?? 0);
1964
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1965
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1966
+ className: lists_module_css_default.listMain,
1967
+ children: row.title ?? "会话 " + shortIdOf(row.id)
1968
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1969
+ className: lists_module_css_default.listSub,
1970
+ children: row.workspace ?? "未归属工作区"
1971
+ })] }),
1972
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.providerLabel ?? displayNameOf(channels, row.provider ?? "") }),
1973
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: row.lastTime === null ? "—" : dayLabelOf(row.lastTime, Date.now()) + " " + fmtTime(row.lastTime) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1974
+ className: lists_module_css_default.listSub,
1975
+ children: ["时长 ", fmtDuration(durationOf(row))]
1976
+ })] }),
1977
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(row.tokens) }),
1978
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.cacheHitRate === null ? "—" : row.cacheHitRate.toFixed(1) + "%" }),
1979
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.cost === null || channel === null || channel.costMode !== "perToken" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1980
+ className: shared_module_css_default.dim,
1981
+ children: costCell.text
1982
+ }) : costCell.text })
1983
+ ] }, row.id);
1984
+ }
1985
+ function durationOf(row) {
1986
+ if (row.firstTime === null || row.lastTime === null) return 0;
1987
+ return Math.max(0, row.lastTime - row.firstTime);
1988
+ }
1989
+ /** 会话头通常不带标题,用 id 的短片段做可读标识。 */
1990
+ function shortIdOf(id) {
1991
+ return id.replace(/^session-/, "").slice(0, 8);
1992
+ }
1993
+ function dayLabelOf(timeMs, nowMs) {
1994
+ if (timeMs === null) return "未知日期";
1995
+ const dateKey = isoDate(new Date(timeMs));
1996
+ if (dateKey === isoDate(new Date(nowMs))) return "今日";
1997
+ if (dateKey === isoDate(/* @__PURE__ */ new Date(nowMs - 864e5))) return "昨日";
1998
+ return dateKey;
1999
+ }
2000
+ //#endregion
2001
+ //#region src/client/KeyUsageSection.tsx
2002
+ /**
2003
+ * 「使用情况」设置页外壳:Tab 路由、范围 / 通道筛选状态与取数。
2004
+ * 台账类数据(通道 / 台账 / 热力图 / 汇总 / 快照)来自本地文件端点;
2005
+ * 项目与会话数据来自宿主会话事件折叠,切到对应 Tab 时才首次拉取。
2006
+ */
2007
+ const TABS = [
2008
+ ["overview", "总览"],
2009
+ ["detail", "使用明细"],
2010
+ ["projects", "项目用量"],
2011
+ ["sessions", "会话用量"],
2012
+ ["quota", "余额与配额"]
2013
+ ];
2014
+ function KeyUsageSection() {
2015
+ const [activeTab, setActiveTab] = (0, react.useState)("overview");
2016
+ const [range, setRange] = (0, react.useState)("month");
2017
+ const [customFrom, setCustomFrom] = (0, react.useState)(() => shiftedIsoDate(-29));
2018
+ const [customTo, setCustomTo] = (0, react.useState)(() => isoDate(/* @__PURE__ */ new Date()));
2019
+ const [channelFilter, setChannelFilter] = (0, react.useState)("ALL");
2020
+ const [expandedPeriods, setExpandedPeriods] = (0, react.useState)({});
2021
+ const [detailSort, setDetailSort] = (0, react.useState)({
2022
+ key: "period",
2023
+ dir: "desc"
2024
+ });
2025
+ const [channels, setChannels] = (0, react.useState)([]);
2026
+ const [quotas, setQuotas] = (0, react.useState)([]);
2027
+ const [summary, setSummary] = (0, react.useState)(null);
2028
+ const [heatmap, setHeatmap] = (0, react.useState)([]);
2029
+ const [usage, setUsage] = (0, react.useState)(null);
2030
+ const [sessions, setSessions] = (0, react.useState)(null);
2031
+ const [projects, setProjects] = (0, react.useState)(null);
2032
+ const [available, setAvailable] = (0, react.useState)(null);
2033
+ const [loading, setLoading] = (0, react.useState)(false);
2034
+ const tooltip = useTooltip();
2035
+ const reload = (0, react.useCallback)(async (includeSessionData, forceQuota = false) => {
2036
+ setLoading(true);
2037
+ const quotaResult = await loadQuota(forceQuota);
2038
+ const [channelResult, summaryResult, heatmapResult] = await Promise.all([
2039
+ loadChannels(),
2040
+ loadSummary(),
2041
+ loadHeatmap()
2042
+ ]);
2043
+ setChannels(channelResult ?? []);
2044
+ setQuotas(quotaResult ?? []);
2045
+ setSummary(summaryResult);
2046
+ setHeatmap(heatmapResult ?? []);
2047
+ if (includeSessionData) {
2048
+ const [sessionResult, projectResult] = await Promise.all([loadSessions(), loadProjects()]);
2049
+ setSessions(sessionResult);
2050
+ setProjects(projectResult);
2051
+ }
2052
+ setLoading(false);
2053
+ }, []);
2054
+ (0, react.useEffect)(() => {
2055
+ reload(false);
2056
+ }, [reload]);
2057
+ (0, react.useEffect)(() => {
2058
+ if (activeTab === "sessions" && sessions === null) loadSessions().then(setSessions);
2059
+ if (activeTab === "projects" && projects === null) loadProjects().then(setProjects);
2060
+ }, [
2061
+ activeTab,
2062
+ sessions,
2063
+ projects
2064
+ ]);
2065
+ const bounds = (0, react.useMemo)(() => {
2066
+ const today = isoDate(/* @__PURE__ */ new Date());
2067
+ if (range === "today") return {
2068
+ from: today,
2069
+ to: today
2070
+ };
2071
+ if (range === "week") return {
2072
+ from: shiftedIsoDate(-6),
2073
+ to: today
2074
+ };
2075
+ if (range === "month") return {
2076
+ from: shiftedIsoDate(-29),
2077
+ to: today
2078
+ };
2079
+ if (range === "custom") return {
2080
+ from: customFrom,
2081
+ to: customTo < customFrom ? customFrom : customTo
2082
+ };
2083
+ return {
2084
+ from: available?.from ?? today,
2085
+ to: available?.to ?? today
2086
+ };
2087
+ }, [
2088
+ range,
2089
+ customFrom,
2090
+ customTo,
2091
+ available
2092
+ ]);
2093
+ (0, react.useEffect)(() => {
2094
+ let cancelled = false;
2095
+ loadUsage(bounds.from, bounds.to, channelFilter).then((result) => {
2096
+ if (cancelled || result === null) return;
2097
+ setUsage(result);
2098
+ if (result.availableFrom !== null && result.availableTo !== null) setAvailable({
2099
+ from: result.availableFrom,
2100
+ to: result.availableTo
2101
+ });
2102
+ });
2103
+ return () => {
2104
+ cancelled = true;
2105
+ };
2106
+ }, [
2107
+ bounds.from,
2108
+ bounds.to,
2109
+ channelFilter
2110
+ ]);
2111
+ const refresh = (0, react.useCallback)(() => {
2112
+ reload(activeTab === "sessions" || activeTab === "projects" || sessions !== null || projects !== null, true);
2113
+ loadUsage(bounds.from, bounds.to, channelFilter).then((result) => {
2114
+ if (result !== null) setUsage(result);
2115
+ });
2116
+ }, [
2117
+ reload,
2118
+ activeTab,
2119
+ sessions,
2120
+ projects,
2121
+ bounds.from,
2122
+ bounds.to,
2123
+ channelFilter
2124
+ ]);
2125
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2126
+ className: shared_module_css_default.section,
2127
+ children: [
2128
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2129
+ className: shared_module_css_default.title,
2130
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "使用情况" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2131
+ className: shared_module_css_default.titleActions,
2132
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2133
+ className: shared_module_css_default.refreshBtn,
2134
+ disabled: loading,
2135
+ onClick: refresh,
2136
+ children: loading ? "刷新中…" : "↻ 刷新"
2137
+ })
2138
+ })]
2139
+ }),
2140
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2141
+ className: shared_module_css_default.tabBar,
2142
+ children: TABS.map(([tabId, label]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2143
+ className: activeTab === tabId ? shared_module_css_default.active : "",
2144
+ onClick: () => setActiveTab(tabId),
2145
+ children: label
2146
+ }, tabId))
2147
+ }),
2148
+ activeTab === "overview" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OverviewTab, {
2149
+ channels,
2150
+ summary,
2151
+ quotas,
2152
+ heatmap,
2153
+ tooltip,
2154
+ onOpenDetail: (provider) => {
2155
+ setChannelFilter(provider);
2156
+ setActiveTab("detail");
2157
+ }
2158
+ }),
2159
+ activeTab === "detail" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DetailTab, {
2160
+ usage,
2161
+ channels,
2162
+ range,
2163
+ onRange: setRange,
2164
+ customFrom,
2165
+ onCustomFrom: setCustomFrom,
2166
+ customTo,
2167
+ onCustomTo: setCustomTo,
2168
+ channelFilter,
2169
+ onChannelFilter: setChannelFilter,
2170
+ expandedPeriods,
2171
+ onTogglePeriod: (periodKey) => setExpandedPeriods((previous) => ({
2172
+ ...previous,
2173
+ [periodKey]: previous[periodKey] !== true
2174
+ })),
2175
+ sort: detailSort,
2176
+ onSort: setDetailSort,
2177
+ bounds,
2178
+ tooltip
2179
+ }),
2180
+ activeTab === "projects" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProjectsTab, {
2181
+ data: projects,
2182
+ channels
2183
+ }),
2184
+ activeTab === "sessions" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionsTab, {
2185
+ data: sessions,
2186
+ channels
2187
+ }),
2188
+ activeTab === "quota" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(QuotaTab, {
2189
+ channels,
2190
+ quotas
2191
+ }),
2192
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2193
+ className: shared_module_css_default.footnote,
2194
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("b", { children: "口径说明" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
2195
+ className: shared_module_css_default.footnoteList,
2196
+ children: [
2197
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { children: [
2198
+ "按天数据优先取宿主用量台账(含官方成本);台账无记录的日期回退本地会话事件折叠,那些天没有官方成本",
2199
+ summary !== null && summary.todaySource === "session" ? "(当前有 " + summary.sessionDates.length + " 天由会话补位)" : "",
2200
+ "。两套来源按天取舍、不相加。"
2201
+ ] }),
2202
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "热力图按可见区间内活跃日的三分位分档(少 / 中 / 高)。" }),
2203
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "项目用量、会话用量与单日「按小时」均来自会话事件,每个结算样本计一次调用(不计成本)。" }),
2204
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "命中率 = 缓存读 ÷(输入 + 输出 + 缓存读)。" }),
2205
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "余额与订阅窗口实时查询官方接口(失败回落本机快照),原样展示,不做换算与建议。" })
2206
+ ]
2207
+ })]
2208
+ }),
2209
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipView, { tooltip: tooltip.tooltip })
2210
+ ]
2211
+ });
2212
+ }
2213
+ //#endregion
2214
+ //#region \0dsh-css:D:\EasyJob\.plugin\dsh-key-usage\src\client\styles\page.module.css.mjs
2215
+ const css = "._2fl9Ga_page{box-sizing:border-box;overscroll-behavior:contain;width:100%;height:100%;min-height:0;overflow-x:hidden;overflow-y:auto}._2fl9Ga_inner{box-sizing:border-box;width:100%;max-width:1240px;margin:0 auto;padding:20px 24px 28px}";
2216
+ const tagId = "dsh-key-usage/page.module.css";
2217
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2218
+ const tag = document.createElement("style");
2219
+ tag.dataset.plugin = "dsh-key-usage";
2220
+ tag.dataset.pluginCss = tagId;
2221
+ tag.textContent = css;
2222
+ document.head.appendChild(tag);
2223
+ }
2224
+ const cssRegistry = globalThis.__DSH_PLUGIN_CSS__ ??= [];
2225
+ if (!cssRegistry.some((e) => e.tagId === tagId)) cssRegistry.push({
2226
+ tagId,
2227
+ css
2228
+ });
2229
+ var page_module_css_default = {
2230
+ "inner": "_2fl9Ga_inner",
2231
+ "page": "_2fl9Ga_page"
2232
+ };
2233
+ //#endregion
2234
+ //#region src/client/UsagePage.tsx
2235
+ /** 宿主会传入根作用域的标准 kit 与绑定的 `t`;本页面当前不需要它们。 */
2236
+ function UsagePage(_props) {
2237
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2238
+ className: page_module_css_default.page,
2239
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2240
+ className: page_module_css_default.inner,
2241
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(KeyUsageSection, {})
2242
+ })
2243
+ });
2244
+ }
2245
+ //#endregion
2246
+ //#region src/client/index.tsx
2247
+ /**
2248
+ * dsh-key-usage 客户端入口:注册「使用情况」侧边栏导航项 + 主区域页面。
2249
+ * UI 逻辑在 UsagePage / KeyUsageSection 里;本文件只做 cordis apply 接线。
2250
+ *
2251
+ * slot 契约(对齐已装的 dsh-context「上下文洞察」写法):
2252
+ * - `sidebar.panellist`:宿主自持整行(标签 / hover / active),插件只提供图标,`id` 是面板标识;
2253
+ * - `main`:同一标识放在 `key` 上,宿主在主内容区按它渲染整页 —— 因此不是弹窗、也不占设置页。
2254
+ */
2255
+ const NS = "dsh-key-usage";
2256
+ /** 侧边栏条目与 main 页面共用的面板标识(宿主 MainPanelId 域,命名空间化避免撞车)。 */
2257
+ const PANEL_ID = NS;
2258
+ const name = NS;
2259
+ const inject = ["slots"];
2260
+ function apply(ctx) {
2261
+ ctx.slots.inject("main", () => ctx.slots.register({
2262
+ name: "main",
2263
+ key: PANEL_ID
2264
+ }, UsagePage));
2265
+ ctx.slots.inject("sidebar.panellist", () => ctx.slots.register({
2266
+ name: "sidebar.panellist",
2267
+ id: PANEL_ID,
2268
+ order: 30,
2269
+ label: () => "使用情况"
2270
+ }, (props) => (0, react.createElement)(UsageIcon, { size: props.size })));
2271
+ }
2272
+ //#endregion
2273
+ exports.apply = apply;
2274
+ exports.inject = inject;
2275
+ exports.name = name;
2276
+ return module.exports;
2277
+ }
2278
+ });
2279
+
2280
+ //# sourceMappingURL=client.js.map