dsh-deepseek-balance-widget 2.1.0 → 2.2.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 +62 -63
- package/README_EN.md +14 -15
- package/lib/client.js +139 -20
- package/lib/index.js +6 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,63 +1,62 @@
|
|
|
1
|
-
# dsh-deepseek-balance-widget
|
|
2
|
-
|
|
3
|
-
[English](README_EN.md) | 中文
|
|
4
|
-
|
|
5
|
-
在 dsh Web 侧边栏显示 AI 余额的多平台插件:内置 **DeepSeek**,可添加 **MiMo
|
|
6
|
-
|
|
7
|
-
## 功能
|
|
8
|
-
|
|
9
|
-

|
|
10
|
-
|
|
11
|
-
- 侧边栏实时显示余额 / 今日消费 / 今日 Token,30 秒自动刷新,并随当前平台切换
|
|
12
|
-
-
|
|
13
|
-
- 弹窗右上角「**+
|
|
14
|
-
- 详情字段:余额、累计消费、今日消费、今日 Token、本月消耗(本月消费 / Tokens)
|
|
15
|
-
- 「**AI
|
|
16
|
-
- 弹窗底部显示版本号,有新版时一键自动更新
|
|
17
|
-
|
|
18
|
-
## 安装
|
|
19
|
-
|
|
20
|
-
需要:已安装 dsh(可用 `dsh web`)。
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
dsh plugin --profile web add dsh-deepseek-balance-widget
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
从 npm 拉取安装,dsh 自动注册到 `dsh.profile.bundles`,完成后**重启 `dsh web`** 即可。
|
|
27
|
-
|
|
28
|
-
也可以直接对 AI
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
[MIT](LICENSE)
|
|
1
|
+
# dsh-deepseek-balance-widget
|
|
2
|
+
|
|
3
|
+
[English](README_EN.md) | 中文
|
|
4
|
+
|
|
5
|
+
在 dsh Web 侧边栏显示 AI 余额的多平台插件:内置 **DeepSeek**,可添加 **MiMo(小米)**,每 30 秒自动刷新。
|
|
6
|
+
|
|
7
|
+
## 功能
|
|
8
|
+
|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
- 侧边栏实时显示余额 / 今日消费 / 今日 Token,30 秒自动刷新,并随当前平台切换
|
|
12
|
+
- 弹窗顶部显示当前平台标题:点「**更换**」在已添加的平台间切换,点「**×**」可移除该平台
|
|
13
|
+
- 弹窗右上角「**+ 添加**」:加入 MiMo
|
|
14
|
+
- 详情字段:余额、累计消费、今日消费、今日 Token、本月消耗(本月消费 / Tokens)
|
|
15
|
+
- 「**AI 帮我配置**」:复制提示语发给智能体,AI 会一步步指导你抓取凭证并完成配置。
|
|
16
|
+
- 弹窗底部显示版本号,有新版时一键自动更新
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
需要:已安装 dsh(可用 `dsh web`)。
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
dsh plugin --profile web add dsh-deepseek-balance-widget
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
从 npm 拉取安装,dsh 自动注册到 `dsh.profile.bundles`,完成后**重启 `dsh web`** 即可。
|
|
27
|
+
|
|
28
|
+
也可以直接对 AI 说:
|
|
29
|
+
|
|
30
|
+
> 帮我用 npm 安装 dsh-deepseek-balance-widget 插件。
|
|
31
|
+
|
|
32
|
+
## 配置
|
|
33
|
+
|
|
34
|
+
插件首次使用会自动创建 DeepSeek 条目,读取你本机的 `DEEPSEEK_API_KEY`。
|
|
35
|
+
|
|
36
|
+
要加入 MiMo,打开弹窗点右上角「+ 添加」,再点「AI 帮我配置」,把提示语发给智能体;也可以直接对 AI 说:
|
|
37
|
+
|
|
38
|
+
> 帮我配置 dsh-deepseek-balance-widget。
|
|
39
|
+
|
|
40
|
+
AI 会接管全部:问 API Key / 引导获取平台 Cookie → 写入本机 → 提醒重启 `dsh web`。
|
|
41
|
+
|
|
42
|
+
| 平台 | 凭据 | 备注 |
|
|
43
|
+
| ----------- | ------------------------- | ------------------- |
|
|
44
|
+
| DeepSeek | `DEEPSEEK_API_KEY` | 必填,余额 |
|
|
45
|
+
| DeepSeek 用量 | `DEEPSEEK_PLATFORM_TOKEN` | 可选,统计累计消费 / 本月 / 今日 |
|
|
46
|
+
| MiMo(小米) | 登录 Cookie | AI 引导获取 |
|
|
47
|
+
|
|
48
|
+
凭据保存在你本机的 `~/.dsh/ai-balances.json`,不随插件分发、不上传。
|
|
49
|
+
|
|
50
|
+
## 卸载
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
dsh plugin --profile web rm dsh-deepseek-balance-widget
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
或者直接对 AI 说:
|
|
57
|
+
|
|
58
|
+
> 帮我用 npm 卸载 dsh-deepseek-balance-widget 插件。
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
[MIT](LICENSE)
|
package/README_EN.md
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
English | [中文](README.md)
|
|
4
4
|
|
|
5
|
-
A multi-provider AI balance widget for the dsh web sidebar. **DeepSeek** is built in; add **MiMo (Xiaomi)**
|
|
5
|
+
A multi-provider AI balance widget for the dsh web sidebar. **DeepSeek** is built in; add **MiMo (Xiaomi)** from the popover. Auto-refreshes every 30 seconds.
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|

