dsh-subusage 0.13.0 → 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 +1 -1
- package/lib/client.js +131 -71
- package/lib/index.js +2 -2
- package/lib/volcengine.js +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
|
-
> **0.13.
|
|
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: "
|
|
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
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
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
|
-
|
|
539
|
-
if (left <=
|
|
540
|
-
return { rank:
|
|
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
|
-
|
|
764
|
-
|
|
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";
|
|
@@ -821,8 +864,24 @@ window.__ModuleLoader__.load({
|
|
|
821
864
|
snapshot = { ...snapshot, ...value, entries: [...entries.values()] };
|
|
822
865
|
emit(); return snapshot;
|
|
823
866
|
};
|
|
867
|
+
const readSettings = () => {
|
|
868
|
+
if (snapshot.settings) return Promise.resolve(snapshot);
|
|
869
|
+
if (!settingsInitial) {
|
|
870
|
+
const request = markRequest([]);
|
|
871
|
+
settingsInitial = Promise.resolve().then(() => call("read", { providerIds: [], force: false, commandCodeAccount: ccAccount })).then(value => merge(value, request)).catch(error => { settingsInitial = null; throw error; });
|
|
872
|
+
}
|
|
873
|
+
return settingsInitial.then(() => snapshot);
|
|
874
|
+
};
|
|
824
875
|
const refresh = (ids, force = false) => {
|
|
825
876
|
if (disposed) return Promise.reject(new Error("Client 已卸载"));
|
|
877
|
+
// Codex 等默认关闭的入口必须先读取用户保存的开关,否则冷启动会过滤自己,
|
|
878
|
+
// 又没有任何 RPC 载入 settings,直到切去默认开启的其他厂商才恢复。
|
|
879
|
+
if (!snapshot.settings && ids.some(id => PROVIDER_META[id]?.defaultEnabled === false)) {
|
|
880
|
+
return readSettings().then(() => {
|
|
881
|
+
if (!snapshot.settings) throw new Error("Client 设置尚未读取");
|
|
882
|
+
return refresh(ids, force);
|
|
883
|
+
});
|
|
884
|
+
}
|
|
826
885
|
// Ark 必须豁免路由检查:它的额度是账号级管控面订阅(一组 AK/SK 查名下全部套餐),
|
|
827
886
|
// providerVisible 对 volc 同样不看推理路由——这里若按 providerRoutable 过滤,
|
|
828
887
|
// 「有套餐但本机没装对应 arkcli 路由」的条目在激活刷新、重置到期补拉与定时刷新时
|
|
@@ -835,8 +894,12 @@ window.__ModuleLoader__.load({
|
|
|
835
894
|
if (inflight.has(id)) return inflight.get(id);
|
|
836
895
|
const startedEpoch = epoch, request = markRequest([id]);
|
|
837
896
|
const task = Promise.resolve().then(() => call("refresh", { providerIds: [id], force: !!force, commandCodeAccount: ccAccount })).then((value) => startedEpoch === epoch ? merge(value, request) : snapshot).catch((error) => {
|
|
838
|
-
//
|
|
839
|
-
|
|
897
|
+
// 传输失败不能沿用旧额度;但也不能当作「凭据消失」的证据。
|
|
898
|
+
// 否则 Codex 的无路由查询豁免随 apiDetected 丢失:药丸隐藏且后续刷新被过滤。
|
|
899
|
+
if (startedEpoch === epoch) {
|
|
900
|
+
const previous = snapshot.entries.find(entry => entry.providerId === id);
|
|
901
|
+
merge({ entries: [{ providerId: id, ...(previous?.apiDetected !== undefined ? { apiDetected: previous.apiDetected } : {}), ...(previous?.keySource ? { keySource: previous.keySource } : {}), state: "error", error: error instanceof Error ? error.message : String(error), freshness: "unknown", coverage: "partial", windows: [], extras: [] }] }, request);
|
|
902
|
+
}
|
|
840
903
|
throw error;
|
|
841
904
|
}).finally(() => { if (inflight.get(id) === task) inflight.delete(id); });
|
|
842
905
|
inflight.set(id, task);
|
|
@@ -895,14 +958,7 @@ window.__ModuleLoader__.load({
|
|
|
895
958
|
return {
|
|
896
959
|
subscribe, getSnapshot: () => snapshot, refresh, activate,
|
|
897
960
|
// 设置页先取本机元数据,不等待所有厂商的网络额度请求。
|
|
898
|
-
readSettings
|
|
899
|
-
if (snapshot.settings) return Promise.resolve(snapshot);
|
|
900
|
-
if (!settingsInitial) {
|
|
901
|
-
const request = markRequest([]);
|
|
902
|
-
settingsInitial = Promise.resolve().then(() => call("read", { providerIds: [], force: false, commandCodeAccount: ccAccount })).then(value => merge(value, request)).catch(error => { settingsInitial = null; throw error; });
|
|
903
|
-
}
|
|
904
|
-
return settingsInitial.then(() => snapshot);
|
|
905
|
-
},
|
|
961
|
+
readSettings,
|
|
906
962
|
getCommandCodeAccount: () => ccAccount, setCommandCodeAccount,
|
|
907
963
|
startMimoLogin: async (request) => { const generation = ++loginGeneration, startedEpoch = epoch; loginJobId = null; return acceptLogin(await call("startMimoLogin", request), generation, startedEpoch); },
|
|
908
964
|
getMimoLoginStatus: async () => { const generation = loginGeneration, startedEpoch = epoch, jobId = loginPending ? loginJobId : null; return acceptLogin(await call("getMimoLoginStatus"), generation, startedEpoch, jobId); },
|
|
@@ -1076,7 +1132,7 @@ window.__ModuleLoader__.load({
|
|
|
1076
1132
|
const activeLabel = ccState.status !== "ready" ? null
|
|
1077
1133
|
: ccState.activeId === "" ? t("ccAccountAuto")
|
|
1078
1134
|
: `${activeName} · ${t("ccAccountActive")}`;
|
|
1079
|
-
const optionCount = ccState.status === "ready" ? ccState.accounts.length
|
|
1135
|
+
const optionCount = ccState.status === "ready" ? ccState.accounts.length : 0;
|
|
1080
1136
|
return h("details", { style: { marginTop: 10, paddingTop: 8, borderTop: "1px solid rgba(128,128,128,.25)" } },
|
|
1081
1137
|
h("summary", { style: { cursor: "pointer", fontSize: 11, opacity: 0.8 } },
|
|
1082
1138
|
`${t("ccAccountsTitle")}:${activeLabel ?? (ccState.status === "loading" || ccState.status === "idle" ? t("loading") : t("ccAccountsUnavailable"))}${optionCount > 1 ? `(${optionCount})` : ""}`),
|
|
@@ -1206,7 +1262,7 @@ window.__ModuleLoader__.load({
|
|
|
1206
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")}` : "");
|
|
1207
1263
|
const windowShort = (w) => multi ? `${t(KIND_SHORT[w.kind] ?? "srolling")} ` : "";
|
|
1208
1264
|
const windowSub = (w) => {
|
|
1209
|
-
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)));
|
|
1210
1266
|
const ms = w.resetsAt ? Date.parse(w.resetsAt) - Date.now() : NaN;
|
|
1211
1267
|
return ms > 0 ? `${head} · ${t("statusResetIn").replace("{rest}", durationText(ms, t))}` : head;
|
|
1212
1268
|
};
|
|
@@ -1233,7 +1289,7 @@ window.__ModuleLoader__.load({
|
|
|
1233
1289
|
return { ...base, key: "blocked", color: worstTier.color, icon: "✕", title, sub: windowSub(worst), short: windowShort(worst) + t(worst.cascade ? "pillBlocked" : "pillLimited"), tinted: true };
|
|
1234
1290
|
}
|
|
1235
1291
|
if (expiryActionable) {
|
|
1236
|
-
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 };
|
|
1237
1293
|
}
|
|
1238
1294
|
if (needConfig) {
|
|
1239
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 };
|
|
@@ -1300,7 +1356,7 @@ window.__ModuleLoader__.load({
|
|
|
1300
1356
|
].filter(Boolean).join(" · ");
|
|
1301
1357
|
const resetParts = [
|
|
1302
1358
|
resetLine(w, t, getLocale),
|
|
1303
|
-
w.cascade ? t("cascadeLimited") + (w.detail && w.detail.blockedBy ? `
|
|
1359
|
+
w.cascade ? t("cascadeLimited") + (w.detail && w.detail.blockedBy?.length ? ` · ${w.detail.blockedBy.map(kind => windowKindLabel(kind, t)).join(" / ")}` : "") : null
|
|
1304
1360
|
].filter(Boolean).join(" · ");
|
|
1305
1361
|
// 档位词只在「需要留意」时出现:充足时绿色长条与大数字已经说明一切,再贴一个「充足」只是噪音。
|
|
1306
1362
|
const alert = tier.rank >= 2;
|
|
@@ -1332,7 +1388,7 @@ window.__ModuleLoader__.load({
|
|
|
1332
1388
|
function UsageWindowList({ windows, t, getLocale, uncertain = false }) {
|
|
1333
1389
|
return (windows || []).map((w, index) => h(react.Fragment, { key: `${w.budgetScope ?? w.kind}:${index}` },
|
|
1334
1390
|
w.groupLabel && w.groupLabel !== windows[index - 1]?.groupLabel
|
|
1335
|
-
? 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))
|
|
1336
1392
|
: null,
|
|
1337
1393
|
h(UsageWindowRow, { w, t, getLocale, uncertain })));
|
|
1338
1394
|
}
|
|
@@ -1445,18 +1501,18 @@ window.__ModuleLoader__.load({
|
|
|
1445
1501
|
display: "inline-flex", alignItems: "center", gap: 8, padding: st.short ? "4px 11px 4px 9px" : "5px 10px", minHeight: 26, boxSizing: "border-box",
|
|
1446
1502
|
borderRadius: 999, border: `1px solid ${st.tinted ? alpha(st.color, ".45") : "rgba(128,128,128,.3)"}`,
|
|
1447
1503
|
background: st.tinted ? alpha(st.color, ".10") : "transparent",
|
|
1448
|
-
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"
|
|
1449
1505
|
}
|
|
1450
1506
|
},
|
|
1451
1507
|
glyph,
|
|
1452
|
-
st.short ? h("span",
|
|
1508
|
+
st.short ? h("span", { style: { overflow: "hidden", textOverflow: "ellipsis" } }, st.short) : null
|
|
1453
1509
|
),
|
|
1454
1510
|
open && h("div", {
|
|
1455
1511
|
role: "dialog",
|
|
1456
1512
|
"aria-label": `${label} ${t("nav")}`,
|
|
1457
1513
|
style: {
|
|
1458
1514
|
position: "absolute", bottom: "calc(100% + 6px)", right: 0, zIndex: 50,
|
|
1459
|
-
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,
|
|
1460
1516
|
background: "var(--dsh-surface, Canvas)", color: "var(--dsh-text, CanvasText)",
|
|
1461
1517
|
border: "1px solid rgba(128,128,128,.28)",
|
|
1462
1518
|
boxShadow: "0 12px 32px rgba(0,0,0,.28)", font: "12px/1.5 sans-serif"
|
|
@@ -1474,16 +1530,19 @@ window.__ModuleLoader__.load({
|
|
|
1474
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,
|
|
1475
1531
|
// 用量账户标注:账户区 ready 时说明上方数字属于哪个账户;自动轮换时明确不跟随实际服务账户。
|
|
1476
1532
|
providerId === "commandcode" && ccState && ccState.status === "ready"
|
|
1477
|
-
? h("p", {
|
|
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 } },
|
|
1478
1536
|
t("ccUsageAccount").replace("{account}", ccState.activeId === "" || ccState.activeId === "default" ? t("ccAccountDefault") : (ccState.accounts.find((a) => a.id === ccState.activeId)?.label ?? ccState.activeId)),
|
|
1479
|
-
ccState.activeId === "" ? h("span",
|
|
1537
|
+
ccState.activeId === "" ? h("span", null, ` · ${t("ccUsageAutoNote")}`) : null)
|
|
1480
1538
|
: null,
|
|
1481
1539
|
// 自动轮换池健康度:默认账户之外的账户也可能已限额,池耗尽时药丸已染红,这里给出逐账户明细。
|
|
1482
1540
|
// 直接函数调用(非 React 元素),与 UsageWindowList 等视觉原语同约定,测试桩可见展开内容。
|
|
1483
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,
|
|
1484
1543
|
// 额度线切换时插一条分组标题:AFP 的日限额只覆盖视觉 / 语音模型与 Harness,与文本模型的
|
|
1485
1544
|
// 5 小时 / 周 / 月不是同一条线,混排会让人把两条线的配额当成一组来互相比较。
|
|
1486
|
-
UsageWindowList({ windows, t, getLocale, uncertain: degraded }),
|
|
1545
|
+
UsageWindowList({ windows, t, getLocale, uncertain: degraded || !!(expiry && expiry.expired) }),
|
|
1487
1546
|
// 余额等补充项:套餐名已在标题行,这里只剩「标签 … 数值」两列。
|
|
1488
1547
|
entry && entry.extras && entry.extras.some((x) => x.kind !== "plan")
|
|
1489
1548
|
? h("div", { style: { marginTop: 10, padding: "6px 12px", borderRadius: 10, background: "rgba(128,128,128,.07)" } },
|
|
@@ -1548,7 +1607,7 @@ window.__ModuleLoader__.load({
|
|
|
1548
1607
|
patch.keyUpdate = { action: draft.keyAction };
|
|
1549
1608
|
if (draft.keyAction === "replace") {
|
|
1550
1609
|
const key = stripInvisible(draft.key);
|
|
1551
|
-
if (!key) throw new Error("API Key 不能为空");
|
|
1610
|
+
if (!key) throw new Error("API Key 不能为空 / API key cannot be empty");
|
|
1552
1611
|
patch.keyUpdate.value = key;
|
|
1553
1612
|
}
|
|
1554
1613
|
if (id === "zai-coding-cn") patch.zai = { ...draft.zai, organization: stripInvisible(draft.zai.organization), project: stripInvisible(draft.zai.project) };
|
|
@@ -1651,7 +1710,7 @@ window.__ModuleLoader__.load({
|
|
|
1651
1710
|
try { window.localStorage.setItem("dsh-subusage:last-provider", id); } catch {}
|
|
1652
1711
|
};
|
|
1653
1712
|
const refresh = async (all) => {
|
|
1654
|
-
setRefreshing(
|
|
1713
|
+
setRefreshing(all ? "all" : "current");
|
|
1655
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); }
|
|
1656
1715
|
catch (error) { if (aliveRef.current) setLoadError(localError(error.code, String(error.message || error), t)); }
|
|
1657
1716
|
finally { if (aliveRef.current) setRefreshing(false); }
|
|
@@ -1719,7 +1778,7 @@ window.__ModuleLoader__.load({
|
|
|
1719
1778
|
}, [usageTab, visibleIds.join("|")]);
|
|
1720
1779
|
const usageId = visibleIds.includes(usageTab) ? usageTab : visibleIds[0];
|
|
1721
1780
|
const usageEntry = entries.find(e => e.providerId === usageId);
|
|
1722
|
-
const
|
|
1781
|
+
const usageTitle = usageId ? providerDisplayName(usageId, "label", getLocale) : "";
|
|
1723
1782
|
// 需要行动时(缺凭据/凭据被拒/Cookie 即将到期)在用量面板里内联修复入口。
|
|
1724
1783
|
const usageAction = usageActionKind(usageEntry, usageId, t);
|
|
1725
1784
|
// Command Code 自动轮换:池耗尽时横幅按池中一条限额窗口染红(与药丸同一结论)。
|
|
@@ -1835,12 +1894,12 @@ window.__ModuleLoader__.load({
|
|
|
1835
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.")),
|
|
1836
1895
|
h("div", { style: { border: 0, margin: 0, padding: 0, minWidth: 0 } },
|
|
1837
1896
|
!editorOpen && notice ? h("p", { role: "status", style: { color: notice.ok ? "inherit" : "#dc2626" } }, notice.text) : null,
|
|
1838
|
-
h("button", { type: "button", role: "switch", "aria-checked": hideWithoutApi, disabled: busy || dirty || !result.settings?.revision, "aria-label": text("
|
|
1839
|
-
h("p", { style: { fontSize: 12, opacity: .75 } }, text("
|
|
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.")),
|
|
1840
1899
|
// 启用的排到最前面,省得每次往下翻;组内保持 PROVIDER_ORDER 的既有顺序(sort 稳定)。
|
|
1841
1900
|
// Ark 的 4 条路由合并成一张卡片:只渲染本机装了的那条作代表(都没装就渲染第一条),
|
|
1842
1901
|
// 由它统一控制整组的检测开关与那组共享的 AK/SK。
|
|
1843
|
-
h("div", { style: { display: "grid", gridTemplateColumns: "repeat(auto-
|
|
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 => {
|
|
1844
1903
|
const isArk = Boolean(PROVIDER_META[id].volc), arkPresent = isArk ? ARK_IDS.filter(key => providerRoutable(result, key)) : [];
|
|
1845
1904
|
const enabled = providerEnabled(result.settings, id), e = entries.find(item => item.providerId === id), editing = tab === id && editorOpen;
|
|
1846
1905
|
// Ark 卡片代表整组:标题用组名、开关写组级字段,并说明本机实际装了哪几条路由。
|
|
@@ -1865,10 +1924,10 @@ window.__ModuleLoader__.load({
|
|
|
1865
1924
|
onClick: () => { if (troubledIds.length) chooseUsage(troubledIds[0]); },
|
|
1866
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"),
|
|
1867
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"}`),
|
|
1868
|
-
style: { ...btnStyle, padding: "2px 8px", fontSize: 12, opacity: .85, cursor: troubledIds.length ? "pointer" : "default", color: troubledIds.length ? "#b7791f" : "inherit" }
|
|
1869
|
-
}, text(`${success}/${visibleIds.length}
|
|
1870
|
-
h("button", { type: "button", disabled: busy || refreshing || !usageId, style: { ...primary, cursor: busy || refreshing || !usageId ? "default" : "pointer" }, onClick: () => refresh(false) }, refreshing ?
|
|
1871
|
-
h("button", { type: "button", disabled: busy || refreshing, style: { ...btnStyle, cursor: busy || refreshing ? "default" : "pointer" }, onClick: () => refresh(true) }, refreshing ?
|
|
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"))
|
|
1872
1931
|
),
|
|
1873
1932
|
loadError ? h("p", { role: "alert", style: { color: "#dc2626", overflowWrap: "anywhere" } }, loadError) : null,
|
|
1874
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,
|
|
@@ -1879,7 +1938,7 @@ window.__ModuleLoader__.load({
|
|
|
1879
1938
|
// 无窗口成功是中性 ℹ 而不是绿 ?(没有额度窗口不能默认标绿)。
|
|
1880
1939
|
const tabStatus = computeStatus(e, { failed: e && e.state !== "ok" ? e.error || e.state : null, failState: e && e.state, providerId: id, t, getLocale });
|
|
1881
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,
|
|
1882
|
-
"aria-controls": "subusage-provider-panel", title: `${
|
|
1941
|
+
"aria-controls": "subusage-provider-panel", title: `${providerDisplayName(id, "label", getLocale)}: ${providerDescription(e, t)}`, "aria-label": `${providerDisplayName(id, "short", getLocale)}: ${providerDescription(e, t)}`,
|
|
1883
1942
|
onClick: () => chooseUsage(id), onKeyDown: (event) => {
|
|
1884
1943
|
let next;
|
|
1885
1944
|
if (event.key === "ArrowRight" || event.key === "ArrowDown") next = (index + 1) % visibleIds.length;
|
|
@@ -1890,23 +1949,24 @@ window.__ModuleLoader__.load({
|
|
|
1890
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" }
|
|
1891
1950
|
}, statusGlyph(tabStatus.color, tabStatus.icon, 14), PROVIDER_META[id].short);
|
|
1892
1951
|
})) : null,
|
|
1893
|
-
!!usageId ? h("section", { role: "tabpanel", id: "subusage-provider-panel", "aria-label":
|
|
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 } },
|
|
1894
1953
|
h("div", { style: { display: "flex", alignItems: "center", gap: 8 } },
|
|
1895
|
-
h("strong", { style: { fontSize: 14 } },
|
|
1954
|
+
h("strong", { style: { fontSize: 14 } }, usageTitle),
|
|
1896
1955
|
h("span", { style: { flex: 1 } }),
|
|
1897
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),
|
|
1898
1957
|
// 一眼结论:与药丸、弹层同一个状态判定。读取成功只说明接口健康,额度档位另有结论。
|
|
1899
1958
|
statusBanner(usageStatus),
|
|
1900
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,
|
|
1901
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)" } },
|
|
1902
|
-
h("span", { style: { flex: 1, minWidth: 150, fontSize: 12 } }, usageAction === "mimo-relogin" ? text("Cookie 已过期,重新登录即可恢复。", "Cookie expired — sign in again to restore.") : usageAction === "mimo-
|
|
1903
|
-
usageAction !== "credentials" ? h("button", { type: "button", style: primary, disabled: busy || loginActive, onClick: startLogin }, usageAction === "mimo-
|
|
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,
|
|
1904
1963
|
h("button", { type: "button", style: usageAction === "credentials" ? primary : btnStyle, disabled: busy, onClick: () => revealEditor(usageId) }, usageAction === "credentials" ? text("配置凭据", "Configure credentials") : text("手动填写", "Enter manually"))) : null,
|
|
1905
1964
|
// 自动轮换池健康度(与弹层同一函数):池耗尽时上方横幅已按池中限额窗口染红。
|
|
1906
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,
|
|
1907
1967
|
// 无窗口时的占位只给「成功但既无窗口也无余额」:余额/加载/需配置/失败等状态横幅已经说明,不再叠加「暂无额度数据」。
|
|
1908
1968
|
// 窗口列表用 usageStatusEntry:池耗尽时它携带合成的限额窗口(非耗尽时与 usageEntry 同一对象)。
|
|
1909
|
-
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) })
|
|
1910
1970
|
: usageStatus.key === "balance" && !(usageStatus.entry && balanceText(usageStatus.entry, t)) ? h("p", { style: { opacity: .75 } }, t("noUsageData")) : null,
|
|
1911
1971
|
canShowUsage(usageEntry) && usageEntry.extras && usageEntry.extras.some((x) => x.kind !== "plan")
|
|
1912
1972
|
? h("div", { style: { marginTop: 10, padding: "6px 12px", borderRadius: 10, background: "rgba(128,128,128,.07)" } },
|
|
@@ -1993,7 +2053,7 @@ window.__ModuleLoader__.load({
|
|
|
1993
2053
|
// 数据来自同一份订阅(实测两条路由的请求 URL 逐字相同)。
|
|
1994
2054
|
const resolved = ROUTE_PROVIDER_ALIAS[provider] ?? provider;
|
|
1995
2055
|
const info = PROVIDER_ORDER.includes(resolved) ? PROVIDER_META[resolved] : null;
|
|
1996
|
-
const meta = info ? { label: resolved === "commandcode" ?
|
|
2056
|
+
const meta = info ? { label: resolved === "commandcode" ? providerDisplayName(resolved, "label", getLocale) : providerDisplayName(resolved, "short", getLocale), id: resolved } : null;
|
|
1997
2057
|
// 所有 Hook 必须先于条件返回:模型切换或重连时 provider 可能暂时为空。
|
|
1998
2058
|
// 稳定引用:否则父组件每次渲染都新建函数,药丸 effect 会反复重启轮询。
|
|
1999
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.
|
|
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: "
|
|
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", "
|
|
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.
|
|
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": [
|