dsh-key-usage 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client/client.js CHANGED
@@ -46,12 +46,25 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
46
46
  }
47
47
  //#endregion
48
48
  //#region src/client/api.ts
49
+ /**
50
+ * 最近一次请求失败的原因,按路径记住。
51
+ * 空态文案需要能指向「怎么做」——最常见的失败是新端点 404(服务端代码要重启宿主才生效)。
52
+ */
53
+ const failureReasons = /* @__PURE__ */ new Map();
54
+ function failureReasonOf(path) {
55
+ return failureReasons.get(path) ?? null;
56
+ }
49
57
  async function fetchJson(path) {
50
58
  try {
51
59
  const response = await fetch(path, { headers: { Accept: "application/json" } });
52
- if (!response.ok) return null;
60
+ if (!response.ok) {
61
+ failureReasons.set(path, response.status === 404 ? "HTTP 404:宿主没有这个端点 —— 服务端改动需要退出重开 DSH(刷新页面不够)" : "HTTP " + String(response.status));
62
+ return null;
63
+ }
64
+ failureReasons.delete(path);
53
65
  return await response.json();
54
- } catch {
66
+ } catch (error) {
67
+ failureReasons.set(path, "请求失败:" + (error instanceof Error ? error.message : String(error)));
55
68
  return null;
56
69
  }
57
70
  }
@@ -88,6 +101,9 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
88
101
  function loadProjects(limit = 200) {
89
102
  return fetchJson("/dsh-key-usage/projects?limit=" + String(limit));
90
103
  }
104
+ function loadModels(limit = 200) {
105
+ return fetchJson("/dsh-key-usage/models?limit=" + String(limit));
106
+ }
91
107
  //#endregion
92
108
  //#region src/client/format.ts
93
109
  /** 展示层格式化:全部与统计口径无关,只负责把数字变成界面文本。 */
@@ -148,63 +164,63 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
148
164
  }
149
165
  //#endregion
150
166
  //#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) {
167
+ const css$6 = ".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}";
168
+ const tagId$6 = "dsh-key-usage/shared.module.css";
169
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$6) + "]") === null) {
154
170
  const tag = document.createElement("style");
155
171
  tag.dataset.plugin = "dsh-key-usage";
156
- tag.dataset.pluginCss = tagId$5;
157
- tag.textContent = css$5;
172
+ tag.dataset.pluginCss = tagId$6;
173
+ tag.textContent = css$6;
158
174
  document.head.appendChild(tag);
159
175
  }
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
176
+ const cssRegistry$6 = globalThis.__DSH_PLUGIN_CSS__ ??= [];
177
+ if (!cssRegistry$6.some((e) => e.tagId === tagId$6)) cssRegistry$6.push({
178
+ tagId: tagId$6,
179
+ css: css$6
164
180
  });
165
181
  var shared_module_css_default = {
166
- "tipSub": "LhdpGW_tipSub",
182
+ "statExtra": "LhdpGW_statExtra",
183
+ "groupLabel": "LhdpGW_groupLabel",
184
+ "statusDot": "LhdpGW_statusDot",
185
+ "statValue": "LhdpGW_statValue",
167
186
  "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",
187
+ "cardGrid4": "LhdpGW_cardGrid4",
188
+ "empty": "LhdpGW_empty",
189
+ "tooltip": "LhdpGW_tooltip",
190
+ "ghost": "LhdpGW_ghost",
191
+ "tipRate": "LhdpGW_tipRate",
192
+ "statLabel": "LhdpGW_statLabel",
174
193
  "footnote": "LhdpGW_footnote",
175
- "tipBar": "LhdpGW_tipBar",
194
+ "disabled": "LhdpGW_disabled",
195
+ "titleActions": "LhdpGW_titleActions",
196
+ "cardGrid3": "LhdpGW_cardGrid3",
176
197
  "tipRow": "LhdpGW_tipRow",
177
- "tipRate": "LhdpGW_tipRate",
198
+ "section": "LhdpGW_section",
199
+ "refreshBtn": "LhdpGW_refreshBtn",
200
+ "active": "LhdpGW_active",
201
+ "card": "LhdpGW_card",
202
+ "title": "LhdpGW_title",
178
203
  "tabBar": "LhdpGW_tabBar",
204
+ "cardGridAuto": "LhdpGW_cardGridAuto",
179
205
  "blockLabel": "LhdpGW_blockLabel",
180
- "empty": "LhdpGW_empty",
181
- "total": "LhdpGW_total",
182
- "statLabel": "LhdpGW_statLabel",
206
+ "sortActive": "LhdpGW_sortActive",
183
207
  "sortArrow": "LhdpGW_sortArrow",
184
- "statusDot": "LhdpGW_statusDot",
208
+ "clickable": "LhdpGW_clickable",
185
209
  "mono": "LhdpGW_mono",
186
- "statExtra": "LhdpGW_statExtra",
187
- "refreshBtn": "LhdpGW_refreshBtn",
210
+ "warning": "LhdpGW_warning",
188
211
  "legend": "LhdpGW_legend",
189
- "footnoteList": "LhdpGW_footnoteList",
190
- "cardGrid4": "LhdpGW_cardGrid4",
212
+ "tipSub": "LhdpGW_tipSub",
213
+ "tipName": "LhdpGW_tipName",
191
214
  "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
215
  "chip": "LhdpGW_chip",
203
- "tooltip": "LhdpGW_tooltip",
204
- "tipAmount": "LhdpGW_tipAmount",
216
+ "tipBar": "LhdpGW_tipBar",
217
+ "chipRow": "LhdpGW_chipRow",
205
218
  "table": "LhdpGW_table",
206
- "warning": "LhdpGW_warning",
207
- "ghost": "LhdpGW_ghost"
219
+ "footnoteList": "LhdpGW_footnoteList",
220
+ "tipTitle": "LhdpGW_tipTitle",
221
+ "total": "LhdpGW_total",
222
+ "tipAmount": "LhdpGW_tipAmount",
223
+ "tipDivider": "LhdpGW_tipDivider"
208
224
  };
209
225
  //#endregion
210
226
  //#region src/client/tooltip.tsx
@@ -355,51 +371,51 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
355
371
  }
356
372
  //#endregion
357
373
  //#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) {
374
+ const css$5 = ".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}";
375
+ const tagId$5 = "dsh-key-usage/overview.module.css";
376
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$5) + "]") === null) {
361
377
  const tag = document.createElement("style");
362
378
  tag.dataset.plugin = "dsh-key-usage";
363
- tag.dataset.pluginCss = tagId$4;
364
- tag.textContent = css$4;
379
+ tag.dataset.pluginCss = tagId$5;
380
+ tag.textContent = css$5;
365
381
  document.head.appendChild(tag);
366
382
  }
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
383
+ const cssRegistry$5 = globalThis.__DSH_PLUGIN_CSS__ ??= [];
384
+ if (!cssRegistry$5.some((e) => e.tagId === tagId$5)) cssRegistry$5.push({
385
+ tagId: tagId$5,
386
+ css: css$5
371
387
  });
