balance-dsh 1.2.2 → 1.2.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/lib/client.js CHANGED
@@ -40,8 +40,23 @@ window.__ModuleLoader__.load({
40
40
  .balance-fab:hover{background:var(--dsw-alias-interactive-bg-hover)}
41
41
  .balance-fab-dark{color:#fff;background:var(--dsw-alias-state-error-primary,#e5484d);border-color:var(--dsw-alias-state-error-primary,#e5484d)}
42
42
  .balance-fab-dark:hover{background:#cf3a3f}
43
- .balance-usage-line{box-sizing:border-box;display:flex;justify-content:center;align-items:center;gap:10px;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px}
44
- .balance-usage-line strong{color:var(--dsw-alias-label-primary);font-weight:600}
43
+ /* ── 消耗行 pill:规格逐条照抄 DSH 原生 StatsPills 的 .pill(_091hzG_pill)── */
44
+ .balance-usage-wrap{min-width:0;display:inline-flex}
45
+ .balance-usage-line{box-sizing:border-box;corner-shape:round;max-width:100%;color:var(--dsw-alias-label-tertiary);font:inherit;font-size:calc(var(--dsh-content-font-size-secondary,13px) - 1px);font-variant-numeric:tabular-nums;line-height:calc(20px + var(--dsh-content-font-delta-secondary,0px));white-space:nowrap;background:0 0;border:none;border-radius:999px;align-items:center;gap:6px;padding:1px 8px;display:inline-flex;cursor:pointer;transition:background .15s var(--ds-ease-in-out,.2s)}
46
+ .balance-usage-line svg{flex:none;width:14px;height:14px}
47
+ .balance-usage-line:hover,.balance-usage-line[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-secondary)}
48
+ .balance-usage-label{text-overflow:ellipsis;min-width:0;overflow:hidden}
49
+ /* ── 消耗明细浮层:规格照抄 DSH 原生「会话统计」面板(.HS2XBG_panel/.title/.titleRule/.details)── */
50
+ .balance-usage-panel{box-sizing:border-box;position:fixed;z-index:1100;border:0;border-radius:var(--dsw-radius-lg,12px);background:var(--dsw-specific-menu,var(--dsw-alias-bg-module-platform,#242526));width:max-content;min-width:min(300px,100vw - 24px);max-width:min(440px,100vw - 24px);backdrop-filter:var(--dsw-menu-backdrop-filter,none);--dsw-elevation-stroke-color:var(--dsw-alias-border-l1);box-shadow:var(--dsw-elevation-prominent,0 8px 24px rgba(0,0,0,.32));color:var(--dsw-alias-label-secondary);cursor:default;padding:16px;font-size:12px;line-height:18px;animation:balance-stats-panel-in .12s var(--ds-ease-in-out,ease-out) both}
51
+ .balance-usage-panel__title{display:flex;justify-content:space-between;gap:16px;margin-bottom:8px;color:var(--dsw-alias-label-primary);font-weight:500}
52
+ .balance-usage-panel__title--second{margin-top:16px}
53
+ .balance-usage-panel__rule{border-top:.5px solid var(--dsw-alias-border-l2);margin-bottom:10px}
54
+ .balance-usage-panel__value{font-variant-numeric:tabular-nums}
55
+ .balance-usage-panel__label{display:inline-flex;align-items:center;gap:6px;min-width:0}
56
+ .balance-usage-panel__label svg{flex:none;width:14px;height:14px}
57
+ .balance-usage-panel__details{display:grid;grid-template-columns:minmax(76px,auto) minmax(0,1fr);gap:6px 16px;margin:0;color:var(--dsw-alias-label-tertiary)}
58
+ .balance-usage-panel__details dt,.balance-usage-panel__details dd{min-width:0;margin:0}
59
+ .balance-usage-panel__details dd{color:var(--dsw-alias-label-secondary);font-variant-numeric:tabular-nums;text-align:right}
45
60
  /* ── 跨会话统计浮层(余额元素的子级,向右/向上展开) ── */
46
61
  .balance-stats-anchor{position:relative;display:flex;min-width:0;overflow:visible}
47
62
  .balance-stats-anchor--wide{width:100%}
@@ -1219,6 +1234,35 @@ function UsageCostLine({ useProjection, sessionId }) {
1219
1234
  const [splitUsage, setSplitUsage] = react.useState(null);
1220
1235
  const [subagentUsage, setSubagentUsage] = react.useState(null);
1221
1236
  const [splitReady, setSplitReady] = react.useState(false);
1237
+ // 消耗明细面板:**点击展开**(对齐 DSH 原生「会话统计」的交互——点开切换 + 选中态 + portal 浮层)
1238
+ const [detailsOpen, setDetailsOpen] = react.useState(false);
1239
+ const detailsAnchorRef = react.useRef(null);
1240
+ const detailsPanelRef = react.useRef(null);
1241
+ // 定位与"点外部关闭"复用 DSH primitives 的 hook,行为/视觉都与原生浮层一致。
1242
+ // 运行时探测(DSH 内部 API 无兼容承诺):hook 不存在时退化为无定位(面板仍渲染),不拖垮消耗行。
1243
+ // 注意这里的条件调用是安全的——primitives 是模块级常量,同一组件生命周期内判定结果恒定。
1244
+ const anchoredPosition =
1245
+ typeof primitives.useAnchoredPosition === "function"
1246
+ ? primitives.useAnchoredPosition({
1247
+ open: detailsOpen,
1248
+ anchorRef: detailsAnchorRef,
1249
+ panelRef: detailsPanelRef,
1250
+ side: "top",
1251
+ gap: 6,
1252
+ margin: 8,
1253
+ })
1254
+ : null;
1255
+ if (typeof primitives.useDismissOnOutsidePointer === "function") {
1256
+ primitives.useDismissOnOutsidePointer(detailsAnchorRef, detailsOpen, setDetailsOpen, detailsPanelRef);
1257
+ }
1258
+ react.useEffect(() => {
1259
+ if (!detailsOpen) return;
1260
+ const onKeyDown = (event) => {
1261
+ if (event.key === "Escape") setDetailsOpen(false);
1262
+ };
1263
+ document.addEventListener("keydown", onKeyDown);
1264
+ return () => document.removeEventListener("keydown", onKeyDown);
1265
+ }, [detailsOpen]);
1222
1266
 
1223
1267
  // 会话 token 总量(未缓存输入 + 缓存读/写 + 输出);为 0 时代表还没有消耗。
1224
1268
  const usageKey = usage
@@ -1286,46 +1330,120 @@ function UsageCostLine({ useProjection, sessionId }) {
1286
1330
  : 0;
1287
1331
  const subCount = hasSubagent ? (subagentUsage.sessions ?? 0) : 0;
1288
1332
 
1289
- const detailLines = [
1290
- "模型:" + (model || "未知(按 deepseek-v4-flash 价估算)"),
1291
- "新增:" + formatTokens(newTokens) + " tokens(未缓存输入 " + formatTokens(uncachedInput) + " + 输出 " + formatTokens(output) + ")",
1292
- "计费:" + formatTokens(billedTotal) + " tokens(含缓存重读 " + formatTokens(cacheRead) + "、缓存写入 " + formatTokens(cacheWrite) + ")",
1333
+ // 缓存命中率口径与 DSH 相同:缓存读取 / (缓存读取 + 未缓存输入)
1334
+ const cacheHitBase = cacheRead + uncachedInput;
1335
+ const cacheHitPercent = cacheHitBase > 0 ? Math.round((cacheRead / cacheHitBase) * 100) : 0;
1336
+
1337
+ // pill = 图标 + 文字标签 + 金额:DSH 那颗 pill 已经在显示「{tok} · 缓存命中 {%}」,
1338
+ // 所以这里不再重复那些数字,但**保留文字说明**(用户要求「最早之前的文字」)。
1339
+ // 图标与 DSH 那颗保持一致(都用 IconDatabaseOutlineRegular)——换别的图标会「不搭配」
1340
+ // (2026-09-28 反馈「图标换了 不搭配」,仪表版被否掉)。
1341
+ // 图标缺失(DSH 改 API)时不渲染,文字照常显示,不影响点击。
1342
+ const children = [
1343
+ typeof primitives.IconDatabaseOutlineRegular === "function"
1344
+ ? jsx.jsx(primitives.IconDatabaseOutlineRegular, { key: "icon" })
1345
+ : null,
1346
+ jsx.jsx("span", { key: "label", className: "balance-usage-label", children: "本会话消耗" }),
1347
+ jsx.jsx("span", { key: "cost", className: "balance-usage-label", children: "≈ ¥" + cost.toFixed(4) }),
1348
+ ];
1349
+
1350
+ // ── 点击展开的明细浮层(结构与 DSH 原生「会话统计」一致:标题 + 分隔线 + dl 两列网格)──
1351
+ const usageDetails = [
1352
+ ["缓存命中", cacheHitPercent + "%"],
1353
+ ["未缓存输入", formatTokens(uncachedInput) + " tok"],
1354
+ ["缓存读取", formatTokens(cacheRead) + " tok"],
1355
+ ];
1356
+ if (cacheWrite > 0) usageDetails.push(["缓存写入", formatTokens(cacheWrite) + " tok"]);
1357
+ usageDetails.push(["输出", formatTokens(output) + " tok"]);
1358
+
1359
+ const costDetails = [
1360
+ ["新增 tokens", formatTokens(newTokens) + " tok"],
1361
+ ["计费 tokens", formatTokens(billedTotal) + " tok"],
1362
+ ["模型", model || "未知(按 flash 价估算)"],
1293
1363
  ];
1294
1364
  if (splitUsable) {
1295
- detailLines.push(
1296
- "时段拆分:高峰 " + formatTokens(peakTokens) + " tokens + 空闲 " + formatTokens(offpeakTokens) + " tokens",
1297
- "估算金额:¥" + cost.toFixed(4) + "(高峰桶按目录价×2、空闲桶按目录价)",
1365
+ costDetails.push(
1366
+ ["高峰 tokens", formatTokens(peakTokens) + " tok"],
1367
+ ["空闲 tokens", formatTokens(offpeakTokens) + " tok"],
1298
1368
  );
1299
- } else {
1300
- detailLines.push("估算金额:¥" + cost.toFixed(4) + "(按当前时段价估算)");
1301
1369
  }
1302
1370
  if (hasSubagent) {
1303
- const subPeak = subagentUsage.peak.inputTokens + subagentUsage.peak.cacheReadTokens + subagentUsage.peak.cacheWriteTokens + subagentUsage.peak.outputTokens;
1304
- const subOff = subagentUsage.offpeak.inputTokens + subagentUsage.offpeak.cacheReadTokens + subagentUsage.offpeak.cacheWriteTokens + subagentUsage.offpeak.outputTokens;
1305
- detailLines.push(
1306
- "子代理消耗:新增 " + formatTokens(subNewTokens) + " tokens、计费 " + formatTokens(subagentUsage.totalTokens) + " tokens(" + subCount + " 个子代理)",
1307
- "子代理金额:¥" + subCost.toFixed(4) + "(高峰桶×2、空闲桶按目录价)",
1371
+ costDetails.push(
1372
+ ["子代理", subCount + " 个"],
1373
+ ["子代理 tokens", "+" + formatTokens(subNewTokens) + " / 计费 " + formatTokens(subagentUsage.totalTokens)],
1308
1374
  );
1309
1375
  }
1310
- const children = [
1311
- jsx.jsx("span", { children: "本会话消耗" }),
1312
- jsx.jsx("span", { children: jsx.jsx("strong", { children: formatTokens(newTokens) + " tokens" }) }),
1313
- ];
1314
- if (billedTotal > newTokens) {
1315
- children.push(jsx.jsx("span", { children: "计费 " + formatTokens(billedTotal) }));
1316
- }
1317
- children.push(jsx.jsxs("span", { children: ["≈ ", jsx.jsx("strong", { children: "¥" + cost.toFixed(4) })] }));
1318
- if (hasSubagent) {
1319
- children.push(jsx.jsx("span", { children: "子代理 +" + formatTokens(subNewTokens) + " ≈ ¥" + subCost.toFixed(4) }));
1320
- }
1321
- return jsx.jsx(primitives.Tooltip, {
1322
- label: detailLines.join("\n"),
1323
- side: "top",
1324
- delayMs: 300,
1325
- children: jsx.jsxs("div", {
1326
- className: "balance-usage-line",
1327
- children,
1376
+
1377
+ const detailRows = (prefix, rows) =>
1378
+ rows.flatMap(([label, value], index) => [
1379
+ jsx.jsx("dt", { key: prefix + index + "t", children: label }),
1380
+ jsx.jsx("dd", { key: prefix + index + "d", children: value }),
1381
+ ]);
1382
+
1383
+ const panelChildren = [
1384
+ jsx.jsxs("div", {
1385
+ className: "balance-usage-panel__title",
1386
+ children: [
1387
+ jsx.jsxs("span", {
1388
+ className: "balance-usage-panel__label",
1389
+ children: [
1390
+ typeof primitives.IconDatabaseOutlineRegular === "function"
1391
+ ? jsx.jsx(primitives.IconDatabaseOutlineRegular, {})
1392
+ : null,
1393
+ "Token 用量",
1394
+ ],
1395
+ }),
1396
+ jsx.jsx("span", { className: "balance-usage-panel__value", children: formatTokens(billedTotal) + " tok" }),
1397
+ ],
1398
+ }),
1399
+ jsx.jsx("div", { className: "balance-usage-panel__rule" }),
1400
+ jsx.jsx("dl", { className: "balance-usage-panel__details", children: detailRows("u", usageDetails) }),
1401
+ jsx.jsxs("div", {
1402
+ className: "balance-usage-panel__title balance-usage-panel__title--second",
1403
+ children: [
1404
+ jsx.jsxs("span", {
1405
+ className: "balance-usage-panel__label",
1406
+ children: [
1407
+ typeof primitives.IconGaugeOutlineRegular === "function"
1408
+ ? jsx.jsx(primitives.IconGaugeOutlineRegular, {})
1409
+ : null,
1410
+ "本会话金额",
1411
+ ],
1412
+ }),
1413
+ jsx.jsx("span", { className: "balance-usage-panel__value", children: "≈ ¥" + cost.toFixed(4) }),
1414
+ ],
1328
1415
  }),
1416
+ jsx.jsx("div", { className: "balance-usage-panel__rule" }),
1417
+ jsx.jsx("dl", { className: "balance-usage-panel__details", children: detailRows("c", costDetails) }),
1418
+ ];
1419
+
1420
+ return jsx.jsxs("div", {
1421
+ className: "balance-usage-wrap",
1422
+ children: [
1423
+ jsx.jsx("button", {
1424
+ type: "button",
1425
+ ref: detailsAnchorRef,
1426
+ className: "balance-usage-line",
1427
+ "aria-haspopup": "dialog",
1428
+ "aria-expanded": detailsOpen,
1429
+ "aria-label": "本会话 Token 用量与金额明细",
1430
+ onClick: () => setDetailsOpen((value) => !value),
1431
+ children,
1432
+ }),
1433
+ detailsOpen
1434
+ ? reactDom.createPortal(
1435
+ jsx.jsx("div", {
1436
+ ref: detailsPanelRef,
1437
+ className: "balance-usage-panel",
1438
+ role: "dialog",
1439
+ "aria-label": "本会话 Token 用量",
1440
+ style: anchoredPosition ?? void 0,
1441
+ children: panelChildren,
1442
+ }),
1443
+ document.body,
1444
+ )
1445
+ : null,
1446
+ ],
1329
1447
  });
1330
1448
  }
1331
1449
 
package/lib/index.js CHANGED
@@ -322,7 +322,7 @@ function formatBalance(balance) {
322
322
  // | { status:"failed" } (网络失败;官方明确“失败绝不变成 0 余额”)
323
323
  // client 是 AccountClientMetadata(version / locale / timezoneOffsetSeconds),
324
324
  // 缺字段会在平台侧组装请求头时抛错,所以三个字段都要给。
325
- const PLUGIN_VERSION = "1.2.2";
325
+ const PLUGIN_VERSION = "1.2.3";
326
326
 
327
327
  /**
328
328
  * 读取官方账号余额并归一化成与 normalizeBalance 相同的形状。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "balance-dsh",
3
- "version": "1.2.2",
3
+ "version": "1.2.3",
4
4
  "description": "查询 DeepSeek 余额并显示当前会话 token 消耗。DeepSeek 账户余额 + 会话 token/费用显示,含高峰/空闲峰谷计费。",
5
5
  "author": "Rannist",
6
6
  "type": "module",