@loommii/dsh-provider-usage 0.8.0 → 0.11.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # dsh-provider-usage — DSH「用量中心」
2
2
 
3
- > 当前版本 **v0.8.0** · npm `@loommii/dsh-provider-usage` · [更新历史](docs/CHANGELOG.md)
3
+ > 当前版本 **v0.11.0** · npm `@loommii/dsh-provider-usage` · [更新历史](docs/CHANGELOG.md)
4
4
 
5
5
  DSH Web GUI 插件 **「用量中心」**:一个常驻右下角的用量卡片,实时显示你的 AI 服务用量 / 余额,不用再打开网页查;附带本地 Token 统计页,不联网也能看自己今天用了多少。
6
6
 
@@ -28,6 +28,7 @@ DSH Web GUI 插件 **「用量中心」**:一个常驻右下角的用量卡片
28
28
  | OpenCode Go(订阅) | 5 小时 / 7 天 / 月度三个窗口的已用百分比 + 进度条 + 重置倒计时 |
29
29
  | DeepSeek(余额) | 账户余额(CNY) |
30
30
  | Command Code(订阅+余额,自定义) | 5 小时 / 周 / 月窗口使用量 + 月度剩余额度(USD),主数字为本月剩余百分比(3 位小数向下取整) |
31
+ | AMD Radeon Cloud (中国)(余额+配额,自定义) | 今日剩余额度(pts)+ 今日已用/限额 + 单日窗口进度条 + 重置倒计时(无周/月窗口;国际版位置预留) |
31
32
 
32
33
  ## 功能
33
34
 
@@ -84,7 +85,8 @@ dsh plugin --profile web remove @loommii/dsh-provider-usage
84
85
  新添加的提供方默认启用。暂停后该实例**不再轮询查询**(卡片 30s 自动刷新跳过它),
85
86
  卡片切换菜单里也**不可选**;暂停的若是当前卡片实例会自动切到其他启用实例,全部暂停时卡片隐藏。
86
87
  启用/暂停只影响查询与卡片选择,编辑 / 删除 / Key 配置都原样保留
87
- - 设置页有两个标签:「提供方」管理实例,「用量统计」查看本地 Token 统计
88
+ - 设置页有三个标签:「提供方」管理实例,「用量统计」查看本地 Token 统计,「卡片设置」自定义卡片显示
89
+ - **卡片设置**:可分别开关卡片上的信息模块(主数字 / 副行信息 / 窗口进度条 / 状态 chip / 提供方名称),也可整体「显示卡片」开关隐藏整卡;隐藏后不再轮询查询。全部模块关闭时卡片视为隐藏,并提供「恢复默认」一键还原
88
90
  - 设置保存在本机浏览器中;删掉实例不会上传或泄露任何 Key
89
91
  - 卡片拖动后的位置会自动保存,下次打开还是老位置
90
92
 
