dsh-subusage 0.13.1 → 0.13.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  </div>
20
20
 
21
- > **0.13.1**:修复已开启 Codex 在状态重建时被过滤,以及传输失败后药丸隐藏、无法继续重试的问题;仍按需刷新,不恢复全量联网初始化。发布与本机升级分开;本页图片是虚构数据离线预览,不是实机验收证明。[查看更新记录](docs/changelog.md)。
21
+ > **0.13.2**:修正用量文案与标签误导(连坐来源、Cookie 续期、方舟待检测标签、Command Code 默认账户),并限制药丸宽度与弹层高度。发布与本机升级分开;本页图片是虚构数据离线预览,不是实机验收证明。[查看更新记录](docs/changelog.md)。
22
22
 
23
23
  ## 快速开始
24
24
 
package/lib/client.js CHANGED
@@ -121,8 +121,13 @@ window.__ModuleLoader__.load({
121
121
  mimoLoginAt: "登录于 {time}",
122
122
  mimoCookieUntracked: "未记录登录时间(旧版本保存的 Cookie),重新登录后开始计时",
123
123
  mimoCookieExpired: "Cookie 已过期,请重新登录",
124
+ mimoCookieExpiredShort: "Cookie 已过期",
124
125
  pillCookieSoon: "Cookie {rest}后到期",
126
+ pillCookieSoonShort: "Cookie 将到期",
125
127
  mimoCookieExpiresAt: "{time} 到期",
128
+ afpGroupText: "文本 / 向量模型",
129
+ afpGroupVision: "视觉 / 语音模型与 Harness",
130
+ groupMemberBudget: "成员预算",
126
131
  noUsageData: "暂无额度数据",
127
132
  statusPaygo: "可按量续用",
128
133
  statusPaygoSub: "已购余额或计费接口支持按量续用;实际请求与余额仍以服务端为准",
@@ -150,7 +155,8 @@ window.__ModuleLoader__.load({
150
155
  ccAccountsHint: "切换对 Command Code 提供方插件全局生效(与其设置页同一开关);上方用量与药丸同步切换为所选账户的额度。设置页与药丸弹层用的是同一个开关。",
151
156
  ccAccountsUnavailable: "账户列表不可用(需要 Command Code 提供方插件在运行)",
152
157
  ccUsageAccount: "上方用量:{account}",
153
- ccUsageAutoNote: "自动轮换时上方用量仍是默认账户(顶层 Key),不跟随实际服务账户",
158
+ ccUsageAutoNote: "自动轮换时下方数字是默认账户(顶层 Key)的额度,不是正在处理请求的账户",
159
+ ccWindowsDefault: "默认账户额度(不是当前请求账户)",
154
160
  ccSwitching: "切换中…",
155
161
  ccSwitchFailed: "切换失败",
156
162
  // Command Code 自动轮换池健康度(其余账户用量由 Host 并行读取,各自缓存 60 秒)。
@@ -279,8 +285,13 @@ window.__ModuleLoader__.load({
279
285
  mimoLoginAt: "Signed in {time}",
280
286
  mimoCookieUntracked: "Login time not recorded (Cookie saved by an older version); sign in again to start the countdown",
281
287
  mimoCookieExpired: "Cookie expired — sign in again",
288
+ mimoCookieExpiredShort: "Cookie expired",
282
289
  pillCookieSoon: "Cookie expires in {rest}",
290
+ pillCookieSoonShort: "Cookie expiring",
283
291
  mimoCookieExpiresAt: "Expires {time}",
292
+ afpGroupText: "Text / embedding models",
293
+ afpGroupVision: "Vision / speech models and Harness",
294
+ groupMemberBudget: "Member budget",
284
295
  noUsageData: "No quota data",
285
296
  statusPaygo: "Paid usage available",
286
297
  statusPaygoSub: "Purchased balance or billing advice permits paid usage; the server and actual balance remain authoritative",
@@ -308,7 +319,8 @@ window.__ModuleLoader__.load({
308
319
  ccAccountsHint: "Switching applies globally to the Command Code provider plugin (same switch as its settings page); the usage above and the pill follow the selected account. This page and the pill popover share one switch.",
309
320
  ccAccountsUnavailable: "Account list unavailable (requires the Command Code provider plugin to be running)",
310
321
  ccUsageAccount: "Usage above: {account}",
311
- ccUsageAutoNote: "with automatic rotation the usage above stays on the default (top-level key) account, not the serving one",
322
+ ccUsageAutoNote: "with automatic rotation the numbers below are the default (top-level key) account, not the serving one",
323
+ ccWindowsDefault: "Default account quota (not the serving account)",
312
324
  ccSwitching: "Switching…",
313
325
  ccSwitchFailed: "Switch failed",
314
326
  // Command Code rotation-pool health (the Host reads the other accounts in parallel, each cached 60s).
@@ -342,13 +354,13 @@ window.__ModuleLoader__.load({
342
354
  const PROVIDER_META = {
343
355
  // 余额型:接口给账户余额而不是订阅窗口,药丸用余额兜底文案显示。
344
356
  "deepseek": { label: "DeepSeek API", short: "DeepSeek API", envName: "DEEPSEEK_API_KEY" },
345
- "deepseek-account": { label: "DeepSeek 账号", short: "DeepSeek 账号", envName: "", managedByPlugin: true },
346
- "zai-coding": { label: "Z.ai Coding (International)", short: "Z.ai Intl", envName: "ZAI_CODING_API_KEY", defaultEnabled: false },
357
+ "deepseek-account": { label: "DeepSeek account", labelZh: "DeepSeek 账号", short: "DeepSeek 账号", envName: "", managedByPlugin: true },
358
+ "zai-coding": { label: "Z.ai Coding (International)", labelZh: "Z.ai Coding(国际)", short: "Z.ai Intl", envName: "ZAI_CODING_API_KEY", defaultEnabled: false },
347
359
  "synthetic": { label: "Synthetic", short: "Synthetic", envName: "SYNTHETIC_API_KEY", defaultEnabled: false },
348
360
  "nanogpt": { label: "NanoGPT", short: "NanoGPT", envName: "NANOGPT_API_KEY", defaultEnabled: false },
349
- "zai-coding-cn": { label: "Z.ai Coding (CN)", short: "Z.ai", envName: "ZAI_CODING_CN_API_KEY" },
361
+ "zai-coding-cn": { label: "Z.ai Coding (CN)", labelZh: "Z.ai Coding(中国)", short: "Z.ai", envName: "ZAI_CODING_CN_API_KEY" },
350
362
  "kimi-coding": { label: "Kimi Coding", short: "Kimi", envName: "KIMI_CODING_API_KEY" },
351
- "xiaomi-token-plan-cn": { label: "Xiaomi Token Plan (CN)", short: "MiMo", envName: "XIAOMI_TOKEN_PLAN_CN_API_KEY" },
363
+ "xiaomi-token-plan-cn": { label: "Xiaomi Token Plan (CN)", labelZh: "小米 MiMo", short: "MiMo", envName: "XIAOMI_TOKEN_PLAN_CN_API_KEY" },
352
364
  "opencode-go": { label: "OpenCode Go", short: "OpenCode Go", envName: "OPENCODE_API_KEY" },
353
365
  // 凭据与多账户由提供方插件管理;managedByPlugin 时不渲染本地凭据编辑。
354
366
  "commandcode": { label: "Command Code", short: "Command", envName: "COMMANDCODE_API_KEY", managedByPlugin: true },
@@ -356,20 +368,20 @@ window.__ModuleLoader__.load({
356
368
  "xai-oauth": { label: "SuperGrok", short: "SuperGrok", envName: "", managedByPlugin: true },
357
369
  // Codex / ChatGPT 订阅:凭据是 Codex CLI 的 OAuth 登录文件,只读、不刷新、不接受本地 Key。
358
370
  // 默认关闭:Codex 的提供方插件自带用量药丸,两个并排只是重复信息(仍可手动开启)。
359
- "openai-codex": { label: "Codex (ChatGPT)", short: "Codex", envName: "", managedByPlugin: true, defaultEnabled: false },
371
+ "openai-codex": { label: "Codex (ChatGPT)", labelZh: "Codex(ChatGPT)", short: "Codex", envName: "", managedByPlugin: true, defaultEnabled: false },
360
372
  // 国际版默认关闭,中国版保持默认开启。
361
- "minimax": { label: "MiniMax (International)", short: "MiniMax", envName: "MINIMAX_API_KEY", defaultEnabled: false },
362
- "minimax-cn": { label: "MiniMax (CN)", short: "MiniMax CN", envName: "MINIMAX_CN_API_KEY" },
373
+ "minimax": { label: "MiniMax (International)", labelZh: "MiniMax(国际)", short: "MiniMax", envName: "MINIMAX_API_KEY", defaultEnabled: false },
374
+ "minimax-cn": { label: "MiniMax (CN)", labelZh: "MiniMax(中国)", short: "MiniMax CN", envName: "MINIMAX_CN_API_KEY" },
363
375
  // 火山方舟:provider id 必须与官方插件 @volcengine/ark-plan-api 注册的路由逐字一致,
364
376
  // 否则选中方舟模型时药丸不会出现。凭据是共享的 IAM AK/SK(volc),与这里的推理
365
377
  // envName 是两套:推理 Key 查不到额度。
366
378
  // arkcli(官方 CLI)安装的路由:id 由 `arkcli helper` 写成 arkcli-<planType>,
367
379
  // 官方 CLI 是当前最主流的安装方式,故默认开启。
368
- "arkcli-agent-plan": { label: "ARK Agent Plan (arkcli)", short: "ARK Plan", envName: "ARKCLI_AGENT_PLAN_API_KEY", volc: true },
369
- "arkcli-coding-plan": { label: "ARK Coding Plan (arkcli)", short: "ARK Code", envName: "ARKCLI_CODING_PLAN_API_KEY", volc: true },
380
+ "arkcli-agent-plan": { label: "ARK Agent Plan (arkcli)", labelZh: "方舟 Agent Plan", short: "ARK Plan", envName: "ARKCLI_AGENT_PLAN_API_KEY", volc: true },
381
+ "arkcli-coding-plan": { label: "ARK Coding Plan (arkcli)", labelZh: "方舟 Coding Plan", short: "ARK Code", envName: "ARKCLI_CODING_PLAN_API_KEY", volc: true },
370
382
  // 企业版/团队版:额度按席位下发,需 AK/SK 有查看席位的权限。
371
- "arkcli-agent-plan-team": { label: "ARK Agent Plan Team (arkcli)", short: "ARK Plan Team", envName: "ARKCLI_AGENT_PLAN_TEAM_API_KEY", volc: true, defaultEnabled: false },
372
- "arkcli-coding-plan-team": { label: "ARK Coding Plan Team (arkcli)", short: "ARK Code Team", envName: "ARKCLI_CODING_PLAN_TEAM_API_KEY", volc: true, defaultEnabled: false },
383
+ "arkcli-agent-plan-team": { label: "ARK Agent Plan Team (arkcli)", labelZh: "方舟 Agent Plan 团队", short: "ARK Plan Team", envName: "ARKCLI_AGENT_PLAN_TEAM_API_KEY", volc: true, defaultEnabled: false },
384
+ "arkcli-coding-plan-team": { label: "ARK Coding Plan Team (arkcli)", labelZh: "方舟 Coding Plan 团队", short: "ARK Code Team", envName: "ARKCLI_CODING_PLAN_TEAM_API_KEY", volc: true, defaultEnabled: false },
373
385
  // 旧插件 @volcengine/ark-plan-api 的国内两条路由已从 Host 移除(实测与 arkcli 查同一份订阅、
374
386
  // 同一组 AK/SK)。它们的 id 不在本表里,由 ROUTE_PROVIDER_ALIAS 在入口归并到 arkcli 路由,
375
387
  // 装了旧插件的用户选中那些模型的药丸照常显示。
@@ -394,6 +406,23 @@ window.__ModuleLoader__.load({
394
406
  // 火山方舟的 4 条路由共用同一组 IAM AK/SK,而一般用户只持有一个套餐,所以设置页只给一张卡片、
395
407
  // 一个开关(组开关落在 Host 的 visibility.ark 上)。这些 id 不能真正合并——药丸靠 provider id 匹配路由。
396
408
  const ARK_IDS = PROVIDER_ORDER.filter(id => PROVIDER_META[id]?.volc);
409
+ const GROUP_LABEL_KEY = { "文本 / 向量模型": "afpGroupText", "视觉 / 语音模型与 Harness": "afpGroupVision", "Member budget": "groupMemberBudget", afpGroupText: "afpGroupText", afpGroupVision: "afpGroupVision", groupMemberBudget: "groupMemberBudget" };
410
+ function providerDisplayName(id, field, getLocale) {
411
+ const meta = PROVIDER_META[id];
412
+ if (!meta) return id;
413
+ const zh = String(typeof getLocale === "function" ? getLocale() : getLocale || "").startsWith("zh");
414
+ if (zh && field === "label" && meta.labelZh) return meta.labelZh;
415
+ if (zh && field === "short" && meta.shortZh) return meta.shortZh;
416
+ return meta[field];
417
+ }
418
+ function displayGroupLabel(label, t) {
419
+ if (!label) return label;
420
+ const key = GROUP_LABEL_KEY[label];
421
+ return key ? t(key) : label;
422
+ }
423
+ function windowKindLabel(kind, t) {
424
+ return t(KIND_LABEL[kind] ?? "ccPoolPeriodUnknown");
425
+ }
397
426
  /**
398
427
  * 旧插件 @volcengine/ark-plan-api 注册的两个国内路由 id **已从本插件移除**:它们与 arkcli 的路由
399
428
  * 查的是同一份订阅(同 Action、同 host、同 AK/SK,实测请求 URL 逐字相同),并列显示只会让人以为
@@ -430,6 +459,17 @@ window.__ModuleLoader__.load({
430
459
  // `subscribed === undefined` 表示还没拉到数据,按「未知」放行——不能因为一次没拉到就把有订阅的藏起来。
431
460
  if (PROVIDER_META[id]?.volc) {
432
461
  if (entry?.subscribed === false) return false;
462
+ // 还没拉到任何订阅结论时,只留管理区那张代表卡,避免四个「待检测」灰标签并排。
463
+ // 一旦有 true/false,就按订阅结果显示(未订阅收起,已订阅即使没推理路由也留下)。
464
+ const arkEntries = Array.isArray(result?.entries) ? result.entries : [];
465
+ const known = ARK_IDS.some(key => {
466
+ const item = arkEntries.find(row => row.providerId === key);
467
+ return item?.subscribed === true || item?.subscribed === false;
468
+ });
469
+ if (!known) {
470
+ const representative = ARK_IDS.find(key => providerRoutable(result, key)) ?? ARK_IDS[0];
471
+ if (id !== representative) return false;
472
+ }
433
473
  } else if (!providerCanQuery(result, id)) return false;
434
474
  // 官方余额与本机账号余额独立于模型路由;刚保存 Key 的旧 no-key 条目不能继续隐藏。
435
475
  if (settings?.hasKeys?.[id]) return true;
@@ -519,9 +559,10 @@ window.__ModuleLoader__.load({
519
559
  }
520
560
  /** 时长紧凑文案:窗口重置倒计时与 Cookie 剩余有效期共用同一档位。 */
521
561
  function durationText(ms, t) {
522
- const mins = Math.max(0, Math.round(ms / 60000));
523
- return mins >= 1440 ? t("restDays").replace("{n}", String(Math.round(mins / 1440)))
524
- : mins >= 60 ? t("restHours").replace("{n}", String(Math.round(mins / 60)))
562
+ if (!(ms > 0)) return t("restMins").replace("{n}", "1");
563
+ const mins = Math.round(ms / 60000);
564
+ return mins >= 1440 ? t("restDays").replace("{n}", String(Math.max(1, Math.floor(mins / 1440))))
565
+ : mins >= 60 ? t("restHours").replace("{n}", String(Math.max(1, Math.floor(mins / 60))))
525
566
  : t("restMins").replace("{n}", String(Math.max(1, mins)));
526
567
  }
527
568
  /** 官方 MiMo 会话 Cookie 自签发起 24 小时有效:到期前 2 小时起提醒,30 分钟内升级为紧急。 */
@@ -533,11 +574,12 @@ window.__ModuleLoader__.load({
533
574
  if (!Number.isFinite(expiresAt)) return null;
534
575
  const left = expiresAt - Date.now();
535
576
  const rest = durationText(left, t);
536
- if (left <= 0) return { rank: 4, icon: "✕", color: "#ef4444", expired: true, rest, expiresAt, label: t("mimoCookieExpired") };
577
+ if (left <= 0) return { rank: 4, icon: "✕", color: "#ef4444", expired: true, rest, expiresAt, label: t("mimoCookieExpired"), short: t("mimoCookieExpiredShort") };
537
578
  const label = t("pillCookieSoon").replace("{rest}", rest);
538
- if (left <= MIMO_COOKIE_URGENT_MS) return { rank: 3, icon: "⚠", color: "#f97316", rest, expiresAt, label };
539
- if (left <= MIMO_COOKIE_SOON_MS) return { rank: 2, icon: "⚠", color: "#eab308", rest, expiresAt, label };
540
- return { rank: 1, icon: "✓", color: "#22c55e", rest, expiresAt, label };
579
+ const short = t("pillCookieSoonShort");
580
+ if (left <= MIMO_COOKIE_URGENT_MS) return { rank: 3, icon: "⚠", color: "#f97316", rest, expiresAt, label, short };
581
+ if (left <= MIMO_COOKIE_SOON_MS) return { rank: 2, icon: "⚠", color: "#eab308", rest, expiresAt, label, short };
582
+ return { rank: 1, icon: "✓", color: "#22c55e", rest, expiresAt, label, short };
541
583
  }
542
584
  /** 设置页有效期说明:登录时间 + 剩余时间;旧版本保存的 Cookie 明确说明未记录。 */
543
585
  function cookieLifetimeLine(xiaomi, t, getLocale) {
@@ -656,22 +698,22 @@ window.__ModuleLoader__.load({
656
698
  function normalizeCookieText(raw) {
657
699
  const text = String(raw || "").trim();
658
700
  if (!text) return "";
659
- if (/\r(?!\n)/.test(String(raw || "")) || /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(String(raw || ""))) throw new Error("Cookie 含非法控制字符");
701
+ if (/\r(?!\n)/.test(String(raw || "")) || /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/.test(String(raw || ""))) throw new Error("Cookie 含非法控制字符 / Cookie contains illegal control characters");
660
702
  const pairs = [];
661
703
  const domainOK = (domain) => !domain || ["platform.xiaomimimo.com", "xiaomimimo.com"].includes(String(domain).toLowerCase().replace(/^\./, ""));
662
704
  const add = (name, value, domain) => {
663
705
  if (!domainOK(domain)) return;
664
- if (typeof name !== "string" || !/^[A-Za-z0-9_!#$%&'*+\-.^`|~]+$/.test(name)) throw new Error("Cookie 名称无效");
665
- if (value === undefined || value === null || typeof value === "object") throw new Error("Cookie 值无效");
706
+ if (typeof name !== "string" || !/^[A-Za-z0-9_!#$%&'*+\-.^`|~]+$/.test(name)) throw new Error("Cookie 名称无效 / Invalid cookie name");
707
+ if (value === undefined || value === null || typeof value === "object") throw new Error("Cookie 值无效 / Invalid cookie value");
666
708
  const v = String(value);
667
- if (!/^("[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*"|[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*)$/.test(v)) throw new Error("Cookie 值含非法分隔符、空白、非 ASCII 字符或 CRLF");
709
+ if (!/^("[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*"|[\x21\x23-\x2b\x2d-\x3a\x3c-\x5b\x5d-\x7e]*)$/.test(v)) throw new Error("Cookie 值含非法分隔符、空白、非 ASCII 字符或 CRLF / Cookie value contains illegal separators, whitespace, non-ASCII or CRLF");
668
710
  pairs.push([name, v]);
669
711
  };
670
712
  if (/^[\[{]/.test(text)) {
671
713
  let data;
672
- try { data = JSON.parse(text); } catch { throw new Error("Cookie JSON 解析失败,请检查格式(原输入已保留)"); }
714
+ try { data = JSON.parse(text); } catch { throw new Error("Cookie JSON 解析失败,请检查格式(原输入已保留) / Cookie JSON parse failed (original input kept)"); }
673
715
  const list = Array.isArray(data) ? data : Array.isArray(data.cookies) ? data.cookies : Object.entries(data).map(([name, value]) => ({ name, value }));
674
- for (const item of list) { if (!item || typeof item !== "object") throw new Error("Cookie JSON 项无效"); add(item.name, item.value, item.domain); }
716
+ for (const item of list) { if (!item || typeof item !== "object") throw new Error("Cookie JSON 项无效 / Invalid Cookie JSON item"); add(item.name, item.value, item.domain); }
675
717
  } else {
676
718
  // TAB 末列可以是合法空值,不能用整行/整段 trim 丢掉分隔符。
677
719
  const lines = String(raw || "").split(/\r?\n/).filter((line) => line.trim()).map((line) => line.includes("\t") ? line : line.trim());
@@ -682,7 +724,7 @@ window.__ModuleLoader__.load({
682
724
  if (netscape && /^#(?!HttpOnly_)/i.test(line.trim())) continue;
683
725
  const cells = line.split("\t").map((v) => v.trim());
684
726
  if (/^(name|名称)$/i.test(cells[0])) { columns = cells.map((v) => v.toLowerCase()); continue; }
685
- if (cells.length < 2) throw new Error("Cookie TAB 表格缺少值");
727
+ if (cells.length < 2) throw new Error("Cookie TAB 表格缺少值 / Cookie TAB table is missing a value");
686
728
  if (!columns && cells.length >= 7 && /^(TRUE|FALSE)$/i.test(cells[1])) { add(cells[5], cells[6], cells[0].replace(/^#HttpOnly_/i, "")); continue; }
687
729
  const domainIndex = columns ? columns.findIndex((v) => v === "domain" || v === "域") : 2;
688
730
  add(cells[0], cells[1], domainIndex >= 0 ? cells[domainIndex] : undefined);
@@ -691,22 +733,22 @@ window.__ModuleLoader__.load({
691
733
  for (const line of lines) for (const part of line.split(";")) {
692
734
  if (!part.trim()) continue;
693
735
  const at = part.indexOf("=");
694
- if (at < 1) throw new Error("Cookie 必须为 name=value");
736
+ if (at < 1) throw new Error("Cookie 必须为 name=value / Cookie must be name=value");
695
737
  add(part.slice(0, at).trim(), part.slice(at + 1).trim());
696
738
  }
697
739
  } else {
698
740
  if (lines[0] === "当前页面 Cookies") lines.shift();
699
- if (lines.length % 2) throw new Error("Cookie 多行清单名称和值必须成对(原输入已保留)");
741
+ if (lines.length % 2) throw new Error("Cookie 多行清单名称和值必须成对(原输入已保留) / Cookie name/value lines must come in pairs (original input kept)");
700
742
  for (let i = 0; i < lines.length; i += 2) add(lines[i], lines[i + 1]);
701
743
  }
702
744
  }
703
- if (!pairs.length) throw new Error("未找到官方 MiMo 域的有效 Cookie(原输入已保留)");
745
+ if (!pairs.length) throw new Error("未找到官方 MiMo 域的有效 Cookie(原输入已保留) / No valid Cookie for the official MiMo domain (original input kept)");
704
746
  return [...new Map(pairs)].map(([name, value]) => `${name}=${value}`).join("; ");
705
747
  }
706
748
  function validateCookieText(raw) {
707
749
  const text = normalizeCookieText(raw);
708
750
  const names = new Map(text.split("; ").map((pair) => { const at = pair.indexOf("="); return [pair.slice(0, at), pair.slice(at + 1).replace(/^"|"$/g, "")]; }));
709
- if (!names.get("api-platform_serviceToken") || !names.get("userId")) throw new Error("Cookie 必须包含非空 api-platform_serviceToken 和 userId(原输入已保留)");
751
+ if (!names.get("api-platform_serviceToken") || !names.get("userId")) throw new Error("Cookie 必须包含非空 api-platform_serviceToken 和 userId(原输入已保留) / Cookie must include non-empty api-platform_serviceToken and userId (original input kept)");
710
752
  return text;
711
753
  }
712
754
  function canShowUsage(entry) {
@@ -760,8 +802,9 @@ window.__ModuleLoader__.load({
760
802
  if (providerId === "xiaomi-token-plan-cn") {
761
803
  const expiry = cookieExpiry(entry, t);
762
804
  if (expiry && expiry.expired) return "mimo-relogin";
763
- // rank ≥ 2 才是「临近到期」(与药丸同一档位判定),否则正常倒计时不必打扰。
764
- if (entry.state === "no-cookie" || authFailed || (expiry && expiry.rank >= 2)) return "mimo-login";
805
+ if (entry.state === "no-cookie" || authFailed) return "mimo-login";
806
+ // rank ≥ 2 才是「临近到期」(与药丸同一档位判定):还没失效,不要说「需要登录」。
807
+ if (expiry && expiry.rank >= 2) return "mimo-renew";
765
808
  return null;
766
809
  }
767
810
  if (entry.state === "no-key" || entry.state === "no-cookie" || authFailed) return "credentials";
@@ -1089,7 +1132,7 @@ window.__ModuleLoader__.load({
1089
1132
  const activeLabel = ccState.status !== "ready" ? null
1090
1133
  : ccState.activeId === "" ? t("ccAccountAuto")
1091
1134
  : `${activeName} · ${t("ccAccountActive")}`;
1092
- const optionCount = ccState.status === "ready" ? ccState.accounts.length + 1 : 0;
1135
+ const optionCount = ccState.status === "ready" ? ccState.accounts.length : 0;
1093
1136
  return h("details", { style: { marginTop: 10, paddingTop: 8, borderTop: "1px solid rgba(128,128,128,.25)" } },
1094
1137
  h("summary", { style: { cursor: "pointer", fontSize: 11, opacity: 0.8 } },
1095
1138
  `${t("ccAccountsTitle")}:${activeLabel ?? (ccState.status === "loading" || ccState.status === "idle" ? t("loading") : t("ccAccountsUnavailable"))}${optionCount > 1 ? `(${optionCount})` : ""}`),
@@ -1219,7 +1262,7 @@ window.__ModuleLoader__.load({
1219
1262
  const windowName = (w) => w.budgetScope ? t({ key: "budgetKey", user: "budgetUser", team: "budgetTeam", scope: "budgetScope" }[w.budgetScope] ?? "budgetScope") : t(KIND_LABEL[w.kind] ?? "wall") + (w.detail?.scope === "all" ? ` · ${t("scopeAllShort")}` : "");
1220
1263
  const windowShort = (w) => multi ? `${t(KIND_SHORT[w.kind] ?? "srolling")} ` : "";
1221
1264
  const windowSub = (w) => {
1222
- const head = t(multi ? "statusWorst" : "statusOnly").replace("{w}", windowName(w)).replace("{p}", String(windowLeft(w)));
1265
+ const head = t(multi && worstTier?.rank >= 2 ? "statusWorst" : "statusOnly").replace("{w}", windowName(w)).replace("{p}", String(windowLeft(w)));
1223
1266
  const ms = w.resetsAt ? Date.parse(w.resetsAt) - Date.now() : NaN;
1224
1267
  return ms > 0 ? `${head} · ${t("statusResetIn").replace("{rest}", durationText(ms, t))}` : head;
1225
1268
  };
@@ -1246,7 +1289,7 @@ window.__ModuleLoader__.load({
1246
1289
  return { ...base, key: "blocked", color: worstTier.color, icon: "✕", title, sub: windowSub(worst), short: windowShort(worst) + t(worst.cascade ? "pillBlocked" : "pillLimited"), tinted: true };
1247
1290
  }
1248
1291
  if (expiryActionable) {
1249
- return { ...base, key: "cookie", color: expiry.color, icon: expiry.icon, title: expiry.label, sub: t("mimoCookieExpiresAt").replace("{time}", new Date(expiry.expiresAt).toLocaleString(getLocale())), short: expiry.label, tinted: true };
1292
+ return { ...base, key: "cookie", color: expiry.color, icon: expiry.icon, title: expiry.label, sub: t("mimoCookieExpiresAt").replace("{time}", new Date(expiry.expiresAt).toLocaleString(getLocale())), short: expiry.short || expiry.label, tinted: true };
1250
1293
  }
1251
1294
  if (needConfig) {
1252
1295
  return { ...base, key: "config", color: INFO, icon: "⚙", title: t(state === "no-key" ? "statusNoKey" : "statusNoCookie"), sub: needConfig, short: t(state === "no-key" ? "needConfigShort" : "statusNoCookie"), tinted: true };
@@ -1313,7 +1356,7 @@ window.__ModuleLoader__.load({
1313
1356
  ].filter(Boolean).join(" · ");
1314
1357
  const resetParts = [
1315
1358
  resetLine(w, t, getLocale),
1316
- w.cascade ? t("cascadeLimited") + (w.detail && w.detail.blockedBy ? ` [${w.detail.blockedBy.join(", ")}]` : "") : null
1359
+ w.cascade ? t("cascadeLimited") + (w.detail && w.detail.blockedBy?.length ? ` · ${w.detail.blockedBy.map(kind => windowKindLabel(kind, t)).join(" / ")}` : "") : null
1317
1360
  ].filter(Boolean).join(" · ");
1318
1361
  // 档位词只在「需要留意」时出现:充足时绿色长条与大数字已经说明一切,再贴一个「充足」只是噪音。
1319
1362
  const alert = tier.rank >= 2;
@@ -1345,7 +1388,7 @@ window.__ModuleLoader__.load({
1345
1388
  function UsageWindowList({ windows, t, getLocale, uncertain = false }) {
1346
1389
  return (windows || []).map((w, index) => h(react.Fragment, { key: `${w.budgetScope ?? w.kind}:${index}` },
1347
1390
  w.groupLabel && w.groupLabel !== windows[index - 1]?.groupLabel
1348
- ? h("p", { role: "note", style: { margin: index ? "14px 0 0" : "6px 0 0", fontSize: 11, opacity: 0.6 } }, w.groupLabel)
1391
+ ? h("p", { role: "note", style: { margin: index ? "14px 0 0" : "6px 0 0", fontSize: 11, opacity: 0.6 } }, displayGroupLabel(w.groupLabel, t))
1349
1392
  : null,
1350
1393
  h(UsageWindowRow, { w, t, getLocale, uncertain })));
1351
1394
  }
@@ -1458,18 +1501,18 @@ window.__ModuleLoader__.load({
1458
1501
  display: "inline-flex", alignItems: "center", gap: 8, padding: st.short ? "4px 11px 4px 9px" : "5px 10px", minHeight: 26, boxSizing: "border-box",
1459
1502
  borderRadius: 999, border: `1px solid ${st.tinted ? alpha(st.color, ".45") : "rgba(128,128,128,.3)"}`,
1460
1503
  background: st.tinted ? alpha(st.color, ".10") : "transparent",
1461
- color: ["balance", "paygo"].includes(st.key) && st.tinted ? `color-mix(in srgb, ${st.color} 65%, currentColor)` : "inherit", font: "inherit", fontSize: 12, fontWeight: 600, cursor: "pointer", whiteSpace: "nowrap"
1504
+ color: ["balance", "paygo"].includes(st.key) && st.tinted ? `color-mix(in srgb, ${st.color} 65%, currentColor)` : "inherit", font: "inherit", fontSize: 12, fontWeight: 600, cursor: "pointer", whiteSpace: "nowrap", maxWidth: "min(46vw, 240px)", overflow: "hidden"
1462
1505
  }
1463
1506
  },
1464
1507
  glyph,
1465
- st.short ? h("span", null, st.short) : null
1508
+ st.short ? h("span", { style: { overflow: "hidden", textOverflow: "ellipsis" } }, st.short) : null
1466
1509
  ),
1467
1510
  open && h("div", {
1468
1511
  role: "dialog",
1469
1512
  "aria-label": `${label} ${t("nav")}`,
1470
1513
  style: {
1471
1514
  position: "absolute", bottom: "calc(100% + 6px)", right: 0, zIndex: 50,
1472
- width: "min(340px, calc(100vw - 24px))", boxSizing: "border-box", overflowWrap: "anywhere", padding: 14, borderRadius: 14,
1515
+ width: "min(340px, calc(100vw - 24px))", maxHeight: "min(70vh, calc(100vh - 72px))", overflowY: "auto", boxSizing: "border-box", overflowWrap: "anywhere", padding: 14, borderRadius: 14,
1473
1516
  background: "var(--dsh-surface, Canvas)", color: "var(--dsh-text, CanvasText)",
1474
1517
  border: "1px solid rgba(128,128,128,.28)",
1475
1518
  boxShadow: "0 12px 32px rgba(0,0,0,.28)", font: "12px/1.5 sans-serif"
@@ -1487,16 +1530,19 @@ window.__ModuleLoader__.load({
1487
1530
  expiry && st.key !== "cookie" ? h("p", { role: "status", style: { margin: "8px 0 0", fontSize: 11, color: expiring ? textTone(expiry.color) : "inherit", opacity: expiring ? 1 : 0.75 } }, `${expiry.label} · ${t("mimoCookieExpiresAt").replace("{time}", new Date(expiry.expiresAt).toLocaleString(getLocale()))}`) : null,
1488
1531
  // 用量账户标注:账户区 ready 时说明上方数字属于哪个账户;自动轮换时明确不跟随实际服务账户。
1489
1532
  providerId === "commandcode" && ccState && ccState.status === "ready"
1490
- ? h("p", { style: { margin: "6px 0 0", fontSize: 11, opacity: 0.75 } },
1533
+ ? h("p", { role: "note", style: ccState.activeId === ""
1534
+ ? { margin: "8px 0 0", fontSize: 11, padding: "6px 8px", borderRadius: 8, background: "rgba(234,179,8,.10)", border: "1px solid rgba(234,179,8,.35)" }
1535
+ : { margin: "6px 0 0", fontSize: 11, opacity: 0.75 } },
1491
1536
  t("ccUsageAccount").replace("{account}", ccState.activeId === "" || ccState.activeId === "default" ? t("ccAccountDefault") : (ccState.accounts.find((a) => a.id === ccState.activeId)?.label ?? ccState.activeId)),
1492
- ccState.activeId === "" ? h("span", { style: { opacity: 0.85 } }, ` · ${t("ccUsageAutoNote")}`) : null)
1537
+ ccState.activeId === "" ? h("span", null, ` · ${t("ccUsageAutoNote")}`) : null)
1493
1538
  : null,
1494
1539
  // 自动轮换池健康度:默认账户之外的账户也可能已限额,池耗尽时药丸已染红,这里给出逐账户明细。
1495
1540
  // 直接函数调用(非 React 元素),与 UsageWindowList 等视觉原语同约定,测试桩可见展开内容。
1496
1541
  pool ? CommandCodePoolSummary({ pool, cc, t }) : null,
1542
+ pool?.mode === "auto" && windows.length ? h("p", { style: { margin: "8px 0 0", fontSize: 11, fontWeight: 600, opacity: 0.8 } }, t("ccWindowsDefault")) : null,
1497
1543
  // 额度线切换时插一条分组标题:AFP 的日限额只覆盖视觉 / 语音模型与 Harness,与文本模型的
1498
1544
  // 5 小时 / 周 / 月不是同一条线,混排会让人把两条线的配额当成一组来互相比较。
1499
- UsageWindowList({ windows, t, getLocale, uncertain: degraded }),
1545
+ UsageWindowList({ windows, t, getLocale, uncertain: degraded || !!(expiry && expiry.expired) }),
1500
1546
  // 余额等补充项:套餐名已在标题行,这里只剩「标签 … 数值」两列。
1501
1547
  entry && entry.extras && entry.extras.some((x) => x.kind !== "plan")
1502
1548
  ? h("div", { style: { marginTop: 10, padding: "6px 12px", borderRadius: 10, background: "rgba(128,128,128,.07)" } },
@@ -1561,7 +1607,7 @@ window.__ModuleLoader__.load({
1561
1607
  patch.keyUpdate = { action: draft.keyAction };
1562
1608
  if (draft.keyAction === "replace") {
1563
1609
  const key = stripInvisible(draft.key);
1564
- if (!key) throw new Error("API Key 不能为空");
1610
+ if (!key) throw new Error("API Key 不能为空 / API key cannot be empty");
1565
1611
  patch.keyUpdate.value = key;
1566
1612
  }
1567
1613
  if (id === "zai-coding-cn") patch.zai = { ...draft.zai, organization: stripInvisible(draft.zai.organization), project: stripInvisible(draft.zai.project) };
@@ -1664,7 +1710,7 @@ window.__ModuleLoader__.load({
1664
1710
  try { window.localStorage.setItem("dsh-subusage:last-provider", id); } catch {}
1665
1711
  };
1666
1712
  const refresh = async (all) => {
1667
- setRefreshing(true);
1713
+ setRefreshing(all ? "all" : "current");
1668
1714
  try { if (!result.settings) await (usageStore.readSettings ?? usageStore.readAll)(); else await usageStore.refresh(all ? PROVIDER_ORDER : usageId ? [usageId] : [], true); if (aliveRef.current) setLoadError(null); }
1669
1715
  catch (error) { if (aliveRef.current) setLoadError(localError(error.code, String(error.message || error), t)); }
1670
1716
  finally { if (aliveRef.current) setRefreshing(false); }
@@ -1732,7 +1778,7 @@ window.__ModuleLoader__.load({
1732
1778
  }, [usageTab, visibleIds.join("|")]);
1733
1779
  const usageId = visibleIds.includes(usageTab) ? usageTab : visibleIds[0];
1734
1780
  const usageEntry = entries.find(e => e.providerId === usageId);
1735
- const usageMeta = PROVIDER_META[usageId];
1781
+ const usageTitle = usageId ? providerDisplayName(usageId, "label", getLocale) : "";
1736
1782
  // 需要行动时(缺凭据/凭据被拒/Cookie 即将到期)在用量面板里内联修复入口。
1737
1783
  const usageAction = usageActionKind(usageEntry, usageId, t);
1738
1784
  // Command Code 自动轮换:池耗尽时横幅按池中一条限额窗口染红(与药丸同一结论)。
@@ -1848,12 +1894,12 @@ window.__ModuleLoader__.load({
1848
1894
  h("p", { style: { opacity: .75 } }, text("开关立即保存,仅控制本插件的订阅检测和显示,不删除凭据或禁用模型路由。", "Switches save immediately and only affect this plugin's usage detection and display, not credentials or model routes.")),
1849
1895
  h("div", { style: { border: 0, margin: 0, padding: 0, minWidth: 0 } },
1850
1896
  !editorOpen && notice ? h("p", { role: "status", style: { color: notice.ok ? "inherit" : "#dc2626" } }, notice.text) : null,
1851
- h("button", { type: "button", role: "switch", "aria-checked": hideWithoutApi, disabled: busy || dirty || !result.settings?.revision, "aria-label": text("没有检测到API的默认隐藏", "Hide providers without detected API credentials"), style: hideWithoutApi ? primary : btnStyle, onClick: () => saveVisibility({ hideWithoutApi: !hideWithoutApi }) }, `${hideWithoutApi ? "✓ " : "○ "}${text("没有检测到API的默认隐藏", "Hide providers without detected API credentials")}`),
1852
- h("p", { style: { fontSize: 12, opacity: .75 } }, text("检测依据为可用 Key 或 MiMo Cookie 是否存在;认证失败和网络错误不会当成未配置 API。隐藏的提供商仍可在这里配置。", "Detection checks for a Key or MiMo Cookie, not request success. Auth/network errors are not treated as missing API credentials. Hidden providers remain configurable here.")),
1897
+ h("button", { type: "button", role: "switch", "aria-checked": hideWithoutApi, disabled: busy || dirty || !result.settings?.revision, "aria-label": text("没配 Key 或登录的,默认不显示", "Hide providers with no key or login"), style: hideWithoutApi ? primary : btnStyle, onClick: () => saveVisibility({ hideWithoutApi: !hideWithoutApi }) }, `${hideWithoutApi ? "✓ " : "○ "}${text("没配 Key 或登录的,默认不显示", "Hide providers with no key or login")}`),
1898
+ h("p", { style: { fontSize: 12, opacity: .75 } }, text("没填 API Key、也没登录的提供商,上面的标签栏先藏起来;认证失败或网络错误不会当成没配置。藏起来的仍可以在下面打开。", "Providers with no API key or login are hidden from the tab bar. Auth or network errors are not treated as missing credentials. Hidden providers stay configurable below.")),
1853
1899
  // 启用的排到最前面,省得每次往下翻;组内保持 PROVIDER_ORDER 的既有顺序(sort 稳定)。
1854
1900
  // Ark 的 4 条路由合并成一张卡片:只渲染本机装了的那条作代表(都没装就渲染第一条),
1855
1901
  // 由它统一控制整组的检测开关与那组共享的 AK/SK。
1856
- h("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 240px), 1fr))", gap: 8 } }, [...PROVIDER_ORDER].filter(id => !PROVIDER_META[id]?.volc || id === (ARK_IDS.find(key => providerRoutable(result, key)) ?? ARK_IDS[0])).sort((a, b) => Number(!providerEnabled(result.settings, a)) - Number(!providerEnabled(result.settings, b))).map(id => {
1902
+ h("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(min(100%, 240px), 1fr))", gap: 8 } }, [...PROVIDER_ORDER].filter(id => !PROVIDER_META[id]?.volc || id === (ARK_IDS.find(key => providerRoutable(result, key)) ?? ARK_IDS[0])).sort((a, b) => Number(!providerEnabled(result.settings, a)) - Number(!providerEnabled(result.settings, b))).map(id => {
1857
1903
  const isArk = Boolean(PROVIDER_META[id].volc), arkPresent = isArk ? ARK_IDS.filter(key => providerRoutable(result, key)) : [];
1858
1904
  const enabled = providerEnabled(result.settings, id), e = entries.find(item => item.providerId === id), editing = tab === id && editorOpen;
1859
1905
  // Ark 卡片代表整组:标题用组名、开关写组级字段,并说明本机实际装了哪几条路由。
@@ -1878,10 +1924,10 @@ window.__ModuleLoader__.load({
1878
1924
  onClick: () => { if (troubledIds.length) chooseUsage(troubledIds[0]); },
1879
1925
  title: troubledIds.length ? text(`跳到 ${PROVIDER_META[troubledIds[0]].short}(共 ${troubledIds.length} 家待处理)`, `Jump to ${PROVIDER_META[troubledIds[0]].short} (${troubledIds.length} need attention)`) : text("全部获取成功", "All fetched"),
1880
1926
  "aria-label": text(`${success}/${visibleIds.length} 家数据获取成功;${troubledIds.length ? `点击跳到 ${PROVIDER_META[troubledIds[0]].short}` : "没有待处理项"}`, `${success}/${visibleIds.length} providers fetched; ${troubledIds.length ? `click to jump to ${PROVIDER_META[troubledIds[0]].short}` : "nothing needs attention"}`),
1881
- style: { ...btnStyle, padding: "2px 8px", fontSize: 12, opacity: .85, cursor: troubledIds.length ? "pointer" : "default", color: troubledIds.length ? "#b7791f" : "inherit" }
1882
- }, text(`${success}/${visibleIds.length} 家数据获取成功`, `${success}/${visibleIds.length} providers fetched`)) : null,
1883
- h("button", { type: "button", disabled: busy || refreshing || !usageId, style: { ...primary, cursor: busy || refreshing || !usageId ? "default" : "pointer" }, onClick: () => refresh(false) }, refreshing ? t("refreshing") : text("刷新当前", "Refresh current")),
1884
- h("button", { type: "button", disabled: busy || refreshing, style: { ...btnStyle, cursor: busy || refreshing ? "default" : "pointer" }, onClick: () => refresh(true) }, refreshing ? t("refreshing") : text("刷新全部", "Refresh all"))
1927
+ style: { ...btnStyle, padding: "2px 8px", fontSize: 12, opacity: .85, cursor: troubledIds.length ? "pointer" : "default", color: troubledIds.length ? "#b7791f" : "inherit", textDecoration: troubledIds.length ? "underline" : "none" }
1928
+ }, text(`${success}/${visibleIds.length} 家数据获取成功${troubledIds.length ? " · 查看问题" : ""}`, `${success}/${visibleIds.length} providers fetched${troubledIds.length ? " · view issues" : ""}`)) : null,
1929
+ h("button", { type: "button", disabled: busy || refreshing || !usageId, style: { ...primary, cursor: busy || refreshing || !usageId ? "default" : "pointer" }, onClick: () => refresh(false) }, refreshing === "current" ? text("正在刷新当前…", "Refreshing current…") : text("刷新当前", "Refresh current")),
1930
+ h("button", { type: "button", disabled: busy || refreshing, style: { ...btnStyle, cursor: busy || refreshing ? "default" : "pointer" }, onClick: () => refresh(true) }, refreshing === "all" ? text("正在刷新全部…", "Refreshing all…") : text("刷新全部", "Refresh all"))
1885
1931
  ),
1886
1932
  loadError ? h("p", { role: "alert", style: { color: "#dc2626", overflowWrap: "anywhere" } }, loadError) : null,
1887
1933
  initialized && !visibleIds.length ? h("p", { role: "status" }, text("没有可显示的提供商,请展开下方「提供商管理」开启、配置 API,或关闭默认隐藏。", "No visible providers. Expand Provider management below to enable a provider, configure API credentials, or turn off auto-hide.")) : null,
@@ -1892,7 +1938,7 @@ window.__ModuleLoader__.load({
1892
1938
  // 无窗口成功是中性 ℹ 而不是绿 ?(没有额度窗口不能默认标绿)。
1893
1939
  const tabStatus = computeStatus(e, { failed: e && e.state !== "ok" ? e.error || e.state : null, failState: e && e.state, providerId: id, t, getLocale });
1894
1940
  return h("button", { key: id, id: `subusage-tab-${id}`, role: "tab", type: "button", tabIndex: active || (!visibleIds.includes(usageId) && index === 0) ? 0 : -1, "aria-selected": active,
1895
- "aria-controls": "subusage-provider-panel", title: `${PROVIDER_META[id].label}: ${providerDescription(e, t)}`, "aria-label": `${PROVIDER_META[id].short}: ${providerDescription(e, t)}`,
1941
+ "aria-controls": "subusage-provider-panel", title: `${providerDisplayName(id, "label", getLocale)}: ${providerDescription(e, t)}`, "aria-label": `${providerDisplayName(id, "short", getLocale)}: ${providerDescription(e, t)}`,
1896
1942
  onClick: () => chooseUsage(id), onKeyDown: (event) => {
1897
1943
  let next;
1898
1944
  if (event.key === "ArrowRight" || event.key === "ArrowDown") next = (index + 1) % visibleIds.length;
@@ -1903,23 +1949,24 @@ window.__ModuleLoader__.load({
1903
1949
  }, style: { ...btnStyle, display: "inline-flex", alignItems: "center", gap: 6, minWidth: 0, padding: "5px 12px 5px 8px", whiteSpace: "nowrap", fontSize: 12, fontWeight: active ? 600 : 400, borderRadius: 999, border: "1px solid transparent", background: active ? alpha(INFO, ".16") : "transparent" }
1904
1950
  }, statusGlyph(tabStatus.color, tabStatus.icon, 14), PROVIDER_META[id].short);
1905
1951
  })) : null,
1906
- !!usageId ? h("section", { role: "tabpanel", id: "subusage-provider-panel", "aria-label": usageMeta.label, style: { marginTop: 12, border: "1px solid rgba(128,128,128,.25)", borderRadius: 14, padding: 16 } },
1952
+ !!usageId ? h("section", { role: "tabpanel", id: "subusage-provider-panel", "aria-label": usageTitle, style: { marginTop: 12, border: "1px solid rgba(128,128,128,.25)", borderRadius: 14, padding: 16 } },
1907
1953
  h("div", { style: { display: "flex", alignItems: "center", gap: 8 } },
1908
- h("strong", { style: { fontSize: 14 } }, usageMeta.label),
1954
+ h("strong", { style: { fontSize: 14 } }, usageTitle),
1909
1955
  h("span", { style: { flex: 1 } }),
1910
1956
  planName(usageEntry) ? h("span", { style: { fontSize: 11, fontWeight: 600, padding: "1px 8px", borderRadius: 999, background: "rgba(128,128,128,.16)", whiteSpace: "nowrap" } }, planName(usageEntry)) : null),
1911
1957
  // 一眼结论:与药丸、弹层同一个状态判定。读取成功只说明接口健康,额度档位另有结论。
1912
1958
  statusBanner(usageStatus),
1913
1959
  usageEntry && usageEntry.error && usageStatus.key !== "error" ? h("p", { role: "alert", title: usageEntry.error, style: { color: "#dc2626", overflowWrap: "anywhere" } }, localError(usageEntry.errorCode, usageEntry.error, t)) : null,
1914
1960
  usageAction ? h("div", { role: "group", "aria-label": text("建议操作", "Suggested action"), style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", margin: "10px 0", padding: "8px 10px", border: "1px solid rgba(234,179,8,.45)", borderRadius: 8, background: "rgba(234,179,8,.08)" } },
1915
- h("span", { style: { flex: 1, minWidth: 150, fontSize: 12 } }, usageAction === "mimo-relogin" ? text("Cookie 已过期,重新登录即可恢复。", "Cookie expired — sign in again to restore.") : usageAction === "mimo-login" ? text("需要登录 MiMo(或 Cookie 即将到期)。", "MiMo sign-in needed (or the cookie is about to expire).") : text("这家还没有可用的凭据。", "No usable credentials for this provider.")),
1916
- usageAction !== "credentials" ? h("button", { type: "button", style: primary, disabled: busy || loginActive, onClick: startLogin }, usageAction === "mimo-relogin" ? text("重新登录", "Sign in again") : text("登录并自动导入", "Sign in & import")) : null,
1961
+ h("span", { style: { flex: 1, minWidth: 150, fontSize: 12 } }, usageAction === "mimo-relogin" ? text("Cookie 已过期,重新登录即可恢复。", "Cookie expired — sign in again to restore.") : usageAction === "mimo-renew" ? text("Cookie 即将到期,现在续期可避免中断。", "Cookie expires soon — renew now to avoid interruption.") : usageAction === "mimo-login" ? text("需要登录 MiMo。", "MiMo sign-in needed.") : text("这家还没有可用的凭据。", "No usable credentials for this provider.")),
1962
+ usageAction !== "credentials" ? h("button", { type: "button", style: primary, disabled: busy || loginActive, onClick: startLogin }, usageAction === "mimo-login" ? text("登录并自动导入", "Sign in & import") : text("重新登录", "Sign in again")) : null,
1917
1963
  h("button", { type: "button", style: usageAction === "credentials" ? primary : btnStyle, disabled: busy, onClick: () => revealEditor(usageId) }, usageAction === "credentials" ? text("配置凭据", "Configure credentials") : text("手动填写", "Enter manually"))) : null,
1918
1964
  // 自动轮换池健康度(与弹层同一函数):池耗尽时上方横幅已按池中限额窗口染红。
1919
1965
  usagePool ? CommandCodePoolSummary({ pool: usagePool, cc, t }) : null,
1966
+ usagePool?.mode === "auto" && canShowUsage(usageStatusEntry) && (usageStatusEntry.windows || []).length ? h("p", { style: { margin: "8px 0 0", fontSize: 11, fontWeight: 600, opacity: 0.8 } }, t("ccWindowsDefault")) : null,
1920
1967
  // 无窗口时的占位只给「成功但既无窗口也无余额」:余额/加载/需配置/失败等状态横幅已经说明,不再叠加「暂无额度数据」。
1921
1968
  // 窗口列表用 usageStatusEntry:池耗尽时它携带合成的限额窗口(非耗尽时与 usageEntry 同一对象)。
1922
- canShowUsage(usageStatusEntry) && (usageStatusEntry.windows || []).length ? UsageWindowList({ windows: usageStatusEntry.windows, t, getLocale, uncertain: usageStatusEntry.coverage === "partial" || usageStatusEntry.freshness === "stale" || usageStatusEntry.state !== "ok" })
1969
+ canShowUsage(usageStatusEntry) && (usageStatusEntry.windows || []).length ? UsageWindowList({ windows: usageStatusEntry.windows, t, getLocale, uncertain: usageStatusEntry.coverage === "partial" || usageStatusEntry.freshness === "stale" || usageStatusEntry.state !== "ok" || !!(cookieExpiry(usageEntry, t)?.expired) })
1923
1970
  : usageStatus.key === "balance" && !(usageStatus.entry && balanceText(usageStatus.entry, t)) ? h("p", { style: { opacity: .75 } }, t("noUsageData")) : null,
1924
1971
  canShowUsage(usageEntry) && usageEntry.extras && usageEntry.extras.some((x) => x.kind !== "plan")
1925
1972
  ? h("div", { style: { marginTop: 10, padding: "6px 12px", borderRadius: 10, background: "rgba(128,128,128,.07)" } },
@@ -2006,7 +2053,7 @@ window.__ModuleLoader__.load({
2006
2053
  // 数据来自同一份订阅(实测两条路由的请求 URL 逐字相同)。
2007
2054
  const resolved = ROUTE_PROVIDER_ALIAS[provider] ?? provider;
2008
2055
  const info = PROVIDER_ORDER.includes(resolved) ? PROVIDER_META[resolved] : null;
2009
- const meta = info ? { label: resolved === "commandcode" ? info.label : info.short, id: resolved } : null;
2056
+ const meta = info ? { label: resolved === "commandcode" ? providerDisplayName(resolved, "label", getLocale) : providerDisplayName(resolved, "short", getLocale), id: resolved } : null;
2010
2057
  // 所有 Hook 必须先于条件返回:模型切换或重连时 provider 可能暂时为空。
2011
2058
  // 稳定引用:否则父组件每次渲染都新建函数,药丸 effect 会反复重启轮询。
2012
2059
  const providerId = meta ? meta.id : null;
package/lib/index.js CHANGED
@@ -11,7 +11,7 @@ import { MimoLogin } from "./mimo-login.js";
11
11
  import { parseVolcAgentPlan, parseVolcCodingPlan, parseVolcSeatAfp, parseVolcSeatCoding, parseVolcSeatIds, volcError, volcSignature } from "./volcengine.js";
12
12
 
13
13
  const name = "dsh-subusage";
14
- const VERSION = "0.13.1";
14
+ const VERSION = "0.13.2";
15
15
  const inject = ["llm"];
16
16
  const PROVIDERS = {
17
17
  "zai-coding-cn": { label: "Z.ai", envName: "ZAI_CODING_CN_API_KEY" },
@@ -774,7 +774,7 @@ export function normalizeCodexUsage(body) {
774
774
  const memberLimit = Number(member?.limit), memberUsed = member?.used == null ? NaN : Number(member.used);
775
775
  if (member && Number.isFinite(memberLimit) && memberLimit > 0 && Number.isFinite(memberUsed) && memberUsed >= 0) {
776
776
  const reset = numeric(member.reset_at) && member.reset_at > 0 ? new Date(member.reset_at * 1000).toISOString() : undefined;
777
- windows.push({ ...windowRow("period", memberUsed / memberLimit * 100, reset, undefined, { used: memberUsed, limit: memberLimit, unit: "credits", scope: "member-budget" }), groupLabel: "Member budget" });
777
+ windows.push({ ...windowRow("period", memberUsed / memberLimit * 100, reset, undefined, { used: memberUsed, limit: memberLimit, unit: "credits", scope: "member-budget" }), groupLabel: "groupMemberBudget" });
778
778
  }
779
779
  const reached = body?.rate_limit_reached_type?.kind;
780
780
  const restricted = control?.reached === true || ["workspace_owner_credits_depleted", "workspace_member_credits_depleted", "workspace_owner_usage_limit_reached", "workspace_member_usage_limit_reached"].includes(reached);
package/lib/volcengine.js CHANGED
@@ -135,10 +135,10 @@ export function parseVolcCodingPlan(body) {
135
135
  // 而日限额只在视觉模型、语音模型与 Harness 上生效,是另一条线。两组各自成组展示并标注影响范围,
136
136
  // 否则把两条线的配额并排放在一起,谁都会把"日比周还高"读成插件算错了。
137
137
  const AFP_WINDOWS = [
138
- ["AFPFiveHour", "5h", "文本 / 向量模型"],
139
- ["AFPWeekly", "week", "文本 / 向量模型"],
140
- ["AFPMonthly", "month", "文本 / 向量模型"],
141
- ["AFPDaily", "day", "视觉 / 语音模型与 Harness"]
138
+ ["AFPFiveHour", "5h", "afpGroupText"],
139
+ ["AFPWeekly", "week", "afpGroupText"],
140
+ ["AFPMonthly", "month", "afpGroupText"],
141
+ ["AFPDaily", "day", "afpGroupVision"]
142
142
  ];
143
143
 
144
144
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-subusage",
3
- "version": "0.13.1",
3
+ "version": "0.13.2",
4
4
  "displayName": "订阅用量显示",
5
5
  "description": "显示独立 DeepSeek API Key 与 DSH 本机账号余额(账号入口默认开启)、火山方舟(arkcli 官方 CLI 路由:Agent / Coding Plan 个人版与企业版席位,设置页合并为一组)、Z.ai(中国与国际)、Kimi、MiMo、OpenCode Go、Command Code、SuperGrok、Codex(ChatGPT 订阅,默认关闭)、MiniMax(中国)、Synthetic、NanoGPT、SiliconFlow、OpenRouter、Novita、Hyperbolic、DeepInfra、Chutes、Ollama Cloud、Vercel AI Gateway、ZenMux、LiteLLM 的订阅余量与余额;共享刷新与凭据管理,默认关闭 17 家。",
6
6
  "keywords": [