|
|
10
10
|
|
|
11
11
|
- Sidebar shows Balance / Today spend / Today tokens, auto-refresh every 30 s; values follow the selected provider
|
|
12
|
-
-
|
|
13
|
-
-
|
|
12
|
+
- Popover header shows the current provider: a "**Switch**" menu changes to another added provider, and "**×**" removes it
|
|
13
|
+
- "**+ Add**" button (top right): add **MiMo**
|
|
14
14
|
- Details: balance, cumulative spend, today spend, today tokens, monthly usage (monthly spend / tokens)
|
|
15
|
-
- **
|
|
15
|
+
- "**AI 帮我配置**": one click copies a prompt; send it to your AI and it guides you through fetching credentials and writing them locally — no digging through cookies by hand
|
|
16
16
|
- Version shown in the popover footer; one-click auto-update when a new version is available
|
|
17
17
|
|
|
18
18
|
## Install
|
|
@@ -25,18 +25,15 @@ dsh plugin --profile web add dsh-deepseek-balance-widget
|
|
|
25
25
|
|
|
26
26
|
Installed from npm and auto-registered by dsh. **Restart `dsh web`** and the balance button appears in the sidebar.
|
|
27
27
|
|
|
28
|
-
Or just tell your AI:
|
|
28
|
+
Or just tell your AI:
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
> Install the dsh-deepseek-balance-widget plugin for me via npm.
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
## Configuration
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
1. Open the popover and click "+ Add".
|
|
36
|
-
2. Pick a provider, then click "AI 帮我配置" and send the copied prompt to your AI.
|
|
37
|
-
3. Your AI walks you through getting the credentials and writes them locally.
|
|
34
|
+
On first use the plugin creates a DeepSeek entry and reads your local `DEEPSEEK_API_KEY`.
|
|
38
35
|
|
|
39
|
-
|
|
36
|
+
To add MiMo, open the popover, click "+ Add", then "AI 帮我配置", and send the copied prompt to your AI. Or just tell your AI:
|
|
40
37
|
|
|
41
38
|
> Configure dsh-deepseek-balance-widget for me.
|
|
42
39
|
|
|
@@ -48,7 +45,7 @@ The AI takes care of everything: asks for your API key / guides you through gett
|
|
|
48
45
|
| DeepSeek usage | `DEEPSEEK_PLATFORM_TOKEN` | Optional, cumulative / monthly / today stats |
|
|
49
46
|
| MiMo (Xiaomi) | Login cookie | Guided by your AI |
|
|
50
47
|
|
|
51
|
-
Credentials are stored locally in `~/.dsh/ai-balances.json`; nothing is shipped with the plugin.
|
|
48
|
+
Credentials are stored locally in `~/.dsh/ai-balances.json`; nothing is shipped with the plugin and nothing is uploaded.
|
|
52
49
|
|
|
53
50
|
## Uninstall
|
|
54
51
|
|
|
@@ -56,8 +53,10 @@ Credentials are stored locally in `~/.dsh/ai-balances.json`; nothing is shipped
|
|
|
56
53
|
dsh plugin --profile web rm dsh-deepseek-balance-widget
|
|
57
54
|
```
|
|
58
55
|
|
|
59
|
-
Or just tell your AI:
|
|
56
|
+
Or just tell your AI:
|
|
57
|
+
|
|
58
|
+
> Uninstall the dsh-deepseek-balance-widget plugin for me via npm.
|
|
60
59
|
|
|
61
60
|
## License
|
|
62
61
|
|
|
63
|
-
[MIT](LICENSE)
|
|
62
|
+
[MIT](LICENSE)
|
package/lib/client.js
CHANGED
|
@@ -66,6 +66,7 @@ window.__ModuleLoader__.load({
|
|
|
66
66
|
spend: "消费",
|
|
67
67
|
needSetup: "需设置",
|
|
68
68
|
remove: "移除",
|
|
69
|
+
switch: "更换",
|
|
69
70
|
mimoCookieHint: "MiMo 余额需用浏览器登录 Cookie 查询。请重新在浏览器登录 platform.xiaomimimo.com 后,将 Cookie 更新到 ~/.dsh/ai-balances.json 对应条目中。",
|
|
70
71
|
readFailed: "读取失败:{0}",
|
|
71
72
|
rawPrefix: "原始响应:",
|
|
@@ -148,6 +149,7 @@ window.__ModuleLoader__.load({
|
|
|
148
149
|
spend: "Spend",
|
|
149
150
|
needSetup: "Setup",
|
|
150
151
|
remove: "Remove",
|
|
152
|
+
switch: "Switch",
|
|
151
153
|
mimoCookieHint: "MiMo balance requires a browser login Cookie. Please log in to platform.xiaomimimo.com again, then update the Cookie in ~/.dsh/ai-balances.json.",
|
|
152
154
|
readFailed: "Read failed: {0}",
|
|
153
155
|
rawPrefix: "Raw response: ",
|
|
@@ -317,16 +319,17 @@ window.__ModuleLoader__.load({
|
|
|
317
319
|
".dshBalanceToday[data-state=err],.dshBalanceTodayTok[data-state=err]{color:#f85149}",
|
|
318
320
|
".dshBalanceToday[data-state=loading],.dshBalanceTodayTok[data-state=loading]{color:var(--dsw-alias-label-tertiary,#7b8494)}",
|
|
319
321
|
".dshBalanceToday[data-state=muted],.dshBalanceTodayTok[data-state=muted]{color:var(--dsw-alias-label-tertiary,#7b8494)}",
|
|
320
|
-
".dshBalancePop{position:fixed;z-index:9999;width:280px;max-height:calc(100vh - 16px);overflow-y:auto;box-sizing:border-box;background:#1b1f27;border:1px solid rgba(255,255,255,.1);border-radius:12px;box-shadow:0 12px 40px rgba(0,0,0,.5);padding:14px 16px;color:#e6e9ef;font-size:13px;line-height:1.5}",
|
|
322
|
+
".dshBalancePop{position:fixed;z-index:9999;width:280px;max-height:calc(100vh - 16px);overflow-x:hidden;overflow-y:auto;box-sizing:border-box;background:#1b1f27;border:1px solid rgba(255,255,255,.1);border-radius:12px;box-shadow:0 12px 40px rgba(0,0,0,.5);padding:14px 16px;color:#e6e9ef;font-size:13px;line-height:1.5}",
|
|
321
323
|
".dshBalancePop::-webkit-scrollbar{width:8px}",
|
|
322
324
|
".dshBalancePop::-webkit-scrollbar-thumb{background:rgba(255,255,255,.18);border-radius:4px}",
|
|
323
|
-
".dshBalancePop h4{margin:0 0 10px;font-size:14px;font-weight:600;display:flex;align-items:center;gap:8px}",
|
|
325
|
+
".dshBalancePop h4{margin:0 0 10px;padding-bottom:10px;border-bottom:1px solid rgba(255,255,255,.08);font-size:14px;font-weight:600;display:flex;align-items:center;gap:8px}",
|
|
324
326
|
".dshBalanceRow{display:flex;justify-content:space-between;gap:12px;padding:3px 0}",
|
|
325
327
|
".dshBalanceRow span:first-child{color:#9aa4b2}",
|
|
326
328
|
".dshBalanceRow span:last-child{font-variant-numeric:tabular-nums}",
|
|
329
|
+
".dshBalanceDivider{height:0;margin:8px 0;border-top:1px solid rgba(255,255,255,.08)}",
|
|
327
330
|
// Footer 内的按钮不许被 flex 压缩成单字符宽度导致文字竖排(英文 label 更长时容易触发)。
|
|
328
331
|
".dshBalanceFoot .dshBalanceBtn,.dshBalanceFoot .dshBalanceVersionBtn{white-space:nowrap;flex-shrink:0}",
|
|
329
|
-
".dshBalanceFoot{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:6px;margin-top:12px;padding-top:10px;
|
|
332
|
+
".dshBalanceFoot{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:6px;margin-top:12px;padding-top:10px;color:#7b8494;font-size:11px}",
|
|
330
333
|
".dshBalanceBtn{appearance:none;cursor:pointer;background:#2d333b;color:#e6e9ef;border:1px solid rgba(255,255,255,.12);border-radius:7px;padding:4px 10px;font:inherit;font-size:12px}",
|
|
331
334
|
".dshBalanceBtn:hover{background:#363d47}",
|
|
332
335
|
".dshBalanceVersion{display:flex;align-items:center;gap:6px;font-size:11px;color:#7b8494}",
|
|
@@ -340,7 +343,7 @@ window.__ModuleLoader__.load({
|
|
|
340
343
|
".dshBalanceDot[data-state=ok]{background:#3fb950}",
|
|
341
344
|
".dshBalanceDot[data-state=err]{background:#f85149}",
|
|
342
345
|
".dshBalanceDot[data-state=loading],.dshBalanceDot[data-state=muted]{background:#d29922}",
|
|
343
|
-
".dshBalanceUsage{display:block;margin-top:
|
|
346
|
+
".dshBalanceUsage{display:block;margin-top:6px}",
|
|
344
347
|
".dshBalanceUsageTitle{font-size:11px;color:#7b8494;letter-spacing:.04em;margin:0 0 2px;display:flex;align-items:baseline;gap:6px}",
|
|
345
348
|
".dshBalanceUsageTitle b{color:#9aa4b2;font-weight:600;text-transform:uppercase}",
|
|
346
349
|
".dshBalanceDaily{margin-top:6px;font-size:12px}",
|
|
@@ -372,11 +375,23 @@ window.__ModuleLoader__.load({
|
|
|
372
375
|
".dshBalanceProvCookie{appearance:none;cursor:pointer;background:transparent;border:0;color:#58a6ff;font:inherit;font-size:11px;text-decoration:underline;padding:0 4px;flex:none}",
|
|
373
376
|
".dshBalanceProvCookie:hover{color:#79b8ff}",
|
|
374
377
|
".dshBalanceTabs{display:flex;gap:6px;margin-bottom:10px;border-bottom:1px solid rgba(255,255,255,.08);padding-bottom:8px}",
|
|
375
|
-
".
|
|
376
|
-
".
|
|
377
|
-
".
|
|
378
|
+
".dshBalanceTabsRow{display:flex;align-items:flex-end;gap:8px;margin-bottom:0;padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,.08)}",
|
|
379
|
+
".dshBalanceTabsRow .dshBalanceTabs{margin-bottom:0;border-bottom:none;padding-bottom:0;flex:1}",
|
|
380
|
+
".dshBalanceTabContent{padding-bottom:8px;border-bottom:1px solid rgba(255,255,255,.08)}",
|
|
381
|
+
".dshBalanceSwitchBtn{appearance:none;cursor:pointer;flex-shrink:0;background:#2d333b;border:1px solid rgba(255,255,255,.14);color:#e6e9ef;font:inherit;font-size:12px;padding:5px 10px;border-radius:7px;white-space:nowrap}",
|
|
382
|
+
".dshBalanceSwitchBtn:hover{background:#353c47}",
|
|
383
|
+
".dshBalanceTab{position:relative;appearance:none;cursor:default;background:transparent;border:0;color:#e6e9ef;font:inherit;font-size:13px;padding:5px 10px;border-radius:7px;display:flex;align-items:center;gap:6px;flex:0 1 auto;justify-content:flex-start;white-space:nowrap}",
|
|
384
|
+
".dshBalanceTab.active{padding-left:0}",
|
|
378
385
|
".dshBalanceTabBal{font-size:12px;font-weight:600;color:#3fb950;font-variant-numeric:tabular-nums}",
|
|
379
386
|
".dshBalanceTabBal[data-state=err]{color:#f85149}",
|
|
387
|
+
".dshBalanceTabTitle{font-size:14px;font-weight:600}",
|
|
388
|
+
".dshBalanceTabMenu{appearance:none;cursor:pointer;margin-left:4px;font-size:16px;font-weight:600;line-height:1;color:#7b8494;user-select:none;width:18px;height:18px;display:flex;align-items:center;justify-content:center;border-radius:4px}",
|
|
389
|
+
".dshBalanceTabMenu:hover{color:#f85149;background:rgba(248,81,73,.12)}",
|
|
390
|
+
".dshBalanceCtxMenu{position:absolute;z-index:10000;background:#161b22;border:1px solid rgba(255,255,255,.12);border-radius:6px;box-shadow:0 8px 24px rgba(0,0,0,.35);padding:4px 0;min-width:120px}",
|
|
391
|
+
".dshBalanceCtxMenuItem{width:100%;text-align:left;background:transparent;border:none;color:#c9d1d9;padding:6px 12px;font-size:13px;cursor:pointer;font:inherit}",
|
|
392
|
+
".dshBalanceCtxMenuItem:hover{background:#21262d;color:#58a6ff}",
|
|
393
|
+
".dshBalanceCtxMenuItem:disabled{cursor:default;color:#5b626a}",
|
|
394
|
+
".dshBalanceCtxMenuItem:disabled:hover{background:transparent;color:#5b626a}",
|
|
380
395
|
".dshBalanceProvActions{display:flex;gap:8px;margin-top:10px;align-items:center}",
|
|
381
396
|
".dshBalanceProvActions .dshBalanceProvRemove{font-size:12px;color:#7b8494}",
|
|
382
397
|
".dshBalanceProvActions .dshBalanceProvRemove:hover{color:#f85149}",
|
|
@@ -815,10 +830,31 @@ window.__ModuleLoader__.load({
|
|
|
815
830
|
const saveBtn = popRef.querySelector("[data-save]");
|
|
816
831
|
if (saveBtn !== null) saveBtn.addEventListener("click", () => onSaveProvider());
|
|
817
832
|
popRef.querySelectorAll("[data-tab]").forEach((tb) => {
|
|
818
|
-
tb.addEventListener("click", () => {
|
|
833
|
+
tb.addEventListener("click", () => {
|
|
834
|
+
closeContextMenu();
|
|
835
|
+
});
|
|
819
836
|
});
|
|
820
|
-
popRef.
|
|
821
|
-
|
|
837
|
+
const tabsEl = popRef.querySelector("[data-tabs]");
|
|
838
|
+
if (tabsEl !== null) {
|
|
839
|
+
tabsEl.style.cursor = "default";
|
|
840
|
+
}
|
|
841
|
+
const switchBtn = popRef.querySelector("[data-switch]");
|
|
842
|
+
if (switchBtn !== null) {
|
|
843
|
+
switchBtn.addEventListener("click", () => {
|
|
844
|
+
if (providers.length <= 1) return;
|
|
845
|
+
const rect = switchBtn.getBoundingClientRect();
|
|
846
|
+
const popRect = popRef.getBoundingClientRect();
|
|
847
|
+
renderProviderSwitchMenu(rect.left - popRect.left, rect.bottom - popRect.top);
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
popRef.querySelectorAll("[data-tab-remove]").forEach((mb) => {
|
|
851
|
+
mb.addEventListener("click", (e) => {
|
|
852
|
+
e.preventDefault();
|
|
853
|
+
e.stopPropagation();
|
|
854
|
+
const tabEl = mb.closest("[data-tab]");
|
|
855
|
+
if (!tabEl) return;
|
|
856
|
+
onRemoveProvider(tabEl.getAttribute("data-tab"));
|
|
857
|
+
});
|
|
822
858
|
});
|
|
823
859
|
const kindSel = popRef.querySelector("[data-f-kind]");
|
|
824
860
|
if (kindSel !== null) kindSel.addEventListener("change", (e) => { addFormState.kind = e.target.value; });
|
|
@@ -839,6 +875,14 @@ window.__ModuleLoader__.load({
|
|
|
839
875
|
setTimeout(() => aiGuideBtn.textContent = t("aiGuide"), 1500);
|
|
840
876
|
});
|
|
841
877
|
}
|
|
878
|
+
const aiGuideCookieBtn = popRef.querySelector("[data-ai-guide-cookie]");
|
|
879
|
+
if (aiGuideCookieBtn !== null) {
|
|
880
|
+
aiGuideCookieBtn.addEventListener("click", () => {
|
|
881
|
+
cookieEditId = aiGuideCookieBtn.getAttribute("data-provider-id");
|
|
882
|
+
cookieManualMode = false;
|
|
883
|
+
renderPopover();
|
|
884
|
+
});
|
|
885
|
+
}
|
|
842
886
|
const cookieCancel = popRef.querySelector("[data-cookie-cancel]");
|
|
843
887
|
if (cookieCancel !== null) cookieCancel.addEventListener("click", () => { cookieEditId = null; cookieEditValue = ""; cookieExtracted = ""; cookieManualMode = false; renderPopover(); });
|
|
844
888
|
const cookieSave = popRef.querySelector("[data-cookie-save]");
|
|
@@ -925,6 +969,7 @@ window.__ModuleLoader__.load({
|
|
|
925
969
|
if (usageSrc && usageSrc.todayTokens) {
|
|
926
970
|
rows += '<div class="dshBalanceRow"><span>' + t("todayTokens") + "</span><span>" + String(usageSrc.todayTokens) + "</span></div>";
|
|
927
971
|
}
|
|
972
|
+
rows += '<div class="dshBalanceDivider"></div>';
|
|
928
973
|
return rows + usageHtmlOf(usageEnvelope);
|
|
929
974
|
}
|
|
930
975
|
|
|
@@ -985,7 +1030,10 @@ window.__ModuleLoader__.load({
|
|
|
985
1030
|
rows += '<div class="dshBalanceRow"><span>' + t("todaySpend") + "</span><span>" + (today ? fmtYuan(today.cost) : "¥0.00") + "</span></div>";
|
|
986
1031
|
rows += '<div class="dshBalanceRow"><span>' + t("todayTokens") + "</span><span>" + (today ? fmtInt(today.tokens) : "0") + "</span></div>";
|
|
987
1032
|
// 本月消耗区块(与 DeepSeek 同款样式)
|
|
988
|
-
|
|
1033
|
+
const usageBlock = mimoUsageHtml(usage);
|
|
1034
|
+
if (usageBlock) {
|
|
1035
|
+
rows += '<div class="dshBalanceDivider"></div>' + usageBlock;
|
|
1036
|
+
}
|
|
989
1037
|
return rows;
|
|
990
1038
|
}
|
|
991
1039
|
|
|
@@ -1020,8 +1068,12 @@ window.__ModuleLoader__.load({
|
|
|
1020
1068
|
if (bal.ok === false) { balanceText = bal.needCookie ? t("needSetup") : t("failed"); stateAttr = ' data-state="err"'; }
|
|
1021
1069
|
else balanceText = formatProviderBalance(bal.data);
|
|
1022
1070
|
}
|
|
1071
|
+
const removeBtn =
|
|
1072
|
+
'<span class="dshBalanceTabMenu" data-tab-remove type="button" role="button" aria-label="' + t("remove") + '" title="' + t("remove") + '">×</span>';
|
|
1073
|
+
const titleCls = isActive ? ' class="dshBalanceTabTitle"' : '';
|
|
1074
|
+
const balanceSpan = isActive ? '' : '<span class="dshBalanceTabBal"' + stateAttr + ">" + balanceText + "</span>";
|
|
1023
1075
|
return '<button class="dshBalanceTab' + (isActive ? " active" : "") + '" type="button" data-tab="' + escapeHtml(p.id) + '">' +
|
|
1024
|
-
escapeHtml(label) + '
|
|
1076
|
+
'<span' + titleCls + '>' + escapeHtml(label) + '</span>' + balanceSpan + removeBtn + "</button>";
|
|
1025
1077
|
}
|
|
1026
1078
|
|
|
1027
1079
|
/** Build the detail pane for the currently active provider. */
|
|
@@ -1030,15 +1082,16 @@ window.__ModuleLoader__.load({
|
|
|
1030
1082
|
if (p.kind === "deepseek") detail = deepseekDetailHtml(lastEnvelope, lastUsage);
|
|
1031
1083
|
else if (p.kind === "mimo") detail = mimoDetailHtml(bal);
|
|
1032
1084
|
|
|
1085
|
+
// 移除操作已放到 provider tab 的右键菜单里,详情区不再显示。
|
|
1033
1086
|
let actions = "";
|
|
1034
|
-
// 所有 provider 详情操作区统一只保留「× 移除」(AI 引导配置入口保留在添加表单里)。
|
|
1035
|
-
actions = '<div class="dshBalanceProvActions">' +
|
|
1036
|
-
'<button class="dshBalanceProvRemove" type="button" data-remove="' + escapeHtml(p.id) + '" title="' + t("remove") + '">× ' + t("remove") + "</button></div>";
|
|
1037
1087
|
|
|
1038
1088
|
let errHint = "";
|
|
1039
1089
|
if (bal && bal.ok === false) {
|
|
1040
1090
|
if (bal.needCookie) {
|
|
1041
|
-
errHint = '<div class="dshBalanceHint" style="margin-top:8px">' + t("mimoCookieHint") + "</div>"
|
|
1091
|
+
errHint = '<div class="dshBalanceHint" style="margin-top:8px">' + t("mimoCookieHint") + "</div>" +
|
|
1092
|
+
'<div class="dshBalanceProvActions" style="margin-top:8px">' +
|
|
1093
|
+
'<button class="dshBalanceBtn" type="button" data-ai-guide-cookie data-provider-id="' + escapeHtml(p.id) + '">' + t("aiGuide") + "</button>" +
|
|
1094
|
+
"</div>";
|
|
1042
1095
|
} else {
|
|
1043
1096
|
const raw = bal.data && bal.data.raw ? String(bal.data.raw).slice(0, 260) : "";
|
|
1044
1097
|
errHint = '<div class="dshBalanceHint" style="margin-top:8px">' + t("readFailed", String(bal.error || "").slice(0, 50)) +
|
|
@@ -1133,9 +1186,12 @@ window.__ModuleLoader__.load({
|
|
|
1133
1186
|
body += '<div class="dshBalanceHint">' + t("noProviders") + "</div>";
|
|
1134
1187
|
} else {
|
|
1135
1188
|
const activeP = providers.find((p) => p.id === activeProviderId) || activeProvider();
|
|
1136
|
-
body += '<div class="
|
|
1137
|
-
|
|
1138
|
-
|
|
1189
|
+
body += '<div class="dshBalanceTabsRow">';
|
|
1190
|
+
body += '<div class="dshBalanceTabs" data-tabs>';
|
|
1191
|
+
body += providerTabHtml(activeP, balances[activeP.id], true);
|
|
1192
|
+
body += '</div>';
|
|
1193
|
+
if (providers.length > 1) {
|
|
1194
|
+
body += '<button class="dshBalanceSwitchBtn" type="button" data-switch title="' + t("switch") + '">' + t("switch") + "</button>";
|
|
1139
1195
|
}
|
|
1140
1196
|
body += '</div><div class="dshBalanceTabContent">' + providerContentHtml(activeP, balances[activeP.id]) + "</div>";
|
|
1141
1197
|
}
|
|
@@ -1198,7 +1254,6 @@ window.__ModuleLoader__.load({
|
|
|
1198
1254
|
|
|
1199
1255
|
/** Handle the "remove" action for a provider. */
|
|
1200
1256
|
async function onRemoveProvider(id) {
|
|
1201
|
-
if (id === "deepseek") return;
|
|
1202
1257
|
if (!window.confirm(t("confirmRemove"))) return;
|
|
1203
1258
|
await removeProvider(id);
|
|
1204
1259
|
if (activeProviderId === id) activeProviderId = null;
|
|
@@ -1221,9 +1276,65 @@ window.__ModuleLoader__.load({
|
|
|
1221
1276
|
}
|
|
1222
1277
|
}
|
|
1223
1278
|
|
|
1279
|
+
/** Close the context menu if it is open. */
|
|
1280
|
+
function closeContextMenu() {
|
|
1281
|
+
if (contextMenuEl !== null) {
|
|
1282
|
+
contextMenuEl.remove();
|
|
1283
|
+
contextMenuEl = null;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
/** Render a dropdown menu to switch between providers. */
|
|
1288
|
+
function renderProviderSwitchMenu(x, y) {
|
|
1289
|
+
closeContextMenu();
|
|
1290
|
+
if (popRef === null || providers.length <= 1) return;
|
|
1291
|
+
const menu = document.createElement("div");
|
|
1292
|
+
menu.className = "dshBalanceCtxMenu";
|
|
1293
|
+
for (const p of providers) {
|
|
1294
|
+
const meta = PROVIDER_META[p.kind] || { label: p.kind };
|
|
1295
|
+
const label = p.label || meta.label;
|
|
1296
|
+
const bal = balances[p.id];
|
|
1297
|
+
let balanceText = "—";
|
|
1298
|
+
if (bal) {
|
|
1299
|
+
if (bal.ok === false) balanceText = bal.needCookie ? t("needSetup") : t("failed");
|
|
1300
|
+
else balanceText = formatProviderBalance(bal.data);
|
|
1301
|
+
}
|
|
1302
|
+
const item = document.createElement("button");
|
|
1303
|
+
item.className = "dshBalanceCtxMenuItem";
|
|
1304
|
+
item.type = "button";
|
|
1305
|
+
item.textContent = label + " " + balanceText;
|
|
1306
|
+
if (p.id === activeProviderId) {
|
|
1307
|
+
item.disabled = true;
|
|
1308
|
+
item.title = currentLang === "en" ? "Currently viewing" : "当前正在查看";
|
|
1309
|
+
} else {
|
|
1310
|
+
item.addEventListener("click", () => {
|
|
1311
|
+
closeContextMenu();
|
|
1312
|
+
activeProviderId = p.id;
|
|
1313
|
+
renderValue(lastEnvelope, lastUsage);
|
|
1314
|
+
renderPopover();
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
menu.appendChild(item);
|
|
1318
|
+
}
|
|
1319
|
+
popRef.appendChild(menu);
|
|
1320
|
+
contextMenuEl = menu;
|
|
1321
|
+
const popRect = popRef.getBoundingClientRect();
|
|
1322
|
+
let left = x;
|
|
1323
|
+
let top = y;
|
|
1324
|
+
const menuW = menu.offsetWidth || 160;
|
|
1325
|
+
const menuH = menu.offsetHeight || 100;
|
|
1326
|
+
if (left + menuW > popRect.width) left = Math.max(4, popRect.width - menuW - 4);
|
|
1327
|
+
if (left < 4) left = 4;
|
|
1328
|
+
if (top + menuH > popRect.height) top = Math.max(4, popRect.height - menuH - 4);
|
|
1329
|
+
if (top < 4) top = 4;
|
|
1330
|
+
menu.style.left = left + "px";
|
|
1331
|
+
menu.style.top = top + "px";
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1224
1334
|
/** Close the popover and detach the outside-click listener. */
|
|
1225
1335
|
function closePopover() {
|
|
1226
1336
|
if (popRef === null) return;
|
|
1337
|
+
closeContextMenu();
|
|
1227
1338
|
popRef.remove();
|
|
1228
1339
|
popRef = null;
|
|
1229
1340
|
if (outsideHandler !== null) {
|
|
@@ -1271,6 +1382,10 @@ window.__ModuleLoader__.load({
|
|
|
1271
1382
|
pop.className = "dshBalancePop";
|
|
1272
1383
|
document.body.appendChild(pop);
|
|
1273
1384
|
popRef = pop;
|
|
1385
|
+
// 点击弹窗内非菜单区域时收起右键菜单。
|
|
1386
|
+
pop.addEventListener("mousedown", (e) => {
|
|
1387
|
+
if (contextMenuEl && !contextMenuEl.contains(e.target)) closeContextMenu();
|
|
1388
|
+
});
|
|
1274
1389
|
// renderPopover() 内部会按入口位置重新定位弹窗。
|
|
1275
1390
|
renderPopover();
|
|
1276
1391
|
|
|
@@ -1306,6 +1421,10 @@ window.__ModuleLoader__.load({
|
|
|
1306
1421
|
let cookieExtracted = "";
|
|
1307
1422
|
/** When true, the cookie editor shows the manual textarea instead of the AI chat guide. */
|
|
1308
1423
|
let cookieManualMode = false;
|
|
1424
|
+
/** Provider switch dropdown menu state. */
|
|
1425
|
+
let contextMenuX = 0;
|
|
1426
|
+
let contextMenuY = 0;
|
|
1427
|
+
let contextMenuEl = null;
|
|
1309
1428
|
/** Result of the most recent /ai-balances-sync call (used for UI hint). */
|
|
1310
1429
|
let lastSyncResult = null;
|
|
1311
1430
|
let timer = null;
|
package/lib/index.js
CHANGED
|
@@ -231,8 +231,12 @@ async function syncProvidersFromDshSettings() {
|
|
|
231
231
|
skipped++;
|
|
232
232
|
continue;
|
|
233
233
|
}
|
|
234
|
-
store
|
|
235
|
-
|
|
234
|
+
// 仅当 store 完全为空(首次初始化,或用户清空了全部 provider)时,才从 dsh 设置自动加回;
|
|
235
|
+
// 否则用户已主动删除该项,不自动复活,确保「删除」持久生效(重启 dsh 也不会回来)。
|
|
236
|
+
if (store.length === 0) {
|
|
237
|
+
store.push({ ...cand, enabled: true });
|
|
238
|
+
added++;
|
|
239
|
+
}
|
|
236
240
|
}
|
|
237
241
|
if (added > 0) await writeStore(store);
|
|
238
242
|
|
|
@@ -1086,11 +1090,6 @@ function apply(ctx) {
|
|
|
1086
1090
|
res.end(JSON.stringify({ ok: false, error: "缺少 id" }));
|
|
1087
1091
|
return;
|
|
1088
1092
|
}
|
|
1089
|
-
if (id === "deepseek") {
|
|
1090
|
-
res.writeHead(400, { "content-type": "application/json; charset=utf-8" });
|
|
1091
|
-
res.end(JSON.stringify({ ok: false, error: "DeepSeek 不能移除" }));
|
|
1092
|
-
return;
|
|
1093
|
-
}
|
|
1094
1093
|
const store = await ensureStore();
|
|
1095
1094
|
const next = store.filter((p) => p.id !== id);
|
|
1096
1095
|
await writeStore(next);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-deepseek-balance-widget",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Multi-provider AI balance widget for the dsh web sidebar: a live, auto-refreshing balance pill plus a detail popover listing DeepSeek and any added providers (MiMo etc.). Keys are stored per-machine in ~/.dsh/ai-balances.json and resolved from the local credential seam, never hardcoded.",
|
|
6
6
|
"keywords": [
|