@loommii/dsh-provider-usage 0.7.0 → 0.10.0
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 +14 -1
- package/cordis.patch.yml +4 -1
- package/docs/CHANGELOG.md +111 -0
- package/lib/assets/amd-radeon-logo.svg +18 -0
- package/lib/assets/cmdsymbol-light-black-bg.svg +5 -0
- package/lib/client.js +254 -88
- package/lib/index.js +200 -8
- package/package.json +3 -6
package/README.md
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
# dsh-provider-usage — DSH「用量中心」
|
|
2
2
|
|
|
3
|
-
> 当前版本 **v0.
|
|
3
|
+
> 当前版本 **v0.10.0** · npm `@loommii/dsh-provider-usage` · [更新历史](docs/CHANGELOG.md)
|
|
4
4
|
|
|
5
5
|
DSH Web GUI 插件 **「用量中心」**:一个常驻右下角的用量卡片,实时显示你的 AI 服务用量 / 余额,不用再打开网页查;附带本地 Token 统计页,不联网也能看自己今天用了多少。
|
|
6
6
|
|
|
7
|
+
## v0.8.0 更新摘要(适配 dsh 0.1.2-alpha)
|
|
8
|
+
|
|
9
|
+
- **适配 dsh `0.1.2-alpha` 破坏性重构**:修复升级后「设置页无用量中心 / 卡片消失」与
|
|
10
|
+
`duplicate loader entry id` 启动崩溃(根因:v0.6.0 包名迁移时 `cordis.patch.yml` 与
|
|
11
|
+
client 注册 id 仍用旧名,叠加 alpha 新的客户端发现机制导致静默失联)
|
|
12
|
+
- host 侧 API 契约、会话文件解析、平台注入(react / slots)经逐项核对在 alpha.4 全部兼容
|
|
13
|
+
- 完整变更与用户升级指引见 [CHANGELOG](docs/CHANGELOG.md)
|
|
14
|
+
|
|
7
15
|
## v0.6.0 更新摘要
|
|
8
16
|
|
|
9
17
|
- **新增提供方 Command Code(订阅+余额混合卡)**:5 小时 / 周 / 月窗口使用量 + 月度剩余额度(USD),主数字为月已用百分比
|
|
@@ -20,6 +28,7 @@ DSH Web GUI 插件 **「用量中心」**:一个常驻右下角的用量卡片
|
|
|
20
28
|
| OpenCode Go(订阅) | 5 小时 / 7 天 / 月度三个窗口的已用百分比 + 进度条 + 重置倒计时 |
|
|
21
29
|
| DeepSeek(余额) | 账户余额(CNY) |
|
|
22
30
|
| Command Code(订阅+余额,自定义) | 5 小时 / 周 / 月窗口使用量 + 月度剩余额度(USD),主数字为本月剩余百分比(3 位小数向下取整) |
|
|
31
|
+
| AMD Radeon Cloud (中国)(余额+配额,自定义) | 今日剩余额度(pts)+ 今日已用/限额 + 单日窗口进度条 + 重置倒计时(无周/月窗口;国际版位置预留) |
|
|
23
32
|
|
|
24
33
|
## 功能
|
|
25
34
|
|
|
@@ -72,6 +81,10 @@ dsh plugin --profile web remove @loommii/dsh-provider-usage
|
|
|
72
81
|
## 使用说明
|
|
73
82
|
|
|
74
83
|
- 提供方实例的增删改、Key 设置都在 **设置 → 用量中心** 里完成,改动即时生效,无需重启
|
|
84
|
+
- 每个实例可在**编辑**里用「启用」开关管理状态:**开 = 启用(绿)/ 关 = 暂停(灰)**,保存后生效;
|
|
85
|
+
新添加的提供方默认启用。暂停后该实例**不再轮询查询**(卡片 30s 自动刷新跳过它),
|
|
86
|
+
卡片切换菜单里也**不可选**;暂停的若是当前卡片实例会自动切到其他启用实例,全部暂停时卡片隐藏。
|
|
87
|
+
启用/暂停只影响查询与卡片选择,编辑 / 删除 / Key 配置都原样保留
|
|
75
88
|
- 设置页有两个标签:「提供方」管理实例,「用量统计」查看本地 Token 统计
|
|
76
89
|
- 设置保存在本机浏览器中;删掉实例不会上传或泄露任何 Key
|
|
77
90
|
- 卡片拖动后的位置会自动保存,下次打开还是老位置
|
package/cordis.patch.yml
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
# dsh-provider-usage M1: 插入插件行(自动 reconcile 进 profile bundles)
|
|
2
|
+
# v0.8.0: name 使用正式包名 @loommii/dsh-provider-usage(与 package.json 一致)。
|
|
3
|
+
# 0.7.0 及以前误用改名前的旧名 dsh-provider-usage,在 dsh 0.1.2-alpha 上
|
|
4
|
+
# 只能靠 profile node_modules 里的旧名残留符号链接解析——重装后插件即失联。
|
|
2
5
|
- insert:
|
|
3
6
|
- id: provider-usage
|
|
4
|
-
name: dsh-provider-usage
|
|
7
|
+
name: '@loommii/dsh-provider-usage'
|
|
5
8
|
config:
|
|
6
9
|
baseUrl: https://opencode.ai/zen/go # {{baseUrl}}/v1/usage 即官方 usage 接口
|
|
7
10
|
timeoutMs: 15000
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,116 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.10.0(未发布)
|
|
4
|
+
|
|
5
|
+
- **新增提供方 AMD Radeon Cloud (中国)(余额+配额卡)**:实测端点 `GET {baseUrl}/usage`(`https://developer.amd.com.cn/radeon/api/v1/usage`,Bearer API Key)——今日剩余 pts 为主数字(3 位小数向下取整百分比)、副行已用/限额 + 剩余(pts)、单日窗口进度条 + 重置倒计时;透传今日/近24h/近30天/累计时间段汇总与按模型拆分(`by_model`)、单次调用明细(`recent`)
|
|
6
|
+
- **无周/月窗口**:AMD 只按日限额计(`daily_cost_limit/used/remaining_usd`),接口无 weekly/monthly 字段;适配器只画「今日」一行进度条
|
|
7
|
+
- **仅自定义添加**:`customOnly`(同 Command Code),Key 加密存插件私有库,默认引用 `RADEON_API_KEY`
|
|
8
|
+
- **国内 / 国际版本**:AMD Radeon Cloud 分两个版本——当前仅接入**中国版**(`developer.amd.com.cn`,显示名「AMD Radeon Cloud (中国)」),国际版 baseUrl/端点不同,适配器位置已预留(后续新增 `amd-radeon-usage-intl` 条目即可)
|
|
9
|
+
- 调研文档:`tmp/AmDRadeonCloud_usage调研.md`(含实测 curl 与字段表;官方文档页路径 404 已记录)
|
|
10
|
+
|
|
11
|
+
## v0.9.0(2026-09-08)
|
|
12
|
+
|
|
13
|
+
### 小功能
|
|
14
|
+
|
|
15
|
+
- **Command Code 官方 logo 替换「CC」文字占位**:卡片头部与切换菜单里的 Command Code 图标
|
|
16
|
+
由紫色渐变底 + 「CC」文字改为官方 ⌘ 符号(白符号·黑底,viewBox 446×446),SVG 内联渲染、
|
|
17
|
+
随容器裁切圆角;原始素材归档到 `lib/assets/cmdsymbol-light-black-bg.svg`(随包发布便于溯源)。
|
|
18
|
+
|
|
19
|
+
## v0.8.0(2026-09-03)
|
|
20
|
+
|
|
21
|
+
### 修复:重启/存储丢失后提供方清单被静默重建(严重数据丢失)
|
|
22
|
+
|
|
23
|
+
**原先的现象**:在用量中心删除某提供方(如 OpenCode Go)后,浏览器存储里的实例清单一旦丢失
|
|
24
|
+
(`dsh web` 重启且访问 origin 变化、清站点数据、浏览器驱逐临时存储、换浏览器等),下次打开页面
|
|
25
|
+
**已删除的默认实例(OpenCode Go/DeepSeek)会凭空复活,而自定义提供方(如 Command Code,`customOnly`
|
|
26
|
+
不在默认清单里)永久消失**;其私有库 Key 成为孤儿,只能重新手动添加。用户实测即「之前删了 OpenCode Go,
|
|
27
|
+
重启后变成 Command Code 被删了」。
|
|
28
|
+
|
|
29
|
+
**根因**:提供方实例清单此前**只存浏览器 localStorage**(host 端零持久化),且
|
|
30
|
+
`loadProviders()` 在存储 key 读取失败时静默走 `DEFAULT_PROVIDERS` 兜底重建默认清单;
|
|
31
|
+
host 也没有任何路由能枚举私有库 Key 供启动对账。
|
|
32
|
+
|
|
33
|
+
**修复后的现象**:提供方实例清单以 host 端 `$DSH_HOME/provider-usage/providers.json` 为
|
|
34
|
+
**单一事实源**(0600,与 credentials.json 同边界),localStorage 降级为写穿透缓存;
|
|
35
|
+
浏览器存储全部丢失后重启,清单从 host 完整恢复,删除仍然生效、自定义实例不再消失。
|
|
36
|
+
|
|
37
|
+
- **host 新增 `GET/POST /api/provider-usage/providers`**(仅回环;POST 整单覆盖保存,幂等)
|
|
38
|
+
- 条目逐个规范化校验:未知 adapter / 缺 id 的脏条目剔除而非整单拒绝;`paused` 严格归一为布尔;
|
|
39
|
+
`ref/name/source/type` 长度钳制;上限 64 个实例
|
|
40
|
+
- 原子写(tmp → rename)+ 0600;损坏文件改名 `providers.json.corrupt-<ts>` 留证并按缺失处理,
|
|
41
|
+
不卡死恢复流程;目录缺失时自动创建
|
|
42
|
+
- **client 启动恢复**:`useProviders` 首个订阅者触发一次 `GET /providers` 去重恢复;
|
|
43
|
+
host 有清单 → 应用为当前清单;host 无文件(首次升级 0.8.0)→ 把本地缓存清单一次性写回 host 固化;
|
|
44
|
+
host 失联 → 继续用本地缓存(乐观值),后续写操作会重试落盘
|
|
45
|
+
- **恢复竞态守卫**:恢复响应迟到且期间用户已增删改实例时,放弃 host 过期数据,
|
|
46
|
+
不把用户刚删的实例复活(按会话内写计数判定)
|
|
47
|
+
- **`setProviders` 支持函数式更新**(修复同族丢更新竞态):所有写方(删除/导入/手动添加/编辑/
|
|
48
|
+
旧数据自愈)改为基于写入时刻的最新清单计算,快速连删两个实例不再复活第一个、
|
|
49
|
+
自愈写入不再覆盖刚添加的实例;传数组仍兼容旧用法
|
|
50
|
+
- host 场景 40(路由契约/落盘/跨重启恢复/脏条目剔除/400/损坏隔离,13 断言)
|
|
51
|
+
|
|
52
|
+
### 小功能
|
|
53
|
+
- **提供方支持启用 / 暂停**:设置 → 用量中心 → 提供方设置,每个实例的**编辑卡**里新增「启用」开关
|
|
54
|
+
- 语义:**开 = 启用(开关亮绿色)/ 关 = 暂停(灰色)**;编辑后点「保存」生效
|
|
55
|
+
- 新添加(导入 / 手动)的提供方**默认启用**
|
|
56
|
+
- 暂停后该实例**不再参与轮询查询**(卡片 30s 自动刷新与设置页打开时的状态查询都跳过它),
|
|
57
|
+
卡片**切换菜单中不可选**;若暂停的正是卡片当前实例,卡片立即切到另一个启用实例,
|
|
58
|
+
全部暂停时卡片隐藏;恢复启用后立即重新生效(即时持久化,无需重启/刷新),
|
|
59
|
+
Key 等配置原样保留
|
|
60
|
+
- 视觉:编辑卡内官方样式 switch(开=绿 / 关=灰)+ 状态文案;列表中暂停行名称与状态点
|
|
61
|
+
置灰为「已暂停」,编辑 / 删除仍可用(启用/暂停只影响查询与卡片选择)
|
|
62
|
+
- 启用/暂停状态随实例清单持久化到 host `providers.json`,跨浏览器/存储丢失后同样保留
|
|
63
|
+
|
|
64
|
+
### 兼容性修复:适配 dsh 0.1.2-alpha(破坏性变更)
|
|
65
|
+
|
|
66
|
+
本次 dsh `0.1.1-rc.2 → 0.1.2-alpha.4` 重构后,插件出现两类症状:升级当天 `dsh web` 启动即崩
|
|
67
|
+
(`duplicate loader entry id: provider-usage`),重新安装后变为 **host 半区正常(7 条 API 路由全部注册)
|
|
68
|
+
但 Web UI 静默失联**(设置页无「用量中心」、无常驻卡片)。两个问题的根源都在插件自身的
|
|
69
|
+
「包名迁移残留」与 alpha 新的客户端发现机制不兼容,本版全部修复:
|
|
70
|
+
|
|
71
|
+
- **`cordis.patch.yml` 的 entry `name` 改为正式包名 `@loommii/dsh-provider-usage`**
|
|
72
|
+
- v0.6.0 包名迁移(`dsh-provider-usage` → `@loommii/dsh-provider-usage`)时漏改了此文件,
|
|
73
|
+
此后一直靠 profile `node_modules` 里改名前遗留的旧名符号链接 `dsh-provider-usage → 工作区`
|
|
74
|
+
才能解析到包(rc.2 环境下侥幸可用)
|
|
75
|
+
- alpha.4 的客户端扫描(`dsh-client-modules` node 半区 `nearestPackage`)从 loader entry
|
|
76
|
+
的模块位置向上查找 package.json,且要求 `name === entry 名`;旧名在重装后解析失败 →
|
|
77
|
+
包被判定为「非客户端包」→ 永不进 `window.__DSH_BOOT__` 组合图 → UI 静默消失(host 不受影响)
|
|
78
|
+
- 升级首日的 `duplicate loader entry id` 崩溃亦与残留链接相关(旧名/新名两条路径同时进树,
|
|
79
|
+
loader `EntryGroup.update` 对同 id 双行 fail-loud);清理残留链接 + 本修复后消除
|
|
80
|
+
- **删除 `dsh.client.inject: ["@deepseek-ai/dsh-client-runtime"]` 死引用**
|
|
81
|
+
- alpha 重构将 `dsh-client-runtime` 并入 `dsh-client-modules`,该包已不存在;
|
|
82
|
+
插件 client 半区从未 require 过 runtime 的任何导出,声明本就多余
|
|
83
|
+
- 两侧加载器对 graph 外的 inject 静默跳过,故这是卫生项而非故障源
|
|
84
|
+
- **`lib/client.js` 的 `__ModuleLoader__.load` 注册 id 同步改为 `@loommii/dsh-provider-usage`**
|
|
85
|
+
- alpha 的 boot graph 行 id = 包名,`serveBundle` 按 id 应答 combo bundle;注册 id 与
|
|
86
|
+
graph 行不一致时模块系统找不到 factory(对官方包此场景 fail-loud,对插件即静默失联)
|
|
87
|
+
- **peerDependencies 放宽**:`@deepseek-ai/dsh-host-webserver` `^0.1.0-rc.6` → `>=0.1.0-rc.6`
|
|
88
|
+
(npm prerelease semver 规则下 `^` 不匹配 `0.1.2-alpha.*`,消除安装时 peer 警告)
|
|
89
|
+
|
|
90
|
+
### 兼容性核对(对 dsh-v0.1.2-alpha.4 源码逐项验证)
|
|
91
|
+
|
|
92
|
+
- `ctx.webServer.register({kind:'exact'|'prefix', path, handler(req,res)})` 契约不变(`dsh-host-webserver` 健在)
|
|
93
|
+
- `window.__ModuleLoader__.load({id, factory})` 仍是注册协议;`exports.inject`(`['slots']`)仍被
|
|
94
|
+
vendored cordis Loader 消费(`Cr.resolve(n.inject)`)
|
|
95
|
+
- 平台 seed 词 `react` / `react-dom/client` / `@deepseek-ai/cordis` / `dsh-client-ui-slots` 全保留
|
|
96
|
+
(React 仍为 18.3.1)
|
|
97
|
+
- `settings.section` 插槽契约保留(`dsh-client-ui-settings` contract/slots.ts)
|
|
98
|
+
- 会话文件格式:JSONL 后端仍默认(`.jsonl.zstd`、`SESSION_FORMAT_VERSION = 0`、目录布局、
|
|
99
|
+
`assistant/message` → `data.usage{inputTokens,outputTokens,cacheReadTokens}` 事件行全部不变),
|
|
100
|
+
本地 Token 统计不受影响;alpha.3 已移除可选 SQLite 后端,此前的前瞻风险解除
|
|
101
|
+
- alpha.4 新增的「web 请求一次性 fetch 审批(SSRF 防护)」为 web 客户端侧,不影响 host 侧出站查询
|
|
102
|
+
|
|
103
|
+
### 用户升级指引(从 ≤0.7.0 升级到 0.8.0)
|
|
104
|
+
|
|
105
|
+
若升级 dsh 后曾出现 `duplicate loader entry id: provider-usage`,profile 的 `node_modules`
|
|
106
|
+
里可能残留改名前的旧名符号链接。重装插件即可清理:
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
dsh plugin --profile web remove @loommii/dsh-provider-usage
|
|
110
|
+
dsh plugin --profile web add github:loommii/dsh-provider-usage # 或 npm: @loommii/dsh-provider-usage
|
|
111
|
+
# 若 node_modules 下仍有 dsh-provider-usage/(旧名,非 @loommii scope)残留目录/链接,手动删除
|
|
112
|
+
```
|
|
113
|
+
|
|
3
114
|
## v0.7.0(2026-09-01)
|
|
4
115
|
|
|
5
116
|
### 小功能
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
|
2
|
+
viewBox="580 280 200 200" style="display:block" xml:space="preserve">
|
|
3
|
+
<style type="text/css">
|
|
4
|
+
.st0{fill-rule:evenodd;clip-rule:evenodd;}
|
|
5
|
+
.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;}
|
|
6
|
+
</style>
|
|
7
|
+
<g>
|
|
8
|
+
<polygon class="st0" points="586,288 778.8,288 778.8,480.8 586,480.8 586,288 "/>
|
|
9
|
+
<path class="st1" d="M658.2,461.3h-10.5l-3.2-7.7h-17.4l-2.9,7.7h-10.3l15.7-39h11.3L658.2,461.3L658.2,461.3L658.2,461.3z
|
|
10
|
+
M635.4,431.6l-5.7,15.2h12L635.4,431.6L635.4,431.6z"/>
|
|
11
|
+
<polygon class="st1" points="696.2,422.3 704.7,422.3 704.7,461.3 695,461.3 695,437 684.4,449.2 682.9,449.2 672.4,437
|
|
12
|
+
672.4,461.3 662.7,461.3 662.7,422.3 671.2,422.3 683.7,436.8 696.2,422.3 "/>
|
|
13
|
+
<path class="st1" d="M729.3,422.3c14.2,0,21.6,8.9,21.6,19.6c0,11.2-7.1,19.4-22.7,19.4h-16.1v-39H729.3L729.3,422.3L729.3,422.3z
|
|
14
|
+
M721.8,454.2h6.3c9.8,0,12.7-6.6,12.7-12.4c0-6.7-3.6-12.4-12.8-12.4h-6.2V454.2L721.8,454.2z"/>
|
|
15
|
+
<polygon class="st1" points="702.5,333.4 660.1,333.4 634,307.5 728.8,307.4 728.8,402.1 702.5,375.6 702.5,333.4 "/>
|
|
16
|
+
<polygon class="st1" points="660.4,376.4 660.2,338.2 633.3,365 633.3,402.8 672.2,402.8 698.5,376.4 660.4,376.4 "/>
|
|
17
|
+
</g>
|
|
18
|
+
</svg>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg width="446" height="446" viewBox="0 0 446 446" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect width="446" height="446" fill="black"/>
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M226.665 18.1979H218.375C166.389 18.1979 129.23 18.2366 100.991 22.0332C73.2761 25.7594 56.8935 32.8027 44.8481 44.8481C32.8027 56.8935 25.7594 73.2761 22.0332 100.991C18.2365 129.23 18.1979 166.389 18.1979 218.375V226.665C18.1979 278.651 18.2365 315.809 22.0332 344.048C25.7594 371.764 32.8027 388.146 44.8481 400.192C56.8935 412.237 73.276 419.28 100.991 423.007C129.23 426.803 166.389 426.842 218.375 426.842H226.665C278.651 426.842 315.809 426.803 344.048 423.007C371.764 419.28 388.146 412.237 400.191 400.192C412.237 388.146 419.28 371.764 423.006 344.048C426.803 315.809 426.842 278.651 426.842 226.665V218.375C426.842 166.389 426.803 129.23 423.006 100.991C419.28 73.2761 412.237 56.8935 400.191 44.8481C388.146 32.8027 371.764 25.7594 344.048 22.0332C315.809 18.2366 278.651 18.1979 226.665 18.1979ZM31.9803 31.9803C0 63.9605 0 115.432 0 218.375V226.665C0 329.608 0 381.079 31.9803 413.059C63.9605 445.04 115.432 445.04 218.375 445.04H226.665C329.608 445.04 381.079 445.04 413.059 413.059C445.04 381.079 445.04 329.608 445.04 226.665V218.375C445.04 115.432 445.04 63.9605 413.059 31.9803C381.079 0 329.608 0 226.665 0H218.375C115.432 0 63.9605 0 31.9803 31.9803Z" fill="white"/>
|
|
4
|
+
<path d="M306.202 85.5845C276.837 85.5845 252.95 109.472 252.95 138.837V161.66H192.09V138.837C192.09 109.472 168.202 85.5845 138.837 85.5845C109.472 85.5845 85.5845 109.472 85.5845 138.837C85.5845 168.202 109.472 192.09 138.837 192.09H161.66V252.95H138.837C109.472 252.95 85.5845 276.837 85.5845 306.202C85.5845 335.568 109.472 359.455 138.837 359.455C168.202 359.455 192.09 335.568 192.09 306.202V283.38H252.95V306.202C252.95 335.568 276.837 359.455 306.202 359.455C335.567 359.455 359.455 335.568 359.455 306.202C359.455 276.837 335.567 252.95 306.202 252.95H283.38V192.09H306.202C335.567 192.09 359.455 168.202 359.455 138.837C359.455 109.472 335.567 85.5845 306.202 85.5845ZM283.38 161.66V138.837C283.38 126.209 293.574 116.015 306.202 116.015C318.831 116.015 329.025 126.209 329.025 138.837C329.025 151.466 318.831 161.66 306.202 161.66H283.38ZM138.837 161.66C126.209 161.66 116.015 151.466 116.015 138.837C116.015 126.209 126.209 116.015 138.837 116.015C151.466 116.015 161.66 126.209 161.66 138.837V161.66H138.837ZM192.09 252.95V192.09H252.95V252.95H192.09ZM306.202 329.025C293.574 329.025 283.38 318.831 283.38 306.202V283.38H306.202C318.831 283.38 329.025 293.574 329.025 306.202C329.025 318.831 318.831 329.025 306.202 329.025ZM138.837 329.025C126.209 329.025 116.015 318.831 116.015 306.202C116.015 293.574 126.209 283.38 138.837 283.38H161.66V306.202C161.66 318.831 151.466 329.025 138.837 329.025Z" fill="white"/>
|
|
5
|
+
</svg>
|
package/lib/client.js
CHANGED
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
// (OpenCode Go 订阅 / DeepSeek 余额),卡片按结果字段自适应渲染(%+倒计时=订阅卡;
|
|
4
4
|
// 金额+币种=余额卡)。
|
|
5
5
|
// 设置项全部即时生效(localStorage 持久化,无需重启)。
|
|
6
|
+
// v0.8.0: 注册 id 必须等于包名 @loommii/dsh-provider-usage —— dsh 0.1.2-alpha 的
|
|
7
|
+
// boot graph 行 id = 包名,serveBundle 按 id 应答 bundle,不一致即客户端静默失联。
|
|
6
8
|
|
|
7
9
|
window.__ModuleLoader__.load({
|
|
8
|
-
id: 'dsh-provider-usage',
|
|
10
|
+
id: '@loommii/dsh-provider-usage',
|
|
9
11
|
factory: (require) => {
|
|
10
12
|
var module = { exports: {} };
|
|
11
13
|
var exports = module.exports;
|
|
@@ -23,9 +25,12 @@ window.__ModuleLoader__.load({
|
|
|
23
25
|
const Z_INDEX = 2147483000;
|
|
24
26
|
// 适配器元数据(与 host ADAPTERS 对应;logo 与显示名用于设置页/菜单)
|
|
25
27
|
const ADAPTER_META = {
|
|
26
|
-
'usage-percent': { displayName: 'OpenCode Go
|
|
28
|
+
'usage-percent': { displayName: 'OpenCode Go' },
|
|
27
29
|
'balance-json': { displayName: 'DeepSeek' },
|
|
28
|
-
'commandcode-credits': { displayName: 'Command Code
|
|
30
|
+
'commandcode-credits': { displayName: 'Command Code' },
|
|
31
|
+
// AMD Radeon Cloud 分国内 / 国际两个版本(国内 = developer.amd.com.cn;国际版 baseUrl/端点不同)。
|
|
32
|
+
// 当前仅接入中国版「AMD Radeon Cloud (中国)」;国际版位置预留,后续接入时新增适配器条目即可。
|
|
33
|
+
'amd-radeon-usage': { displayName: 'AMD Radeon Cloud (中国)' },
|
|
29
34
|
};
|
|
30
35
|
// 供应商实例清单(用户可配置:名称可改、可增删、可同类型多个;key 引用走 DSH 凭证)
|
|
31
36
|
const PROVIDERS_KEY = 'dsh-provider-usage.providers';
|
|
@@ -45,6 +50,17 @@ window.__ModuleLoader__.load({
|
|
|
45
50
|
// DeepSeek:官方鲸 logo PNG(225×225 RGBA)→ data URI,透明底直接展示
|
|
46
51
|
const LOGO_DEEPSEEK = 'data:image/png;base64,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';
|
|
47
52
|
|
|
53
|
+
// Command Code:官方 ⌘ logo(白符号·黑底,viewBox 446×446)→ 内联 SVG(替换原「CC」文字占位)
|
|
54
|
+
const LOGO_CC = React.createElement('svg', { viewBox: '0 0 446 446', 'aria-hidden': 'true', style: { display: 'block', width: '100%', height: '100%' } },
|
|
55
|
+
React.createElement('rect', { width: '446', height: '446', fill: '#000000' }),
|
|
56
|
+
React.createElement('path', { fillRule: 'evenodd', clipRule: 'evenodd', fill: '#FFFFFF', d: 'M226.665 18.1979H218.375C166.389 18.1979 129.23 18.2366 100.991 22.0332C73.2761 25.7594 56.8935 32.8027 44.8481 44.8481C32.8027 56.8935 25.7594 73.2761 22.0332 100.991C18.2365 129.23 18.1979 166.389 18.1979 218.375V226.665C18.1979 278.651 18.2365 315.809 22.0332 344.048C25.7594 371.764 32.8027 388.146 44.8481 400.192C56.8935 412.237 73.276 419.28 100.991 423.007C129.23 426.803 166.389 426.842 218.375 426.842H226.665C278.651 426.842 315.809 426.803 344.048 423.007C371.764 419.28 388.146 412.237 400.191 400.192C412.237 388.146 419.28 371.764 423.006 344.048C426.803 315.809 426.842 278.651 426.842 226.665V218.375C426.842 166.389 426.803 129.23 423.006 100.991C419.28 73.2761 412.237 56.8935 400.191 44.8481C388.146 32.8027 371.764 25.7594 344.048 22.0332C315.809 18.2366 278.651 18.1979 226.665 18.1979ZM31.9803 31.9803C0 63.9605 0 115.432 0 218.375V226.665C0 329.608 0 381.079 31.9803 413.059C63.9605 445.04 115.432 445.04 218.375 445.04H226.665C329.608 445.04 381.079 445.04 413.059 413.059C445.04 381.079 445.04 329.608 445.04 226.665V218.375C445.04 115.432 445.04 63.9605 413.059 31.9803C381.079 0 329.608 0 226.665 0H218.375C115.432 0 63.9605 0 31.9803 31.9803Z' }),
|
|
57
|
+
React.createElement('path', { fill: '#FFFFFF', d: 'M306.202 85.5845C276.837 85.5845 252.95 109.472 252.95 138.837V161.66H192.09V138.837C192.09 109.472 168.202 85.5845 138.837 85.5845C109.472 85.5845 85.5845 109.472 85.5845 138.837C85.5845 168.202 109.472 192.09 138.837 192.09H161.66V252.95H138.837C109.472 252.95 85.5845 276.837 85.5845 306.202C85.5845 335.568 109.472 359.455 138.837 359.455C168.202 359.455 192.09 335.568 192.09 306.202V283.38H252.95V306.202C252.95 335.568 276.837 359.455 306.202 359.455C335.567 359.455 359.455 335.568 359.455 306.202C359.455 276.837 335.567 252.95 306.202 252.95H283.38V192.09H306.202C335.567 192.09 359.455 168.202 359.455 138.837C359.455 109.472 335.567 85.5845 306.202 85.5845ZM283.38 161.66V138.837C283.38 126.209 293.574 116.015 306.202 116.015C318.831 116.015 329.025 126.209 329.025 138.837C329.025 151.466 318.831 161.66 306.202 161.66H283.38ZM138.837 161.66C126.209 161.66 116.015 151.466 116.015 138.837C116.015 126.209 126.209 116.015 138.837 116.015C151.466 116.015 161.66 126.209 161.66 138.837V161.66H138.837ZM192.09 252.95V192.09H252.95V252.95H192.09ZM306.202 329.025C293.574 329.025 283.38 318.831 283.38 306.202V283.38H306.202C318.831 283.38 329.025 293.574 329.025 306.202C329.025 318.831 318.831 329.025 306.202 329.025ZM138.837 329.025C126.209 329.025 116.015 318.831 116.015 306.202C116.015 293.574 126.209 283.38 138.837 283.38H161.66V306.202C161.66 318.831 151.466 329.025 138.837 329.025Z' }));
|
|
58
|
+
|
|
59
|
+
// AMD Radeon Cloud:官方 SVG logo(黑色方块底 + 白色箭头/AMD 字标),viewBox 裁切为正方形
|
|
60
|
+
// 来源:lib/assets/amd-radeon-logo.svg(由 amd-seeklogo.svg 修正 viewBox 后生成)
|
|
61
|
+
const LOGO_AMD_SVG = 'data:image/svg+xml,' + encodeURIComponent(`<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="580 280 200 200" style="display:block"><style>.st0{fill-rule:evenodd;clip-rule:evenodd}.st1{fill-rule:evenodd;clip-rule:evenodd;fill:#FFF}</style><g><polygon class="st0" points="586,288 778.8,288 778.8,480.8 586,480.8"/><path class="st1" d="M658.2 461.3h-10.5l-3.2-7.7h-17.4l-2.9 7.7h-10.3l15.7-39h11.3l17.3 39zm-22.8-29.7l-5.7 15.2h12l-6.3-15.2z"/><polygon class="st1" points="696.2 422.3 704.7 422.3 704.7 461.3 695 461.3 695 437 684.4 449.2 682.9 449.2 672.4 437 672.4 461.3 662.7 461.3 662.7 422.3 671.2 422.3 683.7 436.8"/><path class="st1" d="M729.3 422.3c14.2 0 21.6 8.9 21.6 19.6 0 11.2-7.1 19.4-22.7 19.4h-16.1v-39h17.2zm-7.5 31.9h6.3c9.8 0 12.7-6.6 12.7-12.4 0-6.7-3.6-12.4-12.8-12.4h-6.2v24.8z"/><polygon class="st1" points="702.5 333.4 660.1 333.4 634 307.5 728.8 307.4 728.8 402.1 702.5 375.6 702.5 333.4"/><polygon class="st1" points="660.4 376.4 660.2 338.2 633.3 365 633.3 402.8 672.2 402.8 698.5 376.4 660.4 376.4"/></g></svg>`);
|
|
62
|
+
const LOGO_AMD = React.createElement('img', { src: LOGO_AMD_SVG, alt: '', style: { display: 'block', width: '100%', height: '100%' } });
|
|
63
|
+
|
|
48
64
|
const zh = {
|
|
49
65
|
loading: '查询中…',
|
|
50
66
|
refresh: '刷新',
|
|
@@ -53,6 +69,7 @@ window.__ModuleLoader__.load({
|
|
|
53
69
|
rolling: '5小时滚动',
|
|
54
70
|
weekly: '7天(每周)',
|
|
55
71
|
monthly: '每月',
|
|
72
|
+
daily: '今日',
|
|
56
73
|
used: '已用',
|
|
57
74
|
errorTitle: '无法获取用量',
|
|
58
75
|
nav: '用量中心',
|
|
@@ -76,8 +93,8 @@ window.__ModuleLoader__.load({
|
|
|
76
93
|
'.dsh-pu-card-head{display:flex;align-items:center;gap:8px;margin-bottom:10px}',
|
|
77
94
|
'.dsh-pu-logo{width:30px;height:30px;border-radius:8px;flex:none;display:flex;align-items:center;justify-content:center;overflow:hidden;background:transparent}',
|
|
78
95
|
'.dsh-pu-logo.go svg,.dsh-pu-logo.deepseek img{display:block}',
|
|
79
|
-
'.dsh-pu-cc-logo{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;
|
|
80
|
-
'.dsh-pu-cc-logo-sm{width:22px;height:22px;border-radius:6px
|
|
96
|
+
'.dsh-pu-cc-logo{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;overflow:hidden;flex:none}',
|
|
97
|
+
'.dsh-pu-cc-logo-sm{width:22px;height:22px;border-radius:6px}',
|
|
81
98
|
'.dsh-pu-cc-sub{display:flex;align-items:center;gap:10px;margin:-6px 0 6px;font-size:11px;color:#C9B8BE;white-space:nowrap}',
|
|
82
99
|
'.dsh-pu-cc-sub .dsh-pu-cc-used{color:#EFE0E5;font-weight:600}',
|
|
83
100
|
'.dsh-pu-cc-sub .dsh-pu-cc-remain{color:#FFB3C6;font-weight:600}',
|
|
@@ -119,6 +136,20 @@ window.__ModuleLoader__.load({
|
|
|
119
136
|
'.zGbnIq_credentialDot.dsh-pu-dot-gray{background:var(--dsw-alias-label-tertiary,#8b93a7)}',
|
|
120
137
|
'@keyframes dsh-pu-ms-breathe{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.72)}}@media (prefers-reduced-motion:reduce){.dsh-pu-chip.dsh-pu-chip-loading i,.zGbnIq_credentialDot.dsh-pu-dot-loading{animation:none}}',
|
|
121
138
|
'.ms-plus{color:var(--dsw-alias-brand-primary);font-size:15px;line-height:1}',
|
|
139
|
+
// ── 提供方「启用/暂停」开关(官方 switch 视觉;开=启用=绿 / 关=暂停=灰)──
|
|
140
|
+
'.dsh-pu-switch{box-sizing:border-box;width:36px;height:20px;background:var(--dsw-alias-border-l3);cursor:pointer;border:0;border-radius:10px;flex:none;padding:2px;position:relative}',
|
|
141
|
+
'.dsh-pu-switch:hover:not(:disabled){background:var(--dsw-alias-border-l4)}',
|
|
142
|
+
'.dsh-pu-switch:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:2px}',
|
|
143
|
+
'.dsh-pu-switch-on{background:var(--dsw-alias-state-success-primary)}',
|
|
144
|
+
'.dsh-pu-switch-on:hover:not(:disabled){background:var(--dsw-alias-state-success-primary)}',
|
|
145
|
+
'.dsh-pu-switch-thumb{box-sizing:border-box;background:var(--dsw-alias-label-primary-foreground);border-radius:50%;width:16px;height:16px;display:block;transition:transform .12s}',
|
|
146
|
+
'.dsh-pu-switch-on .dsh-pu-switch-thumb{transform:translate(16px)}',
|
|
147
|
+
'.zGbnIq_editorSwitchRow{align-items:center;gap:10px;display:flex}',
|
|
148
|
+
'.dsh-pu-switchText{color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px}',
|
|
149
|
+
// ── 提供方列表:暂停行(灰点 + 名称变灰,编辑/删除保持可用)──
|
|
150
|
+
'.zGbnIq_rowNameDim{color:var(--dsw-alias-label-tertiary)}',
|
|
151
|
+
'.zGbnIq_rowCard .zGbnIq_credentialDot.dsh-pu-dot-paused{background:var(--dsw-alias-label-tertiary,#8b93a7)}',
|
|
152
|
+
'.zGbnIq_rowPaused .zGbnIq_rowIdentity .zGbnIq_rowName{color:var(--dsw-alias-label-tertiary)}',
|
|
122
153
|
].join('');
|
|
123
154
|
var tag = document.createElement('style');
|
|
124
155
|
tag.dataset.plugin = 'dsh-provider-usage';
|
|
@@ -169,35 +200,67 @@ window.__ModuleLoader__.load({
|
|
|
169
200
|
return s;
|
|
170
201
|
}
|
|
171
202
|
|
|
172
|
-
// ── 供应商实例清单(localStorage
|
|
173
|
-
|
|
203
|
+
// ── 供应商实例清单(v0.8.0:host 端 providers.json 为单一事实源,localStorage 只是写穿透缓存)──
|
|
204
|
+
// 修复「重启后提供方清单被静默重建」:此前清单只存 localStorage,存储 key 一旦丢失
|
|
205
|
+
// (换 origin/清站点数据/浏览器驱逐临时存储),启动就走 DEFAULT_PROVIDERS 兜底 →
|
|
206
|
+
// 已删除的默认实例(OpenCode Go/DeepSeek)复活、自定义实例(如 Command Code)永久消失。
|
|
207
|
+
// 现在启动时从 host 恢复;host 无数据时才一次性迁移本地缓存/默认清单并立即写回 host。
|
|
208
|
+
function normalizeProviders(arr) {
|
|
209
|
+
if (!Array.isArray(arr)) return null;
|
|
210
|
+
return arr.filter(function (p) {
|
|
211
|
+
return p && typeof p === 'object' && typeof p.adapter === 'string' && ADAPTER_META[p.adapter];
|
|
212
|
+
}).map(function (p) {
|
|
213
|
+
var name = typeof p.name === 'string' && p.name ? p.name : (ADAPTER_META[p.adapter] || {}).displayName;
|
|
214
|
+
if (String(p.id || '') === 'deepseek-balance' && name === 'DeepSeek 余额') name = 'DeepSeek'; // 0.4.0 更名
|
|
215
|
+
// source:Key 解析位置(vault=插件私有库直取;dsh=DSH 凭证链);type:创建方式(import=导入 / manual=自定义)
|
|
216
|
+
var source = p.source === 'vault' ? 'vault' : 'dsh';
|
|
217
|
+
// 显式 type 优先;旧数据无 type 时由 source 推导(vault→manual,其余→import)
|
|
218
|
+
var type = p.type === 'manual' || p.type === 'import' ? p.type : (source === 'vault' ? 'manual' : 'import');
|
|
219
|
+
// paused:true=暂停(不轮询查询、卡片菜单不可选);旧数据无该字段 = false(启用)
|
|
220
|
+
return { id: String(p.id || ''), name: name, adapter: p.adapter, ref: typeof p.ref === 'string' ? p.ref : '', source: source, type: type, paused: p.paused === true };
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
function readProvidersCache() {
|
|
174
224
|
try {
|
|
175
|
-
|
|
176
|
-
if (Array.isArray(arr)) {
|
|
177
|
-
if (arr.length === 0) return []; // 用户删光后保持空清单,不复活默认实例
|
|
178
|
-
return arr.filter(function (p) {
|
|
179
|
-
return p && typeof p === 'object' && typeof p.adapter === 'string' && ADAPTER_META[p.adapter];
|
|
180
|
-
}).map(function (p) {
|
|
181
|
-
var name = typeof p.name === 'string' && p.name ? p.name : (ADAPTER_META[p.adapter] || {}).displayName;
|
|
182
|
-
if (String(p.id || '') === 'deepseek-balance' && name === 'DeepSeek 余额') name = 'DeepSeek'; // 0.4.0 更名
|
|
183
|
-
// source:Key 解析位置(vault=插件私有库直取;dsh=DSH 凭证链);type:创建方式(import=导入 / manual=自定义)
|
|
184
|
-
var source = p.source === 'vault' ? 'vault' : 'dsh';
|
|
185
|
-
// 显式 type 优先;旧数据无 type 时由 source 推导(vault→manual,其余→import)
|
|
186
|
-
var type = p.type === 'manual' || p.type === 'import' ? p.type : (source === 'vault' ? 'manual' : 'import');
|
|
187
|
-
return { id: String(p.id || ''), name: name, adapter: p.adapter, ref: typeof p.ref === 'string' ? p.ref : '', source: source, type: type };
|
|
188
|
-
});
|
|
189
|
-
}
|
|
225
|
+
return normalizeProviders(JSON.parse(window.localStorage.getItem(PROVIDERS_KEY) || 'null'));
|
|
190
226
|
} catch (e) { /* ignore */ }
|
|
191
|
-
return
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
function saveProvidersCache(list) {
|
|
230
|
+
try { window.localStorage.setItem(PROVIDERS_KEY, JSON.stringify(list)); } catch (e) { /* ignore */ }
|
|
192
231
|
}
|
|
193
|
-
|
|
232
|
+
// host 恢复完成前的乐观缓存:优先用 localStorage 旧数据(多数场景与 host 一致),否则默认清单
|
|
233
|
+
var providersState = readProvidersCache() || DEFAULT_PROVIDERS.slice();
|
|
194
234
|
var providersListeners = new Set();
|
|
235
|
+
// 本次页面会话内的写入计数:恢复响应迟到时,若期间用户已写入则放弃应用 host 过期数据(防复活)
|
|
236
|
+
var providersMutations = 0;
|
|
195
237
|
function saveProviders() {
|
|
196
|
-
|
|
238
|
+
// 写穿透:localStorage 缓存 + host 落盘(fire-and-forget;host 失联时本地缓存仍可用)
|
|
239
|
+
saveProvidersCache(providersState);
|
|
240
|
+
try {
|
|
241
|
+
fetch('/api/provider-usage/providers', {
|
|
242
|
+
method: 'POST',
|
|
243
|
+
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
244
|
+
body: JSON.stringify({ providers: providersState }),
|
|
245
|
+
}).catch(function () { /* ignore */ });
|
|
246
|
+
} catch (e) { /* ignore */ }
|
|
247
|
+
}
|
|
248
|
+
// 内部应用(不记写入数):host 恢复/一次性迁移专用
|
|
249
|
+
function applyProvidersUpdate(update) {
|
|
250
|
+
var next = typeof update === 'function' ? update(providersState) : update;
|
|
251
|
+
providersState = next;
|
|
252
|
+
saveProviders();
|
|
253
|
+
providersListeners.forEach(function (fn) { fn(); });
|
|
197
254
|
}
|
|
198
255
|
function getProviders() { return providersState; }
|
|
199
|
-
|
|
200
|
-
|
|
256
|
+
// v0.8.0 修复(丢更新竞态):所有写方共享同一份 providersState 快照,整体覆盖式写入
|
|
257
|
+
// 在写入交错时会把先写的整单覆盖掉(如连删两个实例会复活第一个)。
|
|
258
|
+
// 现支持函数式更新:setProviders(function (prev) { return next; }) —— next 基于写入时刻的最新状态计算;
|
|
259
|
+
// 兼容旧用法:传数组视为恒等函数。
|
|
260
|
+
function setProviders(update) {
|
|
261
|
+
providersMutations += 1;
|
|
262
|
+
var next = typeof update === 'function' ? update(providersState) : update;
|
|
263
|
+
providersState = next;
|
|
201
264
|
saveProviders();
|
|
202
265
|
providersListeners.forEach(function (fn) { fn(); });
|
|
203
266
|
}
|
|
@@ -205,15 +268,43 @@ window.__ModuleLoader__.load({
|
|
|
205
268
|
providersListeners.add(fn);
|
|
206
269
|
return function () { providersListeners.delete(fn); };
|
|
207
270
|
}
|
|
271
|
+
// host 恢复只执行一次(首个订阅者触发;fetch 去重)
|
|
272
|
+
var hostRestoreStarted = false;
|
|
208
273
|
function useProviders() {
|
|
209
274
|
var [p, setP] = useState(getProviders);
|
|
210
|
-
useEffect(function () {
|
|
275
|
+
useEffect(function () {
|
|
276
|
+
var unsub = subscribeProviders(function () { setP(getProviders()); });
|
|
277
|
+
if (!hostRestoreStarted) {
|
|
278
|
+
hostRestoreStarted = true;
|
|
279
|
+
fetch('/api/provider-usage/providers', { headers: { accept: 'application/json' } })
|
|
280
|
+
.then(function (r) { return r.json().catch(function () { return null; }); })
|
|
281
|
+
.then(function (j) {
|
|
282
|
+
if (!j || !j.ok) return;
|
|
283
|
+
// 恢复期间用户已做过修改(增删改实例)→ 放弃 host 过期数据,避免把用户刚删的实例复活
|
|
284
|
+
if (providersMutations > 0) return;
|
|
285
|
+
if (j.exists && Array.isArray(j.providers)) {
|
|
286
|
+
// 主路径:host 有清单 → 恢复(host 为单一事实源;本地缓存仅作 host 失联时的乐观值)
|
|
287
|
+
applyProvidersUpdate(j.providers);
|
|
288
|
+
} else {
|
|
289
|
+
// 一次性迁移:host 无文件(首次升级到 0.8.0)→ 把当前缓存清单写回 host 固化
|
|
290
|
+
applyProvidersUpdate(getProviders());
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
.catch(function () { /* host 失联:继续用本地缓存,后续写操作会重试落盘 */ });
|
|
294
|
+
}
|
|
295
|
+
return unsub;
|
|
296
|
+
}, []);
|
|
211
297
|
return p;
|
|
212
298
|
}
|
|
213
|
-
/**
|
|
299
|
+
/** 可用(未暂停)的实例清单:卡片轮询与切换只在这些实例里进行。 */
|
|
300
|
+
function activeProviders(providers) {
|
|
301
|
+
return providers.filter(function (p) { return !p.paused; });
|
|
302
|
+
}
|
|
303
|
+
/** 当前选中实例;若被删除或已暂停则回落到第一个可用实例(全暂停/无实例 → null)。 */
|
|
214
304
|
function currentProvider(providers, providerId) {
|
|
215
|
-
|
|
216
|
-
|
|
305
|
+
var actives = activeProviders(providers);
|
|
306
|
+
for (var i = 0; i < actives.length; i++) if (actives[i].id === providerId) return actives[i];
|
|
307
|
+
return actives[0] || null;
|
|
217
308
|
}
|
|
218
309
|
|
|
219
310
|
// ── 工具 ──────────────────────────────────────────────────────────
|
|
@@ -326,7 +417,7 @@ window.__ModuleLoader__.load({
|
|
|
326
417
|
return function () { window.removeEventListener('resize', onResize); };
|
|
327
418
|
}, [s.right, s.bottom, s.visible, data]);
|
|
328
419
|
var current = currentProvider(providers, s.providerId);
|
|
329
|
-
var multiple = providers.length > 1;
|
|
420
|
+
var multiple = activeProviders(providers).length > 1;
|
|
330
421
|
var curKey = current ? current.id + '|' + current.adapter + '|' + current.ref : 'none';
|
|
331
422
|
|
|
332
423
|
var load = useCallback(function (force) {
|
|
@@ -372,12 +463,15 @@ window.__ModuleLoader__.load({
|
|
|
372
463
|
var meta = data && data.fetchedAt ? fmtTime(data.fetchedAt) + (data.stale ? ' · ' + zh.stale : '') : zh.loading;
|
|
373
464
|
var isBalance = current.adapter === 'balance-json';
|
|
374
465
|
var isCommandCode = current.adapter === 'commandcode-credits';
|
|
375
|
-
var
|
|
466
|
+
var isAmd = current.adapter === 'amd-radeon-usage';
|
|
467
|
+
var logoClass = isBalance ? 'deepseek' : (isCommandCode ? 'commandcode' : (isAmd ? 'amd' : 'go'));
|
|
376
468
|
var logoEl = isBalance
|
|
377
469
|
? React.createElement('img', { src: LOGO_DEEPSEEK, width: 24, height: 24, alt: '' })
|
|
378
470
|
: (isCommandCode
|
|
379
|
-
? React.createElement('span', { className: 'dsh-pu-cc-logo', 'aria-hidden': 'true' },
|
|
380
|
-
:
|
|
471
|
+
? React.createElement('span', { className: 'dsh-pu-cc-logo', 'aria-hidden': 'true' }, LOGO_CC)
|
|
472
|
+
: (isAmd
|
|
473
|
+
? React.createElement('span', { className: 'dsh-pu-cc-logo', 'aria-hidden': 'true' }, LOGO_AMD)
|
|
474
|
+
: LOGO_OCGO));
|
|
381
475
|
|
|
382
476
|
var body = null;
|
|
383
477
|
if (loading && !data) {
|
|
@@ -399,38 +493,44 @@ window.__ModuleLoader__.load({
|
|
|
399
493
|
(data.monthly && data.monthly.usedPct !== null
|
|
400
494
|
? React.createElement(WindowRow, { key: 'm', label: zh.monthly, win: data.monthly, bar: 'magenta' })
|
|
401
495
|
: null)].filter(Boolean)
|
|
402
|
-
:
|
|
403
|
-
|
|
404
|
-
|
|
496
|
+
: (isAmd
|
|
497
|
+
// AMD:单日窗口进度条(对齐网页 Today Used / Remaining;无周/月)
|
|
498
|
+
? [React.createElement(WindowRow, { key: 'd', label: zh.daily, win: data.windows.daily, bar: 'magenta' })]
|
|
499
|
+
: [React.createElement(WindowRow, { key: 'r', label: zh.rolling, win: data.windows.rolling, bar: 'pink' }),
|
|
500
|
+
React.createElement(WindowRow, { key: 'w', label: zh.weekly, win: data.windows.weekly, bar: 'orange' }),
|
|
501
|
+
React.createElement(WindowRow, { key: 'm', label: zh.monthly, win: data.windows.monthly, bar: 'magenta' })]))
|
|
405
502
|
: null;
|
|
406
503
|
var warn = data.isValid === false && data.invalidMessage
|
|
407
504
|
? React.createElement('div', { className: 'dsh-pu-error' }, data.invalidMessage)
|
|
408
505
|
: null;
|
|
409
|
-
// Command Code 混合卡:主数字 =
|
|
506
|
+
// Command Code / AMD 混合卡:主数字 = 剩余百分比(3 位小数向下取整),副行 = 已用/总额 + 剩余额度
|
|
507
|
+
// Command Code 读 monthly.remainingPct;AMD 读 windows.daily.remainingPct(今日)
|
|
410
508
|
var cc = null;
|
|
411
|
-
|
|
412
|
-
|
|
509
|
+
var remainPct = null;
|
|
510
|
+
if (isCommandCode || isAmd) {
|
|
511
|
+
var pctInfo = isAmd ? (data.windows && data.windows.daily) : (data.monthly || null);
|
|
413
512
|
// 新服务端直接给 remainingPct;旧服务端只有 usedPct → 按 100−usedPct 兜底(与 host 公式数学等价)
|
|
414
|
-
|
|
415
|
-
? (
|
|
416
|
-
?
|
|
417
|
-
: (
|
|
513
|
+
remainPct = pctInfo
|
|
514
|
+
? (pctInfo.remainingPct !== undefined && pctInfo.remainingPct !== null
|
|
515
|
+
? pctInfo.remainingPct
|
|
516
|
+
: (pctInfo.usedPct === undefined || pctInfo.usedPct === null
|
|
418
517
|
? null
|
|
419
|
-
: Math.floor(Math.max(0, Math.min(100, 100 -
|
|
518
|
+
: Math.floor(Math.max(0, Math.min(100, 100 - pctInfo.usedPct)) * 1000) / 1000))
|
|
420
519
|
: null;
|
|
421
|
-
var
|
|
422
|
-
var
|
|
423
|
-
var
|
|
520
|
+
var unitSym = isAmd ? '' : '$';
|
|
521
|
+
var usedStr = data.used === null ? '--' : Number(data.used).toFixed(isAmd ? 3 : 2);
|
|
522
|
+
var quotaStr = data.totalQuota === null ? '--' : Number(data.totalQuota).toFixed(isAmd ? 3 : 2);
|
|
523
|
+
var remainStr = data.remaining === null ? '--' : Number(data.remaining).toFixed(isAmd ? 3 : 2);
|
|
424
524
|
cc = React.createElement('div', { className: 'dsh-pu-cc-sub' },
|
|
425
|
-
React.createElement('span', { className: 'dsh-pu-cc-used' }, '已用
|
|
426
|
-
React.createElement('span', { className: 'dsh-pu-cc-remain' }, '剩余
|
|
525
|
+
React.createElement('span', { className: 'dsh-pu-cc-used' }, (isAmd ? zh.daily + ' ' : '') + '已用 ' + unitSym + usedStr + ' / ' + unitSym + quotaStr + (isAmd ? ' pts' : '')),
|
|
526
|
+
React.createElement('span', { className: 'dsh-pu-cc-remain' }, (isAmd ? zh.daily + ' ' : '') + '剩余 ' + unitSym + remainStr + (isAmd ? ' pts' : '')));
|
|
427
527
|
}
|
|
428
528
|
// 窗口行/主值/警告条已覆盖全部信息,extra 全文不再展示(保持卡片干净)
|
|
429
529
|
var extra = null;
|
|
430
530
|
body = React.createElement('div', null,
|
|
431
531
|
React.createElement('div', { className: 'dsh-pu-remain' },
|
|
432
|
-
React.createElement('span', { className: 'dsh-pu-remain-big' }, isCommandCode ? (remainPct === null ? '--' : remainPct.toFixed(3) + '%') : main),
|
|
433
|
-
React.createElement('span', { className: 'dsh-pu-remain-label' }, isCommandCode ? ' ' + zh.remaining + '(' + zh.monthly + ')' : ' ' + mainLabel)),
|
|
532
|
+
React.createElement('span', { className: 'dsh-pu-remain-big' }, (isCommandCode || isAmd) ? (remainPct === null ? '--' : remainPct.toFixed(3) + '%') : main),
|
|
533
|
+
React.createElement('span', { className: 'dsh-pu-remain-label' }, (isCommandCode || isAmd) ? ' ' + zh.remaining + '(' + (isAmd ? zh.daily : zh.monthly) + ')' : ' ' + mainLabel)),
|
|
434
534
|
warn,
|
|
435
535
|
cc,
|
|
436
536
|
winRows,
|
|
@@ -440,13 +540,15 @@ window.__ModuleLoader__.load({
|
|
|
440
540
|
var menu = null;
|
|
441
541
|
if (multiple && menuOpen) {
|
|
442
542
|
menu = React.createElement('div', { ref: menuRef, className: 'dsh-pu-menu' },
|
|
443
|
-
providers.map(function (p) {
|
|
543
|
+
activeProviders(providers).map(function (p) {
|
|
444
544
|
var active = p.id === current.id;
|
|
445
545
|
var mini = p.adapter === 'balance-json'
|
|
446
546
|
? React.createElement('img', { src: LOGO_DEEPSEEK, width: 16, height: 16, alt: '' })
|
|
447
547
|
: (p.adapter === 'commandcode-credits'
|
|
448
|
-
? React.createElement('span', { className: 'dsh-pu-cc-logo dsh-pu-cc-logo-sm', 'aria-hidden': 'true' },
|
|
449
|
-
:
|
|
548
|
+
? React.createElement('span', { className: 'dsh-pu-cc-logo dsh-pu-cc-logo-sm', 'aria-hidden': 'true' }, LOGO_CC)
|
|
549
|
+
: (p.adapter === 'amd-radeon-usage'
|
|
550
|
+
? React.createElement('span', { className: 'dsh-pu-cc-logo dsh-pu-cc-logo-sm', 'aria-hidden': 'true' }, LOGO_AMD)
|
|
551
|
+
: LOGO_OCGO));
|
|
450
552
|
return React.createElement('button', {
|
|
451
553
|
key: p.id, type: 'button', className: 'dsh-pu-menu-item' + (active ? ' active' : ''),
|
|
452
554
|
onClick: function (e) { if (draggedRef.current) { draggedRef.current = false; return; } e.stopPropagation(); setSettings({ providerId: p.id }); setMenuOpen(false); },
|
|
@@ -767,6 +869,8 @@ window.__ModuleLoader__.load({
|
|
|
767
869
|
var [queryStatus, setQueryStatus] = useState({});
|
|
768
870
|
var [errOpen, setErrOpen] = useState({}); // 展开的错误详情(按实例 id)
|
|
769
871
|
var refsKey = providers.map(function (p) { return p.ref || ''; }).join(',');
|
|
872
|
+
// 暂停态也进依赖:切「恢复/暂停」后让设置页状态查询重新跑(暂停的不查、恢复的马上查)
|
|
873
|
+
var refsPauseKey = providers.map(function (p) { return (p.paused ? '1' : '0') + ':' + (p.ref || ''); }).join(',');
|
|
770
874
|
// 添加卡:mode = null | 'import' | 'manual';编辑卡:edit = null | { id, kind, adapter, ref }
|
|
771
875
|
var [mode, setMode] = useState(null);
|
|
772
876
|
var [edit, setEdit] = useState(null);
|
|
@@ -777,14 +881,22 @@ window.__ModuleLoader__.load({
|
|
|
777
881
|
var [formError, setFormError] = useState(null);
|
|
778
882
|
|
|
779
883
|
function removeProvider(p) {
|
|
780
|
-
var next = providers.filter(function (x) { return x.id !== p.id; });
|
|
781
884
|
if (p.source === 'vault' && p.ref) {
|
|
782
885
|
// 删除自定义实例时同步清理插件私有库中的 Key(fire-and-forget)
|
|
783
886
|
fetch('/api/provider-usage/credentials?ref=' + encodeURIComponent(p.ref), { headers: { accept: 'application/json' } })
|
|
784
887
|
.catch(function () { /* ignore */ });
|
|
785
888
|
}
|
|
786
|
-
|
|
787
|
-
|
|
889
|
+
// v0.8.0:函数式更新 —— 基于写入时刻的最新清单计算,连删/交错写入不再互相覆盖
|
|
890
|
+
var next = null;
|
|
891
|
+
setProviders(function (prev) {
|
|
892
|
+
next = prev.filter(function (x) { return x.id !== p.id; });
|
|
893
|
+
return next;
|
|
894
|
+
});
|
|
895
|
+
// 若当前卡片实例被删除,回落到第一个可用实例(跳过已暂停的)
|
|
896
|
+
if (currentProvider(next, s.providerId || '') === null) {
|
|
897
|
+
var first = activeProviders(next)[0];
|
|
898
|
+
setSettings({ providerId: first ? first.id : '' });
|
|
899
|
+
}
|
|
788
900
|
}
|
|
789
901
|
|
|
790
902
|
function openImport() {
|
|
@@ -807,7 +919,7 @@ window.__ModuleLoader__.load({
|
|
|
807
919
|
setMode('manual');
|
|
808
920
|
setEdit(null);
|
|
809
921
|
setFormError(null);
|
|
810
|
-
setForm({ provider: '', name: '
|
|
922
|
+
setForm({ provider: '', name: 'AMD Radeon Cloud (中国)', adapter: 'amd-radeon-usage', key: '', nameTouched: false });
|
|
811
923
|
}
|
|
812
924
|
function cancelAdd() { setMode(null); setEdit(null); setFormError(null); }
|
|
813
925
|
|
|
@@ -818,8 +930,8 @@ window.__ModuleLoader__.load({
|
|
|
818
930
|
var item = null;
|
|
819
931
|
for (var i = 0; i < dshItems.length; i++) if (dshItems[i].route === form.provider) item = dshItems[i];
|
|
820
932
|
if (!item) { setFormError('导入项不存在'); return; }
|
|
821
|
-
var inst = { id: item.route + '-' + Date.now(), name: name, adapter: item.adapter, ref: item.ref, source: 'dsh', type: 'import' };
|
|
822
|
-
setProviders(
|
|
933
|
+
var inst = { id: item.route + '-' + Date.now(), name: name, adapter: item.adapter, ref: item.ref, source: 'dsh', type: 'import', paused: false };
|
|
934
|
+
setProviders(function (prev) { return prev.concat([inst]); });
|
|
823
935
|
setSettings({ providerId: inst.id });
|
|
824
936
|
setMode(null);
|
|
825
937
|
}
|
|
@@ -840,8 +952,8 @@ window.__ModuleLoader__.load({
|
|
|
840
952
|
});
|
|
841
953
|
var j = await resp.json().catch(function () { return null; });
|
|
842
954
|
if (!j || !j.ok) { setFormError((j && j.error && j.error.message) || '保存 Key 失败'); return; }
|
|
843
|
-
var inst = { id: instId, name: name, adapter: form.adapter, ref: instId, source: 'vault', type: 'manual' };
|
|
844
|
-
setProviders(
|
|
955
|
+
var inst = { id: instId, name: name, adapter: form.adapter, ref: instId, source: 'vault', type: 'manual', paused: false };
|
|
956
|
+
setProviders(function (prev) { return prev.concat([inst]); });
|
|
845
957
|
setSettings({ providerId: inst.id });
|
|
846
958
|
setMode(null);
|
|
847
959
|
} catch (e) {
|
|
@@ -851,11 +963,12 @@ window.__ModuleLoader__.load({
|
|
|
851
963
|
}
|
|
852
964
|
}
|
|
853
965
|
|
|
854
|
-
// ──
|
|
966
|
+
// ── 编辑:导入的只改名称 + 启用状态;自定义(vault)可改名称 + 覆盖 Key + 启用状态 ──
|
|
967
|
+
// enabled = 表单暂存值(false 表示“暂停”);开关只暂存在表单里,点「保存」才真正写入实例
|
|
855
968
|
function startEdit(p) {
|
|
856
969
|
setMode(null);
|
|
857
|
-
setEdit({ id: p.id, kind: p.type === 'manual' ? 'manual' : 'import', adapter: p.adapter, ref: p.ref });
|
|
858
|
-
setForm({ provider: '', name: p.name, adapter: p.adapter, key: '', ref: p.ref, nameTouched: true });
|
|
970
|
+
setEdit({ id: p.id, kind: p.type === 'manual' ? 'manual' : 'import', adapter: p.adapter, ref: p.ref, paused: !!p.paused });
|
|
971
|
+
setForm({ provider: '', name: p.name, adapter: p.adapter, key: '', ref: p.ref, nameTouched: true, enabled: !p.paused, _editId: p.id });
|
|
859
972
|
setFormError(null);
|
|
860
973
|
}
|
|
861
974
|
|
|
@@ -883,10 +996,30 @@ window.__ModuleLoader__.load({
|
|
|
883
996
|
setBusy(false);
|
|
884
997
|
}
|
|
885
998
|
}
|
|
886
|
-
|
|
999
|
+
// 名称 + 启用状态在同一次 setProviders 里写入(避免两次基于旧数组的写入互相覆盖)
|
|
1000
|
+
var enabled2 = form.enabled === false ? false : true;
|
|
1001
|
+
// v0.8.0:函数式更新 —— next 基于写入时刻的最新清单计算,不再依赖渲染闭包的旧 providers
|
|
1002
|
+
var next = null;
|
|
1003
|
+
setProviders(function (prev) {
|
|
1004
|
+
next = prev.map(function (p) {
|
|
1005
|
+
return p.id === edit.id ? Object.assign({}, p, { name: name, paused: !enabled2 }) : p;
|
|
1006
|
+
});
|
|
1007
|
+
return next;
|
|
1008
|
+
});
|
|
1009
|
+
// 卡片当前实例处理(基于更新后的 next):
|
|
1010
|
+
// - 停用的正是当前卡片实例 → 显式切到下一个仍启用的(currentProvider 会自动回落,不能依赖它判空)
|
|
1011
|
+
// - 恢复某实例且此前没有任何可用实例(卡片隐藏态)→ 恢复的这个直接成为卡片当前实例
|
|
1012
|
+
if (!enabled2 && (s.providerId || '') === edit.id) {
|
|
1013
|
+
var fallback = null;
|
|
1014
|
+
var act = activeProviders(next);
|
|
1015
|
+
for (var i = 0; i < act.length; i++) if (act[i].id !== edit.id) { fallback = act[i]; break; }
|
|
1016
|
+
setSettings({ providerId: fallback ? fallback.id : '' });
|
|
1017
|
+
} else if (enabled2 && !currentProvider(next, s.providerId || '')) {
|
|
1018
|
+
setSettings({ providerId: edit.id });
|
|
1019
|
+
}
|
|
887
1020
|
if (keyChanged) setNonce(function (n) { return n + 1; });
|
|
888
1021
|
setEdit(null);
|
|
889
|
-
setForm({ provider: '', name: '', adapter: 'usage-percent', key: '', ref: '', nameTouched: false });
|
|
1022
|
+
setForm({ provider: '', name: '', adapter: 'usage-percent', key: '', ref: '', nameTouched: false, enabled: true, _editId: null });
|
|
890
1023
|
}
|
|
891
1024
|
|
|
892
1025
|
// ── 状态查询(打开设置页时:describe + 每实例实际查询)──
|
|
@@ -900,6 +1033,7 @@ window.__ModuleLoader__.load({
|
|
|
900
1033
|
var jobs = [];
|
|
901
1034
|
providers.forEach(function (p) {
|
|
902
1035
|
if (!p.ref) return;
|
|
1036
|
+
if (p.paused) return; // 已暂停的实例不做任何查询(设置页打开时也只显示「已暂停」灰点)
|
|
903
1037
|
if (configuredMap && !configuredMap[p.ref]) return;
|
|
904
1038
|
var key = p.adapter + '|' + p.ref;
|
|
905
1039
|
setQueryStatus(function (prev) {
|
|
@@ -941,13 +1075,16 @@ window.__ModuleLoader__.load({
|
|
|
941
1075
|
j.refs.forEach(function (x) { map[x.name] = !!x.configured; storeMap[x.name] = x.store; });
|
|
942
1076
|
setRefStatus(map);
|
|
943
1077
|
// 旧数据自愈:source 非 vault 但 Key 只存在插件私有库 → 升级为手动实例(可改名称+Key)
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1078
|
+
// v0.8.0:函数式更新,避免覆盖恢复/添加等交错写入
|
|
1079
|
+
setProviders(function (prev) {
|
|
1080
|
+
var changed = false;
|
|
1081
|
+
var next = prev.map(function (p) {
|
|
1082
|
+
if (p.source === 'vault' || !p.ref) return p;
|
|
1083
|
+
if (storeMap[p.ref] === 'vault') { changed = true; return Object.assign({}, p, { source: 'vault' }); }
|
|
1084
|
+
return p;
|
|
1085
|
+
});
|
|
1086
|
+
return changed ? next : prev;
|
|
949
1087
|
});
|
|
950
|
-
if (changed) setProviders(next);
|
|
951
1088
|
return startQueries(map, nonce > 0);
|
|
952
1089
|
}
|
|
953
1090
|
return startQueries(null, nonce > 0);
|
|
@@ -958,7 +1095,7 @@ window.__ModuleLoader__.load({
|
|
|
958
1095
|
startQueries(null, nonce > 0);
|
|
959
1096
|
});
|
|
960
1097
|
return function () { alive = false; };
|
|
961
|
-
}, [refsKey, nonce]);
|
|
1098
|
+
}, [refsKey, refsPauseKey, nonce]);
|
|
962
1099
|
|
|
963
1100
|
var adapterOptions = Object.keys(ADAPTER_META).map(function (a) {
|
|
964
1101
|
return React.createElement('option', { key: a, value: a }, ADAPTER_META[a].displayName);
|
|
@@ -1009,7 +1146,7 @@ window.__ModuleLoader__.load({
|
|
|
1009
1146
|
className: 'zGbnIq_input zGbnIq_selectInput', value: form.adapter,
|
|
1010
1147
|
onChange: function (e) {
|
|
1011
1148
|
var next = Object.assign({}, form, { adapter: e.target.value });
|
|
1012
|
-
if (!form.nameTouched) next.name = e.target.value === 'balance-json' ? 'DeepSeek' : (e.target.value === 'commandcode-credits' ? 'Command Code' : 'OpenCode Go');
|
|
1149
|
+
if (!form.nameTouched) next.name = e.target.value === 'balance-json' ? 'DeepSeek' : (e.target.value === 'commandcode-credits' ? 'Command Code' : (e.target.value === 'amd-radeon-usage' ? 'AMD Radeon Cloud (中国)' : 'OpenCode Go'));
|
|
1013
1150
|
setForm(next);
|
|
1014
1151
|
},
|
|
1015
1152
|
}, adapterOptions)),
|
|
@@ -1033,6 +1170,19 @@ window.__ModuleLoader__.load({
|
|
|
1033
1170
|
React.createElement('button', { type: 'button', className: 'zGbnIq_primaryButton', disabled: busy, onClick: saveManual }, '保存')));
|
|
1034
1171
|
} else if (edit) {
|
|
1035
1172
|
var editManual = edit.kind === 'manual';
|
|
1173
|
+
var enabled = form.enabled === false ? false : true;
|
|
1174
|
+
var enableToggle = React.createElement('div', { className: 'zGbnIq_field' },
|
|
1175
|
+
React.createElement('span', { className: 'zGbnIq_fieldLabel' }, '启用'),
|
|
1176
|
+
React.createElement('label', { className: 'zGbnIq_editorSwitchRow' },
|
|
1177
|
+
React.createElement('button', {
|
|
1178
|
+
type: 'button', role: 'switch',
|
|
1179
|
+
'aria-checked': enabled ? 'true' : 'false',
|
|
1180
|
+
'aria-label': enabled ? ('已启用 ' + form.name + ',点击暂停') : ('已暂停 ' + form.name + ',点击启用'),
|
|
1181
|
+
className: 'dsh-pu-switch' + (enabled ? ' dsh-pu-switch-on' : ''),
|
|
1182
|
+
onClick: function () { setForm(Object.assign({}, form, { enabled: !enabled })); },
|
|
1183
|
+
}, React.createElement('span', { className: 'dsh-pu-switch-thumb' })),
|
|
1184
|
+
React.createElement('span', { className: 'dsh-pu-switchText' }, enabled ? '已启用' : '已暂停')),
|
|
1185
|
+
React.createElement('span', { className: 'zGbnIq_advancedHint' }, enabled ? '启用中:正常轮询查询,卡片可切换选择' : '已暂停:不再轮询查询,卡片菜单中不可选(Key 与配置保留)'));
|
|
1036
1186
|
addCard = React.createElement('div', { className: 'zGbnIq_editor' },
|
|
1037
1187
|
React.createElement('div', { className: 'zGbnIq_editorHeader' },
|
|
1038
1188
|
React.createElement('span', { className: 'zGbnIq_editorTitle' }, editManual ? '编辑自定义提供方' : '编辑提供方')),
|
|
@@ -1048,6 +1198,7 @@ window.__ModuleLoader__.load({
|
|
|
1048
1198
|
className: 'zGbnIq_input', value: form.name,
|
|
1049
1199
|
onChange: function (e) { setForm(Object.assign({}, form, { name: e.target.value, nameTouched: true })); },
|
|
1050
1200
|
})),
|
|
1201
|
+
enableToggle,
|
|
1051
1202
|
editManual ? React.createElement('div', { className: 'zGbnIq_field' },
|
|
1052
1203
|
React.createElement('span', { className: 'zGbnIq_fieldLabel' }, 'API 密钥'),
|
|
1053
1204
|
React.createElement('input', {
|
|
@@ -1094,7 +1245,8 @@ window.__ModuleLoader__.load({
|
|
|
1094
1245
|
: providers.map(function (pl) {
|
|
1095
1246
|
var qs = pl.ref ? queryStatus[pl.adapter + '|' + pl.ref] : null;
|
|
1096
1247
|
var refKnownRow = pl.ref ? Object.prototype.hasOwnProperty.call(refStatus, pl.ref) : true;
|
|
1097
|
-
|
|
1248
|
+
// 已暂停的实例不再轮询/查询,状态点固定为「已暂停」灰点,查询状态一律不展示
|
|
1249
|
+
var status = (pl.paused || !pl.ref) ? null : statusOf(refKnownRow, refCheckFailed, !!refStatus[pl.ref], qs);
|
|
1098
1250
|
return React.createElement(ProviderRow, {
|
|
1099
1251
|
key: pl.id,
|
|
1100
1252
|
provider: pl,
|
|
@@ -1142,7 +1294,7 @@ window.__ModuleLoader__.load({
|
|
|
1142
1294
|
usagePanel);
|
|
1143
1295
|
}
|
|
1144
1296
|
// 提供方单行:状态点 + 名称 + 编辑/删除 + 展开详情
|
|
1145
|
-
// 状态判定全部交给 statusOf
|
|
1297
|
+
// 状态判定全部交给 statusOf;本组件只做渲染;启用/暂停开关在编辑弹层里管理
|
|
1146
1298
|
function ProviderRow(props) {
|
|
1147
1299
|
var pl = props.provider;
|
|
1148
1300
|
var status = props.status;
|
|
@@ -1150,24 +1302,36 @@ window.__ModuleLoader__.load({
|
|
|
1150
1302
|
var onToggle = props.onToggle;
|
|
1151
1303
|
var onEdit = props.onEdit;
|
|
1152
1304
|
var onRemove = props.onRemove;
|
|
1153
|
-
var
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1305
|
+
var paused = pl.paused === true;
|
|
1306
|
+
var dotEl;
|
|
1307
|
+
if (paused) {
|
|
1308
|
+
// 已暂停:灰点「已暂停」,不可展开(暂停时不再查询,无新鲜结果/错误可看)
|
|
1309
|
+
dotEl = React.createElement('span', {
|
|
1310
|
+
className: 'zGbnIq_credentialDot dsh-pu-dot-paused',
|
|
1311
|
+
role: 'img', 'aria-label': '已暂停',
|
|
1312
|
+
});
|
|
1313
|
+
} else if (status && status.dotCls) {
|
|
1314
|
+
dotEl = React.createElement('span', {
|
|
1315
|
+
className: 'zGbnIq_credentialDot' + status.dotCls,
|
|
1316
|
+
role: 'img', 'aria-label': status.label,
|
|
1317
|
+
style: status.clickable ? { cursor: 'pointer' } : undefined,
|
|
1318
|
+
onClick: status.clickable ? onToggle : undefined,
|
|
1319
|
+
});
|
|
1320
|
+
} else {
|
|
1321
|
+
dotEl = null;
|
|
1322
|
+
}
|
|
1159
1323
|
var detailEl = null;
|
|
1160
|
-
if (expanded && status.detailKind) {
|
|
1324
|
+
if (!paused && expanded && status && status.detailKind) {
|
|
1161
1325
|
if (status.detailKind === 'err') {
|
|
1162
1326
|
detailEl = React.createElement('p', { className: 'zGbnIq_error', style: { margin: '10px 0 0' } }, status.detailText);
|
|
1163
1327
|
} else if (status.detailKind === 'resp') {
|
|
1164
1328
|
detailEl = React.createElement('div', { className: 'zGbnIq_json' }, status.detailText);
|
|
1165
1329
|
}
|
|
1166
1330
|
}
|
|
1167
|
-
return React.createElement('li', { key: pl.id, className: 'zGbnIq_rowCard' },
|
|
1331
|
+
return React.createElement('li', { key: pl.id, className: 'zGbnIq_rowCard' + (paused ? ' zGbnIq_rowPaused' : '') },
|
|
1168
1332
|
React.createElement('div', { className: 'zGbnIq_rowHead' },
|
|
1169
1333
|
React.createElement('span', { className: 'zGbnIq_rowIdentity' },
|
|
1170
|
-
React.createElement('span', { className: 'zGbnIq_rowName' }, pl.name),
|
|
1334
|
+
React.createElement('span', { className: 'zGbnIq_rowName' + (paused ? ' zGbnIq_rowNameDim' : '') }, pl.name),
|
|
1171
1335
|
dotEl),
|
|
1172
1336
|
React.createElement('span', { className: 'zGbnIq_rowActions' },
|
|
1173
1337
|
React.createElement('button', { type: 'button', className: 'zGbnIq_secondaryButton', onClick: onEdit }, '编辑'),
|
|
@@ -1196,6 +1360,8 @@ window.__ModuleLoader__.load({
|
|
|
1196
1360
|
try {
|
|
1197
1361
|
if (!getSettings().visible) return; // 用户主动隐藏,不自动复活
|
|
1198
1362
|
if (getProviders().length === 0) return; // 无提供方实例:卡片本就渲染 null,避免 10s 空转重挂
|
|
1363
|
+
// 全部已暂停同理:currentProvider 回落为 null → 卡片渲染 null,无需重挂
|
|
1364
|
+
if (!currentProvider(getProviders(), getSettings().providerId)) return;
|
|
1199
1365
|
if (rootEl && rootEl.isConnected && document.querySelector('.dsh-pu-card')) return;
|
|
1200
1366
|
mount();
|
|
1201
1367
|
} catch (e) {
|
package/lib/index.js
CHANGED
|
@@ -4,14 +4,15 @@
|
|
|
4
4
|
// balance-json(余额型) :DeepSeek {{baseUrl}}/user/balance → balance_infos[]
|
|
5
5
|
// 凭证链 per provider:DSH 设置 provider.apiKeyEnv → 凭证服务 → 环境变量。
|
|
6
6
|
// 路由:/api/provider-usage/opencode-go(兼容旧版)、/api/provider-usage/query?provider=<id>、
|
|
7
|
-
// /api/provider-usage/templates / credentials / dsh-providers / credential-refs / reset-local-stats
|
|
7
|
+
// /api/provider-usage/templates / credentials / dsh-providers / credential-refs / reset-local-stats(仅回环)、
|
|
8
|
+
// /api/provider-usage/providers(提供方实例清单持久化,v0.8.0 —— host 端 providers.json 为单一事实源)。
|
|
8
9
|
// 本地 Token 统计(v0.5.0):/api/provider-usage/local-usage(累计+按模型汇总,按天物化存储),
|
|
9
10
|
// 数据源 = 自读 $DSH_HOME/sessions 会话文件(fzstd 解压,assistant/message 事件 data.usage),只读、不联网;
|
|
10
11
|
// 存储 = $DSH_HOME/provider-usage/daily-stats/YYYY-MM-DD.json:历史天封存(deps mtime 校验),只算今天。
|
|
11
12
|
// 缓存:每 provider 独立 30s 新鲜窗口 + 并发去重 + 3 快照环;stale-while-error。
|
|
12
13
|
|
|
13
14
|
import { join, dirname } from 'node:path'
|
|
14
|
-
import { stat as fsStat } from 'node:fs/promises'
|
|
15
|
+
import { stat as fsStat, readFile, writeFile, rename, chmod, mkdir } from 'node:fs/promises'
|
|
15
16
|
import { readFileSync } from 'node:fs'
|
|
16
17
|
import { createSecureStore, resolveProviderUsageDir } from './secure-store.js'
|
|
17
18
|
import * as daily from './daily-stats.js'
|
|
@@ -20,8 +21,8 @@ export const name = 'provider-usage'
|
|
|
20
21
|
|
|
21
22
|
/** 插件版本单一来源:package.json(UA 标识用;bundle 内嵌等读不到时回退字面量)。 */
|
|
22
23
|
const PLUGIN_VERSION = (() => {
|
|
23
|
-
try { return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version || '0.
|
|
24
|
-
catch { return '0.
|
|
24
|
+
try { return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version || '0.10.0' }
|
|
25
|
+
catch { return '0.10.0' }
|
|
25
26
|
})()
|
|
26
27
|
const USER_AGENT = 'dsh-provider-usage/' + PLUGIN_VERSION
|
|
27
28
|
|
|
@@ -50,7 +51,7 @@ const LOCAL_DEPS_CHECK_MS = 300000
|
|
|
50
51
|
const ADAPTERS = [
|
|
51
52
|
{
|
|
52
53
|
id: 'usage-percent',
|
|
53
|
-
displayName: 'OpenCode Go
|
|
54
|
+
displayName: 'OpenCode Go',
|
|
54
55
|
baseUrl: 'https://opencode.ai/zen/go',
|
|
55
56
|
usagePath: '/v1/usage',
|
|
56
57
|
defaultCredentialRef: 'OPENCODE_GO_API_KEY',
|
|
@@ -70,7 +71,7 @@ const ADAPTERS = [
|
|
|
70
71
|
// Command Code:DSH 非内置提供方(pi-ai 注册表无 commandcode),只允许「添加自定义提供方」
|
|
71
72
|
// (customOnly),Key 只存插件私有库(settingsProviderKey: null → 凭证链跳过 DSH 模型设置)。
|
|
72
73
|
id: 'commandcode-credits',
|
|
73
|
-
displayName: 'Command Code
|
|
74
|
+
displayName: 'Command Code',
|
|
74
75
|
baseUrl: 'https://api.commandcode.ai',
|
|
75
76
|
usagePath: '/alpha/billing/credits',
|
|
76
77
|
subscriptionPath: '/alpha/billing/subscriptions',
|
|
@@ -79,6 +80,21 @@ const ADAPTERS = [
|
|
|
79
80
|
customOnly: true,
|
|
80
81
|
description: 'Command Code 订阅使用量(5小时/周窗口)+ 月度剩余额度(USD)',
|
|
81
82
|
},
|
|
83
|
+
{
|
|
84
|
+
// AMD Radeon Cloud(Model API):DSH 非内置提供方,只允许「添加自定义提供方」(customOnly),
|
|
85
|
+
// Key 只存插件私有库。实测可用端点 = {baseUrl}/usage(文档页 /api/profile/model-usage 404,见 tmp 调研)。
|
|
86
|
+
// 返回今日剩余额度(pts)+ 今日/近24h/近30天/累计汇总 + 按模型拆分;无周/月窗口。
|
|
87
|
+
// AMD Radeon Cloud 分国内 / 国际两个版本:本条目 = 中国版(developer.amd.com.cn);国际版
|
|
88
|
+
// baseUrl/端点不同,预留位置——后续接入时新增一个 amd-radeon-usage-intl 适配器条目即可。
|
|
89
|
+
id: 'amd-radeon-usage',
|
|
90
|
+
displayName: 'AMD Radeon Cloud (中国)',
|
|
91
|
+
baseUrl: 'https://developer.amd.com.cn/radeon/api/v1',
|
|
92
|
+
usagePath: '/usage',
|
|
93
|
+
defaultCredentialRef: 'RADEON_API_KEY',
|
|
94
|
+
settingsProviderKey: null,
|
|
95
|
+
customOnly: true,
|
|
96
|
+
description: 'AMD Radeon Cloud (中国) Model API 用量/余额(今日剩余 pts + 时间窗口汇总)',
|
|
97
|
+
},
|
|
82
98
|
]
|
|
83
99
|
|
|
84
100
|
/** 旧 provider id → 适配器 id(向后兼容 /api/provider-usage/opencode-go 与 query?provider=…)。 */
|
|
@@ -87,6 +103,9 @@ const LEGACY_PROVIDER_MAP = { 'opencode-go': 'usage-percent', 'deepseek-balance'
|
|
|
87
103
|
/** Command Code 混合卡适配器(模块级常量,避免每次查询重复 find)。 */
|
|
88
104
|
const COMMANDCODE_ADAPTER = ADAPTERS.find((a) => a.id === 'commandcode-credits') || null
|
|
89
105
|
|
|
106
|
+
/** AMD Radeon Cloud 余额卡适配器(模块级常量,避免每次查询重复 find)。 */
|
|
107
|
+
const AMD_RADEON_ADAPTER = ADAPTERS.find((a) => a.id === 'amd-radeon-usage') || null
|
|
108
|
+
|
|
90
109
|
function isLoopbackRequest(req) {
|
|
91
110
|
const addr = req.socket && req.socket.remoteAddress
|
|
92
111
|
if (addr !== '127.0.0.1' && addr !== '::1' && addr !== '::ffff:127.0.0.1') return false
|
|
@@ -384,6 +403,95 @@ function parseCommandCodeCredits(creditsBody, subBody) {
|
|
|
384
403
|
}
|
|
385
404
|
}
|
|
386
405
|
|
|
406
|
+
// ── AMD Radeon Cloud(v0.10 调研新增):余额+配额卡 ─────────────────────
|
|
407
|
+
// 官方接口(实测):GET {baseUrl}/usage → 今日剩余 pts + 四个时间段汇总 + 按模型拆分。
|
|
408
|
+
// 无周/月窗口(文档页 /api/profile/model-usage 404;接入以实测 /usage 为准,见 tmp 调研)。
|
|
409
|
+
// 数据形态对齐 Command Code 混合卡:主数字 = 今日剩余百分比(3 位小数向下取整),
|
|
410
|
+
// 副行 = 已用/限额 + 剩余(单位 pts,等价 USD,网页用 pts 表述),单日窗口进度条。
|
|
411
|
+
|
|
412
|
+
/** 今日剩余百分比:钳制 [0,100] 后保留 3 位小数向下取整(与 commandcodeMonthlyRemainingPct 同语义)。 */
|
|
413
|
+
function amdRadeonRemainingPct(limitUsd, remainingUsd) {
|
|
414
|
+
const limit = Number(limitUsd)
|
|
415
|
+
if (!Number.isFinite(limit) || !(limit > 0)) return null
|
|
416
|
+
const remaining = Number(remainingUsd)
|
|
417
|
+
if (!Number.isFinite(remaining)) return null
|
|
418
|
+
const pct = Math.max(0, Math.min(100, (remaining / limit) * 100))
|
|
419
|
+
return Math.floor(pct * 1000) / 1000
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** 把 /usage 响应换算成结构化结果(余额 + 单日窗口 + 时间段/按模型汇总透传)。 */
|
|
423
|
+
function parseAmdRadeonUsage(body) {
|
|
424
|
+
let data = body
|
|
425
|
+
if (typeof data === 'string') {
|
|
426
|
+
try { data = JSON.parse(data) } catch { return { error: 'bad-json' } }
|
|
427
|
+
}
|
|
428
|
+
const limitUsd = data && data.daily_cost_limit_usd
|
|
429
|
+
const usedUsd = data && data.daily_cost_used_usd
|
|
430
|
+
const remainingUsd = data && data.daily_cost_remaining_usd
|
|
431
|
+
// 文档说 502 时汇总字段全 0 且 status 说明原因;字段缺失视为不可用
|
|
432
|
+
if (limitUsd === undefined || limitUsd === null || usedUsd === undefined || usedUsd === null || remainingUsd === undefined || remainingUsd === null) {
|
|
433
|
+
return { error: 'no-usage' }
|
|
434
|
+
}
|
|
435
|
+
const toNum = (v) => { const n = Number(v); return Number.isFinite(n) ? n : null }
|
|
436
|
+
const limit = toNum(limitUsd)
|
|
437
|
+
const used = toNum(usedUsd)
|
|
438
|
+
const remaining = toNum(remainingUsd)
|
|
439
|
+
const remainingPct = amdRadeonRemainingPct(limit, remaining)
|
|
440
|
+
const usedPct = (limit !== null && used !== null && limit > 0) ? Math.max(0, Math.min(100, (used / limit) * 100)) : null
|
|
441
|
+
const nextResetAt = typeof data.daily_reset_at === 'string' && data.daily_reset_at ? data.daily_reset_at : null
|
|
442
|
+
const periodStartedAt = typeof data.period_started_at === 'string' && data.period_started_at ? data.period_started_at : null
|
|
443
|
+
|
|
444
|
+
// 时间段汇总(today / last_24_hours / last_30_days / all_time 同构)→ 透传原始字段
|
|
445
|
+
const windowOf = (name) => {
|
|
446
|
+
const w = data && data[name]
|
|
447
|
+
if (!w || typeof w !== 'object') return null
|
|
448
|
+
return {
|
|
449
|
+
requests: w.requests,
|
|
450
|
+
errors: w.errors,
|
|
451
|
+
totalTokens: w.total_tokens,
|
|
452
|
+
promptTokens: w.prompt_tokens,
|
|
453
|
+
completionTokens: w.completion_tokens,
|
|
454
|
+
reasoningTokens: w.reasoning_tokens,
|
|
455
|
+
cachedTokens: w.cached_tokens,
|
|
456
|
+
kvCacheHitRate: w.kv_cache_hit_rate,
|
|
457
|
+
cost: w.cost,
|
|
458
|
+
lastRequestAt: w.last_request_at,
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const extraParts = []
|
|
463
|
+
if (used !== null && limit !== null) extraParts.push('今日已用 ' + used.toFixed(3) + ' / ' + limit.toFixed(3) + ' pts')
|
|
464
|
+
if (remaining !== null) extraParts.push('今日剩余 ' + remaining.toFixed(3) + ' pts')
|
|
465
|
+
if (nextResetAt) extraParts.push('重置 ' + formatCountdown(nextResetAt))
|
|
466
|
+
if (data.rpm_limit !== undefined) extraParts.push('RPM ' + data.rpm_limit)
|
|
467
|
+
|
|
468
|
+
return {
|
|
469
|
+
planName: 'AMD Radeon Cloud (中国)',
|
|
470
|
+
remaining,
|
|
471
|
+
unit: 'pts',
|
|
472
|
+
used,
|
|
473
|
+
totalQuota: limit,
|
|
474
|
+
nextResetAt,
|
|
475
|
+
extra: extraParts.join(';'),
|
|
476
|
+
// 单日窗口:卡片只有一行进度条(对齐网页 "Today Used / Today Remaining / RPM Limit")
|
|
477
|
+
windows: {
|
|
478
|
+
daily: { usedPct, remainingPct, used, cap: limit, resetsAt: nextResetAt, periodStartedAt },
|
|
479
|
+
},
|
|
480
|
+
// 透传时间段汇总与按模型拆分(供副行/展开/本地统计对照)
|
|
481
|
+
periods: {
|
|
482
|
+
today: windowOf('today'),
|
|
483
|
+
last_24_hours: windowOf('last_24_hours'),
|
|
484
|
+
last_30_days: windowOf('last_30_days'),
|
|
485
|
+
all_time: windowOf('all_time'),
|
|
486
|
+
},
|
|
487
|
+
byModel: (Array.isArray(data.by_model) ? data.by_model : null),
|
|
488
|
+
recent: (Array.isArray(data.recent) ? data.recent : null),
|
|
489
|
+
cards: null,
|
|
490
|
+
isValid: true,
|
|
491
|
+
invalidMessage: null,
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
|
|
387
495
|
// ── 插件主体 ────────────────────────────────────────────────────────
|
|
388
496
|
|
|
389
497
|
export function apply(ctx, rawConfig) {
|
|
@@ -406,12 +514,67 @@ export function apply(ctx, rawConfig) {
|
|
|
406
514
|
? rawConfig.sessionsDir
|
|
407
515
|
: join(dirname(resolveProviderUsageDir()), 'sessions'),
|
|
408
516
|
dailyDir: join(resolveProviderUsageDir(), 'daily-stats'),
|
|
517
|
+
// 提供方实例清单(v0.8.0):host 端单一事实源(localStorage 只是缓存),防重启后清单被静默重建
|
|
518
|
+
providersFile: join(resolveProviderUsageDir(), 'providers.json'),
|
|
409
519
|
}
|
|
410
520
|
|
|
411
521
|
// cordis 配置的 baseUrl 仅覆盖 opencode-go(历史语义),其余 preset 用内置 URL(插件自维护)
|
|
412
522
|
const effectiveBaseUrl = (cfg) =>
|
|
413
523
|
cfg.id === 'usage-percent' && config.baseUrlOverride ? config.baseUrlOverride : cfg.baseUrl
|
|
414
524
|
|
|
525
|
+
// ── 提供方实例清单持久化(v0.8.0)─
|
|
526
|
+
// 修复「重启后提供方清单被静默重建」:实例清单此前只存浏览器 localStorage,
|
|
527
|
+
// 存储 key 丢失(换 origin/清站点数据/浏览器驱逐)后 loadProviders() 走 DEFAULT_PROVIDERS
|
|
528
|
+
// 兜底 → 已删除的默认实例复活、自定义实例(如 Command Code)永久消失。
|
|
529
|
+
// 现在 host 端 providers.json 为单一事实源;客户端启动时从 host 恢复,写操作同步落盘。
|
|
530
|
+
const PROVIDER_ADAPTER_IDS = new Set(ADAPTERS.map((a) => a.id))
|
|
531
|
+
const PROVIDERS_MAX = 64
|
|
532
|
+
|
|
533
|
+
/** 规范并校验一个提供方实例(不合法字段一律剔除,而非整单拒绝——避免脏数据把恢复流程卡死)。 */
|
|
534
|
+
function normalizeProviderEntry(raw) {
|
|
535
|
+
if (!raw || typeof raw !== 'object') return null
|
|
536
|
+
if (typeof raw.adapter !== 'string' || !PROVIDER_ADAPTER_IDS.has(raw.adapter)) return null
|
|
537
|
+
const id = typeof raw.id === 'string' && raw.id.trim() !== '' ? raw.id.trim().slice(0, 128) : null
|
|
538
|
+
if (!id) return null
|
|
539
|
+
const name = typeof raw.name === 'string' && raw.name.trim() !== '' ? raw.name.trim().slice(0, 64) : ''
|
|
540
|
+
const ref = typeof raw.ref === 'string' ? raw.ref.slice(0, 128) : ''
|
|
541
|
+
const source = raw.source === 'vault' ? 'vault' : 'dsh'
|
|
542
|
+
const type = raw.type === 'manual' || raw.type === 'import' ? raw.type : (source === 'vault' ? 'manual' : 'import')
|
|
543
|
+
return { id, name, adapter: raw.adapter, ref, source, type, paused: raw.paused === true }
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/** 读清单:providers.json 缺失/损坏 → null(调用方决定兜底,损坏文件同时改名留证)。 */
|
|
547
|
+
async function readProvidersDoc() {
|
|
548
|
+
let raw
|
|
549
|
+
try {
|
|
550
|
+
raw = await readFile(config.providersFile, 'utf8')
|
|
551
|
+
} catch (e) {
|
|
552
|
+
if (e && e.code === 'ENOENT') return null
|
|
553
|
+
return null // 其它读错误按缺失处理
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
const doc = JSON.parse(raw)
|
|
557
|
+
if (!doc || doc.version !== 1 || !Array.isArray(doc.providers)) throw new Error('bad doc')
|
|
558
|
+
const providers = doc.providers.map(normalizeProviderEntry).filter(Boolean)
|
|
559
|
+
return { version: 1, providers }
|
|
560
|
+
} catch (e) {
|
|
561
|
+
// 损坏文件改名留证(.corrupt-<ts>),下次写盘重建;绝不静默吞掉用户清单
|
|
562
|
+
try { await rename(config.providersFile, config.providersFile + '.corrupt-' + Date.now()) } catch { /* ignore */ }
|
|
563
|
+
return null
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** 写清单:原子写(tmp → rename)+ 0600(与 credentials.json 同款边界);目录缺失时先建(0700)。 */
|
|
568
|
+
async function writeProvidersDoc(providers) {
|
|
569
|
+
const doc = { version: 1, providers, savedAt: Date.now() }
|
|
570
|
+
await mkdir(dirname(config.providersFile), { recursive: true, mode: 0o700 }).catch(() => {})
|
|
571
|
+
const tmp = config.providersFile + '.tmp'
|
|
572
|
+
await writeFile(tmp, JSON.stringify(doc, null, 2), { mode: 0o600 })
|
|
573
|
+
await chmod(tmp, 0o600)
|
|
574
|
+
try { await rename(tmp, config.providersFile) } catch (e) { await writeFile(config.providersFile, JSON.stringify(doc, null, 2), { mode: 0o600 }) }
|
|
575
|
+
return doc
|
|
576
|
+
}
|
|
577
|
+
|
|
415
578
|
// 私有加密凭证库(方案 B,懒初始化:首次使用时生成密钥)
|
|
416
579
|
let secure = null
|
|
417
580
|
async function secureStore() {
|
|
@@ -490,6 +653,9 @@ export function apply(ctx, rawConfig) {
|
|
|
490
653
|
totalQuota: value.totalQuota,
|
|
491
654
|
nextResetAt: value.nextResetAt,
|
|
492
655
|
monthly: value.monthly,
|
|
656
|
+
periods: value.periods,
|
|
657
|
+
byModel: value.byModel,
|
|
658
|
+
recent: value.recent,
|
|
493
659
|
extra: value.extra,
|
|
494
660
|
windows: value.windows,
|
|
495
661
|
cards: value.cards,
|
|
@@ -629,10 +795,14 @@ export function apply(ctx, rawConfig) {
|
|
|
629
795
|
return fail(cfg, state, 'parse', '接口响应不是有效 JSON', res.status)
|
|
630
796
|
}
|
|
631
797
|
|
|
632
|
-
const parsed = cfg.id === 'balance-json'
|
|
798
|
+
const parsed = cfg.id === 'balance-json'
|
|
799
|
+
? parseBalance(body)
|
|
800
|
+
: (AMD_RADEON_ADAPTER && cfg.id === AMD_RADEON_ADAPTER.id ? parseAmdRadeonUsage(body) : parseUsage(body))
|
|
633
801
|
if (parsed.error === 'no-usage') {
|
|
634
802
|
recordSnapshot(key, { attemptAt, httpStatus: res.status, error: 'no-usage' })
|
|
635
|
-
return fail(cfg, state, 'parse',
|
|
803
|
+
return fail(cfg, state, 'parse', (AMD_RADEON_ADAPTER && cfg.id === AMD_RADEON_ADAPTER.id)
|
|
804
|
+
? '没有找到 AMD Radeon Cloud (中国) 用量数据(daily_cost_*)'
|
|
805
|
+
: '没有找到 OpenCode Go 用量数据', res.status)
|
|
636
806
|
}
|
|
637
807
|
if (parsed.error === 'no-balance') {
|
|
638
808
|
recordSnapshot(key, { attemptAt, httpStatus: res.status, error: 'no-balance' })
|
|
@@ -1154,6 +1324,28 @@ export function apply(ctx, rawConfig) {
|
|
|
1154
1324
|
return { ok: true, refs }
|
|
1155
1325
|
}),
|
|
1156
1326
|
}),
|
|
1327
|
+
// 提供方实例清单持久化(v0.8.0)─:host 端 providers.json 为单一事实源。
|
|
1328
|
+
// GET 读清单(缺失返回 ok:true + items:null,由客户端走一次性迁移);
|
|
1329
|
+
// POST 整单覆盖保存(幂等)。仅回环,条目逐个规范化,不合法条目剔除。
|
|
1330
|
+
ctx.webServer.register({
|
|
1331
|
+
kind: 'exact',
|
|
1332
|
+
path: '/api/provider-usage/providers',
|
|
1333
|
+
handler: jsonRoute(async (req, body) => {
|
|
1334
|
+
if (req.method === 'POST') {
|
|
1335
|
+
if (!body || !Array.isArray(body.providers)) {
|
|
1336
|
+
return { ok: false, error: { type: 'bad-providers', message: 'providers 必须是数组', httpStatus: 400 } }
|
|
1337
|
+
}
|
|
1338
|
+
if (body.providers.length > PROVIDERS_MAX) {
|
|
1339
|
+
return { ok: false, error: { type: 'too-many-providers', message: '提供方实例最多 ' + PROVIDERS_MAX + ' 个', httpStatus: 400 } }
|
|
1340
|
+
}
|
|
1341
|
+
const providers = body.providers.map(normalizeProviderEntry).filter(Boolean)
|
|
1342
|
+
await writeProvidersDoc(providers)
|
|
1343
|
+
return { ok: true, providers }
|
|
1344
|
+
}
|
|
1345
|
+
const doc = await readProvidersDoc()
|
|
1346
|
+
return { ok: true, providers: doc ? doc.providers : null, exists: doc !== null }
|
|
1347
|
+
}, true),
|
|
1348
|
+
}),
|
|
1157
1349
|
]
|
|
1158
1350
|
return () => { for (const dispose of disposers) { if (typeof dispose === 'function') dispose() } }
|
|
1159
1351
|
}, 'provider-usage: routes')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loommii/dsh-provider-usage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "DSH 插件(用量中心):常驻用量卡片 · 多提供方用量/余额查询(OpenCode Go 订阅 + DeepSeek 余额 + Command Code 订阅/余额,对齐 cc-switch 查询语义)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -48,15 +48,12 @@
|
|
|
48
48
|
"patch": "./cordis.patch.yml"
|
|
49
49
|
},
|
|
50
50
|
"client": {
|
|
51
|
-
"platform": "web"
|
|
52
|
-
"inject": [
|
|
53
|
-
"@deepseek-ai/dsh-client-runtime"
|
|
54
|
-
]
|
|
51
|
+
"platform": "web"
|
|
55
52
|
}
|
|
56
53
|
},
|
|
57
54
|
"peerDependencies": {
|
|
58
55
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
59
|
-
"@deepseek-ai/dsh-host-webserver": "
|
|
56
|
+
"@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.6",
|
|
60
57
|
"react": "^18.2.0",
|
|
61
58
|
"react-dom": "^18.2.0"
|
|
62
59
|
},
|