372
388
  var overview_module_css_default = {
373
- "keyEnv": "J7Idbq_keyEnv",
374
389
  "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
390
  "keyDisplay": "J7Idbq_keyDisplay",
384
- "disabledRow": "J7Idbq_disabledRow",
391
+ "warn": "J7Idbq_warn",
392
+ "cellKey": "J7Idbq_cellKey",
393
+ "miniBar": "J7Idbq_miniBar",
394
+ "free": "J7Idbq_free",
385
395
  "sub": "J7Idbq_sub",
386
- "heatGridCol": "J7Idbq_heatGridCol",
387
- "clickable": "J7Idbq_clickable",
388
- "danger": "J7Idbq_danger",
389
396
  "heatmapTitle": "J7Idbq_heatmapTitle",
390
- "free": "J7Idbq_free",
391
- "miniBar": "J7Idbq_miniBar",
392
- "heatmapCard": "J7Idbq_heatmapCard",
393
- "tag": "J7Idbq_tag",
397
+ "idle": "J7Idbq_idle",
394
398
  "rowLink": "J7Idbq_rowLink",
395
- "empty": "J7Idbq_empty",
396
- "heatWeek": "J7Idbq_heatWeek",
399
+ "heatCell": "J7Idbq_heatCell",
400
+ "keyEnv": "J7Idbq_keyEnv",
401
+ "heatmapHead": "J7Idbq_heatmapHead",
397
402
  "heatMonths": "J7Idbq_heatMonths",
403
+ "heatGridCol": "J7Idbq_heatGridCol",
398
404
  "keyTable": "J7Idbq_keyTable",
399
- "dim": "J7Idbq_dim",
400
- "cellKey": "J7Idbq_cellKey",
405
+ "heatmapCard": "J7Idbq_heatmapCard",
406
+ "danger": "J7Idbq_danger",
407
+ "disabledRow": "J7Idbq_disabledRow",
408
+ "heatmapBody": "J7Idbq_heatmapBody",
409
+ "heatWeekdays": "J7Idbq_heatWeekdays",
410
+ "tag": "J7Idbq_tag",
401
411
  "heatmapScroll": "J7Idbq_heatmapScroll",
402
- "heatCell": "J7Idbq_heatCell"
412
+ "off": "J7Idbq_off",
413
+ "heatGrid": "J7Idbq_heatGrid",
414
+ "heatWeek": "J7Idbq_heatWeek",
415
+ "dim": "J7Idbq_dim",
416
+ "quotaCell": "J7Idbq_quotaCell",
417
+ "clickable": "J7Idbq_clickable",
418
+ "empty": "J7Idbq_empty"
403
419
  };
404
420
  //#endregion
405
421
  //#region src/client/components/Heatmap.tsx
@@ -487,6 +503,12 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
487
503
  monthLabels: labels
488
504
  };
489
505
  }, [days, heatmapByDate]);
506
+ const scrollRef = (0, react.useRef)(null);
507
+ (0, react.useEffect)(() => {
508
+ const node = scrollRef.current;
509
+ if (node === null) return;
510
+ node.scrollLeft = node.scrollWidth - node.clientWidth;
511
+ }, []);
490
512
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
491
513
  className: overview_module_css_default.heatmapCard,
492
514
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -506,6 +528,7 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
506
528
  })]
507
529
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
508
530
  className: overview_module_css_default.heatmapScroll,
531
+ ref: scrollRef,
509
532
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
510
533
  className: overview_module_css_default.heatmapBody,
511
534
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -703,45 +726,87 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
703
726
  });
704
727
  }
705
728
  //#endregion
729
+ //#region src/client/export.ts
730
+ /**
731
+ * 导出工具:纯函数生成 CSV / JSON 文本 + 用 Blob 触发浏览器下载。
732
+ * 零依赖、零服务端往返,导出的永远是**客户端当前筛选后**的数据。
733
+ *
734
+ * 两条口径约束:
735
+ * - CSV 必须带 UTF-8 BOM:Excel 默认按本地代码页解码,没有 BOM 中文会变乱码;
736
+ * - JSON 不能带 BOM:`JSON.parse` 遇到 BOM 会直接抛错。
737
+ */
738
+ const UTF8_BOM = "";
739
+ const CSV_MIME = "text/csv;charset=utf-8";
740
+ const JSON_MIME = "application/json;charset=utf-8";
741
+ /** 表头 + 数据行 → CSV 文本(转义见 `csvCell`,换行用 RFC 4180 的 CRLF)。 */
742
+ function toCsv(rows, columns) {
743
+ const lines = [columns.map((column) => csvCell(column.label)).join(",")];
744
+ for (const row of rows) lines.push(columns.map((column) => csvCell(column.value(row))).join(","));
745
+ return UTF8_BOM + lines.join("\r\n") + "\r\n";
746
+ }
747
+ function toJson(value) {
748
+ return JSON.stringify(value, null, 2) + "\n";
749
+ }
750
+ /** 下载文件名:`dsh-usage-<主题>-<当天>.<扩展名>`,主题用英文短名避免跨端中文文件名乱码。 */
751
+ function exportFileName(topic, extension) {
752
+ return "dsh-usage-" + topic + "-" + isoDate(/* @__PURE__ */ new Date()) + "." + extension;
753
+ }
754
+ function downloadFile(name, text, mime) {
755
+ const objectUrl = URL.createObjectURL(new Blob([text], { type: mime }));
756
+ const anchor = document.createElement("a");
757
+ anchor.href = objectUrl;
758
+ anchor.download = name;
759
+ document.body.appendChild(anchor);
760
+ anchor.click();
761
+ anchor.remove();
762
+ window.setTimeout(() => URL.revokeObjectURL(objectUrl), 0);
763
+ }
764
+ /** 逗号、引号、换行都必须包引号并把内部引号翻倍,否则标题里的逗号会撑破列。 */
765
+ function csvCell(value) {
766
+ if (value === null) return "";
767
+ const text = String(value);
768
+ return /[",\r\n]/.test(text) ? "\"" + text.replace(/"/g, "\"\"") + "\"" : text;
769
+ }
770
+ //#endregion
706
771
  //#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) {
772
+ const css$4 = ".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}";
773
+ const tagId$4 = "dsh-key-usage/detail.module.css";
774
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$4) + "]") === null) {
710
775
  const tag = document.createElement("style");
711
776
  tag.dataset.plugin = "dsh-key-usage";
712
- tag.dataset.pluginCss = tagId$3;
713
- tag.textContent = css$3;
777
+ tag.dataset.pluginCss = tagId$4;
778
+ tag.textContent = css$4;
714
779
  document.head.appendChild(tag);
715
780
  }
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
781
+ const cssRegistry$4 = globalThis.__DSH_PLUGIN_CSS__ ??= [];
782
+ if (!cssRegistry$4.some((e) => e.tagId === tagId$4)) cssRegistry$4.push({
783
+ tagId: tagId$4,
784
+ css: css$4
720
785
  });
721
786
  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",
787
+ "toggleArrow": "V9QVRa_toggleArrow",
728
788
  "barColumn": "V9QVRa_barColumn",
729
- "subHead": "V9QVRa_subHead",
730
- "segOut": "V9QVRa_segOut",
731
- "rangeCaption": "V9QVRa_rangeCaption",
732
- "barLabels": "V9QVRa_barLabels",
789
+ "barsDense": "V9QVRa_barsDense",
790
+ "shareBar": "V9QVRa_shareBar",
733
791
  "subModel": "V9QVRa_subModel",
734
- "segCache": "V9QVRa_segCache",
735
- "subName": "V9QVRa_subName",
792
+ "rangeTabs": "V9QVRa_rangeTabs",
793
+ "rangeCaption": "V9QVRa_rangeCaption",
794
+ "subHead": "V9QVRa_subHead",
795
+ "subChannel": "V9QVRa_subChannel",
796
+ "subNumber": "V9QVRa_subNumber",
797
+ "rangeInput": "V9QVRa_rangeInput",
798
+ "chartTitle": "V9QVRa_chartTitle",
736
799
  "segIn": "V9QVRa_segIn",
800
+ "segOut": "V9QVRa_segOut",
737
801
  "subRow": "V9QVRa_subRow",
738
- "subNumber": "V9QVRa_subNumber",
802
+ "bars": "V9QVRa_bars",
803
+ "segCache": "V9QVRa_segCache",
804
+ "subRowCell": "V9QVRa_subRowCell",
805
+ "rangeLeft": "V9QVRa_rangeLeft",
739
806
  "active": "V9QVRa_active",
740
- "rangeTabs": "V9QVRa_rangeTabs",
741
807
  "rangeBar": "V9QVRa_rangeBar",
742
- "rangeInput": "V9QVRa_rangeInput",
743
- "subChannel": "V9QVRa_subChannel",
744
- "toggleArrow": "V9QVRa_toggleArrow"
808
+ "subName": "V9QVRa_subName",
809
+ "barLabels": "V9QVRa_barLabels"
745
810
  };
