dsh-api-dashboard 1.0.0 → 1.0.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 +3 -2
- package/package.json +10 -6
- package/src/index.js +92 -34
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
> [!CAUTION]
|
|
6
6
|
> **给安装/维护本插件的 AI 代理的一则警告 —— 动手前必读 [`AGENTS.md`](AGENTS.md)。**
|
|
7
7
|
>
|
|
8
|
-
> 1.
|
|
8
|
+
> 1. **推荐安装方式**:`dsh plugin --profile web add dsh-api-dashboard`(已发布到 npm,一行命令搞定)。也可用「下载→解压→`dsh plugin add file:`」三步法。`pnpm add <远程tarball>` **装不上**。
|
|
9
9
|
> 2. **数据准确性红线**:部分平台余额解析有待验证项(OpenRouter/SiliconFlow/Novita/one-api/xAI),且「限流窗口 ≠ 真实余额」——**不要伪造或硬塞一个数字**,查不到就如实显示「未开放」。
|
|
10
10
|
> 3. **推送前先问用户**;改 `client/client.js` 后需**重启 dsh web GUI** 才生效。
|
|
11
11
|
>
|
|
@@ -103,7 +103,7 @@ dsh plugin --profile web add file:/root/dsha-api-dashboard
|
|
|
103
103
|
|
|
104
104
|
| 方式 | 结果 |
|
|
105
105
|
|------|------|
|
|
106
|
-
| `dsh plugin add dsh-api-dashboard`(裸包名) |
|
|
106
|
+
| `dsh plugin add dsh-api-dashboard`(裸包名) | ✅ 推荐!已发布到 npm,一行命令安装 |
|
|
107
107
|
| `pnpm add <远程 tarball URL>` | ❌ pnpm 不剥离 codeload 压缩包的顶层目录,装出来是空壳 |
|
|
108
108
|
| 手动软链 node_modules | ✅ 可用但繁琐,层级写错会被启动校准摘除,不推荐 |
|
|
109
109
|
|
|
@@ -186,6 +186,7 @@ v0.6.2 — 检查更新补全反馈:无论「已是最新」还是「有新版
|
|
|
186
186
|
v0.7.0 — 玻璃 UI 改造(方案 A 第一阶段):半屏抽屉玻璃拟态(blur 仅限抽屉主背景)、卡片/按钮/输入框半透明+高光、大圆角、输入框 focus 高光阴影;用 color-mix 让玻璃色跟随主题 token(暗色不发白)
|
|
187
187
|
v0.7.1 — 修复负余额显示正常(负数→红);智谱解析保留百分比(不把限流窗口当余额);设置面板新增「返回」按钮与 IconBack;新增 AGENTS.md(给维护 AI 的必读须知)+ README 顶部 AI 警告块
|
|
188
188
|
v1.0.0 — 正式版:插件改名「哦鲸鲸」+ 自定义图标(/api-dashboard/icon);全量 UI 改版(固定色值去主题变量、三段式头部、卡片右栏上下结构、选中模型置顶、分组折叠、详情/设置/版本更新区重排);若干样式补全与清理
|
|
189
|
+
v1.0.2 — 余额解析稳健性(平台字段存在性校验,缺字段时如实显示「无法解析/未开放」而非误报 0);智谱配额解析重写(按 `limits.unit` 区分 5 小时窗口、周配额、工具维度,`percentage` 修正为「已用/填充度」,不再把「已用完」误显示为「余额 100%」);价格表更新(DeepSeek V4 新增 `deepseek-v4-flash-vision-exp`、`deepseek-chat/reasoner/r1` 标记为已退役;通用价格表新增现役主力 OpenAI GPT-5.6 / Claude 4.x / Gemini 3.x / Kimi K3 / StepFun 3.x,旧模型标为历史参考);自更新下载包增加包名校验
|
|
189
190
|
|
|
190
191
|
## License
|
|
191
192
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-api-dashboard",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "多平台 API 余额/用量看板插件:官方真实品牌图标,三层 UI 直接在输入框下方显示当前选中平台余额与估算消耗,点开底部抽屉切换所有平台;支持国内外平台与自定义中转站自动探测余额(查不到标未开放);自定义安全/预警阈值,余额告警提醒;进入页面自动刷新",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|
|
@@ -46,13 +46,11 @@
|
|
|
46
46
|
]
|
|
47
47
|
}
|
|
48
48
|
},
|
|
49
|
-
"dependencies": {
|
|
50
|
-
"@deepseek-ai/schemastery": "^3.18.1",
|
|
51
|
-
"zod": "^4.4.3"
|
|
52
|
-
},
|
|
53
49
|
"peerDependencies": {
|
|
54
50
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
55
|
-
"@deepseek-ai/dsh-credentials": "^0.1.0-rc.6"
|
|
51
|
+
"@deepseek-ai/dsh-credentials": "^0.1.0-rc.6",
|
|
52
|
+
"@deepseek-ai/schemastery": "^3.18.1",
|
|
53
|
+
"zod": "^4.4.3"
|
|
56
54
|
},
|
|
57
55
|
"peerDependenciesMeta": {
|
|
58
56
|
"@deepseek-ai/cordis": {
|
|
@@ -60,6 +58,12 @@
|
|
|
60
58
|
},
|
|
61
59
|
"@deepseek-ai/dsh-credentials": {
|
|
62
60
|
"optional": true
|
|
61
|
+
},
|
|
62
|
+
"@deepseek-ai/schemastery": {
|
|
63
|
+
"optional": true
|
|
64
|
+
},
|
|
65
|
+
"zod": {
|
|
66
|
+
"optional": true
|
|
63
67
|
}
|
|
64
68
|
},
|
|
65
69
|
"license": "MIT"
|
package/src/index.js
CHANGED
|
@@ -123,8 +123,11 @@ export async function applyUpdate({ targets = null, timeoutMs = 30000, remoteVer
|
|
|
123
123
|
// 2) 解压 (--strip-components=1 剥离 codeload 顶层目录)
|
|
124
124
|
mkdirSync(extractDir, { recursive: true })
|
|
125
125
|
execFileSync('tar', ['xzf', tgzPath, '-C', extractDir, '--strip-components=1'], { timeout: 20000 })
|
|
126
|
-
// 3) 校验: 版本号匹配预期 + 关键文件齐全 (装坏了宁可拒绝, 保住回滚机会)
|
|
126
|
+
// 3) 校验: 版本号匹配预期 + 关键文件齐全 + 包名一致 (装坏了宁可拒绝, 保住回滚机会)
|
|
127
127
|
if (readVersionAt(extractDir) !== wantVersion) throw new Error('extracted version mismatch')
|
|
128
|
+
let extractedName = null
|
|
129
|
+
try { extractedName = JSON.parse(readFileSync(join(extractDir, 'package.json'), 'utf8')).name } catch { /* 忽略 */ }
|
|
130
|
+
if (extractedName !== 'dsh-api-dashboard') throw new Error('extracted package name mismatch')
|
|
128
131
|
for (const rel of ['src/index.js', 'client/client.js', 'cordis.patch.yml']) {
|
|
129
132
|
if (!existsSync(join(extractDir, rel))) throw new Error(`missing file after extract: ${rel}`)
|
|
130
133
|
}
|
|
@@ -261,12 +264,37 @@ export const isWeekend = (timestamp = Date.now()) => {
|
|
|
261
264
|
|
|
262
265
|
|
|
263
266
|
// ============================================================
|
|
264
|
-
// 通用模型价格表 (
|
|
265
|
-
//
|
|
266
|
-
//
|
|
267
|
+
// 通用模型价格表 — 每百万token, 币种按各平台官方数字直接填入(多为 USD, 本表不做 CNY 换算,
|
|
268
|
+
// 仅用于 balanceCost 投影中非 DeepSeek 模型的"估算", 不保证精确).
|
|
269
|
+
// ⚠️ DeepSeek 计费请走上面 V4_RATES 峰谷 CNY 表(官方价, 准确); 本通用表覆盖 OpenAI/Claude/Gemini/国产等。
|
|
270
|
+
// 来源: ① 现役主力(2026-08) 来自 NousResearch hermes-agent usage_pricing.py(追踪各官方文档, 逐条注明 source_url);
|
|
271
|
+
// ② 旧模型(2025-08) 为历史参考价. 仅做参考, 实际以平台为准.
|
|
267
272
|
// ============================================================
|
|
268
273
|
export const MODEL_PRICES = {
|
|
269
|
-
//
|
|
274
|
+
// —— 现役主力 (2026-08, USD/百万tokens, 来源: hermes-agent 追踪官方文档) ——
|
|
275
|
+
// OpenAI GPT-5.6 系列
|
|
276
|
+
'gpt-5.6-sol': { cacheHit: 0.50, cacheMiss: 5.00, output: 30.00 },
|
|
277
|
+
'gpt-5.6-terra': { cacheHit: 0.25, cacheMiss: 2.50, output: 15.00 },
|
|
278
|
+
'gpt-5.6-luna': { cacheHit: 0.10, cacheMiss: 1.00, output: 6.00 },
|
|
279
|
+
// Anthropic Claude 4.x / 5
|
|
280
|
+
'claude-opus-4-8': { cacheHit: 0.50, cacheMiss: 5.00, output: 25.00 },
|
|
281
|
+
'claude-sonnet-5': { cacheHit: 0.20, cacheMiss: 2.00, output: 10.00 },
|
|
282
|
+
'claude-sonnet-4-6': { cacheHit: 0.30, cacheMiss: 3.00, output: 15.00 },
|
|
283
|
+
'claude-haiku-4-5': { cacheHit: 0.10, cacheMiss: 1.00, output: 5.00 },
|
|
284
|
+
// Google Gemini 3.x
|
|
285
|
+
'gemini-3.6-flash': { cacheHit: 0.15, cacheMiss: 1.50, output: 7.50 },
|
|
286
|
+
'gemini-3.5-flash': { cacheHit: 0.15, cacheMiss: 1.50, output: 9.00 },
|
|
287
|
+
'gemini-3.1-pro': { cacheHit: 0.20, cacheMiss: 2.00, output: 12.00 },
|
|
288
|
+
'gemini-2.5-pro': { cacheHit: 0.125, cacheMiss: 1.25, output: 10.00 },
|
|
289
|
+
// —— 国产主力 (2026-08) ——
|
|
290
|
+
// Kimi K3 (来源: benchlm.ai / morphllm.com 2026-08, 输入/输出 $3/$15; 缓存折扣未取到 → 暂按无折扣)
|
|
291
|
+
'kimi-k3': { cacheHit: 3.00, cacheMiss: 3.00, output: 15.00 },
|
|
292
|
+
// 阶跃星辰 (来源: platform.stepfun.com 官方定价 2026; 列序=输入/缓存命中/输出, CNY1M)
|
|
293
|
+
'step-3.7-flash': { cacheHit: 0.27, cacheMiss: 1.35, output: 8.10 },
|
|
294
|
+
'step-3.5-flash': { cacheHit: 0.14, cacheMiss: 0.70, output: 2.10 },
|
|
295
|
+
// ⚠️ 通义 Qwen3 / 智谱 GLM-5 的现役官方一手价未取到可信数字, 不虚构; 遇到这些模型会落 defaultPrices(未定价),
|
|
296
|
+
// 旧 qwen-*/glm-4 条目仍在下方作历史参考.
|
|
297
|
+
// —— 以下为历史/参考模型 (2025-08, 实际以平台为准) ——
|
|
270
298
|
'gpt-4o': { cacheHit: 1.25, cacheMiss: 2.5, output: 10 },
|
|
271
299
|
'gpt-4o-mini': { cacheHit: 0.075, cacheMiss: 0.15, output: 0.6 },
|
|
272
300
|
'gpt-4-turbo': { cacheHit: 5, cacheMiss: 10, output: 30 },
|
|
@@ -282,7 +310,9 @@ export const MODEL_PRICES = {
|
|
|
282
310
|
'gemini-2.0-flash': { cacheHit: 0.05, cacheMiss: 0.1, output: 0.4 },
|
|
283
311
|
'gemini-2.0-pro': { cacheHit: 1.25, cacheMiss: 2.5, output: 10 },
|
|
284
312
|
'gemini-1.5-pro': { cacheHit: 1.75, cacheMiss: 3.5, output: 10.5 },
|
|
285
|
-
// DeepSeek (标准价兜底)
|
|
313
|
+
// DeepSeek (标准价兜底) — ⚠️ 2026-07-24 起 deepseek-chat / deepseek-reasoner / deepseek-r1 已 RETIRED,
|
|
314
|
+
// 官方 API 调用会直接报错(不再重定向到 V4)。现役仅 deepseek-v4-flash / deepseek-v4-pro / deepseek-v4-flash-vision-exp。
|
|
315
|
+
// 保留这三条仅作为「若仍在用的旧配置」的估算占位, 真实计费请走上面 V4 峰谷表。
|
|
286
316
|
'deepseek-chat': { cacheHit: 0.1, cacheMiss: 1, output: 2 },
|
|
287
317
|
'deepseek-reasoner': { cacheHit: 0.2, cacheMiss: 2, output: 8 },
|
|
288
318
|
'deepseek-r1': { cacheHit: 0.2, cacheMiss: 2, output: 8 },
|
|
@@ -462,7 +492,8 @@ function checkAlerts(balances, config, ctx) {
|
|
|
462
492
|
// 余额解析适配器
|
|
463
493
|
// ============================================================
|
|
464
494
|
|
|
465
|
-
|
|
495
|
+
// 纯函数, 导出便于单测 (不影响对外行为)
|
|
496
|
+
export function parseResponse(queryType, json) {
|
|
466
497
|
switch (queryType) {
|
|
467
498
|
case 'deepseek': {
|
|
468
499
|
const infos = Array.isArray(json?.balance_infos) ? json.balance_infos : []
|
|
@@ -488,12 +519,17 @@ function parseResponse(queryType, json) {
|
|
|
488
519
|
case 'siliconflow': {
|
|
489
520
|
const d = json?.data
|
|
490
521
|
if (!d) return null
|
|
491
|
-
|
|
522
|
+
// 数据红线: totalBalance 字段名/单位未实测, 缺字段即放弃, 不造假 0。
|
|
523
|
+
if (d.totalBalance == null) return null
|
|
524
|
+
return { total: toAmount(d.totalBalance), currency: 'CNY', available: null, used: null, note: '硅基流动总余额(字段待实测)' }
|
|
492
525
|
}
|
|
493
526
|
case 'openrouter': {
|
|
494
527
|
const d = json?.data
|
|
495
528
|
if (!d) return null
|
|
496
|
-
|
|
529
|
+
// 数据红线: total_credits / total_usage 字段名未用真实 key 实测, 可能不叫这个名。
|
|
530
|
+
// 若两个预期字段都缺失 → 视为解析失败(返回 null, 前端标"无法解析/未开放"), 绝不显示假"余额0"。
|
|
531
|
+
if (d.total_credits == null && d.total_usage == null) return null
|
|
532
|
+
return { total: toAmount(d.total_credits) - toAmount(d.total_usage), currency: 'USD', available: toAmount(d.total_credits), used: toAmount(d.total_usage), note: 'OpenRouter 余额(字段待实测)' }
|
|
497
533
|
}
|
|
498
534
|
case 'novita': {
|
|
499
535
|
if (!json || !('availableBalance' in json)) return null
|
|
@@ -501,14 +537,15 @@ function parseResponse(queryType, json) {
|
|
|
501
537
|
return { total: raw / 10000, currency: 'USD', available: null, used: null, note: 'Novita (0.0001单位)' }
|
|
502
538
|
}
|
|
503
539
|
case 'stepfun': {
|
|
504
|
-
if (!json ||
|
|
505
|
-
const b = toAmount(json
|
|
506
|
-
return { total: b, currency: 'CNY', available: null, used: null, note: '
|
|
540
|
+
if (!json || json.balance == null) return null
|
|
541
|
+
const b = toAmount(json.balance)
|
|
542
|
+
return { total: b, currency: 'CNY', available: null, used: null, note: '阶跃星辰余额' }
|
|
507
543
|
}
|
|
508
544
|
case 'quota': {
|
|
509
|
-
const
|
|
510
|
-
if (quota
|
|
511
|
-
|
|
545
|
+
const q = json?.data
|
|
546
|
+
if (!q || (q.quota == null && q.username == null)) return null
|
|
547
|
+
const quota = toAmount(q.quota)
|
|
548
|
+
return { total: quota / 500000, currency: 'USD', available: null, used: null, note: 'one-api quota (÷500000, 系数待实测)' }
|
|
512
549
|
}
|
|
513
550
|
case 'openai-billing': {
|
|
514
551
|
const limit = toAmount(json?.hard_limit_usd)
|
|
@@ -516,27 +553,45 @@ function parseResponse(queryType, json) {
|
|
|
516
553
|
return { total: limit, currency: 'USD', available: limit, used: null, note: 'OpenAI 订阅硬上限' }
|
|
517
554
|
}
|
|
518
555
|
case 'glm': {
|
|
519
|
-
// 智谱
|
|
520
|
-
//
|
|
521
|
-
//
|
|
522
|
-
//
|
|
556
|
+
// 智谱 Coding Plan 配额(实测 2026-08-30; unit 含义转自 z.ai 前端源码 / cc-switch):
|
|
557
|
+
// unit=3 → TOKENS_LIMIT 5小时滚动窗口(主显示) [Lite:2000]
|
|
558
|
+
// unit=6 → TOKENS_LIMIT 周配额(部分套餐/国际站) [Lite:10000]
|
|
559
|
+
// unit=5 → TIME_LIMIT 工具/搜索类(月度)
|
|
560
|
+
// limit.remaining = 该维度当前剩余(积分);
|
|
561
|
+
// limit.percentage = 该维度【填充度/已用】(100=用完, 0=未用)——绝不是"剩余%";
|
|
562
|
+
// limit.nextResetTime = 该维度下次重置时间(仅滚动/周期维度有)。
|
|
563
|
+
// ⚠️ 严禁把 percentage 当"剩余%"显示: 会把"周配额已用完(remaining:0)"误显示成"余额 100%, 剩 100"。
|
|
564
|
+
// 真实返回: [{unit:3,remaining:2000,pct:0},{unit:6,remaining:0,pct:100,nextResetTime:...}]
|
|
523
565
|
const limits = Array.isArray(json?.data?.limits) ? json.data.limits : []
|
|
524
566
|
const num = (v) => { const n = Number(v); return Number.isFinite(n) ? n : null }
|
|
567
|
+
const rem = (l) => num(l?.remaining ?? l?.remaining_quota)
|
|
525
568
|
const pctOf = (l) => { const n = Number(l?.percentage ?? l?.remaining_percentage); return Number.isFinite(n) && n >= 0 && n <= 100 ? n : null }
|
|
526
|
-
const
|
|
527
|
-
//
|
|
528
|
-
const
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
569
|
+
const UNIT_LABEL = { 3: '5小时窗口', 6: '周配额', 5: '工具(月度)' }
|
|
570
|
+
// 主显示优先: unit=3(5小时滚动窗口), 其次 unit=6(周配额), 再按剩余降序
|
|
571
|
+
const score = (l) => (l.unit === 3 ? 0 : (l.unit === 6 ? 1 : 2))
|
|
572
|
+
const sorted = limits.slice().sort((a, b) => score(a) - score(b) || (rem(b) ?? -1) - (rem(a) ?? -1))
|
|
573
|
+
const main = sorted.find((l) => rem(l) !== null && rem(l) > 0)
|
|
574
|
+
const usedUp = limits.filter((l) => rem(l) === 0 && pctOf(l) === 100)
|
|
575
|
+
if (main) {
|
|
576
|
+
const r = rem(main)
|
|
577
|
+
const extra = usedUp.length
|
|
578
|
+
? '; ' + usedUp.map((l) => `${UNIT_LABEL[l.unit] ?? '其他'}已用完(${l.nextResetTime ? '待重置' : '已限流'})`).join('; ')
|
|
579
|
+
: ''
|
|
580
|
+
return {
|
|
581
|
+
total: r, currency: 'tokens', available: r, used: null, percent: null,
|
|
582
|
+
note: `智谱${UNIT_LABEL[main.unit] ?? ''}剩 ${r} 积分${extra}`,
|
|
583
|
+
resetAt: usedUp[0]?.nextResetTime ?? main?.nextResetTime ?? null,
|
|
584
|
+
}
|
|
533
585
|
}
|
|
534
|
-
//
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
586
|
+
// 无可用>0 → 全部用尽
|
|
587
|
+
if (usedUp.length) {
|
|
588
|
+
const l = usedUp[0]
|
|
589
|
+
return { total: 0, currency: 'tokens', available: 0, used: null, percent: null, note: '智谱配额已用完(0)' + (l.nextResetTime ? ', 待重置' : ''), resetAt: l?.nextResetTime ?? null }
|
|
590
|
+
}
|
|
591
|
+
// 兜底: 只有 percentage 无 remaining → 限流填充度(非余额)
|
|
592
|
+
if (limits.length > 0) {
|
|
593
|
+
const p = pctOf(limits[0])
|
|
594
|
+
if (p !== null) return { total: p, currency: '%', available: null, used: null, percent: true, note: '智谱限流填充度%(非余额)', resetAt: limits[0]?.nextResetTime ?? null }
|
|
540
595
|
}
|
|
541
596
|
return null
|
|
542
597
|
}
|
|
@@ -544,6 +599,7 @@ function parseResponse(queryType, json) {
|
|
|
544
599
|
case 'kimi': {
|
|
545
600
|
const u = json?.usage
|
|
546
601
|
if (!u) return null
|
|
602
|
+
if (u.limit == null && u.remaining == null) return null
|
|
547
603
|
const limit = toAmount(u.limit), remaining = toAmount(u.remaining)
|
|
548
604
|
return { total: limit, currency: 'tokens', available: remaining, used: limit - remaining, note: 'Kimi 套餐剩余 tokens', percent: limit > 0 ? (remaining / limit) * 100 : null }
|
|
549
605
|
}
|
|
@@ -1046,8 +1102,10 @@ export function apply(ctx, config) {
|
|
|
1046
1102
|
const mk = (p) => p ? { cacheHit: p.cacheHit, cacheMiss: p.cacheMiss, output: p.output } : null
|
|
1047
1103
|
// v4 峰谷系列
|
|
1048
1104
|
const models = []
|
|
1049
|
-
for (const key of ['deepseek-v4-flash', 'deepseek-v4-pro']) {
|
|
1050
|
-
|
|
1105
|
+
for (const key of ['deepseek-v4-flash', 'deepseek-v4-pro', 'deepseek-v4-flash-vision-exp']) {
|
|
1106
|
+
// v4-flash-vision-exp 与 flash 同价 (vision 不加价), 复用 flash 费率
|
|
1107
|
+
const src = key === 'deepseek-v4-flash-vision-exp' ? 'deepseek-v4-flash' : key
|
|
1108
|
+
models.push({ model: key, peak: mk(table.peak[src]), offPeak: mk(table.offPeak[src]), peakValley: true })
|
|
1051
1109
|
}
|
|
1052
1110
|
sendJson(res, 200, { ok: true, currency: cfg.currency ?? 'CNY', peakNow: isPeakTime(), weekend: isWeekend(), models })
|
|
1053
1111
|
},
|