balance-dsh 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -303,6 +303,10 @@ window.__ModuleLoader__.load({
303
303
  .balance-settings__segment{box-sizing:border-box;padding:2px 12px;border:none;border-radius:999px;corner-shape:round;background:0 0;color:var(--dsw-alias-label-secondary);font:inherit;font-size:12px;line-height:18px;font-weight:500;cursor:pointer;white-space:nowrap;transition:background .15s var(--ds-ease-in-out,.2s),color .15s var(--ds-ease-in-out,.2s)}
304
304
  .balance-settings__segment:hover{color:var(--dsw-alias-label-primary)}
305
305
  .balance-settings__segment--on{background:var(--dsw-alias-bg-module-platform,var(--dsw-alias-bg-base,#2a2a2c));color:var(--dsw-alias-label-primary);box-shadow:0 1px 2px rgba(0,0,0,.2)}
306
+ /* 检查更新的结果:一行说明,不是可点区域。有新版本时提亮成品牌色提示 */
307
+ .balance-settings__update{margin:12px 0 0;padding:8px 10px;border-radius:var(--dsw-radius-md,8px);background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.1));font-size:12px;line-height:17px;color:var(--dsw-alias-label-secondary)}
308
+ .balance-settings__update--newer{border:.5px solid color-mix(in srgb,var(--dsw-alias-brand-primary,#4d6bfe) 30%,transparent);background:color-mix(in srgb,var(--dsw-alias-brand-primary,#4d6bfe) 10%,transparent);color:var(--dsw-alias-label-primary)}
309
+ .balance-settings__update--error{color:var(--dsw-alias-label-tertiary)}
306
310
  .balance-settings__actions{display:flex;align-items:center;gap:8px;margin:14px 0 0;padding:12px 0 0;border-top:.5px solid var(--dsw-alias-border-l2,rgba(127,127,127,.22))}
307
311
  .balance-settings__status{flex:1;min-width:0;font-size:12px;line-height:17px;color:var(--dsw-alias-label-tertiary)}
308
312
  .balance-settings__status--error{color:var(--dsw-alias-state-error-primary,#e5484d)}
@@ -900,6 +904,31 @@ window.__ModuleLoader__.load({
900
904
  return settingsCache.value;
901
905
  }
902
906
 
907
+ // ─── 检查更新 ─────────────────────────────────────────────────────────────
908
+ /** 查 npm 上有没有新版本(/balance-api?update=1)。
909
+ * 返回 { ok, current, latest, newer, error };有新版时 newer = true。
910
+ * 只是查版本号,不下载任何东西;失败时 ok = false 且 error 带原因。 */
911
+ async function fetchUpdateInfo() {
912
+ const res = await fetch("/balance-api?update=1", { headers: { accept: "application/json" }, cache: "no-store" });
913
+ if (!res || !res.ok) throw new Error("HTTP " + (res ? res.status : "?"));
914
+ const data = await res.json();
915
+ const current =
916
+ data && typeof data.current === "string" && data.current !== ""
917
+ ? data.current
918
+ : String(settingsCache.version || "");
919
+ if (!data || data.ok !== true) {
920
+ return {
921
+ ok: false,
922
+ current,
923
+ latest: "",
924
+ newer: false,
925
+ error: data && typeof data.error === "string" ? data.error : "未知原因",
926
+ };
927
+ }
928
+ const latest = typeof data.latest === "string" ? data.latest : "";
929
+ return { ok: true, current, latest, newer: compareVersion(latest, current) > 0, error: "" };
930
+ }
931
+
903
932
  /** 订阅设置(首次挂载异步拉一次)。 */
904
933
  function useSettings() {
905
934
  const [, force] = react.useState(0);
@@ -2384,6 +2413,8 @@ function UsageCostLine({ useProjection, sessionId }) {
2384
2413
  const [draft, setDraft] = react.useState(null);
2385
2414
  const [saving, setSaving] = react.useState(false);
2386
2415
  const [status, setStatus] = react.useState({ kind: "", text: "" });
2416
+ // 检查更新的状态:idle / checking / ok / error。ok 时 newer 表示有没有新版。
2417
+ const [updateCheck, setUpdateCheck] = react.useState({ kind: "idle" });
2387
2418
 
2388
2419
  // 拿到设置后灌一次草稿;draft 已有内容就不再覆盖,避免打断正在输入的用户
2389
2420
  react.useEffect(() => {
@@ -2433,6 +2464,39 @@ function UsageCostLine({ useProjection, sessionId }) {
2433
2464
  setDraft(next);
2434
2465
  queueSave(next);
2435
2466
  };
2467
+ // 检查更新:只查版本号。结果就地把「当前版本 → 最新版本」说清楚,
2468
+ // 升级动作交给 /store 或 npm(插件不改自己的安装,避免踩 DSH 的 profile 目录)。
2469
+ const onCheckUpdate = () => {
2470
+ if (updateCheck.kind === "checking") return;
2471
+ setUpdateCheck({ kind: "checking" });
2472
+ void fetchUpdateInfo()
2473
+ .then((info) => {
2474
+ if (info.ok !== true) {
2475
+ setUpdateCheck({
2476
+ kind: "error",
2477
+ text: "检查失败:" + info.error + "(可稍后重试)",
2478
+ });
2479
+ return;
2480
+ }
2481
+ if (info.newer) {
2482
+ setUpdateCheck({
2483
+ kind: "ok",
2484
+ newer: true,
2485
+ text: "有新版本 " + info.latest + "(当前 " + info.current + ")。在 /store 或插件市场更新。",
2486
+ });
2487
+ } else {
2488
+ setUpdateCheck({
2489
+ kind: "ok",
2490
+ newer: false,
2491
+ text: "已是最新版本 " + info.current + "。",
2492
+ });
2493
+ }
2494
+ })
2495
+ .catch((error) => {
2496
+ const message = error instanceof Error ? error.message : String(error);
2497
+ setUpdateCheck({ kind: "error", text: "检查失败:" + message + "(可稍后重试)" });
2498
+ });
2499
+ };
2436
2500
 
2437
2501
  // 插件列表里的单行摘要
2438
2502
  if (view === "summary") {
@@ -2719,6 +2783,19 @@ function UsageCostLine({ useProjection, sessionId }) {
2719
2783
  numberRow("peakMultiplier", "高峰时段倍率", "官方为 2(高峰价 = 空闲价 × 倍率)。", 1, 10, "倍"),
2720
2784
  ]),
2721
2785
 
2786
+ // 检查更新的结果:只在点过按钮之后出现,避免设置页默认就挂一行字
2787
+ updateCheck.kind === "ok" || updateCheck.kind === "error"
2788
+ ? jsx.jsx("div", {
2789
+ className: "balance-settings__update" +
2790
+ (updateCheck.kind === "error"
2791
+ ? " balance-settings__update--error"
2792
+ : updateCheck.newer
2793
+ ? " balance-settings__update--newer"
2794
+ : ""),
2795
+ children: updateCheck.text,
2796
+ })
2797
+ : null,
2798
+
2722
2799
  jsx.jsxs("div", {
2723
2800
  className: "balance-settings__actions",
2724
2801
  children: [
@@ -2727,6 +2804,13 @@ function UsageCostLine({ useProjection, sessionId }) {
2727
2804
  (status.kind === "error" ? " balance-settings__status--error" : status.kind === "ok" ? " balance-settings__status--ok" : ""),
2728
2805
  children: saving ? "保存中…" : status.text,
2729
2806
  }),
2807
+ jsx.jsx("button", {
2808
+ type: "button",
2809
+ className: "balance-settings__btn",
2810
+ onClick: onCheckUpdate,
2811
+ disabled: updateCheck.kind === "checking",
2812
+ children: updateCheck.kind === "checking" ? "检查中…" : "检查更新",
2813
+ }),
2730
2814
  jsx.jsx("button", {
2731
2815
  type: "button",
2732
2816
  className: "balance-settings__btn",
package/lib/index.js CHANGED
@@ -345,7 +345,38 @@ function formatBalance(balance) {
345
345
  // | { status:"failed" } (网络失败;官方明确“失败绝不变成 0 余额”)
346
346
  // client 是 AccountClientMetadata(version / locale / timezoneOffsetSeconds),
347
347
  // 缺字段会在平台侧组装请求头时抛错,所以三个字段都要给。
348
- const PLUGIN_VERSION = "2.0.0";
348
+ const PLUGIN_VERSION = "2.0.1";
349
+
350
+ // ─── 检查更新 ───────────────────────────────────────────────────────────────
351
+ // 只查 npm registry 的 latest 标签(一个很小的 JSON),不下载任何东西。
352
+ // 结果缓存 10 分钟:设置页的按钮连着点也不会把 registry 打爆。
353
+ const UPDATE_CACHE_MS = 10 * 60 * 1000;
354
+ const UPDATE_REGISTRY_URL = "https://registry.npmjs.org/balance-dsh/latest";
355
+ let updateCache = { at: 0, value: null };
356
+
357
+ /** 查 npm 上 balance-dsh 的最新版本。成功返回 { latest },失败返回 { error }(失败不缓存,下次可重试)。 */
358
+ async function fetchLatestVersion() {
359
+ const now = Date.now();
360
+ if (updateCache.value && now - updateCache.at < UPDATE_CACHE_MS) return updateCache.value;
361
+ if (typeof fetch !== "function") return { error: "当前运行环境没有 fetch" };
362
+ try {
363
+ let signal;
364
+ if (typeof AbortSignal !== "undefined" && typeof AbortSignal.timeout === "function") {
365
+ signal = AbortSignal.timeout(8000);
366
+ }
367
+ const res = await fetch(UPDATE_REGISTRY_URL, { headers: { accept: "application/json" }, signal });
368
+ if (!res || !res.ok) throw new Error("registry 返回 HTTP " + (res ? res.status : "?"));
369
+ const data = await res.json();
370
+ const latest = data && typeof data.version === "string" ? data.version.trim() : "";
371
+ if (latest === "") throw new Error("registry 返回里没有 version 字段");
372
+ updateCache = { at: now, value: { latest } };
373
+ return updateCache.value;
374
+ } catch (error) {
375
+ // 超时 / 断网 / 被墙都走这里,只影响这个按钮,不影响余额与设置
376
+ return { error: error instanceof Error ? error.message : String(error) };
377
+ }
378
+ }
379
+
349
380
 
350
381
  /**
351
382
  * 读取官方账号余额并归一化成与 normalizeBalance 相同的形状。
@@ -1475,6 +1506,23 @@ export function apply(ctx, config = {}) {
1475
1506
  writeJson(res, 405, { ok: false, error: "method not allowed" });
1476
1507
  return;
1477
1508
  }
1509
+ // 检查更新:GET /balance-api?update=1 → 返回 npm 上的最新版本。
1510
+ // 与余额无关,独立成一路,失败也不影响其它字段。
1511
+ let wantUpdate = false;
1512
+ try {
1513
+ wantUpdate = new URL(req.url, "http://localhost").searchParams.get("update") === "1";
1514
+ } catch {
1515
+ wantUpdate = false;
1516
+ }
1517
+ if (wantUpdate) {
1518
+ const found = await fetchLatestVersion();
1519
+ if (found && found.error) {
1520
+ writeJson(res, 200, { ok: false, current: PLUGIN_VERSION, error: found.error });
1521
+ } else {
1522
+ writeJson(res, 200, { ok: true, current: PLUGIN_VERSION, latest: found.latest });
1523
+ }
1524
+ return;
1525
+ }
1478
1526
  // 客户端把当前会话使用的模型通过 ?model= 传入,服务端按模型取价;
1479
1527
  // 可选 ?session= 传入会话 id,服务端返回该会话的分时段 usage(跨时段精确计费用)
1480
1528
  let model = "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "balance-dsh",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "在 DeepSeek Harness 中显示 DeepSeek 账户余额和当前会话 token/费用消耗(含高峰/空闲峰谷计费)。仅支持 DeepSeek 官方模型与账号,暂不支持其他大模型。",
5
5
  "author": "Rannist",
6
6
  "type": "module",