746
811
  //#endregion
747
812
  //#region src/client/components/ShareBar.tsx
@@ -878,11 +943,31 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
878
943
  })] });
879
944
  }
880
945
  //#endregion
946
+ //#region src/client/rows.ts
947
+ function dropEmptyRows(rows, tokensOf) {
948
+ const kept = rows.filter((row) => tokensOf(row) > 0);
949
+ return {
950
+ rows: kept,
951
+ hidden: rows.length - kept.length
952
+ };
953
+ }
954
+ /** 列表底部的一行小字:说明隐藏了什么、为什么。 */
955
+ function hiddenEmptyRowsText(hidden) {
956
+ return "已隐藏 " + String(hidden) + " 行无 Token 记录(调用失败或未产生用量;调用次数仍计入合计)";
957
+ }
958
+ //#endregion
881
959
  //#region src/client/components/DailyDetailTable.tsx
882
960
  /**
883
961
  * 每日细目:以「天」为折叠单位,展开行用原型子表格展示模型维度
884
962
  * (模型 + 调用 / 输入 / 输出 / 缓存读 / 命中率,不占主表列)。
885
963
  */
964
+ /** 输入/输出为 0 时不再显示 `0`(全缓存命中或纯输出模型的 0 是噪音),改显示淡化的 `—`。 */
965
+ function tokenCellOf(value) {
966
+ return value === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
967
+ className: shared_module_css_default.dim,
968
+ children: "—"
969
+ }) : fmtTokens(value);
970
+ }
886
971
  function hitRateOf$1(cacheReadTokens, inputTokens, outputTokens) {
887
972
  const total = inputTokens + outputTokens + cacheReadTokens;
888
973
  if (total <= 0) return "—";
@@ -941,7 +1026,9 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
941
1026
  }
942
1027
  }
943
1028
  const comparer = COMPARERS[sort.key] ?? COMPARERS.period;
944
- const ordered = [...aggregates.values()].sort((first, second) => {
1029
+ const nonEmpty = [...aggregates.values()].filter((entry) => entry.totalTokens > 0);
1030
+ const hiddenEmpty = aggregates.size - nonEmpty.length;
1031
+ const ordered = nonEmpty.sort((first, second) => {
945
1032
  const compared = comparer(first, second);
946
1033
  return sort.dir === "asc" ? compared : -compared;
947
1034
  });
@@ -971,116 +1058,143 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
971
1058
  className: shared_module_css_default.empty,
972
1059
  children: "所选范围内无用量记录"
973
1060
  })] });
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,
1061
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
1062
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1063
+ className: shared_module_css_default.blockLabel,
1064
+ children: "每日细目"
1065
+ }),
1066
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1067
+ className: shared_module_css_default.table,
1068
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1069
+ header("period", "日期"),
1070
+ header("total", "总计"),
1071
+ header("calls", "调用"),
1072
+ header("input", "输入"),
1073
+ header("output", "输出"),
1074
+ header("cacheRead", "缓存读"),
1075
+ header("hit", "命中率")
1076
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tbody", { children: [ordered.map((period) => {
1077
+ const modelsInPeriod = modelRows.filter((row) => row.period === period.period).filter((row) => row.totalTokens > 0).sort((first, second) => second.totalTokens - first.totalTokens);
1078
+ const expandable = modelsInPeriod.length > 1;
1079
+ const isOpen = expandable && expandedPeriods[period.period] === true;
1080
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
1081
+ className: expandable ? shared_module_css_default.clickable : "",
1082
+ onClick: expandable ? () => onTogglePeriod(period.period) : void 0,
1036
1083
  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
- })
1084
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [expandable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1085
+ className: detail_module_css_default.toggleArrow,
1086
+ children: isOpen ? "▼" : "▶"
1087
+ }), period.period] }),
1088
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(period.totalTokens) }),
1089
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtCalls(period.calls) }),
1090
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: tokenCellOf(period.inputTokens) }),
1091
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: tokenCellOf(period.outputTokens) }),
1092
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(period.cacheReadTokens) }),
1093
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: hitRateOf$1(period.cacheReadTokens, period.inputTokens, period.outputTokens) })
1067
1094
  ]
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
- })] });
1095
+ }), isOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
1096
+ className: detail_module_css_default.subRowCell,
1097
+ colSpan: 7,
1098
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1099
+ className: detail_module_css_default.subHead,
1100
+ children: [
1101
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {}),
1102
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1103
+ className: detail_module_css_default.subNumber,
1104
+ children: "调用"
1105
+ }),
1106
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1107
+ className: detail_module_css_default.subNumber,
1108
+ children: "输入"
1109
+ }),
1110
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1111
+ className: detail_module_css_default.subNumber,
1112
+ children: "输出"
1113
+ }),
1114
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1115
+ className: detail_module_css_default.subNumber,
1116
+ children: "缓存读"
1117
+ }),
1118
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1119
+ className: detail_module_css_default.subNumber,
1120
+ children: "命中率"
1121
+ })
1122
+ ]
1123
+ }), modelsInPeriod.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1124
+ className: detail_module_css_default.subRow,
1125
+ children: [
1126
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1127
+ className: detail_module_css_default.subName,
1128
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1129
+ className: detail_module_css_default.subModel,
1130
+ children: row.model
1131
+ }), channelFilter === "ALL" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1132
+ className: detail_module_css_default.subChannel,
1133
+ children: displayNameOf(channels, row.provider)
1134
+ })]
1135
+ }),
1136
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1137
+ className: detail_module_css_default.subNumber,
1138
+ children: fmtCalls(row.calls)
1139
+ }),
1140
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1141
+ className: detail_module_css_default.subNumber,
1142
+ children: tokenCellOf(row.inputTokens)
1143
+ }),
1144
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1145
+ className: detail_module_css_default.subNumber,
1146
+ children: tokenCellOf(row.outputTokens)
1147
+ }),
1148
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1149
+ className: detail_module_css_default.subNumber,
1150
+ children: fmtTokens(row.cacheReadTokens)
1151
+ }),
1152
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1153
+ className: detail_module_css_default.subNumber,
1154
+ children: hitRateOf$1(row.cacheReadTokens, row.inputTokens, row.outputTokens)
1155
+ })
1156
+ ]
1157
+ }, row.period + "|" + row.model + "|" + row.provider))]
1158
+ }) })] }, period.period);
1159
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
1160
+ className: shared_module_css_default.total,
1161
+ children: [
1162
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: "合计" }),
1163
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(totalTokens) }),
1164
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtCalls(totals?.calls ?? 0) }),
1165
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(totals?.inputTokens ?? 0) }),
1166
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(totals?.outputTokens ?? 0) }),
1167
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(totals?.cacheReadTokens ?? 0) }),
1168
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: hitRateOf$1(totals?.cacheReadTokens ?? 0, totals?.inputTokens ?? 0, totals?.outputTokens ?? 0) })
1169
+ ]
1170
+ })] })]
1171
+ }),
1172
+ hiddenEmpty > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1173
+ className: shared_module_css_default.statExtra,
1174
+ children: hiddenEmptyRowsText(hiddenEmpty)
1175
+ })
1176
+ ] });
1177
+ }
1178
+ //#endregion
1179
+ //#region \0dsh-css:D:\EasyJob\.plugin\dsh-key-usage\src\client\styles\export.module.css.mjs
1180
+ const css$3 = ".m5lYYW_exportGroup{align-items:center;gap:6px;margin-left:auto;display:inline-flex}.m5lYYW_exportBtn{border:1px solid var(--dsw-alias-border-l2,#2c2c34);color:var(--dsw-alias-label-secondary,#9a9aa6);font:inherit;cursor:pointer;white-space:nowrap;background:0 0;border-radius:999px;padding:4px 11px;font-size:11.5px}.m5lYYW_exportBtn:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#80808c1a);color:var(--dsw-alias-label-primary,#e8e8ec)}.m5lYYW_exportBtn:disabled{opacity:.5;cursor:default}";
1181
+ const tagId$3 = "dsh-key-usage/export.module.css";
1182
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$3) + "]") === null) {
1183
+ const tag = document.createElement("style");
1184
+ tag.dataset.plugin = "dsh-key-usage";
1185
+ tag.dataset.pluginCss = tagId$3;
1186
+ tag.textContent = css$3;
1187
+ document.head.appendChild(tag);
1083
1188
  }
