@duke-dsh-plugins/dsh-token-stats 1.5.3 → 1.5.4
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/client.js +98 -8
- package/index.js +4 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ dsh plugin --profile web add https://github.com/MoonlitDropOfBlood/dsh-token-sta
|
|
|
65
65
|
- 切换模型时自动切换 provider;每 60s 自动刷新,**点击读数立即强制刷新**;
|
|
66
66
|
- 未配置对应 API Key 或拉取失败时显示 `⚠`(悬停查看原因);当前 provider 不在支持列表时不占位。
|
|
67
67
|
|
|
68
|
-
> **MiMo(小米)配置**:MiMo 的用量走 dashboard admin API(非公开 endpoint),纯 API Key(Bearer)实测会被 401 拒绝,可靠凭证是浏览器登录 Cookie。打开 platform.xiaomimimo.com → 订阅管理,F12 → Network 任一 `/api/v1/tokenPlan/*` 请求 → 复制完整 `Cookie` 请求头值,粘到 DSH 凭据的 `XIAOMI_MIMO_COOKIE`(或把 API Key 配到 `XIAOMI_MIMO_API_KEY`,插件会先 Bearer 后 Cookie 自动重试)。按 route 派生命名的凭据(设置页自动生成,如 `XIAOMI_TOKEN_PLAN_CN_API_KEY`)同样识别;composer 读数按 route id 匹配(`xiaomi-mimo` / `xiaomimimo` / `xiaomi-token-plan-cn` / 名字含 xiaomi 或 mimo
|
|
68
|
+
> **MiMo(小米)配置**:MiMo 的用量走 dashboard admin API(非公开 endpoint),纯 API Key(Bearer)实测会被 401 拒绝,可靠凭证是浏览器登录 Cookie。打开 platform.xiaomimimo.com → 订阅管理,F12 → Network 任一 `/api/v1/tokenPlan/*` 请求 → 复制完整 `Cookie` 请求头值,粘到 DSH 凭据的 `XIAOMI_MIMO_COOKIE`(或把 API Key 配到 `XIAOMI_MIMO_API_KEY`,插件会先 Bearer 后 Cookie 自动重试)。按 route 派生命名的凭据(设置页自动生成,如 `XIAOMI_TOKEN_PLAN_CN_API_KEY`)同样识别;composer 读数按 route id 匹配(`xiaomi-mimo` / `xiaomimimo` / `xiaomi-token-plan-cn` / 名字含 xiaomi 或 mimo 均可)。**v1.5.4 起也可以直接在「设置 → Token 统计 → 套餐余额 → 凭据设置」里粘贴保存**,无需手改凭据库(mimo 写入 `XIAOMI_MIMO_COOKIE`,不影响 LLM 路由的 API Key)。
|
|
69
69
|
|
|
70
70
|
## 工作原理
|
|
71
71
|
|
package/client.js
CHANGED
|
@@ -33,6 +33,15 @@ window.__ModuleLoader__.load({
|
|
|
33
33
|
.ts-head-title{font-size:16px;font-weight:500;line-height:24px;margin:0;flex:1}
|
|
34
34
|
.ts-refresh{box-sizing:border-box;height:28px;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:14px;background:transparent;color:var(--dsw-alias-label-secondary);padding:0 12px;font-size:12px;line-height:26px}
|
|
35
35
|
.ts-refresh:hover{color:var(--dsw-alias-label-primary);border-color:var(--dsw-alias-border-l1)}
|
|
36
|
+
.ts-cred-block{display:flex;flex-direction:column;gap:8px}
|
|
37
|
+
.ts-cred-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
38
|
+
.ts-cred-label{min-width:64px;font-size:12px;color:var(--dsw-alias-label-secondary)}
|
|
39
|
+
.ts-cred-input{box-sizing:border-box;height:28px;flex:1;min-width:220px;font:inherit;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);padding:0 10px;font-size:12px}
|
|
40
|
+
.ts-cred-input:focus{outline:none;border-color:var(--dsw-alias-border-l1)}
|
|
41
|
+
.ts-cred-save{box-sizing:border-box;height:28px;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);padding:0 12px;font-size:12px}
|
|
42
|
+
.ts-cred-save:hover{border-color:var(--dsw-alias-border-l1)}
|
|
43
|
+
.ts-cred-save:disabled{opacity:.5;cursor:default}
|
|
44
|
+
.ts-cred-msg{font-size:12px;color:var(--dsw-alias-label-secondary)}
|
|
36
45
|
.ts-status{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:18px;min-height:18px}
|
|
37
46
|
.ts-tabs{display:inline-flex;gap:4px;padding:3px;background:var(--dsw-alias-bg-layer-2);border:1px solid var(--dsw-alias-border-l1);border-radius:10px;align-self:flex-start}
|
|
38
47
|
.ts-tab{box-sizing:border-box;height:26px;font:inherit;cursor:pointer;border:none;background:transparent;color:var(--dsw-alias-label-secondary);border-radius:7px;padding:0 14px;font-size:13px;line-height:26px}
|
|
@@ -658,6 +667,17 @@ window.__ModuleLoader__.load({
|
|
|
658
667
|
if (lower.includes("mimo") || lower.includes("xiaomi")) return "mimo";
|
|
659
668
|
return null;
|
|
660
669
|
}
|
|
670
|
+
// 凭据设置行写入的 ref — 与 host QUOTA_PROVIDERS.refs 首选项对齐。
|
|
671
|
+
// mimo 写 XIAOMI_MIMO_COOKIE(README 文档路径),不触碰 LLM 路由的
|
|
672
|
+
// API Key ref(host probe 顺序 Cookie 优先,v1.5.4)。
|
|
673
|
+
const QUOTA_CRED_REFS = {
|
|
674
|
+
minimax: "MINIMAX_CN_API_KEY",
|
|
675
|
+
deepseek: "DEEPSEEK_API_KEY",
|
|
676
|
+
kimi: "KIMI_CODING_API_KEY",
|
|
677
|
+
openrouter: "OPENROUTER_API_KEY",
|
|
678
|
+
zhipu: "ZAI_CODING_CN_API_KEY",
|
|
679
|
+
mimo: "XIAOMI_MIMO_COOKIE",
|
|
680
|
+
};
|
|
661
681
|
const QUOTA_PROVIDER_ORDER = ["minimax", "deepseek", "kimi", "openrouter", "zhipu", "mimo"];
|
|
662
682
|
|
|
663
683
|
function quotaProviderLabel(p) {
|
|
@@ -910,7 +930,8 @@ window.__ModuleLoader__.load({
|
|
|
910
930
|
}
|
|
911
931
|
|
|
912
932
|
// ---- Settings 页余额区块 ----------------------------------------------
|
|
913
|
-
//
|
|
933
|
+
// 卡片 = 已配置的 provider;未配置或凭据失效(auth_failed)的 provider
|
|
934
|
+
// 在「凭据设置」行里粘贴凭据(remote.credentials.set → force 重拉)。
|
|
914
935
|
|
|
915
936
|
function QuotaCard(props) {
|
|
916
937
|
const p = props.provider;
|
|
@@ -978,6 +999,9 @@ window.__ModuleLoader__.load({
|
|
|
978
999
|
|
|
979
1000
|
function QuotaSection() {
|
|
980
1001
|
const [quotas, setQuotas] = React.useState(null);
|
|
1002
|
+
const [credDrafts, setCredDrafts] = React.useState({});
|
|
1003
|
+
const [credMsgs, setCredMsgs] = React.useState({});
|
|
1004
|
+
const [credSaving, setCredSaving] = React.useState({});
|
|
981
1005
|
const load = React.useCallback(async (force) => {
|
|
982
1006
|
try {
|
|
983
1007
|
const payload = unwrapRemote(await remote.getAllQuotas(force === true));
|
|
@@ -992,16 +1016,46 @@ window.__ModuleLoader__.load({
|
|
|
992
1016
|
return () => clearInterval(t);
|
|
993
1017
|
}, [load]);
|
|
994
1018
|
|
|
1019
|
+
const saveCred = React.useCallback(async (p, value) => {
|
|
1020
|
+
const ref = QUOTA_CRED_REFS[p];
|
|
1021
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
1022
|
+
if (!ref || !trimmed) return;
|
|
1023
|
+
setCredSaving((s) => ({ ...s, [p]: true }));
|
|
1024
|
+
try {
|
|
1025
|
+
// 官方 credentials 命名空间(settings-models 同款用法);不写进
|
|
1026
|
+
// inject 硬依赖,保存时才惰性取,取不到就提示错误。
|
|
1027
|
+
const ns = (ctx.remote && ctx.remote.credentials) || ctx.get("remote.credentials");
|
|
1028
|
+
if (!ns || typeof ns.set !== "function") throw new Error("credentials Remote 不可用");
|
|
1029
|
+
const r = await ns.set(ref, trimmed);
|
|
1030
|
+
if (r && r.ok) {
|
|
1031
|
+
setCredDrafts((d) => ({ ...d, [p]: "" }));
|
|
1032
|
+
setCredMsgs((m) => ({ ...m, [p]: "已保存,正在拉取…" }));
|
|
1033
|
+
await load(true);
|
|
1034
|
+
setCredMsgs((m) => ({ ...m, [p]: "已保存 ✓" }));
|
|
1035
|
+
} else {
|
|
1036
|
+
const msg = r && r.error && r.error.message ? r.error.message : "保存失败";
|
|
1037
|
+
setCredMsgs((m) => ({ ...m, [p]: msg }));
|
|
1038
|
+
}
|
|
1039
|
+
} catch (e) {
|
|
1040
|
+
setCredMsgs((m) => ({ ...m, [p]: e instanceof Error ? e.message : String(e) }));
|
|
1041
|
+
} finally {
|
|
1042
|
+
setCredSaving((s) => ({ ...s, [p]: false }));
|
|
1043
|
+
}
|
|
1044
|
+
}, [load]);
|
|
1045
|
+
|
|
995
1046
|
if (!quotas) return null;
|
|
996
1047
|
const names = QUOTA_PROVIDER_ORDER.filter((p) => quotas[p]);
|
|
997
|
-
//
|
|
1048
|
+
// 卡片 = 非 unconfigured;设置行 = unconfigured 或 auth_failed。
|
|
998
1049
|
const configured = names.filter((p) => !(quotas[p] && quotas[p].ok === false && quotas[p].kind === "unconfigured"));
|
|
999
|
-
|
|
1050
|
+
const needsCred = names.filter(
|
|
1051
|
+
(p) => quotas[p] && quotas[p].ok === false && (quotas[p].kind === "unconfigured" || quotas[p].kind === "auth_failed"),
|
|
1052
|
+
);
|
|
1053
|
+
if (configured.length === 0 && needsCred.length === 0) return null;
|
|
1000
1054
|
return React.createElement(
|
|
1001
1055
|
"div", { className: "ts-block" },
|
|
1002
1056
|
React.createElement(
|
|
1003
1057
|
"div", { className: "ts-block-title" },
|
|
1004
|
-
"套餐余额
|
|
1058
|
+
"套餐余额",
|
|
1005
1059
|
" ",
|
|
1006
1060
|
React.createElement(
|
|
1007
1061
|
"a",
|
|
@@ -1016,10 +1070,46 @@ window.__ModuleLoader__.load({
|
|
|
1016
1070
|
"(刷新)",
|
|
1017
1071
|
),
|
|
1018
1072
|
),
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1073
|
+
configured.length > 0
|
|
1074
|
+
? React.createElement(
|
|
1075
|
+
"div", { className: "ts-quota-list" },
|
|
1076
|
+
configured.map((p) => React.createElement(QuotaCard, { key: p, provider: p, value: quotas[p] })),
|
|
1077
|
+
)
|
|
1078
|
+
: null,
|
|
1079
|
+
needsCred.length > 0
|
|
1080
|
+
? React.createElement(
|
|
1081
|
+
"div", { className: "ts-cred-block" },
|
|
1082
|
+
React.createElement("div", { className: "ts-block-title" }, "凭据设置(粘贴后保存,自动重新拉取)"),
|
|
1083
|
+
needsCred.map((p) => {
|
|
1084
|
+
const isAuthFailed = quotas[p] && quotas[p].kind === "auth_failed";
|
|
1085
|
+
return React.createElement(
|
|
1086
|
+
"div", { key: p, className: "ts-cred-row" },
|
|
1087
|
+
React.createElement("span", { className: "ts-cred-label" }, quotaProviderLabel(p)),
|
|
1088
|
+
React.createElement("input", {
|
|
1089
|
+
type: "password",
|
|
1090
|
+
className: "ts-cred-input",
|
|
1091
|
+
placeholder: p === "mimo"
|
|
1092
|
+
? (isAuthFailed ? "凭据已失效:重新粘贴 Cookie 或 API Key" : "粘贴登录 Cookie 或 API Key")
|
|
1093
|
+
: "粘贴 API Key",
|
|
1094
|
+
value: credDrafts[p] || "",
|
|
1095
|
+
onChange: (e) => setCredDrafts((d) => ({ ...d, [p]: e.target.value })),
|
|
1096
|
+
onKeyDown: (e) => {
|
|
1097
|
+
if (e.key === "Enter") saveCred(p, credDrafts[p] || "");
|
|
1098
|
+
},
|
|
1099
|
+
}),
|
|
1100
|
+
React.createElement(
|
|
1101
|
+
"button", {
|
|
1102
|
+
className: "ts-cred-save",
|
|
1103
|
+
disabled: !!credSaving[p] || !(credDrafts[p] && credDrafts[p].trim()),
|
|
1104
|
+
onClick: () => saveCred(p, credDrafts[p] || ""),
|
|
1105
|
+
},
|
|
1106
|
+
credSaving[p] ? "保存中…" : "保存",
|
|
1107
|
+
),
|
|
1108
|
+
credMsgs[p] ? React.createElement("span", { className: "ts-cred-msg" }, credMsgs[p]) : null,
|
|
1109
|
+
);
|
|
1110
|
+
}),
|
|
1111
|
+
)
|
|
1112
|
+
: null,
|
|
1023
1113
|
);
|
|
1024
1114
|
}
|
|
1025
1115
|
|
package/index.js
CHANGED
|
@@ -149,8 +149,11 @@ const QUOTA_PROVIDERS = {
|
|
|
149
149
|
// XIAOMI_MIMO_API_KEY / XIAOMI_MIMO_COOKIE(或 MIMO_*)任一 ref;route 派生
|
|
150
150
|
// 名(设置页按 route 生成的 XIAOMI_TOKEN_PLAN_CN_API_KEY/_COOKIE,与
|
|
151
151
|
// 其他 provider 的 MINIMAX_CN_* 同规则)同样识别。
|
|
152
|
+
// Cookie 类 ref 排最前:README 的可靠路径是 Cookie,且 Token 统计页「凭据
|
|
153
|
+
// 设置」写入 XIAOMI_MIMO_COOKIE——已存在但失效的 API Key(如 route 派生名
|
|
154
|
+
// 下的 tp- key 被 dashboard 401 拒)不能挡住用户新粘贴 Cookie(v1.5.4)。
|
|
152
155
|
mimo: {
|
|
153
|
-
refs: ["
|
|
156
|
+
refs: ["XIAOMI_MIMO_COOKIE", "XIAOMI_TOKEN_PLAN_CN_COOKIE", "XIAOMI_MIMO_API_KEY", "XIAOMI_TOKEN_PLAN_CN_API_KEY", "MIMO_COOKIE", "MIMO_API_KEY"],
|
|
154
157
|
urls: {
|
|
155
158
|
XIAOMI_TOKEN_PLAN_CN_API_KEY: "https://platform.xiaomimimo.com/api/v1/tokenPlan/usage",
|
|
156
159
|
XIAOMI_TOKEN_PLAN_CN_COOKIE: "https://platform.xiaomimimo.com/api/v1/tokenPlan/usage",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@duke-dsh-plugins/dsh-token-stats",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.4",
|
|
4
4
|
"description": "Token usage statistics for DeepSeek Harness (7/30-day per-model charts + yearly heatmap) plus an inline coding-plan balance/quota readout in the composer (minimax/deepseek/kimi/openrouter/zhipu).",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|