package/docs/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.11.0(未发布)
4
+
5
+ ### 新功能:卡片设置(用量中心第三页)
6
+
7
+ - **新增「卡片设置」页**(用量中心二级菜单变为「提供方 / 用量统计 / 卡片设置」三页):
8
+ 5 个开关分别控制卡片信息模块——主数字、副行信息、窗口进度条、状态 chip、提供方名称;
9
+ 另有「显示卡片」总开关隐藏整卡;改动即时生效并持久化到 localStorage
10
+ - **隐藏即停轮询**:`visible=false` 或全部信息模块关闭时卡片完全隐藏(不再渲染空壳),
11
+ 30s 自动刷新与查询请求随之停止,重新显示后立即恢复
12
+ - **持久化健壮性**:设置版本升 v6,v3~v5 旧配置自动迁移;card 子项逐项布尔归一化,
13
+ 脏数据/非布尔值回落默认(杜绝开关显示与实际渲染分裂);新增跨页签 `storage` 同步,
14
+ 避免多页签 last-write-wins 覆盖丢配置;localStorage 写入失败时设置页内联提示(4s 自动消失)
15
+ - **恢复默认**:卡片设置页新增两段式确认「恢复默认」按钮,一键还原全部开关并重新显示卡片
16
+ - **全关空态引导**:所有模块关闭时,设置页提供方/卡片设置页显示「卡片已隐藏」提示
17
+ - **可访问性修正**:设置开关行由 `label` 包裹 `button` 改为语义化 `div + role="switch"` 结构
18
+ - README 使用说明同步更新(两个标签 → 三个标签,补充卡片设置说明)
19
+
20
+ ## v0.10.0(2026-09-09)
21
+
22
+ - **新增提供方 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`)
23
+ - **无周/月窗口**:AMD 只按日限额计(`daily_cost_limit/used/remaining_usd`),接口无 weekly/monthly 字段;适配器只画「今日」一行进度条
24
+ - **仅自定义添加**:`customOnly`(同 Command Code),Key 加密存插件私有库,默认引用 `RADEON_API_KEY`
25
+ - **国内 / 国际版本**:AMD Radeon Cloud 分两个版本——当前仅接入**中国版**(`developer.amd.com.cn`,显示名「AMD Radeon Cloud (中国)」),国际版 baseUrl/端点不同,适配器位置已预留(后续新增 `amd-radeon-usage-intl` 条目即可)
26
+ - 调研文档:`tmp/AmDRadeonCloud_usage调研.md`(含实测 curl 与字段表;官方文档页路径 404 已记录)
27
+
28
+ ## v0.9.0(2026-09-08)
29
+
30
+ ### 小功能
31
+
32
+ - **Command Code 官方 logo 替换「CC」文字占位**:卡片头部与切换菜单里的 Command Code 图标
33
+ 由紫色渐变底 + 「CC」文字改为官方 ⌘ 符号(白符号·黑底,viewBox 446×446),SVG 内联渲染、
34
+ 随容器裁切圆角;原始素材归档到 `lib/assets/cmdsymbol-light-black-bg.svg`(随包发布便于溯源)。
35
+
3
36
  ## v0.8.0(2026-09-03)
4
37
 
5
38
  ### 修复:重启/存储丢失后提供方清单被静默重建(严重数据丢失)
@@ -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
@@ -20,14 +20,47 @@ window.__ModuleLoader__.load({
20
20
 
21
21
  const inject = ['slots'];
22
22
  const SETTINGS_KEY = 'dsh-provider-usage.settings';
23
- const DEFAULT_SETTINGS = { visible: true, refreshMs: 30000, providerId: 'opencode-go', right: 16, bottom: 16, settingsVersion: 5 };
23
+ const SETTINGS_VERSION = 6;
24
+ // 卡片信息模块显示开关:单一数据源(渲染门控、设置面板、迁移归一化共用)。
25
+ // 默认全开;新增模块只需在此加一项并同步 zh 文案。
26
+ const CARD_MODULES = [
27
+ { key: 'showMainNumber', default: true },
28
+ { key: 'showSubRow', default: true },
29
+ { key: 'showWindowBars', default: true },
30
+ { key: 'showChip', default: true },
31
+ { key: 'showProviderName', default: true },
32
+ ];
33
+ function defaultCard() {
34
+ var out = {};
35
+ for (var i = 0; i < CARD_MODULES.length; i++) out[CARD_MODULES[i].key] = CARD_MODULES[i].default;
36
+ return out;
37
+ }
38
+ function normalizeCard(raw) {
39
+ var out = defaultCard();
40
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
41
+ for (var i = 0; i < CARD_MODULES.length; i++) {
42
+ var k = CARD_MODULES[i].key;
43
+ if (typeof raw[k] === 'boolean') out[k] = raw[k];
44
+ }
45
+ }
46
+ return out;
47
+ }
48
+ function cardModuleOn(card, key) {
49
+ return (card && card[key]) !== false;
50
+ }
51
+ const DEFAULT_SETTINGS = { visible: true, refreshMs: 30000, providerId: 'opencode-go', right: 16, bottom: 16, settingsVersion: SETTINGS_VERSION,
52
+ card: defaultCard(),
53
+ };
24
54
  // 卡片常驻 body 顶层(z-index 与 dsh-pet 同值,≈int32 上限)
25
55
  const Z_INDEX = 2147483000;
26
56
  // 适配器元数据(与 host ADAPTERS 对应;logo 与显示名用于设置页/菜单)
27
57
  const ADAPTER_META = {
28
- 'usage-percent': { displayName: 'OpenCode Go(订阅额度)' },
58
+ 'usage-percent': { displayName: 'OpenCode Go' },
29
59
  'balance-json': { displayName: 'DeepSeek' },
30
- 'commandcode-credits': { displayName: 'Command Code(订阅+余额)' },
60
+ 'commandcode-credits': { displayName: 'Command Code' },
61
+ // AMD Radeon Cloud 分国内 / 国际两个版本(国内 = developer.amd.com.cn;国际版 baseUrl/端点不同)。
62
+ // 当前仅接入中国版「AMD Radeon Cloud (中国)」;国际版位置预留,后续接入时新增适配器条目即可。
63
+ 'amd-radeon-usage': { displayName: 'AMD Radeon Cloud (中国)' },
31
64
  };
32
65
  // 供应商实例清单(用户可配置:名称可改、可增删、可同类型多个;key 引用走 DSH 凭证)
33
66
  const PROVIDERS_KEY = 'dsh-provider-usage.providers';
@@ -47,6 +80,17 @@ window.__ModuleLoader__.load({
47
80
  // DeepSeek:官方鲸 logo PNG(225×225 RGBA)→ data URI,透明底直接展示
48
81
  const LOGO_DEEPSEEK = 'data:image/png;base64,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';
49
82
 
83
+ // Command Code:官方 ⌘ logo(白符号·黑底,viewBox 446×446)→ 内联 SVG(替换原「CC」文字占位)
84
+ const LOGO_CC = React.createElement('svg', { viewBox: '0 0 446 446', 'aria-hidden': 'true', style: { display: 'block', width: '100%', height: '100%' } },
85
+ React.createElement('rect', { width: '446', height: '446', fill: '#000000' }),
86
+ 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' }),
87
+ 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' }));
88
+
89
+ // AMD Radeon Cloud:官方 SVG logo(黑色方块底 + 白色箭头/AMD 字标),viewBox 裁切为正方形
90
+ // 来源:lib/assets/amd-radeon-logo.svg(由 amd-seeklogo.svg 修正 viewBox 后生成)
91
+ 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>`);
92
+ const LOGO_AMD = React.createElement('img', { src: LOGO_AMD_SVG, alt: '', style: { display: 'block', width: '100%', height: '100%' } });
93
+
50
94
  const zh = {
51
95
  loading: '查询中…',
52
96
  refresh: '刷新',
@@ -55,9 +99,31 @@ window.__ModuleLoader__.load({
55
99
  rolling: '5小时滚动',
56
100
  weekly: '7天(每周)',
57
101
  monthly: '每月',
102
+ daily: '今日',
58
103
  used: '已用',
59
104
  errorTitle: '无法获取用量',
60
105
  nav: '用量中心',
106
+ // 卡片设置页
107
+ cardSettings: '卡片设置',
108
+ cardSettingsDesc: '自定义卡片上显示的信息模块。',
109
+ showCard: '显示卡片',
110
+ showCardHint: '关闭后卡片完全隐藏(设置页仍可正常管理提供方与用量统计)。',
111
+ showMainNumber: '主数字',
112
+ showMainNumberHint: '卡片核心数值(剩余额度或剩余百分比)。',
113
+ showSubRow: '副行信息',
114
+ showSubRowHint: '已用/总额 + 剩余额度(Command Code / AMD 等百分比卡)。',
115
+ showWindowBars: '窗口进度条',
116
+ showWindowBarsHint: '5小时滚动/7天/每月(或 AMD 今日)用量进度条。',
117
+ showChip: '状态 chip',
118
+ showChipHint: '卡片右上角状态标签(正常/查询中/未更新/注意)。',
119
+ showProviderName: '提供方名称',
120
+ showProviderNameHint: '卡片头部提供方名称与切换菜单。',
121
+ allModulesOff: '所有信息模块已关闭,卡片已隐藏(可在这里重新开启)。',
122
+ saveFailed: '设置保存失败:浏览器本地存储不可用,本次修改刷新后将丢失。',
123
+ resetCard: '恢复默认',
124
+ resetCardConfirm: '再次点击确认恢复默认',
125
+ // 用量中心副标题(随标签数量变化)
126
+ navDesc: '管理提供方、查看本地用量统计,并自定义卡片显示。',
61
127
  };
62
128
 
63
129
  var styleId = 'dsh-provider-usage/styles.css';
@@ -78,8 +144,8 @@ window.__ModuleLoader__.load({
78
144
  '.dsh-pu-card-head{display:flex;align-items:center;gap:8px;margin-bottom:10px}',
79
145
  '.dsh-pu-logo{width:30px;height:30px;border-radius:8px;flex:none;display:flex;align-items:center;justify-content:center;overflow:hidden;background:transparent}',
80
146
  '.dsh-pu-logo.go svg,.dsh-pu-logo.deepseek img{display:block}',
81
- '.dsh-pu-cc-logo{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;background:linear-gradient(135deg,#5B3A9E,#8E5BD8);color:#fff;font-size:13px;font-weight:800;letter-spacing:.5px;font-family:inherit;flex:none}',
82
- '.dsh-pu-cc-logo-sm{width:22px;height:22px;border-radius:6px;font-size:10px}',
147
+ '.dsh-pu-cc-logo{display:flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:8px;overflow:hidden;flex:none}',
148
+ '.dsh-pu-cc-logo-sm{width:22px;height:22px;border-radius:6px}',
83
149
  '.dsh-pu-cc-sub{display:flex;align-items:center;gap:10px;margin:-6px 0 6px;font-size:11px;color:#C9B8BE;white-space:nowrap}',
84
150
  '.dsh-pu-cc-sub .dsh-pu-cc-used{color:#EFE0E5;font-weight:600}',
85
151
  '.dsh-pu-cc-sub .dsh-pu-cc-remain{color:#FFB3C6;font-weight:600}',
@@ -93,7 +159,7 @@ window.__ModuleLoader__.load({
93
159
  '.dsh-pu-menu-item.active{background:rgba(255,179,198,.14);color:#FFB3C6}',
94
160
  '.dsh-pu-menu-item .mini-logo{width:22px;height:22px;border-radius:6px;flex:none;display:flex;align-items:center;justify-content:center;overflow:hidden}',
95
161
  '.dsh-pu-menu-item .check{margin-left:auto;color:#FFB3C6;font-size:12px}',
96
- '.dsh-pu-card-title{font-size:14px;font-weight:600;color:#EFE0E5;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
162
+ '.dsh-pu-card-title{font-size:14px;font-weight:600;color:#EFE0E5;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}',
97
163
  '.dsh-pu-chip{display:inline-flex;align-items:center;gap:5px;border-radius:999px;padding:2px 8px;font-size:10px;font-weight:600;flex:none}',
98
164
  '.dsh-pu-chip.ok{background:rgba(102,187,106,.14);color:#7ED68A}',
99
165
  '.dsh-pu-chip.warn{background:rgba(255,167,38,.15);color:#FFC46B}',
@@ -135,6 +201,10 @@ window.__ModuleLoader__.load({
135
201
  '.zGbnIq_rowNameDim{color:var(--dsw-alias-label-tertiary)}',
136
202
  '.zGbnIq_rowCard .zGbnIq_credentialDot.dsh-pu-dot-paused{background:var(--dsw-alias-label-tertiary,#8b93a7)}',
137
203
  '.zGbnIq_rowPaused .zGbnIq_rowIdentity .zGbnIq_rowName{color:var(--dsw-alias-label-tertiary)}',
204
+ // ── 卡片设置页:开关行文本弹性(label 语义、flex 排布见 CardSettingsPanel)──
205
+ '.dsh-pu-switchRowText{flex-direction:column;gap:2px;min-width:0;flex:1;display:flex}',
206
+ '.dsh-pu-switchRowText .zGbnIq_advancedHint{margin:0}',
207
+ '.dsh-pu-cardEmpty{border:1px dashed var(--dsw-alias-border-l3);border-radius:10px;margin:2px 0 10px;padding:8px 10px;color:var(--dsw-alias-label-tertiary);font-size:11px;line-height:16px}',
138
208
  ].join('');
139
209
  var tag = document.createElement('style');
140
210
  tag.dataset.plugin = 'dsh-provider-usage';
@@ -148,10 +218,11 @@ window.__ModuleLoader__.load({
148
218
  try {
149
219
  var s = JSON.parse(window.localStorage.getItem(SETTINGS_KEY) || 'null');
150
220
  if (s && typeof s === 'object' &&
151
- (s.settingsVersion === DEFAULT_SETTINGS.settingsVersion || s.settingsVersion === 3 || s.settingsVersion === 4)) {
152
- // v3v4 新增 providerId(默认 opencode-go),其余偏好保留
221
+ (s.settingsVersion === SETTINGS_VERSION || s.settingsVersion === 3 || s.settingsVersion === 4 || s.settingsVersion === 5)) {
222
+ // v5v6 新增 card 显示开关(默认全开;仅布尔值生效),其余偏好保留
153
223
  return Object.assign({}, DEFAULT_SETTINGS, s, {
154
224
  providerId: typeof s.providerId === 'string' ? s.providerId : DEFAULT_SETTINGS.providerId,
225
+ card: normalizeCard(s.card),
155
226
  });
156
227
  }
157
228
  // 设置结构升级:v2→v3 移除用户可配置的 zIndex(固定与 dsh-pet 一致),其余偏好保留
@@ -166,14 +237,21 @@ window.__ModuleLoader__.load({
166
237
  }
167
238
  var settingsState = loadSettings();
168
239
  var settingsListeners = new Set();
240
+ // 保存失败时通知(先广播 onPersistFailed,再广播常规刷新):
241
+ // 订阅者根据回调结果展示“无法保存”提示;卡片渲染不依赖持久化结果,避免闪烁。
169
242
  function saveSettings() {
170
- try { window.localStorage.setItem(SETTINGS_KEY, JSON.stringify(settingsState)); } catch (e) { /* ignore */ }
243
+ var ok = true;
244
+ try { window.localStorage.setItem(SETTINGS_KEY, JSON.stringify(settingsState)); }
245
+ catch (e) { ok = false; }
246
+ return ok;
171
247
  }
172
248
  function getSettings() { return settingsState; }
173
249
  function setSettings(patch) {
174
- settingsState = Object.assign({}, settingsState, patch);
175
- saveSettings();
176
- settingsListeners.forEach(function (fn) { fn(); });
250
+ settingsState = Object.assign({}, settingsState, typeof patch === 'function' ? patch(settingsState) : patch);
251
+ var ok = saveSettings();
252
+ settingsListeners.forEach(function (fn) {
253
+ try { fn(ok ? null : new Error('settings persist failed')); } catch (e) { /* ignore */ }
254
+ });
177
255
  }
178
256
  function subscribeSettings(fn) {
179
257
  settingsListeners.add(fn);
@@ -184,6 +262,16 @@ window.__ModuleLoader__.load({
184
262
  useEffect(function () { return subscribeSettings(function () { setS(getSettings()); }); }, []);
185
263
  return s;
186
264
  }
265
+ // 跨页签同步:其它页签写入后重读并广播(消除 last-write-wins 覆盖丢配置)
266
+ if (typeof window !== 'undefined' && window.addEventListener) {
267
+ window.addEventListener('storage', function (e) {
268
+ if (e.key !== SETTINGS_KEY) return;
269
+ settingsState = loadSettings();
270
+ settingsListeners.forEach(function (fn) {
271
+ try { fn(null); } catch (err) { /* ignore */ }
272
+ });
273
+ });
274
+ }
187
275
 
188
276
  // ── 供应商实例清单(v0.8.0:host 端 providers.json 为单一事实源,localStorage 只是写穿透缓存)──
189
277
  // 修复「重启后提供方清单被静默重建」:此前清单只存 localStorage,存储 key 一旦丢失
@@ -332,6 +420,9 @@ window.__ModuleLoader__.load({
332
420
  // ── 用量卡片(布局借鉴 Rainytoken 小组件卡片,常驻右下角)──────────
333
421
  function UsageCard() {
334
422
  var s = useSettings();
423
+ var card = s.card || {};
424
+ function cardOn(key) { return cardModuleOn(card, key); }
425
+ var allModulesOff = !cardOn('showMainNumber') && !cardOn('showSubRow') && !cardOn('showWindowBars') && !cardOn('showChip') && !cardOn('showProviderName');
335
426
  var providers = useProviders();
336
427
  var [data, setData] = useState(null);
337
428
  var [loading, setLoading] = useState(false);
@@ -417,11 +508,14 @@ window.__ModuleLoader__.load({
417
508
  .finally(function () { setLoading(false); });
418
509
  }, [curKey]);
419
510
 
511
+ // 仅在卡片实际可见时轮询:s.visible=false 或全部模块关闭(空壳隐藏)时不发起请求
512
+ var pollActive = s.visible && !allModulesOff;
420
513
  useEffect(function () {
514
+ if (!pollActive) return undefined;
421
515
  load();
422
516
  var timer = window.setInterval(load, s.refreshMs || 30000);
423
517
  return function () { window.clearInterval(timer); };
424
- }, [load, s.refreshMs]);
518
+ }, [load, pollActive, s.refreshMs]);
425
519
 
426
520
  // 点击卡片外部关闭菜单
427
521
  useEffect(function () {
@@ -434,7 +528,7 @@ window.__ModuleLoader__.load({
434
528
  return function () { document.removeEventListener('mousedown', onDoc); };
435
529
  }, [menuOpen]);
436
530
 
437
- if (!s.visible || !current) return null;
531
+ if (!s.visible || allModulesOff || !current) return null;
438
532
 
439
533
  // 状态语义:请求中=呼吸绿“查询中”(优先于旧数据状态);正常/注意(stale 或余额不足)/错误
440
534
  var loadingCls = loading ? ' dsh-pu-chip-loading' : '';
@@ -448,12 +542,15 @@ window.__ModuleLoader__.load({
448
542
  var meta = data && data.fetchedAt ? fmtTime(data.fetchedAt) + (data.stale ? ' · ' + zh.stale : '') : zh.loading;
449
543
  var isBalance = current.adapter === 'balance-json';
450
544
  var isCommandCode = current.adapter === 'commandcode-credits';
451
- var logoClass = isBalance ? 'deepseek' : (isCommandCode ? 'commandcode' : 'go');
545
+ var isAmd = current.adapter === 'amd-radeon-usage';
546
+ var logoClass = isBalance ? 'deepseek' : (isCommandCode ? 'commandcode' : (isAmd ? 'amd' : 'go'));
452
547
  var logoEl = isBalance
453
548
  ? React.createElement('img', { src: LOGO_DEEPSEEK, width: 24, height: 24, alt: '' })
454
549
  : (isCommandCode
455
- ? React.createElement('span', { className: 'dsh-pu-cc-logo', 'aria-hidden': 'true' }, 'CC')
456
- : LOGO_OCGO);
550
+ ? React.createElement('span', { className: 'dsh-pu-cc-logo', 'aria-hidden': 'true' }, LOGO_CC)
551
+ : (isAmd
552
+ ? React.createElement('span', { className: 'dsh-pu-cc-logo', 'aria-hidden': 'true' }, LOGO_AMD)
553
+ : LOGO_OCGO));
457
554
 
458
555
  var body = null;
459
556
  if (loading && !data) {
@@ -475,41 +572,47 @@ window.__ModuleLoader__.load({
475
572
  (data.monthly && data.monthly.usedPct !== null
476
573
  ? React.createElement(WindowRow, { key: 'm', label: zh.monthly, win: data.monthly, bar: 'magenta' })
477
574
  : null)].filter(Boolean)
478
- : [React.createElement(WindowRow, { key: 'r', label: zh.rolling, win: data.windows.rolling, bar: 'pink' }),
479
- React.createElement(WindowRow, { key: 'w', label: zh.weekly, win: data.windows.weekly, bar: 'orange' }),
480
- React.createElement(WindowRow, { key: 'm', label: zh.monthly, win: data.windows.monthly, bar: 'magenta' })])
575
+ : (isAmd
576
+ // AMD:单日窗口进度条(对齐网页 Today Used / Remaining;无周/月)
577
+ ? [React.createElement(WindowRow, { key: 'd', label: zh.daily, win: data.windows.daily, bar: 'magenta' })]
578
+ : [React.createElement(WindowRow, { key: 'r', label: zh.rolling, win: data.windows.rolling, bar: 'pink' }),
579
+ React.createElement(WindowRow, { key: 'w', label: zh.weekly, win: data.windows.weekly, bar: 'orange' }),
580
+ React.createElement(WindowRow, { key: 'm', label: zh.monthly, win: data.windows.monthly, bar: 'magenta' })]))
481
581
  : null;
482
582
  var warn = data.isValid === false && data.invalidMessage
483
583
  ? React.createElement('div', { className: 'dsh-pu-error' }, data.invalidMessage)
484
584
  : null;
485
- // Command Code 混合卡:主数字 = 月剩余百分比(3 位小数向下取整),副行 = 已用/总额 + 剩余额度
585
+ // Command Code / AMD 混合卡:主数字 = 剩余百分比(3 位小数向下取整),副行 = 已用/总额 + 剩余额度
586
+ // Command Code 读 monthly.remainingPct;AMD 读 windows.daily.remainingPct(今日)
486
587
  var cc = null;
487
- if (isCommandCode) {
488
- var monthlyInfo = data.monthly || null;
588
+ var remainPct = null;
589
+ if (isCommandCode || isAmd) {
590
+ var pctInfo = isAmd ? (data.windows && data.windows.daily) : (data.monthly || null);
489
591
  // 新服务端直接给 remainingPct;旧服务端只有 usedPct → 按 100−usedPct 兜底(与 host 公式数学等价)
490
- var remainPct = monthlyInfo
491
- ? (monthlyInfo.remainingPct !== undefined && monthlyInfo.remainingPct !== null
492
- ? monthlyInfo.remainingPct
493
- : (monthlyInfo.usedPct === undefined || monthlyInfo.usedPct === null
592
+ remainPct = pctInfo
593
+ ? (pctInfo.remainingPct !== undefined && pctInfo.remainingPct !== null
594
+ ? pctInfo.remainingPct
595
+ : (pctInfo.usedPct === undefined || pctInfo.usedPct === null
494
596
  ? null
495
- : Math.floor(Math.max(0, Math.min(100, 100 - monthlyInfo.usedPct)) * 1000) / 1000))
597
+ : Math.floor(Math.max(0, Math.min(100, 100 - pctInfo.usedPct)) * 1000) / 1000))
496
598
  : null;
497
- var usedStr = data.used === null ? '--' : Number(data.used).toFixed(2);
498
- var quotaStr = data.totalQuota === null ? '--' : Number(data.totalQuota).toFixed(2);
499
- var remainStr = data.remaining === null ? '--' : Number(data.remaining).toFixed(2);
599
+ var unitSym = isAmd ? '' : '$';
600
+ var usedStr = data.used === null ? '--' : Number(data.used).toFixed(isAmd ? 3 : 2);
601
+ var quotaStr = data.totalQuota === null ? '--' : Number(data.totalQuota).toFixed(isAmd ? 3 : 2);
602
+ var remainStr = data.remaining === null ? '--' : Number(data.remaining).toFixed(isAmd ? 3 : 2);
500
603
  cc = React.createElement('div', { className: 'dsh-pu-cc-sub' },
501
- React.createElement('span', { className: 'dsh-pu-cc-used' }, '已用 $' + usedStr + ' / $' + quotaStr),
502
- React.createElement('span', { className: 'dsh-pu-cc-remain' }, '剩余 $' + remainStr));
604
+ React.createElement('span', { className: 'dsh-pu-cc-used' }, (isAmd ? zh.daily + ' ' : '') + '已用 ' + unitSym + usedStr + ' / ' + unitSym + quotaStr + (isAmd ? ' pts' : '')),
605
+ React.createElement('span', { className: 'dsh-pu-cc-remain' }, (isAmd ? zh.daily + ' ' : '') + '剩余 ' + unitSym + remainStr + (isAmd ? ' pts' : '')));
503
606
  }
504
607
  // 窗口行/主值/警告条已覆盖全部信息,extra 全文不再展示(保持卡片干净)
505
608
  var extra = null;
506
609
  body = React.createElement('div', null,
507
- React.createElement('div', { className: 'dsh-pu-remain' },
508
- React.createElement('span', { className: 'dsh-pu-remain-big' }, isCommandCode ? (remainPct === null ? '--' : remainPct.toFixed(3) + '%') : main),
509
- React.createElement('span', { className: 'dsh-pu-remain-label' }, isCommandCode ? ' ' + zh.remaining + '(' + zh.monthly + ')' : ' ' + mainLabel)),
610
+ cardOn('showMainNumber') ? React.createElement('div', { className: 'dsh-pu-remain' },
611
+ React.createElement('span', { className: 'dsh-pu-remain-big' }, (isCommandCode || isAmd) ? (remainPct === null ? '--' : remainPct.toFixed(3) + '%') : main),
612
+ React.createElement('span', { className: 'dsh-pu-remain-label' }, (isCommandCode || isAmd) ? ' ' + zh.remaining + '(' + (isAmd ? zh.daily : zh.monthly) + ')' : ' ' + mainLabel)) : null,
510
613
  warn,
511
- cc,
512
- winRows,
614
+ cardOn('showSubRow') ? cc : null,
615
+ cardOn('showWindowBars') ? winRows : null,
513
616
  extra);
514
617
  }
515
618
 
@@ -521,8 +624,10 @@ window.__ModuleLoader__.load({
521
624
  var mini = p.adapter === 'balance-json'
522
625
  ? React.createElement('img', { src: LOGO_DEEPSEEK, width: 16, height: 16, alt: '' })
523
626
  : (p.adapter === 'commandcode-credits'
524
- ? React.createElement('span', { className: 'dsh-pu-cc-logo dsh-pu-cc-logo-sm', 'aria-hidden': 'true' }, 'CC')
525
- : LOGO_OCGO);
627
+ ? React.createElement('span', { className: 'dsh-pu-cc-logo dsh-pu-cc-logo-sm', 'aria-hidden': 'true' }, LOGO_CC)
628
+ : (p.adapter === 'amd-radeon-usage'
629
+ ? React.createElement('span', { className: 'dsh-pu-cc-logo dsh-pu-cc-logo-sm', 'aria-hidden': 'true' }, LOGO_AMD)
630
+ : LOGO_OCGO));
526
631
  return React.createElement('button', {
527
632
  key: p.id, type: 'button', className: 'dsh-pu-menu-item' + (active ? ' active' : ''),
528
633
  onClick: function (e) { if (draggedRef.current) { draggedRef.current = false; return; } e.stopPropagation(); setSettings({ providerId: p.id }); setMenuOpen(false); },
@@ -545,9 +650,9 @@ window.__ModuleLoader__.load({
545
650
  onClick: function (e) { if (draggedRef.current) { draggedRef.current = false; return; } e.stopPropagation(); if (multiple) setMenuOpen(!menuOpen); },
546
651
  },
547
652
  React.createElement('span', { className: 'dsh-pu-logo ' + logoClass }, logoEl),
548
- React.createElement('span', { className: 'dsh-pu-card-title' }, title),
653
+ cardOn('showProviderName') ? React.createElement('span', { className: 'dsh-pu-card-title' }, title) : null,
549
654
  multiple ? React.createElement('span', { className: 'caret' }, menuOpen ? '▴' : '▾') : null),
550
- React.createElement('span', { className: 'dsh-pu-chip ' + level + loadingCls }, React.createElement('i', null), chipText)),
655
+ cardOn('showChip') ? React.createElement('span', { className: 'dsh-pu-chip ' + level + loadingCls }, React.createElement('i', null), chipText) : null),
551
656
  menu,
552
657
  body,
553
658
  React.createElement('div', { className: 'dsh-pu-card-foot' },
@@ -835,6 +940,95 @@ window.__ModuleLoader__.load({
835
940
  }
836
941
  return { level: 'err', label: '查询失败' + (qs.err ? ':' + qs.err : ''), clickable: true, detailKind: 'err', dotCls: ' zGbnIq_credentialDotMissing', detailText: '查询失败:' + (qs.err || '未知错误') };
837
942
  }
943
+ // 卡片设置页:5 个开关控制卡片信息模块显示;即时生效 + 保存失败反馈 + 恢复默认
944
+ function CardSettingsPanel() {
945
+ var s = useSettings();
946
+ var card = s.card || {};
947
+ // 持久化失败提示(setSettings 回调收到非空 err 时显示;下次成功自动清除)
948
+ var [persistFailed, setPersistFailed] = useState(false);
949
+ var persistErrTimer = useRef(null);
950
+ useEffect(function () {
951
+ var dispose = subscribeSettings(function (err) {
952
+ if (err) {
953
+ setPersistFailed(true);
954
+ window.clearTimeout(persistErrTimer.current);
955
+ persistErrTimer.current = window.setTimeout(function () { setPersistFailed(false); }, 4000);
956
+ } else {
957
+ setPersistFailed(false);
958
+ }
959
+ });
960
+ return function () {
961
+ dispose();
962
+ window.clearTimeout(persistErrTimer.current);
963
+ };
964
+ }, []);
965
+ var [resetArm, setResetArm] = useState(false);
966
+ var resetTimer = useRef(null);
967
+ useEffect(function () {
968
+ return function () { window.clearTimeout(resetTimer.current); };
969
+ }, []);
970
+ function armReset() {
971
+ setResetArm(true);
972
+ window.clearTimeout(resetTimer.current);
973
+ resetTimer.current = window.setTimeout(function () { setResetArm(false); }, 4000);
974
+ }
975
+ function doReset() {
976
+ setResetArm(false);
977
+ window.clearTimeout(resetTimer.current);
978
+ setSettings({ visible: true, card: defaultCard() });
979
+ }
980
+ function toggle(key) {
981
+ setSettings(function (prev) {
982
+ var cur = (prev && prev.card) || {};
983
+ var next = Object.assign({}, cur);
984
+ next[key] = !cardModuleOn(cur, key);
985
+ return { card: next };
986
+ });
987
+ }
988
+ var rows = [
989
+ { key: 'showMainNumber', label: zh.showMainNumber, hint: zh.showMainNumberHint },
990
+ { key: 'showSubRow', label: zh.showSubRow, hint: zh.showSubRowHint },
991
+ { key: 'showWindowBars', label: zh.showWindowBars, hint: zh.showWindowBarsHint },
992
+ { key: 'showChip', label: zh.showChip, hint: zh.showChipHint },
993
+ { key: 'showProviderName', label: zh.showProviderName, hint: zh.showProviderNameHint },
994
+ ];
995
+ var cardOn = s.visible !== false;
996
+ var allOff = !rows.some(function (r) { return cardModuleOn(card, r.key); });
997
+ function switchRow(id, on, ariaLabel, text, hint, onChange) {
998
+ return React.createElement('div', { className: 'zGbnIq_rowCard' },
999
+ React.createElement('div', { className: 'zGbnIq_editorSwitchRow', style: { justifyContent: 'space-between' } },
1000
+ React.createElement('div', { className: 'dsh-pu-switchRowText' },
1001
+ React.createElement('span', { className: 'dsh-pu-switchText' }, text),
1002
+ React.createElement('span', { className: 'zGbnIq_advancedHint' }, hint)),
1003
+ React.createElement('button', {
1004
+ type: 'button', role: 'switch', id: id,
1005
+ 'aria-checked': on ? 'true' : 'false',
1006
+ 'aria-label': ariaLabel,
1007
+ className: 'dsh-pu-switch' + (on ? ' dsh-pu-switch-on' : ''),
1008
+ onClick: onChange,
1009
+ }, React.createElement('span', { className: 'dsh-pu-switch-thumb' }))));
1010
+ }
1011
+ return React.createElement('div', { className: 'zGbnIq_section' },
1012
+ React.createElement('h2', { className: 'zGbnIq_title' }, zh.cardSettings),
1013
+ React.createElement('p', { className: 'zGbnIq_intro' }, zh.cardSettingsDesc),
1014
+ persistFailed ? React.createElement('p', { className: 'zGbnIq_error' }, zh.saveFailed) : null,
1015
+ React.createElement('div', { className: 'zGbnIq_rows' },
1016
+ switchRow('pu-switch-visible', cardOn, zh.showCard, zh.showCard, zh.showCardHint,
1017
+ function () { setSettings({ visible: !cardOn }); }),
1018
+ allOff ? React.createElement('p', { className: 'zGbnIq_advancedHint', style: { margin: '0' } }, zh.allModulesOff) : null,
1019
+ rows.map(function (r) {
1020
+ return switchRow('pu-switch-' + r.key, cardModuleOn(card, r.key), r.label, r.label, r.hint,
1021
+ function () { toggle(r.key); });
1022
+ })),
1023
+ React.createElement('div', { className: 'zGbnIq_editorActions', style: { marginTop: '4px' } },
1024
+ React.createElement('button', {
1025
+ type: 'button',
1026
+ className: 'zGbnIq_secondaryButton',
1027
+ 'data-arm': resetArm ? 'true' : undefined,
1028
+ 'aria-label': resetArm ? zh.resetCardConfirm : zh.resetCard,
1029
+ onClick: resetArm ? doReset : armReset,
1030
+ }, resetArm ? zh.resetCardConfirm : zh.resetCard)));
1031
+ }
838
1032
  function SettingsCard() {
839
1033
  var s = useSettings();
840
1034
  var providers = useProviders();
@@ -893,7 +1087,7 @@ window.__ModuleLoader__.load({
893
1087
  setMode('manual');
894
1088
  setEdit(null);
895
1089
  setFormError(null);
896
- setForm({ provider: '', name: 'Command Code', adapter: 'commandcode-credits', key: '', nameTouched: false });
1090
+ setForm({ provider: '', name: 'AMD Radeon Cloud (中国)', adapter: 'amd-radeon-usage', key: '', nameTouched: false });
897
1091
  }
898
1092
  function cancelAdd() { setMode(null); setEdit(null); setFormError(null); }
899
1093
 
@@ -1120,7 +1314,7 @@ window.__ModuleLoader__.load({
1120
1314
  className: 'zGbnIq_input zGbnIq_selectInput', value: form.adapter,
1121
1315
  onChange: function (e) {
1122
1316
  var next = Object.assign({}, form, { adapter: e.target.value });
1123
- if (!form.nameTouched) next.name = e.target.value === 'balance-json' ? 'DeepSeek' : (e.target.value === 'commandcode-credits' ? 'Command Code' : 'OpenCode Go');
1317
+ 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'));
1124
1318
  setForm(next);
1125
1319
  },
1126
1320
  }, adapterOptions)),
@@ -1186,12 +1380,15 @@ window.__ModuleLoader__.load({
1186
1380
  React.createElement('button', { type: 'button', className: 'zGbnIq_primaryButton', disabled: busy, onClick: saveEdit }, '保存')));
1187
1381
  }
1188
1382
 
1189
- // 插件页同款二级菜单(官方 dsh-client-ui-settings-plugins 结构):提供方设置 / 用量统计 两个互不影响的分页
1383
+ // 插件页同款二级菜单(官方 dsh-client-ui-settings-plugins 结构):提供方设置 / 用量统计 / 卡片设置 三个互不影响的分页
1190
1384
  var tabsList = [
1191
1385
  { id: 'providers', label: '提供方设置' },
1192
1386
  { id: 'usage', label: '用量统计' },
1387
+ { id: 'card-settings', label: zh.cardSettings },
1193
1388
  ];
1194
1389
  var [tab, setTab] = useState('providers');
1390
+ // 卡片因 visible=false 或全部模块关闭而隐藏时,在提供方页顶部给出可恢复的提示(卡片设置 tab 有恢复入口)
1391
+ var cardHidden = !s.visible || !CARD_MODULES.some(function (m) { return cardModuleOn(s.card, m.key); });
1195
1392
  var tabsRef = useRef([]);
1196
1393
  function onTabKey(e, idx) {
1197
1394
  var len = tabsList.length;
@@ -1212,6 +1409,7 @@ window.__ModuleLoader__.load({
1212
1409
  var providersPanel = React.createElement('div', { className: 'zGbnIq_section' },
1213
1410
  React.createElement('h2', { className: 'zGbnIq_title' }, '提供方'),
1214
1411
  React.createElement('p', { className: 'zGbnIq_intro' }, '填入各供应方的 Key 引用即可查询其用量与余额。'),
1412
+ cardHidden ? React.createElement('p', { className: 'dsh-pu-cardEmpty' }, zh.allModulesOff) : null,
1215
1413
  React.createElement('ul', { className: 'zGbnIq_rows' },
1216
1414
  providers.length === 0
1217
1415
  ? React.createElement('li', { className: 'zGbnIq_rowCard' },
@@ -1242,6 +1440,9 @@ window.__ModuleLoader__.load({
1242
1440
  var usagePanel = React.createElement('div', { className: 'pbvGtq_panel', role: 'tabpanel', id: 'pu-tab-usage', 'aria-labelledby': 'pu-tab-btn-usage', hidden: tab !== 'usage' },
1243
1441
  React.createElement(UsageStatsPage, { active: tab === 'usage' }));
1244
1442
 
1443
+ // 页面 3:卡片设置(卡片信息模块显示开关)
1444
+ var cardSettingsPanel = React.createElement('div', { className: 'pbvGtq_panel', role: 'tabpanel', id: 'pu-tab-card-settings', 'aria-labelledby': 'pu-tab-btn-card-settings', hidden: tab !== 'card-settings' },
1445
+ React.createElement(CardSettingsPanel));
1245
1446
  // 二级菜单(插件页同款:下划线高亮 + 方向键切换)
1246
1447
  var tabsBar = React.createElement('div', { className: 'pbvGtq_tabs', role: 'tablist', 'aria-label': '用量中心视图' },
1247
1448
  tabsList.map(function (t, idx) {
@@ -1262,10 +1463,11 @@ window.__ModuleLoader__.load({
1262
1463
 
1263
1464
  return React.createElement('div', { className: 'pbvGtq_section' },
1264
1465
  React.createElement('h2', { className: 'pbvGtq_heading' }, '用量中心'),
1265
- React.createElement('p', { className: 'pbvGtq_intro' }, '管理提供方并查看本地用量统计。'),
1466
+ React.createElement('p', { className: 'pbvGtq_intro' }, zh.navDesc),
1266
1467
  tabsBar,
1267
1468
  React.createElement('div', { className: 'pbvGtq_panel', role: 'tabpanel', id: 'pu-tab-providers', 'aria-labelledby': 'pu-tab-btn-providers', hidden: tab !== 'providers' }, providersPanel),
1268
- usagePanel);
1469
+ usagePanel,
1470
+ cardSettingsPanel);
1269
1471
  }
1270
1472
  // 提供方单行:状态点 + 名称 + 编辑/删除 + 展开详情
1271
1473
  // 状态判定全部交给 statusOf;本组件只做渲染;启用/暂停开关在编辑弹层里管理
package/lib/index.js CHANGED
@@ -21,8 +21,8 @@ export const name = 'provider-usage'
21
21
 
22
22
  /** 插件版本单一来源:package.json(UA 标识用;bundle 内嵌等读不到时回退字面量)。 */
23
23
  const PLUGIN_VERSION = (() => {
24
- try { return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version || '0.7.0' }
25
- catch { return '0.7.0' }
24
+ try { return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).version || '0.11.0' }
25
+ catch { return '0.11.0' }
26
26
  })()
27
27
  const USER_AGENT = 'dsh-provider-usage/' + PLUGIN_VERSION
28
28
 
@@ -51,7 +51,7 @@ const LOCAL_DEPS_CHECK_MS = 300000
51
51
  const ADAPTERS = [
52
52
  {
53
53
  id: 'usage-percent',
54
- displayName: 'OpenCode Go(订阅额度)',
54
+ displayName: 'OpenCode Go',
55
55
  baseUrl: 'https://opencode.ai/zen/go',
56
56
  usagePath: '/v1/usage',
57
57
  defaultCredentialRef: 'OPENCODE_GO_API_KEY',
@@ -71,7 +71,7 @@ const ADAPTERS = [
71
71
  // Command Code:DSH 非内置提供方(pi-ai 注册表无 commandcode),只允许「添加自定义提供方」
72
72
  // (customOnly),Key 只存插件私有库(settingsProviderKey: null → 凭证链跳过 DSH 模型设置)。
73
73
  id: 'commandcode-credits',
74
- displayName: 'Command Code(订阅+余额)',
74
+ displayName: 'Command Code',
75
75
  baseUrl: 'https://api.commandcode.ai',
76
76
  usagePath: '/alpha/billing/credits',
77
77
  subscriptionPath: '/alpha/billing/subscriptions',
@@ -80,6 +80,21 @@ const ADAPTERS = [
80
80
  customOnly: true,
81
81
  description: 'Command Code 订阅使用量(5小时/周窗口)+ 月度剩余额度(USD)',
82
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
+ },
83
98
  ]
84
99
 
85
100
  /** 旧 provider id → 适配器 id(向后兼容 /api/provider-usage/opencode-go 与 query?provider=…)。 */
@@ -88,6 +103,9 @@ const LEGACY_PROVIDER_MAP = { 'opencode-go': 'usage-percent', 'deepseek-balance'
88
103
  /** Command Code 混合卡适配器(模块级常量,避免每次查询重复 find)。 */
89
104
  const COMMANDCODE_ADAPTER = ADAPTERS.find((a) => a.id === 'commandcode-credits') || null
90
105
 
106
+ /** AMD Radeon Cloud 余额卡适配器(模块级常量,避免每次查询重复 find)。 */
107
+ const AMD_RADEON_ADAPTER = ADAPTERS.find((a) => a.id === 'amd-radeon-usage') || null
108
+
91
109
  function isLoopbackRequest(req) {
92
110
  const addr = req.socket && req.socket.remoteAddress
93
111
  if (addr !== '127.0.0.1' && addr !== '::1' && addr !== '::ffff:127.0.0.1') return false
@@ -385,6 +403,95 @@ function parseCommandCodeCredits(creditsBody, subBody) {
385
403
  }
386
404
  }
387
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
+
388
495
  // ── 插件主体 ────────────────────────────────────────────────────────
389
496
 
390
497
  export function apply(ctx, rawConfig) {
@@ -546,6 +653,9 @@ export function apply(ctx, rawConfig) {
546
653
  totalQuota: value.totalQuota,
547
654
  nextResetAt: value.nextResetAt,
548
655
  monthly: value.monthly,
656
+ periods: value.periods,
657
+ byModel: value.byModel,
658
+ recent: value.recent,
549
659
  extra: value.extra,
550
660
  windows: value.windows,
551
661
  cards: value.cards,
@@ -685,10 +795,14 @@ export function apply(ctx, rawConfig) {
685
795
  return fail(cfg, state, 'parse', '接口响应不是有效 JSON', res.status)
686
796
  }
687
797
 
688
- const parsed = cfg.id === 'balance-json' ? parseBalance(body) : parseUsage(body)
798
+ const parsed = cfg.id === 'balance-json'
799
+ ? parseBalance(body)
800
+ : (AMD_RADEON_ADAPTER && cfg.id === AMD_RADEON_ADAPTER.id ? parseAmdRadeonUsage(body) : parseUsage(body))
689
801
  if (parsed.error === 'no-usage') {
690
802
  recordSnapshot(key, { attemptAt, httpStatus: res.status, error: 'no-usage' })
691
- return fail(cfg, state, 'parse', '没有找到 OpenCode Go 用量数据', res.status)
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)
692
806
  }
693
807
  if (parsed.error === 'no-balance') {
694
808
  recordSnapshot(key, { attemptAt, httpStatus: res.status, error: 'no-balance' })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loommii/dsh-provider-usage",
3
- "version": "0.8.0",
3
+ "version": "0.11.0",
4
4
  "description": "DSH 插件(用量中心):常驻用量卡片 · 多提供方用量/余额查询(OpenCode Go 订阅 + DeepSeek 余额 + Command Code 订阅/余额,对齐 cc-switch 查询语义)",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",