1189
+ const cssRegistry$3 = globalThis.__DSH_PLUGIN_CSS__ ??= [];
1190
+ if (!cssRegistry$3.some((e) => e.tagId === tagId$3)) cssRegistry$3.push({
1191
+ tagId: tagId$3,
1192
+ css: css$3
1193
+ });
1194
+ var export_module_css_default = {
1195
+ "exportGroup": "m5lYYW_exportGroup",
1196
+ "exportBtn": "m5lYYW_exportBtn"
1197
+ };
1084
1198
  //#endregion
1085
1199
  //#region src/client/tabs/DetailTab.tsx
1086
1200
  const RANGE_TABS = [
@@ -1092,6 +1206,49 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1092
1206
  ];
1093
1207
  /** 图表最多铺开的列数:超过这个跨度的范围按天全画会挤成一团。 */
1094
1208
  const MAX_AXIS_DAYS = 366;
1209
+ /** 导出列:命中率与成本给数值(不带单位),方便在表格里继续算。 */
1210
+ const EXPORT_COLUMNS$3 = [
1211
+ {
1212
+ key: "date",
1213
+ label: "日期",
1214
+ value: (row) => row.date
1215
+ },
1216
+ {
1217
+ key: "calls",
1218
+ label: "调用",
1219
+ value: (row) => row.calls
1220
+ },
1221
+ {
1222
+ key: "inputTokens",
1223
+ label: "输入",
1224
+ value: (row) => row.inputTokens
1225
+ },
1226
+ {
1227
+ key: "outputTokens",
1228
+ label: "输出",
1229
+ value: (row) => row.outputTokens
1230
+ },
1231
+ {
1232
+ key: "cacheReadTokens",
1233
+ label: "缓存读",
1234
+ value: (row) => row.cacheReadTokens
1235
+ },
1236
+ {
1237
+ key: "cacheHitRate",
1238
+ label: "命中率(%)",
1239
+ value: (row) => hitRateValueOf(row)
1240
+ },
1241
+ {
1242
+ key: "cost",
1243
+ label: "成本(元)",
1244
+ value: (row) => row.cost
1245
+ }
1246
+ ];
1247
+ /** 命中率数值(导出用;界面上的带 % 文本由 `hitRateOf` 生成)。 */
1248
+ function hitRateValueOf(row) {
1249
+ const total = row.inputTokens + row.outputTokens + row.cacheReadTokens;
1250
+ return total > 0 ? Math.round(row.cacheReadTokens / total * 1e3) / 10 : null;
1251
+ }
1095
1252
  function hitRateOf(cacheReadTokens, inputTokens, outputTokens) {
1096
1253
  const total = inputTokens + outputTokens + cacheReadTokens;
1097
1254
  return total > 0 ? (cacheReadTokens / total * 100).toFixed(1) + "%" : "—";
@@ -1130,6 +1287,11 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1130
1287
  const costCell = costCellText(singleChannel, safeTotals.cost);
1131
1288
  const singleDay = bounds.from === bounds.to;
1132
1289
  const rangeDays = Math.min(daysBetween(bounds.from, bounds.to), MAX_AXIS_DAYS);
1290
+ /** 导出当前范围与通道筛选后的每日明细(按日期升序,不跟随表格排序)。 */
1291
+ const exportDaily = (format) => {
1292
+ if (format === "csv") downloadFile(exportFileName("daily", "csv"), toCsv(usage.days, EXPORT_COLUMNS$3), CSV_MIME);
1293
+ else downloadFile(exportFileName("daily", "json"), toJson(usage.days), JSON_MIME);
1294
+ };
1133
1295
  const activeProviders = /* @__PURE__ */ new Set();
1134
1296
  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
1297
  const hourlyRows = [];
@@ -1228,9 +1390,9 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1228
1390
  const chartRows = useHourAxis ? hourlyRows : dailyRows;
1229
1391
  const chartTitle = useHourAxis ? "按小时 Tokens(" + bounds.from + ")" : "按天 Tokens(" + rangeDays + " 天)";
1230
1392
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
1231
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1393
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1232
1394
  className: detail_module_css_default.rangeBar,
1233
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1395
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1234
1396
  className: detail_module_css_default.rangeLeft,
1235
1397
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1236
1398
  className: detail_module_css_default.rangeTabs,
@@ -1257,7 +1419,20 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1257
1419
  onChange: (event) => props.onCustomTo(event.target.value)
1258
1420
  })
1259
1421
  ] })]
1260
- })
1422
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1423
+ className: export_module_css_default.exportGroup,
1424
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1425
+ className: export_module_css_default.exportBtn,
1426
+ disabled: usage.days.length === 0,
1427
+ onClick: () => exportDaily("csv"),
1428
+ children: "导出 CSV"
1429
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1430
+ className: export_module_css_default.exportBtn,
1431
+ disabled: usage.days.length === 0,
1432
+ onClick: () => exportDaily("json"),
1433
+ children: "导出 JSON"
1434
+ })]
1435
+ })]
1261
1436
  }),
1262
1437
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1263
1438
  className: shared_module_css_default.cardGrid3,
@@ -1374,7 +1549,7 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1374
1549
  }
1375
1550
  //#endregion
1376
1551
  //#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}";
1552
+ 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}.QpFKma_rowArchived td{opacity:.5}.QpFKma_archivedTag{color:var(--dsw-alias-label-tertiary,#67676f);border:1px solid var(--dsw-alias-border-l2,#2c2c34);border-radius:999px;margin-left:6px;padding:0 6px;font-size:10px;font-weight:500}";
1378
1553
  const tagId$2 = "dsh-key-usage/lists.module.css";
1379
1554
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$2) + "]") === null) {
1380
1555
  const tag = document.createElement("style");
@@ -1389,19 +1564,329 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1389
1564
  css: css$2
1390
1565
  });
1391
1566
  var lists_module_css_default = {
1392
- "listTitle": "QpFKma_listTitle",
1393
- "listBar": "QpFKma_listBar",
1394
1567
  "filterSelect": "QpFKma_filterSelect",
1395
1568
  "dayCount": "QpFKma_dayCount",
1396
1569
  "searchInput": "QpFKma_searchInput",
1397
- "listSub": "QpFKma_listSub",
1570
+ "archivedTag": "QpFKma_archivedTag",
1398
1571
  "listMain": "QpFKma_listMain",
1572
+ "listTitle": "QpFKma_listTitle",
1573
+ "listBar": "QpFKma_listBar",
1399
1574
  "sessionDay": "QpFKma_sessionDay",
1400
- "filterBar": "QpFKma_filterBar"
1575
+ "listSub": "QpFKma_listSub",
1576
+ "filterBar": "QpFKma_filterBar",
1577
+ "rowArchived": "QpFKma_rowArchived"
1401
1578
  };
1402
1579
  //#endregion
1580
+ //#region src/client/tabs/ModelsTab.tsx
1581
+ /** 按模型:`provider + model` 聚合的用量与估算成本(数据来自宿主会话事件折叠)。 */
1582
+ const MODELS_PATH = "/dsh-key-usage/models";
1583
+ /** 导出列:取原始数值(金额、命中率不带单位),方便直接进表格做计算。 */
1584
+ const EXPORT_COLUMNS$2 = [
1585
+ {
1586
+ key: "provider",
1587
+ label: "通道 id",
1588
+ value: (row) => row.provider
1589
+ },
1590
+ {
1591
+ key: "providerLabel",
1592
+ label: "通道",
1593
+ value: (row) => row.providerLabel
1594
+ },
1595
+ {
1596
+ key: "model",
1597
+ label: "模型",
1598
+ value: (row) => row.model
1599
+ },
1600
+ {
1601
+ key: "calls",
1602
+ label: "调用",
1603
+ value: (row) => row.calls
1604
+ },
1605
+ {
1606
+ key: "tokens",
1607
+ label: "Tokens",
1608
+ value: (row) => row.tokens
1609
+ },
1610
+ {
1611
+ key: "inputTokens",
1612
+ label: "输入",
1613
+ value: (row) => row.buckets.inputTokens
1614
+ },
1615
+ {
1616
+ key: "outputTokens",
1617
+ label: "输出",
1618
+ value: (row) => row.buckets.outputTokens
1619
+ },
1620
+ {
1621
+ key: "cacheReadTokens",
1622
+ label: "缓存读",
1623
+ value: (row) => row.buckets.cacheReadTokens
1624
+ },
1625
+ {
1626
+ key: "cacheHitRate",
1627
+ label: "命中率(%)",
1628
+ value: (row) => row.cacheHitRate
1629
+ },
1630
+ {
1631
+ key: "cost",
1632
+ label: "成本(估算,元)",
1633
+ value: (row) => row.cost
1634
+ },
1635
+ {
1636
+ key: "costMode",
1637
+ label: "计费模式",
1638
+ value: (row) => row.costMode
1639
+ },
1640
+ {
1641
+ key: "sessionCount",
1642
+ label: "会话数",
1643
+ value: (row) => row.sessionCount
1644
+ },
1645
+ {
1646
+ key: "lastTime",
1647
+ label: "最近活跃",
1648
+ value: (row) => fmtDateTime(row.lastTime)
1649
+ }
1650
+ ];
1651
+ function ModelsTab(props) {
1652
+ const [query, setQuery] = (0, react.useState)("");
1653
+ const [channelFilter, setChannelFilter] = (0, react.useState)("ALL");
1654
+ const [sortKey, setSortKey] = (0, react.useState)("tokensDesc");
1655
+ const [nowMs] = (0, react.useState)(() => Date.now());
1656
+ const rows = props.data?.rows ?? [];
1657
+ const visible = (0, react.useMemo)(() => {
1658
+ const keyword = query.trim().toLowerCase();
1659
+ const filtered = dropEmptyRows(rows, (row) => row.tokens).rows.filter((row) => {
1660
+ const haystack = (row.model + " " + row.provider + " " + row.providerLabel).toLowerCase();
1661
+ const matchesQuery = keyword === "" || haystack.includes(keyword);
1662
+ const matchesChannel = channelFilter === "ALL" || row.provider === channelFilter;
1663
+ return matchesQuery && matchesChannel;
1664
+ });
1665
+ const sorters = {
1666
+ tokensDesc: (first, second) => second.tokens - first.tokens,
1667
+ callsDesc: (first, second) => second.calls - first.calls,
1668
+ costDesc: (first, second) => (second.cost ?? 0) - (first.cost ?? 0),
1669
+ recent: (first, second) => (second.lastTime ?? 0) - (first.lastTime ?? 0)
1670
+ };
1671
+ return [...filtered].sort(sorters[sortKey]);
1672
+ }, [
1673
+ rows,
1674
+ query,
1675
+ channelFilter,
1676
+ sortKey
1677
+ ]);
1678
+ const totalTokens = rows.reduce((total, row) => total + row.tokens, 0);
1679
+ const hiddenEmpty = dropEmptyRows(rows, (row) => row.tokens).hidden;
1680
+ /** 导出当前筛选与排序后的行(JSON 取整行,CSV 按固定列映射)。 */
1681
+ const exportRows = (format) => {
1682
+ if (format === "csv") downloadFile(exportFileName("models", "csv"), toCsv(visible, EXPORT_COLUMNS$2), CSV_MIME);
1683
+ else downloadFile(exportFileName("models", "json"), toJson(visible), JSON_MIME);
1684
+ };
1685
+ if (props.data === null) {
1686
+ const reason = failureReasonOf(MODELS_PATH);
1687
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1688
+ className: shared_module_css_default.empty,
1689
+ children: reason === null ? "按模型数据读取中…" : "按模型数据不可用:" + reason
1690
+ });
1691
+ }
1692
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
1693
+ props.data.coverage.status !== "complete" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1694
+ className: shared_module_css_default.warning,
1695
+ children: coverageText$1(props.data.coverage.status, props.data.coverage.skippedSessions)
1696
+ }),
1697
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1698
+ className: lists_module_css_default.filterBar,
1699
+ children: [
1700
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
1701
+ className: lists_module_css_default.searchInput,
1702
+ placeholder: "搜索模型名、通道…",
1703
+ value: query,
1704
+ onChange: (event) => setQuery(event.target.value)
1705
+ }),
1706
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1707
+ className: lists_module_css_default.filterSelect,
1708
+ value: channelFilter,
1709
+ onChange: (event) => setChannelFilter(event.target.value),
1710
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1711
+ value: "ALL",
1712
+ children: "全部通道"
1713
+ }), props.channels.map((channel) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1714
+ value: channel.provider,
1715
+ children: channel.displayName
1716
+ }, channel.provider))]
1717
+ }),
1718
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
1719
+ className: lists_module_css_default.filterSelect,
1720
+ value: sortKey,
1721
+ onChange: (event) => setSortKey(event.target.value),
1722
+ children: [
1723
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1724
+ value: "tokensDesc",
1725
+ children: "Tokens 最多"
1726
+ }),
1727
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1728
+ value: "callsDesc",
1729
+ children: "调用最多"
1730
+ }),
1731
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1732
+ value: "costDesc",
1733
+ children: "成本最高"
1734
+ }),
1735
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
1736
+ value: "recent",
1737
+ children: "最近活跃"
1738
+ })
1739
+ ]
1740
+ }),
1741
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1742
+ className: export_module_css_default.exportGroup,
1743
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1744
+ className: export_module_css_default.exportBtn,
1745
+ disabled: visible.length === 0,
1746
+ onClick: () => exportRows("csv"),
1747
+ children: "导出 CSV"
1748
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1749
+ className: export_module_css_default.exportBtn,
1750
+ disabled: visible.length === 0,
1751
+ onClick: () => exportRows("json"),
1752
+ children: "导出 JSON"
1753
+ })]
1754
+ })
1755
+ ]
1756
+ }),
1757
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1758
+ className: shared_module_css_default.table,
1759
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1760
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "模型" }),
1761
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "调用" }),
1762
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "Tokens" }),
1763
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "占比" }),
1764
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "命中率" }),
1765
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "成本" }),
1766
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("th", { children: "最近活跃" })
1767
+ ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tbody", { children: [visible.map((row) => {
1768
+ const percent = totalTokens > 0 ? Math.round(row.tokens / totalTokens * 100) : 0;
1769
+ const channel = channelOf(props.channels, row.provider);
1770
+ const costCell = costCellText(channel, row.cost ?? 0);
1771
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
1772
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1773
+ className: lists_module_css_default.listTitle,
1774
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1775
+ className: lists_module_css_default.listMain,
1776
+ children: row.model
1777
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1778
+ className: lists_module_css_default.listSub,
1779
+ children: [
1780
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1781
+ className: shared_module_css_default.statusDot,
1782
+ style: {
1783
+ background: channelColorOf(props.channels, row.provider),
1784
+ marginRight: 4
1785
+ }
1786
+ }),
1787
+ row.providerLabel,
1788
+ " · ",
1789
+ row.sessionCount,
1790
+ " 个会话"
1791
+ ]
1792
+ })] })
1793
+ }) }),
1794
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtCalls(row.calls) }),
1795
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(row.tokens) }),
1796
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [
1797
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1798
+ className: lists_module_css_default.listBar,
1799
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("i", { style: {
1800
+ width: Math.max(2, percent) + "%",
1801
+ background: channelColorOf(props.channels, row.provider)
1802
+ } })
1803
+ }),
1804
+ " ",
1805
+ percent,
1806
+ "%"
1807
+ ] }),
1808
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.cacheHitRate === null ? "—" : row.cacheHitRate.toFixed(1) + "%" }),
1809
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.cost === null || channel?.costMode !== "perToken" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1810
+ className: shared_module_css_default.dim,
1811
+ children: costCell.text
1812
+ }) : costCell.text }),
1813
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtRelative(row.lastTime, nowMs) })
1814
+ ] }, row.provider + "|" + row.model);
1815
+ }), visible.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("tr", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
1816
+ colSpan: 7,
1817
+ className: shared_module_css_default.empty,
1818
+ children: "没有符合条件的模型"
1819
+ }) })] })]
1820
+ }),
1821
+ hiddenEmpty > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1822
+ className: shared_module_css_default.statExtra,
1823
+ children: hiddenEmptyRowsText(hiddenEmpty)
1824
+ }),
1825
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1826
+ className: shared_module_css_default.statExtra,
1827
+ children: [
1828
+ "模型用量按宿主会话事件折叠(每个结算样本计一次调用);成本按台账模型均价估算",
1829
+ props.data.priced ? "" : "(台账无成本数据,暂不可估)",
1830
+ ";命中率 = 缓存读 ÷(输入 + 输出 + 缓存读)"
1831
+ ]
1832
+ })
1833
+ ] });
1834
+ }
1835
+ function coverageText$1(status, skipped) {
1836
+ if (status === "unavailable") return "会话数据不可用(宿主未提供会话服务或全部读取失败)";
1837
+ return "会话数据不完整:" + String(skipped) + " 个会话读取失败,未计入统计";
1838
+ }
1839
+ //#endregion
1403
1840
  //#region src/client/tabs/ProjectsTab.tsx
1404
1841
  /** 项目用量:按会话工作目录聚合(数据来自宿主会话事件折叠)。 */
1842
+ /** 导出列:成本与命中率都给原始数值,方便在表格里继续算。 */
1843
+ const EXPORT_COLUMNS$1 = [
1844
+ {
1845
+ key: "workspace",
1846
+ label: "项目路径",
1847
+ value: (row) => row.workspace
1848
+ },
1849
+ {
1850
+ key: "dominantProvider",
1851
+ label: "主要通道",
1852
+ value: (row) => row.dominantProvider
1853
+ },
1854
+ {
1855
+ key: "calls",
1856
+ label: "调用",
1857
+ value: (row) => row.calls
1858
+ },
1859
+ {
1860
+ key: "tokens",
1861
+ label: "Tokens",
1862
+ value: (row) => row.tokens
1863
+ },
1864
+ {
1865
+ key: "cacheHitRate",
1866
+ label: "命中率(%)",
1867
+ value: (row) => row.cacheHitRate
1868
+ },
1869
+ {
1870
+ key: "cost",
1871
+ label: "成本(估算,元)",
1872
+ value: (row) => row.cost
1873
+ },
1874
+ {
1875
+ key: "sessionCount",
1876
+ label: "会话数",
1877
+ value: (row) => row.sessionCount
1878
+ },
1879
+ {
1880
+ key: "archivedSessionCount",
1881
+ label: "已归档会话数",
1882
+ value: (row) => row.archivedSessionCount
1883
+ },
1884
+ {
1885
+ key: "lastTime",
1886
+ label: "最近活跃",
1887
+ value: (row) => fmtDateTime(row.lastTime)
1888
+ }
1889
+ ];
1405
1890
  function ProjectsTab(props) {
1406
1891
  const [query, setQuery] = (0, react.useState)("");
1407
1892
  const [channelFilter, setChannelFilter] = (0, react.useState)("ALL");
@@ -1410,7 +1895,7 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1410
1895
  const rows = props.data?.rows ?? [];
1411
1896
  const visible = (0, react.useMemo)(() => {
1412
1897
  const keyword = query.trim().toLowerCase();
1413
- const filtered = rows.filter((row) => {
1898
+ const filtered = dropEmptyRows(rows, (row) => row.tokens).rows.filter((row) => {
1414
1899
  const matchesQuery = keyword === "" || row.workspace.toLowerCase().includes(keyword);
1415
1900
  const matchesChannel = channelFilter === "ALL" || row.providers[channelFilter] !== void 0;
1416
1901
  return matchesQuery && matchesChannel;
@@ -1429,6 +1914,12 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1429
1914
  sortKey
1430
1915
  ]);
1431
1916
  const totalTokens = rows.reduce((total, row) => total + row.tokens, 0);
1917
+ const hiddenEmpty = dropEmptyRows(rows, (row) => row.tokens).hidden;
1918
+ /** 导出当前筛选与排序后的项目行(JSON 取整行,CSV 按固定列映射)。 */
1919
+ const exportRows = (format) => {
1920
+ if (format === "csv") downloadFile(exportFileName("projects", "csv"), toCsv(visible, EXPORT_COLUMNS$1), CSV_MIME);
1921
+ else downloadFile(exportFileName("projects", "json"), toJson(visible), JSON_MIME);
1922
+ };
1432
1923
  if (props.data === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1433
1924
  className: shared_module_css_default.empty,
1434
1925
  children: "会话数据读取中或不可用…"
@@ -1481,6 +1972,20 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1481
1972
  children: "调用最多"
1482
1973
  })
1483
1974
  ]
1975
+ }),
1976
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
1977
+ className: export_module_css_default.exportGroup,
1978
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1979
+ className: export_module_css_default.exportBtn,
1980
+ disabled: visible.length === 0,
1981
+ onClick: () => exportRows("csv"),
1982
+ children: "导出 CSV"
1983
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
1984
+ className: export_module_css_default.exportBtn,
1985
+ disabled: visible.length === 0,
1986
+ onClick: () => exportRows("json"),
1987
+ children: "导出 JSON"
1988
+ })]
1484
1989
  })
1485
1990
  ]
1486
1991
  }),
@@ -1510,7 +2015,8 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1510
2015
  row.workspace,
1511
2016
  " · ",
1512
2017
  row.sessionCount,
1513
- " 个会话"
2018
+ " 个会话",
2019
+ row.archivedSessionCount > 0 ? "(含 " + row.archivedSessionCount + " 个已归档,仍计入)" : ""
1514
2020
  ]
1515
2021
  })] })
1516
2022
  }) }),
@@ -1540,6 +2046,10 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1540
2046
  children: "没有符合条件的项目"
1541
2047
  }) })] })]
1542
2048
  }),
2049
+ hiddenEmpty > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2050
+ className: shared_module_css_default.statExtra,
2051
+ children: hiddenEmptyRowsText(hiddenEmpty)
2052
+ }),
1543
2053
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1544
2054
  className: shared_module_css_default.statExtra,
1545
2055
  children: [
@@ -1575,26 +2085,26 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1575
2085
  css: css$1
1576
2086
  });
1577
2087
  var quota_module_css_default = {
1578
- "windowItem": "F7iUHa_windowItem",
1579
2088
  "windowBar": "F7iUHa_windowBar",
1580
- "snapshotHint": "F7iUHa_snapshotHint",
2089
+ "windowRow": "F7iUHa_windowRow",
1581
2090
  "liveChip": "F7iUHa_liveChip",
1582
- "quotaEnv": "F7iUHa_quotaEnv",
1583
- "balanceItem": "F7iUHa_balanceItem",
1584
- "balanceStrip": "F7iUHa_balanceStrip",
2091
+ "bsLabel": "F7iUHa_bsLabel",
2092
+ "quotaHead": "F7iUHa_quotaHead",
1585
2093
  "quotaTitle": "F7iUHa_quotaTitle",
1586
2094
  "snapshotStale": "F7iUHa_snapshotStale",
1587
- "quotaList": "F7iUHa_quotaList",
2095
+ "windowItem": "F7iUHa_windowItem",
1588
2096
  "windowValue": "F7iUHa_windowValue",
1589
- "windowName": "F7iUHa_windowName",
1590
- "bsValue": "F7iUHa_bsValue",
1591
- "quotaCard": "F7iUHa_quotaCard",
1592
- "quotaHead": "F7iUHa_quotaHead",
2097
+ "windowReset": "F7iUHa_windowReset",
2098
+ "snapshotHint": "F7iUHa_snapshotHint",
1593
2099
  "quotaProvider": "F7iUHa_quotaProvider",
1594
2100
  "windowHead": "F7iUHa_windowHead",
1595
- "windowRow": "F7iUHa_windowRow",
1596
- "bsLabel": "F7iUHa_bsLabel",
1597
- "windowReset": "F7iUHa_windowReset"
2101
+ "quotaList": "F7iUHa_quotaList",
2102
+ "balanceItem": "F7iUHa_balanceItem",
2103
+ "balanceStrip": "F7iUHa_balanceStrip",
2104
+ "windowName": "F7iUHa_windowName",
2105
+ "bsValue": "F7iUHa_bsValue",
2106
+ "quotaEnv": "F7iUHa_quotaEnv",
2107
+ "quotaCard": "F7iUHa_quotaCard"
1598
2108
  };
1599
2109
  //#endregion
1600
2110
  //#region src/client/tabs/QuotaTab.tsx
@@ -1728,13 +2238,13 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1728
2238
  "(来自官方接口,最多缓存 1 分钟)"
1729
2239
  ]
1730
2240
  }),
1731
- snapshot !== null && !isLive && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2241
+ snapshot !== null && !isLive && snapshot.supported && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1732
2242
  className: staleDaysOf(snapshot.updatedAt) >= 1 ? quota_module_css_default.snapshotStale : quota_module_css_default.snapshotHint,
1733
2243
  children: [
1734
2244
  "实时查询未成功(",
1735
2245
  errorTextOf(snapshot.error),
1736
- "),显示本机快照",
1737
- snapshot.updatedAt === null ? "" : " · 快照时间 " + fmtDateTime(snapshot.updatedAt) + (staleDaysOf(snapshot.updatedAt) >= 1 ? ",已 " + staleDaysOf(snapshot.updatedAt) + " 天未更新" : "")
2246
+ ")",
2247
+ snapshot.updatedAt === null ? ",且暂无可用快照 —— 点右上角「刷新」重试" : ",显示快照 · 快照时间 " + fmtDateTime(snapshot.updatedAt) + (staleDaysOf(snapshot.updatedAt) >= 1 ? ",已 " + staleDaysOf(snapshot.updatedAt) + " 天未更新" : "")
1738
2248
  ]
1739
2249
  })
1740
2250
  ]
@@ -1801,22 +2311,84 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1801
2311
  //#endregion
1802
2312
  //#region src/client/tabs/SessionsTab.tsx
1803
2313
  /** 会话用量:按会话列出 tokens / 命中率 / 时长,按最近活跃时按日分组。 */
2314
+ /** 导出列:标题可能带逗号或换行,由 toCsv 统一转义。 */
2315
+ const EXPORT_COLUMNS = [
2316
+ {
2317
+ key: "id",
2318
+ label: "会话 id",
2319
+ value: (row) => row.id
2320
+ },
2321
+ {
2322
+ key: "title",
2323
+ label: "标题",
2324
+ value: (row) => row.title
2325
+ },
2326
+ {
2327
+ key: "workspace",
2328
+ label: "工作区",
2329
+ value: (row) => row.workspace
2330
+ },
2331
+ {
2332
+ key: "provider",
2333
+ label: "通道",
2334
+ value: (row) => row.provider
2335
+ },
2336
+ {
2337
+ key: "firstTime",
2338
+ label: "首次时间",
2339
+ value: (row) => fmtDateTime(row.firstTime)
2340
+ },
2341
+ {
2342
+ key: "lastTime",
2343
+ label: "最近时间",
2344
+ value: (row) => fmtDateTime(row.lastTime)
2345
+ },
2346
+ {
2347
+ key: "calls",
2348
+ label: "调用",
2349
+ value: (row) => row.calls
2350
+ },
2351
+ {
2352
+ key: "tokens",
2353
+ label: "Tokens",
2354
+ value: (row) => row.tokens
2355
+ },
2356
+ {
2357
+ key: "cacheHitRate",
2358
+ label: "命中率(%)",
2359
+ value: (row) => row.cacheHitRate
2360
+ },
2361
+ {
2362
+ key: "cost",
2363
+ label: "成本(估算,元)",
2364
+ value: (row) => row.cost
2365
+ },
2366
+ {
2367
+ key: "archived",
2368
+ label: "已归档",
2369
+ value: (row) => row.archived ? "是" : "否"
2370
+ }
2371
+ ];
1804
2372
  function SessionsTab(props) {
1805
2373
  const [query, setQuery] = (0, react.useState)("");
1806
2374
  const [channelFilter, setChannelFilter] = (0, react.useState)("ALL");
1807
2375
  const [daysFilter, setDaysFilter] = (0, react.useState)("30");
1808
2376
  const [sortKey, setSortKey] = (0, react.useState)("recent");
2377
+ const [archiveFilter, setArchiveFilter] = (0, react.useState)("hide");
1809
2378
  const [nowMs] = (0, react.useState)(() => Date.now());
1810
2379
  const rows = props.data?.rows ?? [];
2380
+ const archivedCount = rows.filter((row) => row.archived).length;
2381
+ const hiddenEmpty = dropEmptyRows(rows, (row) => row.tokens).hidden;
1811
2382
  const visible = (0, react.useMemo)(() => {
1812
2383
  const keyword = query.trim().toLowerCase();
1813
2384
  const maxAgeDays = Number(daysFilter);
1814
- const filtered = rows.filter((row) => {
2385
+ const filtered = dropEmptyRows(rows, (row) => row.tokens).rows.filter((row) => {
1815
2386
  const title = (row.title ?? "") + " " + row.id + " " + (row.workspace ?? "");
1816
2387
  const matchesQuery = keyword === "" || title.toLowerCase().includes(keyword);
1817
2388
  const matchesChannel = channelFilter === "ALL" || row.provider === channelFilter;
1818
2389
  const matchesAge = maxAgeDays === 0 || row.lastTime === null || nowMs - row.lastTime <= maxAgeDays * 864e5;
1819
- return matchesQuery && matchesChannel && matchesAge;
2390
+ const matchesArchive = archiveFilter === "all" || (archiveFilter === "only" ? row.archived : !row.archived);
2391
+ return matchesQuery && matchesChannel && matchesAge && matchesArchive;
1820
2392
  });
1821
2393
  const sorters = {
1822
2394
  recent: (first, second) => (second.lastTime ?? 0) - (first.lastTime ?? 0),
@@ -1831,8 +2403,14 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1831
2403
  channelFilter,
1832
2404
  daysFilter,
1833
2405
  sortKey,
1834
- nowMs
2406
+ nowMs,
2407
+ archiveFilter
1835
2408
  ]);
2409
+ /** 只导当前筛选后的会话行(归档筛选、时间窗口、搜索都算数)。 */
2410
+ const exportRows = (format) => {
2411
+ if (format === "csv") downloadFile(exportFileName("sessions", "csv"), toCsv(visible, EXPORT_COLUMNS), CSV_MIME);
2412
+ else downloadFile(exportFileName("sessions", "json"), toJson(visible), JSON_MIME);
2413
+ };
1836
2414
  if (props.data === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1837
2415
  className: shared_module_css_default.empty,
1838
2416
  children: "会话数据读取中或不可用…"
@@ -1914,9 +2492,51 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1914
2492
  children: "时长最长"
1915
2493
  })
1916
2494
  ]
2495
+ }),
2496
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
2497
+ className: lists_module_css_default.filterSelect,
2498
+ value: archiveFilter,
2499
+ onChange: (event) => setArchiveFilter(event.target.value),
2500
+ title: "已归档会话只做标记,仍然计入统计",
2501
+ children: [
2502
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2503
+ value: "hide",
2504
+ children: "隐藏已归档"
2505
+ }),
2506
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2507
+ value: "all",
2508
+ children: "全部对话(显示已归档)"
2509
+ }),
2510
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
2511
+ value: "only",
2512
+ children: "仅显示已归档"
2513
+ })
2514
+ ]
2515
+ }),
2516
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2517
+ className: export_module_css_default.exportGroup,
2518
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2519
+ className: export_module_css_default.exportBtn,
2520
+ disabled: visible.length === 0,
2521
+ onClick: () => exportRows("csv"),
2522
+ children: "导出 CSV"
2523
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
2524
+ className: export_module_css_default.exportBtn,
2525
+ disabled: visible.length === 0,
2526
+ onClick: () => exportRows("json"),
2527
+ children: "导出 JSON"
2528
+ })]
1917
2529
  })
1918
2530
  ]
1919
2531
  }),
2532
+ archiveFilter === "hide" && archivedCount > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2533
+ className: shared_module_css_default.statExtra,
2534
+ children: [
2535
+ "另有 ",
2536
+ archivedCount,
2537
+ " 个已归档会话未显示(归档只是标记,用量仍计入上方统计)"
2538
+ ]
2539
+ }),
1920
2540
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("table", {
1921
2541
  className: shared_module_css_default.table,
1922
2542
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("thead", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", { children: [
@@ -1952,6 +2572,10 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1952
2572
  }) })
1953
2573
  ] })]
1954
2574
  }),
2575
+ hiddenEmpty > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2576
+ className: shared_module_css_default.statExtra,
2577
+ children: hiddenEmptyRowsText(hiddenEmpty)
2578
+ }),
1955
2579
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1956
2580
  className: shared_module_css_default.statExtra,
1957
2581
  children: ["会话用量按宿主会话事件折叠(每个结算样本计一次调用);成本按台账模型均价估算", props.data.priced ? "" : "(台账无成本数据,暂不可估)"]
@@ -1961,26 +2585,32 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
1961
2585
  function sessionRowOf(row, channels) {
1962
2586
  const channel = channelOf(channels, row.provider);
1963
2587
  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);
2588
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("tr", {
2589
+ className: row.archived ? lists_module_css_default.rowArchived : void 0,
2590
+ children: [
2591
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2592
+ className: lists_module_css_default.listMain,
2593
+ children: [row.title ?? "会话 " + shortIdOf(row.id), row.archived && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2594
+ className: lists_module_css_default.archivedTag,
2595
+ children: "已归档"
2596
+ })]
2597
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2598
+ className: lists_module_css_default.listSub,
2599
+ children: row.workspace ?? "未归属工作区"
2600
+ })] }),
2601
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.providerLabel ?? displayNameOf(channels, row.provider ?? "") }),
2602
+ /* @__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", {
2603
+ className: lists_module_css_default.listSub,
2604
+ children: ["时长 ", fmtDuration(durationOf(row))]
2605
+ })] }),
2606
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: fmtTokens(row.tokens) }),
2607
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.cacheHitRate === null ? "—" : row.cacheHitRate.toFixed(1) + "%" }),
2608
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", { children: row.cost === null || channel === null || channel.costMode !== "perToken" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2609
+ className: shared_module_css_default.dim,
2610
+ children: costCell.text
2611
+ }) : costCell.text })
2612
+ ]
2613
+ }, row.id);
1984
2614
  }
1985
2615
  function durationOf(row) {
1986
2616
  if (row.firstTime === null || row.lastTime === null) return 0;
@@ -2008,6 +2638,7 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
2008
2638
  ["overview", "总览"],
2009
2639
  ["detail", "使用明细"],
2010
2640
  ["projects", "项目用量"],
2641
+ ["models", "按模型"],
2011
2642
  ["sessions", "会话用量"],
2012
2643
  ["quota", "余额与配额"]
2013
2644
  ];
@@ -2029,6 +2660,7 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
2029
2660
  const [usage, setUsage] = (0, react.useState)(null);
2030
2661
  const [sessions, setSessions] = (0, react.useState)(null);
2031
2662
  const [projects, setProjects] = (0, react.useState)(null);
2663
+ const [models, setModels] = (0, react.useState)(null);
2032
2664
  const [available, setAvailable] = (0, react.useState)(null);
2033
2665
  const [loading, setLoading] = (0, react.useState)(false);
2034
2666
  const tooltip = useTooltip();
@@ -2045,9 +2677,14 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
2045
2677
  setSummary(summaryResult);
2046
2678
  setHeatmap(heatmapResult ?? []);
2047
2679
  if (includeSessionData) {
2048
- const [sessionResult, projectResult] = await Promise.all([loadSessions(), loadProjects()]);
2680
+ const [sessionResult, projectResult, modelResult] = await Promise.all([
2681
+ loadSessions(),
2682
+ loadProjects(),
2683
+ loadModels()
2684
+ ]);
2049
2685
  setSessions(sessionResult);
2050
2686
  setProjects(projectResult);
2687
+ setModels(modelResult);
2051
2688
  }
2052
2689
  setLoading(false);
2053
2690
  }, []);
@@ -2057,10 +2694,12 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
2057
2694
  (0, react.useEffect)(() => {
2058
2695
  if (activeTab === "sessions" && sessions === null) loadSessions().then(setSessions);
2059
2696
  if (activeTab === "projects" && projects === null) loadProjects().then(setProjects);
2697
+ if (activeTab === "models" && models === null) loadModels().then(setModels);
2060
2698
  }, [
2061
2699
  activeTab,
2062
2700
  sessions,
2063
- projects
2701
+ projects,
2702
+ models
2064
2703
  ]);
2065
2704
  const bounds = (0, react.useMemo)(() => {
2066
2705
  const today = isoDate(/* @__PURE__ */ new Date());
@@ -2109,7 +2748,7 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
2109
2748
  channelFilter
2110
2749
  ]);
2111
2750
  const refresh = (0, react.useCallback)(() => {
2112
- reload(activeTab === "sessions" || activeTab === "projects" || sessions !== null || projects !== null, true);
2751
+ reload(activeTab === "sessions" || activeTab === "projects" || activeTab === "models" || sessions !== null || projects !== null || models !== null, true);
2113
2752
  loadUsage(bounds.from, bounds.to, channelFilter).then((result) => {
2114
2753
  if (result !== null) setUsage(result);
2115
2754
  });
@@ -2118,6 +2757,7 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
2118
2757
  activeTab,
2119
2758
  sessions,
2120
2759
  projects,
2760
+ models,
2121
2761
  bounds.from,
2122
2762
  bounds.to,
2123
2763
  channelFilter
@@ -2181,6 +2821,10 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
2181
2821
  data: projects,
2182
2822
  channels
2183
2823
  }),
2824
+ activeTab === "models" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ModelsTab, {
2825
+ data: models,
2826
+ channels
2827
+ }),
2184
2828
  activeTab === "sessions" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionsTab, {
2185
2829
  data: sessions,
2186
2830
  channels
@@ -2200,7 +2844,8 @@ window.__ModuleLoader__.load({ id: "dsh-key-usage", factory: (require) => {
2200
2844
  "。两套来源按天取舍、不相加。"
2201
2845
  ] }),
2202
2846
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "热力图按可见区间内活跃日的三分位分档(少 / 中 / 高)。" }),
2203
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "项目用量、会话用量与单日「按小时」均来自会话事件,每个结算样本计一次调用(不计成本)。" }),
2847
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "项目用量、会话用量、按模型与单日「按小时」均来自会话事件,每个结算样本计一次调用(不计成本)。" }),
2848
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "已归档会话只是标记:用量照常计入,会话列表默认隐藏、可切换显示(显示时淡化并标注)。" }),
2204
2849
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "命中率 = 缓存读 ÷(输入 + 输出 + 缓存读)。" }),
2205
2850
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: "余额与订阅窗口实时查询官方接口(失败回落本机快照),原样展示,不做换算与建议。" })
2206
2851
  ]