dsh-deepseek-balance-widget 2.0.0 → 2.1.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 +33 -7
- package/README_EN.md +33 -7
- package/lib/client.js +400 -130
- package/lib/index.js +34 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,29 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
[English](README_EN.md) | 中文
|
|
4
4
|
|
|
5
|
-
在 dsh Web 侧边栏显示 AI
|
|
5
|
+
在 dsh Web 侧边栏显示 AI 余额的多平台插件:内置 **DeepSeek**,可添加 **MiMo(小米)** 等平台,每 30 秒自动刷新。
|
|
6
6
|
|
|
7
7
|
## 功能
|
|
8
8
|
|
|
9
9
|

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

|
|
9
|
+

|
|
10
10
|
|
|
11
|
-
- Sidebar
|
|
12
|
-
-
|
|
13
|
-
-
|
|
11
|
+
- Sidebar shows Balance / Today spend / Today tokens, auto-refresh every 30 s; values follow the selected provider
|
|
12
|
+
- **Tabs** in the popover switch between providers (DeepSeek / MiMo…), sidebar updates with the tab
|
|
13
|
+
- **"+ Add"** button: add MiMo or any other AI provider at any time
|
|
14
|
+
- Details: balance, cumulative spend, today spend, today tokens, monthly usage (monthly spend / tokens)
|
|
15
|
+
- **"AI 帮我配置"**: credentials are handled by your AI — no digging through cookies by hand
|
|
16
|
+
- Version shown in the popover footer; one-click auto-update when a new version is available
|
|
14
17
|
|
|
15
18
|
## Install
|
|
16
19
|
|
|
20
|
+
Requires: dsh installed (with `dsh web` working).
|
|
21
|
+
|
|
17
22
|
```bash
|
|
18
23
|
dsh plugin --profile web add dsh-deepseek-balance-widget
|
|
19
24
|
```
|
|
@@ -24,7 +29,26 @@ Or just tell your AI: *Install the dsh-deepseek-balance-widget plugin for me via
|
|
|
24
29
|
|
|
25
30
|
## Configuration
|
|
26
31
|
|
|
27
|
-
|
|
32
|
+
On first use the plugin creates a DeepSeek entry and reads your local `DEEPSEEK_API_KEY`. Other providers are added via "+ Add". Either:
|
|
33
|
+
|
|
34
|
+
**Option 1: Add inside the popover**
|
|
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.
|
|
38
|
+
|
|
39
|
+
**Option 2: Just tell your AI**
|
|
40
|
+
|
|
41
|
+
> Configure dsh-deepseek-balance-widget for me.
|
|
42
|
+
|
|
43
|
+
The AI takes care of everything: asks for your API key / guides you through getting the platform cookie → writes the local config → reminds you to **restart `dsh web`**.
|
|
44
|
+
|
|
45
|
+
| Provider | Credential | Notes |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| DeepSeek | `DEEPSEEK_API_KEY` | Required, balance |
|
|
48
|
+
| DeepSeek usage | `DEEPSEEK_PLATFORM_TOKEN` | Optional, cumulative / monthly / today stats |
|
|
49
|
+
| MiMo (Xiaomi) | Login cookie | Guided by your AI |
|
|
50
|
+
|
|
51
|
+
Credentials are stored locally in `~/.dsh/ai-balances.json`; nothing is shipped with the plugin.
|
|
28
52
|
|
|
29
53
|
## Uninstall
|
|
30
54
|
|
|
@@ -32,6 +56,8 @@ Open the popover → "+ Add" → pick a provider → "AI 帮我配置" → sen
|
|
|
32
56
|
dsh plugin --profile web rm dsh-deepseek-balance-widget
|
|
33
57
|
```
|
|
34
58
|
|
|
59
|
+
Or just tell your AI: *Uninstall the dsh-deepseek-balance-widget plugin for me via npm.*
|
|
60
|
+
|
|
35
61
|
## License
|
|
36
62
|
|
|
37
|
-
[MIT](LICENSE)
|
|
63
|
+
[MIT](LICENSE)
|
package/lib/client.js
CHANGED
|
@@ -19,11 +19,261 @@ window.__ModuleLoader__.load({
|
|
|
19
19
|
/** One-shot sync from dsh settings to the local balance store. */
|
|
20
20
|
const SYNC_ENDPOINT = "/ai-balances-sync";
|
|
21
21
|
/** Client-side provider metadata (label only; balance logic lives on host). */
|
|
22
|
-
const PROVIDER_META = {
|
|
22
|
+
const PROVIDER_META = {
|
|
23
|
+
deepseek: { label: { zh: "DeepSeek", en: "DeepSeek" } },
|
|
24
|
+
mimo: { label: { zh: "MiMo(小米)", en: "MiMo" } }
|
|
25
|
+
};
|
|
23
26
|
/** Auto-refresh cadence, milliseconds. */
|
|
24
27
|
const REFRESH_MS = 30000;
|
|
25
|
-
/**
|
|
26
|
-
const
|
|
28
|
+
/** LocalStorage key for the UI language preference (no longer used; dsh settings drive language). */
|
|
29
|
+
const LANG_KEY = null;
|
|
30
|
+
/** Supported UI languages. */
|
|
31
|
+
const I18N = {
|
|
32
|
+
zh: {
|
|
33
|
+
entryAriaLabel: "AI 余额(余额 / 今日消费 / 今日Token)",
|
|
34
|
+
balance: "余额",
|
|
35
|
+
todaySpend: "今日消费",
|
|
36
|
+
todayTokens: "今日Token",
|
|
37
|
+
checking: "检查中…",
|
|
38
|
+
checkFailed: "检查失败",
|
|
39
|
+
upToDate: "已是最新",
|
|
40
|
+
updating: "更新中…",
|
|
41
|
+
updateSuccess: "成功,请重启 dsh",
|
|
42
|
+
updateFailed: "更新失败",
|
|
43
|
+
failed: "失败",
|
|
44
|
+
usageUnavailable: "用量接口不可用({0})",
|
|
45
|
+
usageNotConfigured: "未配置 DEEPSEEK_PLATFORM_TOKEN,仅显示余额。对 AI 说「帮我配置用量统计」,AI 会帮你设置(可选)。",
|
|
46
|
+
usageThisMonth: "本月消耗",
|
|
47
|
+
usageDisabled: "未启用",
|
|
48
|
+
periodSpend: "{0}消耗",
|
|
49
|
+
all: "全部",
|
|
50
|
+
thisMonthSpend: "本月消费",
|
|
51
|
+
tokens: "Tokens",
|
|
52
|
+
yi: "亿",
|
|
53
|
+
wan: "万",
|
|
54
|
+
error: "错误",
|
|
55
|
+
cumulativeSpend: "累计消费",
|
|
56
|
+
items: "项",
|
|
57
|
+
mimoFieldLabels: {
|
|
58
|
+
code: "状态码", message: "消息", frozenBalance: "冻结余额", overdraftLimit: "透支额度",
|
|
59
|
+
remainingOverdraftLimit: "剩余透支额度", giftBalance: "赠送余额", cashBalance: "现金余额",
|
|
60
|
+
rechargeBalance: "充值余额", accountBalance: "账户余额", walletBalance: "钱包余额",
|
|
61
|
+
plan: "套餐", expiredAt: "过期时间"
|
|
62
|
+
},
|
|
63
|
+
dailyUsage: "每日消耗",
|
|
64
|
+
date: "日期",
|
|
65
|
+
requests: "请求",
|
|
66
|
+
spend: "消费",
|
|
67
|
+
needSetup: "需设置",
|
|
68
|
+
remove: "移除",
|
|
69
|
+
mimoCookieHint: "MiMo 余额需用浏览器登录 Cookie 查询。请重新在浏览器登录 platform.xiaomimimo.com 后,将 Cookie 更新到 ~/.dsh/ai-balances.json 对应条目中。",
|
|
70
|
+
readFailed: "读取失败:{0}",
|
|
71
|
+
rawPrefix: "原始响应:",
|
|
72
|
+
aiGuide: "AI 帮我配置",
|
|
73
|
+
configMethod: "配置方式",
|
|
74
|
+
aiGuideNote: "点「AI 帮我配置」会复制一句话,发给智能体后它会读取本机教程文件、一步步引导你抓取凭证并写入插件。数据仅存本机,不上传。",
|
|
75
|
+
localKeyNote: "密钥仅保存在本机 ~/.dsh/ai-balances.json,不会上传。",
|
|
76
|
+
platform: "平台",
|
|
77
|
+
mimoOption: "MiMo(小米)",
|
|
78
|
+
deepseekOption: "DeepSeek",
|
|
79
|
+
name: "名称",
|
|
80
|
+
namePlaceholderDeepseek: "例如 DeepSeek",
|
|
81
|
+
namePlaceholderMimo: "例如 MiMo",
|
|
82
|
+
save: "保存",
|
|
83
|
+
cancel: "取消",
|
|
84
|
+
saving: "保存中…",
|
|
85
|
+
aiChatGuideTitle: "让 AI 在聊天里帮你配置",
|
|
86
|
+
step1: "Step 1",
|
|
87
|
+
step2: "Step 2",
|
|
88
|
+
openMimoBalance: "打开 platform.xiaomimimo.com/#/console/balance",
|
|
89
|
+
copyPromptToChat: "复制提示语 → 和智能体对话",
|
|
90
|
+
step1Desc: "点击下方链接打开 MiMo 余额页并登录",
|
|
91
|
+
step2Desc: "复制下方提示语,发送给智能体,AI 会在对话中一步步引导你完成配置",
|
|
92
|
+
manualPasteCookie: "我要手动粘贴 Cookie",
|
|
93
|
+
manualCookieTitle: "手动粘贴 Cookie",
|
|
94
|
+
cookiePlaceholder: "粘贴 Cookie / cURL / 请求头…",
|
|
95
|
+
extractResult: "识别结果:",
|
|
96
|
+
cookieNotFound: "未识别到 Cookie,请粘贴完整 cURL 或请求头",
|
|
97
|
+
backToAiGuide: "返回 AI 引导",
|
|
98
|
+
syncFailed: "同步失败:{0}",
|
|
99
|
+
syncAddedN: "已从 dsh 设置同步 {0} 个平台",
|
|
100
|
+
aiBalance: "AI 余额",
|
|
101
|
+
add: "+ 添加",
|
|
102
|
+
noProviders: "还没有任何 AI 余额,点右上角「+ 添加」。",
|
|
103
|
+
updatedAt: "更新于 ",
|
|
104
|
+
checkUpdate: "检查更新",
|
|
105
|
+
update: "更新",
|
|
106
|
+
fillName: "请填写名称",
|
|
107
|
+
saveFailed: "保存失败:{0}",
|
|
108
|
+
confirmRemove: "确定移除该 AI 余额?",
|
|
109
|
+
pasteCookieHint: "请粘贴 Cookie 或 cURL,AI 会自动提取",
|
|
110
|
+
copied: "已复制",
|
|
111
|
+
copyFailed: "复制失败",
|
|
112
|
+
refresh: "刷新"
|
|
113
|
+
},
|
|
114
|
+
en: {
|
|
115
|
+
entryAriaLabel: "AI Balance (Balance / Today Spend / Today Tokens)",
|
|
116
|
+
balance: "Balance",
|
|
117
|
+
todaySpend: "Today Spend",
|
|
118
|
+
todayTokens: "Today Tokens",
|
|
119
|
+
checking: "Checking…",
|
|
120
|
+
checkFailed: "Check failed",
|
|
121
|
+
upToDate: "Up to date",
|
|
122
|
+
updating: "Updating…",
|
|
123
|
+
updateSuccess: "Success, please restart dsh",
|
|
124
|
+
updateFailed: "Update failed",
|
|
125
|
+
failed: "Failed",
|
|
126
|
+
usageUnavailable: "Usage API unavailable ({0})",
|
|
127
|
+
usageNotConfigured: "DEEPSEEK_PLATFORM_TOKEN not configured, showing balance only. Tell the AI 'help me configure usage stats' to set it up (optional).",
|
|
128
|
+
usageThisMonth: "This Month",
|
|
129
|
+
usageDisabled: "Disabled",
|
|
130
|
+
periodSpend: "{0} Usage",
|
|
131
|
+
all: "All",
|
|
132
|
+
thisMonthSpend: "This Month Spend",
|
|
133
|
+
tokens: "Tokens",
|
|
134
|
+
yi: "B",
|
|
135
|
+
wan: "M",
|
|
136
|
+
error: "Error",
|
|
137
|
+
cumulativeSpend: "Cumulative Spend",
|
|
138
|
+
items: "items",
|
|
139
|
+
mimoFieldLabels: {
|
|
140
|
+
code: "Code", message: "Message", frozenBalance: "Frozen Balance", overdraftLimit: "Overdraft Limit",
|
|
141
|
+
remainingOverdraftLimit: "Remaining Overdraft", giftBalance: "Gift Balance", cashBalance: "Cash Balance",
|
|
142
|
+
rechargeBalance: "Recharge Balance", accountBalance: "Account Balance", walletBalance: "Wallet Balance",
|
|
143
|
+
plan: "Plan", expiredAt: "Expires At"
|
|
144
|
+
},
|
|
145
|
+
dailyUsage: "Daily Usage",
|
|
146
|
+
date: "Date",
|
|
147
|
+
requests: "Requests",
|
|
148
|
+
spend: "Spend",
|
|
149
|
+
needSetup: "Setup",
|
|
150
|
+
remove: "Remove",
|
|
151
|
+
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
|
+
readFailed: "Read failed: {0}",
|
|
153
|
+
rawPrefix: "Raw response: ",
|
|
154
|
+
aiGuide: "AI Configure",
|
|
155
|
+
configMethod: "Configuration",
|
|
156
|
+
aiGuideNote: "Clicking 'AI Configure' copies a prompt. Send it to the AI and it will read the local guide, walk you through capturing credentials, and write them to the plugin. Data stays local.",
|
|
157
|
+
localKeyNote: "Keys are only stored locally in ~/.dsh/ai-balances.json and are never uploaded.",
|
|
158
|
+
platform: "Platform",
|
|
159
|
+
mimoOption: "MiMo (Xiaomi)",
|
|
160
|
+
deepseekOption: "DeepSeek",
|
|
161
|
+
name: "Name",
|
|
162
|
+
namePlaceholderDeepseek: "e.g. DeepSeek",
|
|
163
|
+
namePlaceholderMimo: "e.g. MiMo",
|
|
164
|
+
save: "Save",
|
|
165
|
+
cancel: "Cancel",
|
|
166
|
+
saving: "Saving…",
|
|
167
|
+
aiChatGuideTitle: "Let the AI configure it in chat",
|
|
168
|
+
step1: "Step 1",
|
|
169
|
+
step2: "Step 2",
|
|
170
|
+
openMimoBalance: "Open platform.xiaomimimo.com/#/console/balance",
|
|
171
|
+
copyPromptToChat: "Copy prompt → chat with AI",
|
|
172
|
+
step1Desc: "Click the link below to open the MiMo balance page and log in",
|
|
173
|
+
step2Desc: "Copy the prompt below and send it to the AI. It will guide you step by step.",
|
|
174
|
+
manualPasteCookie: "I'll paste the Cookie manually",
|
|
175
|
+
manualCookieTitle: "Paste Cookie manually",
|
|
176
|
+
cookiePlaceholder: "Paste Cookie / cURL / headers…",
|
|
177
|
+
extractResult: "Extracted: ",
|
|
178
|
+
cookieNotFound: "No Cookie found. Paste the full cURL or headers.",
|
|
179
|
+
backToAiGuide: "Back to AI guide",
|
|
180
|
+
syncFailed: "Sync failed: {0}",
|
|
181
|
+
syncAddedN: "Synced {0} providers from dsh settings",
|
|
182
|
+
aiBalance: "AI Balance",
|
|
183
|
+
add: "+ Add",
|
|
184
|
+
noProviders: "No AI balances yet. Click '+ Add' in the top right.",
|
|
185
|
+
updatedAt: "Updated ",
|
|
186
|
+
checkUpdate: "Check update",
|
|
187
|
+
update: "Update",
|
|
188
|
+
fillName: "Please enter a name",
|
|
189
|
+
saveFailed: "Save failed: {0}",
|
|
190
|
+
confirmRemove: "Remove this AI balance?",
|
|
191
|
+
pasteCookieHint: "Paste a Cookie or cURL; the AI will extract it",
|
|
192
|
+
copied: "Copied",
|
|
193
|
+
copyFailed: "Copy failed",
|
|
194
|
+
refresh: "Refresh"
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
/** Authoritative source: host route reads ~/.dsh/settings.yaml locale.preference. */
|
|
198
|
+
const LOCALE_ENDPOINT = "/deepseek-balance-locale";
|
|
199
|
+
async function fetchDshLang() {
|
|
200
|
+
try {
|
|
201
|
+
const res = await fetch(LOCALE_ENDPOINT, { method: "GET", headers: { Accept: "application/json" } });
|
|
202
|
+
if (!res.ok) return null;
|
|
203
|
+
const env = await res.json();
|
|
204
|
+
if (env && (env.lang === "en" || env.lang === "zh")) return env.lang;
|
|
205
|
+
} catch { /* ignore */ }
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
/** Fallback: read dsh's <html lang> attribute (only useful if dsh updates it). */
|
|
209
|
+
function detectLangFromHtml() {
|
|
210
|
+
const raw = (document?.documentElement?.lang || "").trim().toLowerCase();
|
|
211
|
+
if (raw.startsWith("en")) return "en";
|
|
212
|
+
if (raw.startsWith("zh")) return "zh";
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
let currentLang = "zh";
|
|
216
|
+
function applyLangChange(next) {
|
|
217
|
+
if (next !== currentLang) {
|
|
218
|
+
currentLang = next;
|
|
219
|
+
if (typeof remountEntry === "function") remountEntry();
|
|
220
|
+
if (typeof updateEntryLabels === "function") updateEntryLabels();
|
|
221
|
+
if (lastEnvelope !== void 0) renderValue(lastEnvelope, lastUsage);
|
|
222
|
+
if (popRef !== null) renderPopover();
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** Poll the host route (authoritative settings.yaml) and re-render on change. */
|
|
226
|
+
async function pollDshLang() {
|
|
227
|
+
const next = await fetchDshLang();
|
|
228
|
+
if (next !== null) applyLangChange(next);
|
|
229
|
+
else {
|
|
230
|
+
const fb = detectLangFromHtml();
|
|
231
|
+
if (fb !== null) applyLangChange(fb);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (typeof document !== "undefined" && document.documentElement) {
|
|
235
|
+
// 启动时立即问宿主端;之后每 8 秒问一次。
|
|
236
|
+
pollDshLang();
|
|
237
|
+
setInterval(pollDshLang, 8000);
|
|
238
|
+
// dsh 偶尔改 <html lang> 时也能跟上。
|
|
239
|
+
new MutationObserver(() => {
|
|
240
|
+
const fb = detectLangFromHtml();
|
|
241
|
+
if (fb !== null) applyLangChange(fb);
|
|
242
|
+
}).observe(document.documentElement, { attributes: true, attributeFilter: ["lang"] });
|
|
243
|
+
}
|
|
244
|
+
/** Translate a key, optionally replacing {0}, {1}... placeholders. */
|
|
245
|
+
function t(key, ...args) {
|
|
246
|
+
const dict = I18N[currentLang] || I18N.zh;
|
|
247
|
+
let text = dict[key];
|
|
248
|
+
if (text === void 0) text = I18N.zh[key] ?? key;
|
|
249
|
+
if (args.length && typeof text === "string") {
|
|
250
|
+
text = text.replace(/\{(\d+)\}/g, (_, n) => String(args[Number(n)] ?? ""));
|
|
251
|
+
}
|
|
252
|
+
return text;
|
|
253
|
+
}
|
|
254
|
+
/** Update sidebar entry stat labels to the current language. */
|
|
255
|
+
function updateEntryLabels() {
|
|
256
|
+
// 用 data-stat-key 按语义查找 label,避免位置错乱(即便 dsh 注入了其他 .dshBalanceStatLabel 也不会错位)。
|
|
257
|
+
const entry = document.querySelector("[data-dsh-balance-entry]");
|
|
258
|
+
if (!entry) return;
|
|
259
|
+
const spendLabel = entry.querySelector('[data-stat-key="todaySpend"] .dshBalanceStatLabel');
|
|
260
|
+
const tokenLabel = entry.querySelector('[data-stat-key="todayTokens"] .dshBalanceStatLabel');
|
|
261
|
+
if (spendLabel) spendLabel.textContent = t("todaySpend");
|
|
262
|
+
if (tokenLabel) tokenLabel.textContent = t("todayTokens");
|
|
263
|
+
entry.setAttribute("aria-label", t("entryAriaLabel"));
|
|
264
|
+
}
|
|
265
|
+
/** Remove the entry from DOM; dsh's rootObserver (childList:true) will rebuild it via createEntry(),
|
|
266
|
+
* which calls statHtml with the current currentLang — so the new entry comes up in the right language. */
|
|
267
|
+
function remountEntry() {
|
|
268
|
+
const entry = document.querySelector("[data-dsh-balance-entry]");
|
|
269
|
+
if (!entry) return;
|
|
270
|
+
entry.remove();
|
|
271
|
+
}
|
|
272
|
+
/** How long the "update succeeded, restart dsh" message stays visible, milliseconds.
|
|
273
|
+
* Set to infinite so the message persists until dsh is restarted.
|
|
274
|
+
* dsh restart re-executes client.js, resetting all variables (including updateSuccessUntil),
|
|
275
|
+
* which makes the success message disappear and restores the normal version check button. */
|
|
276
|
+
const UPDATE_SUCCESS_MS = Number.MAX_SAFE_INTEGER;
|
|
27
277
|
/** Absolute path to the AI-setup guide file the chat AI should read before guiding the user. */
|
|
28
278
|
const AI_GUIDE_FILE = "D:/deepseek harness内置余额显示功能/main program/dsh-deepseek-balance-widget/AI_BALANCE_SETUP_GUIDE.md";
|
|
29
279
|
/** Prompt copied to the chat when the user asks the AI to configure MiMo Cookie. */
|
|
@@ -74,7 +324,9 @@ window.__ModuleLoader__.load({
|
|
|
74
324
|
".dshBalanceRow{display:flex;justify-content:space-between;gap:12px;padding:3px 0}",
|
|
75
325
|
".dshBalanceRow span:first-child{color:#9aa4b2}",
|
|
76
326
|
".dshBalanceRow span:last-child{font-variant-numeric:tabular-nums}",
|
|
77
|
-
|
|
327
|
+
// Footer 内的按钮不许被 flex 压缩成单字符宽度导致文字竖排(英文 label 更长时容易触发)。
|
|
328
|
+
".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;border-top:1px solid rgba(255,255,255,.08);color:#7b8494;font-size:11px}",
|
|
78
330
|
".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}",
|
|
79
331
|
".dshBalanceBtn:hover{background:#363d47}",
|
|
80
332
|
".dshBalanceVersion{display:flex;align-items:center;gap:6px;font-size:11px;color:#7b8494}",
|
|
@@ -194,9 +446,9 @@ window.__ModuleLoader__.load({
|
|
|
194
446
|
for (const child of root.children) if (child.tagName === "BUTTON") return child;
|
|
195
447
|
}
|
|
196
448
|
|
|
197
|
-
/** One labeled stat column (label above value). */
|
|
198
|
-
function statHtml(label, valueHtml) {
|
|
199
|
-
return '<span class="dshBalanceStat"><span class="dshBalanceStatLabel">' + label + "</span>" + valueHtml + "</span>";
|
|
449
|
+
/** One labeled stat column (label above value). key 用作 data-stat-key 标记,便于按语义查找。 */
|
|
450
|
+
function statHtml(label, valueHtml, key) {
|
|
451
|
+
return '<span class="dshBalanceStat" data-stat-key="' + key + '"><span class="dshBalanceStatLabel">' + label + "</span>" + valueHtml + "</span>";
|
|
200
452
|
}
|
|
201
453
|
|
|
202
454
|
/** Build the entry row (a detached button). */
|
|
@@ -205,11 +457,11 @@ window.__ModuleLoader__.load({
|
|
|
205
457
|
entry.type = "button";
|
|
206
458
|
entry.dataset.dshBalanceEntry = "";
|
|
207
459
|
entry.className = "dshBalanceEntry" + (variant === "compact" ? " compact" : "");
|
|
208
|
-
entry.setAttribute("aria-label", "
|
|
460
|
+
entry.setAttribute("aria-label", t("entryAriaLabel"));
|
|
209
461
|
entry.innerHTML =
|
|
210
|
-
statHtml('<span class="dshBalanceStatLabel dshBalanceProviderLabel"
|
|
211
|
-
statHtml("
|
|
212
|
-
statHtml("
|
|
462
|
+
statHtml('<span class="dshBalanceStatLabel dshBalanceProviderLabel">' + t("balance") + "</span>", '<span class="dshBalanceValue" data-state="loading">…</span>', "balance") +
|
|
463
|
+
statHtml(t("todaySpend"), '<span class="dshBalanceToday" data-state="loading">…</span>', "todaySpend") +
|
|
464
|
+
statHtml(t("todayTokens"), '<span class="dshBalanceTodayTok" data-state="loading">…</span>', "todayTokens");
|
|
213
465
|
return entry;
|
|
214
466
|
}
|
|
215
467
|
|
|
@@ -261,31 +513,31 @@ window.__ModuleLoader__.load({
|
|
|
261
513
|
async function handleUpdateClick(btn) {
|
|
262
514
|
btn.disabled = true;
|
|
263
515
|
const originalText = btn.textContent;
|
|
264
|
-
btn.textContent = "
|
|
516
|
+
btn.textContent = t("checking");
|
|
265
517
|
const version = await fetchVersion();
|
|
266
518
|
lastVersion = version;
|
|
267
519
|
if (!version || version.ok === false) {
|
|
268
|
-
btn.textContent = "
|
|
520
|
+
btn.textContent = t("checkFailed");
|
|
269
521
|
btn.title = version ? String(version.error || "") : "";
|
|
270
522
|
setTimeout(() => { btn.textContent = originalText; btn.disabled = false; btn.title = ""; }, 2000);
|
|
271
523
|
return;
|
|
272
524
|
}
|
|
273
525
|
if (!version.updateAvailable) {
|
|
274
|
-
btn.textContent = "
|
|
526
|
+
btn.textContent = t("upToDate");
|
|
275
527
|
setTimeout(() => { btn.textContent = originalText; btn.disabled = false; }, 1500);
|
|
276
528
|
return;
|
|
277
529
|
}
|
|
278
|
-
btn.textContent = "
|
|
530
|
+
btn.textContent = t("updating");
|
|
279
531
|
const result = await updatePackage();
|
|
280
532
|
if (result.ok) {
|
|
281
|
-
btn.textContent = "
|
|
533
|
+
btn.textContent = t("updateSuccess");
|
|
282
534
|
btn.disabled = true;
|
|
283
535
|
// Keep the success message visible for a while before reverting to version info.
|
|
284
536
|
updateSuccessUntil = Date.now() + UPDATE_SUCCESS_MS;
|
|
285
537
|
setTimeout(() => checkVersion(), UPDATE_SUCCESS_MS);
|
|
286
538
|
} else {
|
|
287
|
-
btn.textContent = "
|
|
288
|
-
btn.title = String(result.error || "
|
|
539
|
+
btn.textContent = t("updateFailed");
|
|
540
|
+
btn.title = String(result.error || t("updateFailed")) + (result.output ? "\n" + result.output.slice(-200) : "");
|
|
289
541
|
setTimeout(() => { btn.textContent = originalText; btn.disabled = false; btn.title = ""; }, 3000);
|
|
290
542
|
}
|
|
291
543
|
}
|
|
@@ -389,8 +641,8 @@ window.__ModuleLoader__.load({
|
|
|
389
641
|
if (n === void 0 || n === null || n === "") return "—";
|
|
390
642
|
const v = Number(String(n).replace(/,/g, ""));
|
|
391
643
|
if (!Number.isFinite(v)) return String(n);
|
|
392
|
-
if (v >= 1e8) return (v / 1e8).toFixed(1) + "
|
|
393
|
-
if (v >= 1e4) return (v / 1e4).toFixed(1) + "
|
|
644
|
+
if (v >= 1e8) return (v / 1e8).toFixed(1) + t("yi");
|
|
645
|
+
if (v >= 1e4) return (v / 1e4).toFixed(1) + t("wan");
|
|
394
646
|
return v.toLocaleString();
|
|
395
647
|
}
|
|
396
648
|
|
|
@@ -472,6 +724,18 @@ window.__ModuleLoader__.load({
|
|
|
472
724
|
|
|
473
725
|
/** Render balance + today stats into the entry spans. */
|
|
474
726
|
function renderValue(envelope, usageEnvelope) {
|
|
727
|
+
// 每次刷新都从 DOM 重新拿 entry,并按当前语言同步所有 stat label —— 避免 dsh 自愈重建或 React 重渲染导致 label 不同步。
|
|
728
|
+
// 直接问宿主端读 settings.yaml(权威)。fire-and-forget:不等返回,下次轮询或下次 refresh 时自动同步。
|
|
729
|
+
pollDshLang();
|
|
730
|
+
const entry = document.querySelector("[data-dsh-balance-entry]");
|
|
731
|
+
if (entry) {
|
|
732
|
+
entryRef = entry;
|
|
733
|
+
const spendLabel = entry.querySelector('[data-stat-key="todaySpend"] .dshBalanceStatLabel');
|
|
734
|
+
const tokenLabel = entry.querySelector('[data-stat-key="todayTokens"] .dshBalanceStatLabel');
|
|
735
|
+
if (spendLabel) spendLabel.textContent = t("todaySpend");
|
|
736
|
+
if (tokenLabel) tokenLabel.textContent = t("todayTokens");
|
|
737
|
+
entry.setAttribute("aria-label", t("entryAriaLabel"));
|
|
738
|
+
}
|
|
475
739
|
const provider = activeProvider();
|
|
476
740
|
const isDeepseek = provider && provider.kind === "deepseek";
|
|
477
741
|
const bal = isDeepseek ? envelope : (balances[provider.id] || null);
|
|
@@ -481,26 +745,22 @@ window.__ModuleLoader__.load({
|
|
|
481
745
|
const todayEl = entryRef && entryRef.querySelector(".dshBalanceToday");
|
|
482
746
|
const tokEl = entryRef && entryRef.querySelector(".dshBalanceTodayTok");
|
|
483
747
|
|
|
484
|
-
// 平台名称(提醒这是哪个 provider
|
|
748
|
+
// 平台名称(提醒这是哪个 provider 的余额);按当前语言查 PROVIDER_META,避免宿主端硬编码的中文混入英文界面。
|
|
485
749
|
if (labelEl !== null) {
|
|
486
|
-
|
|
750
|
+
const meta = provider ? PROVIDER_META[provider.kind] : null;
|
|
751
|
+
const metaLabel = meta ? meta.label[currentLang] : null;
|
|
752
|
+
const fallbackLabel = provider ? (provider.label || provider.kind) : t("balance");
|
|
753
|
+
labelEl.textContent = metaLabel || fallbackLabel;
|
|
487
754
|
}
|
|
488
755
|
|
|
489
|
-
//
|
|
756
|
+
// 余额(统一显示货币符号,与 MiMo 入口保持一致)
|
|
490
757
|
if (valueEl !== null) {
|
|
491
758
|
if (!bal || bal.ok === false) {
|
|
492
759
|
valueEl.dataset.state = "err";
|
|
493
|
-
valueEl.textContent = "
|
|
760
|
+
valueEl.textContent = t("failed");
|
|
494
761
|
} else {
|
|
495
|
-
const info = (bal.data && bal.data.balance_infos && bal.data.balance_infos[0]) || null;
|
|
496
|
-
let total = void 0;
|
|
497
|
-
if (info !== null) {
|
|
498
|
-
total = info.total_balance;
|
|
499
|
-
} else if (bal.data && bal.data.total !== void 0) {
|
|
500
|
-
total = bal.data.total;
|
|
501
|
-
}
|
|
502
762
|
valueEl.dataset.state = "ok";
|
|
503
|
-
valueEl.textContent =
|
|
763
|
+
valueEl.textContent = formatProviderBalance(bal.data);
|
|
504
764
|
}
|
|
505
765
|
}
|
|
506
766
|
|
|
@@ -540,8 +800,10 @@ window.__ModuleLoader__.load({
|
|
|
540
800
|
if (popRef === null) return;
|
|
541
801
|
popRef.innerHTML = popoverHtml();
|
|
542
802
|
positionPopover();
|
|
543
|
-
const
|
|
544
|
-
if (
|
|
803
|
+
const refreshBtn = popRef.querySelector("[data-refresh]");
|
|
804
|
+
if (refreshBtn !== null) refreshBtn.addEventListener("click", () => { refresh(true); checkVersion(); });
|
|
805
|
+
const langBtn = popRef.querySelector("[data-lang-toggle]");
|
|
806
|
+
if (langBtn !== null) langBtn.remove();
|
|
545
807
|
const updateBtn = popRef.querySelector("[data-update-cmd]");
|
|
546
808
|
if (updateBtn !== null) {
|
|
547
809
|
updateBtn.addEventListener("click", () => handleUpdateClick(updateBtn));
|
|
@@ -570,11 +832,11 @@ window.__ModuleLoader__.load({
|
|
|
570
832
|
const prompt = AI_GUIDE_PROMPT_BY_KIND[addFormState.kind] || MIMO_COOKIE_GUIDE_PROMPT;
|
|
571
833
|
try {
|
|
572
834
|
await navigator.clipboard.writeText(prompt);
|
|
573
|
-
aiGuideBtn.textContent = "
|
|
835
|
+
aiGuideBtn.textContent = t("copied");
|
|
574
836
|
} catch {
|
|
575
|
-
aiGuideBtn.textContent = "
|
|
837
|
+
aiGuideBtn.textContent = t("copyFailed");
|
|
576
838
|
}
|
|
577
|
-
setTimeout(() => aiGuideBtn.textContent = "
|
|
839
|
+
setTimeout(() => aiGuideBtn.textContent = t("aiGuide"), 1500);
|
|
578
840
|
});
|
|
579
841
|
}
|
|
580
842
|
const cookieCancel = popRef.querySelector("[data-cookie-cancel]");
|
|
@@ -589,9 +851,9 @@ window.__ModuleLoader__.load({
|
|
|
589
851
|
const previewEl = popRef.querySelector(".dshBalanceCookiePreview");
|
|
590
852
|
if (previewEl !== null) {
|
|
591
853
|
previewEl.dataset.found = String(!!cookieExtracted);
|
|
592
|
-
previewEl.innerHTML = '<b
|
|
854
|
+
previewEl.innerHTML = '<b>' + t("extractResult") + "</b>" + (cookieExtracted
|
|
593
855
|
? '<code>' + escapeHtml(cookieExtracted.slice(0, 180) + (cookieExtracted.length > 180 ? '…' : '')) + '</code>'
|
|
594
|
-
:
|
|
856
|
+
: t("cookieNotFound"));
|
|
595
857
|
}
|
|
596
858
|
});
|
|
597
859
|
}
|
|
@@ -603,11 +865,11 @@ window.__ModuleLoader__.load({
|
|
|
603
865
|
const prompt = MIMO_COOKIE_GUIDE_PROMPT;
|
|
604
866
|
try {
|
|
605
867
|
await navigator.clipboard.writeText(prompt);
|
|
606
|
-
copyStep2.textContent = "
|
|
868
|
+
copyStep2.textContent = t("copied");
|
|
607
869
|
} catch {
|
|
608
|
-
copyStep2.textContent = "
|
|
870
|
+
copyStep2.textContent = t("copyFailed");
|
|
609
871
|
}
|
|
610
|
-
setTimeout(() => copyStep2.textContent = "
|
|
872
|
+
setTimeout(() => copyStep2.textContent = t("copyPromptToChat"), 1500);
|
|
611
873
|
});
|
|
612
874
|
}
|
|
613
875
|
const cookieManual = popRef.querySelector("[data-cookie-manual]");
|
|
@@ -621,18 +883,19 @@ window.__ModuleLoader__.load({
|
|
|
621
883
|
const usage = usageStateOf(usageEnvelope);
|
|
622
884
|
if (!usage.src) {
|
|
623
885
|
const hint = usage.state === "err"
|
|
624
|
-
? "
|
|
625
|
-
: "
|
|
886
|
+
? t("usageUnavailable", String(usage.error || "").slice(0, 60))
|
|
887
|
+
: t("usageNotConfigured");
|
|
626
888
|
return '<div class="dshBalanceUsage">' +
|
|
627
|
-
'<div class="dshBalanceUsageTitle"><b
|
|
889
|
+
'<div class="dshBalanceUsageTitle"><b>' + t("usageThisMonth") + "</b><span>" + t("usageDisabled") + "</span></div>" +
|
|
628
890
|
'<div class="dshBalanceHint">' + hint + "</div>" +
|
|
629
891
|
"</div>";
|
|
630
892
|
}
|
|
631
893
|
const src = usage.src;
|
|
632
894
|
return '<div class="dshBalanceUsage">' +
|
|
633
|
-
|
|
634
|
-
'<div class="
|
|
635
|
-
'<div class="dshBalanceRow"><span>
|
|
895
|
+
// 宿主端 src.apiKey 是硬编码的中文「API Key 全部」,英文下不能用;DeepSeek 当前只有 1 个 API Key,直接用 i18n key。
|
|
896
|
+
'<div class="dshBalanceUsageTitle"><b>' + t("usageThisMonth") + "</b><span>" + t("all") + "</span></div>" +
|
|
897
|
+
'<div class="dshBalanceRow"><span>' + t("thisMonthSpend") + "</span><span>" + String(src.totalSpend ?? "—") + "</span></div>" +
|
|
898
|
+
'<div class="dshBalanceRow"><span>' + t("tokens") + "</span><span>" + String(src.tokens ?? "—") + "</span></div>" +
|
|
636
899
|
"</div>";
|
|
637
900
|
}
|
|
638
901
|
|
|
@@ -643,24 +906,24 @@ window.__ModuleLoader__.load({
|
|
|
643
906
|
const cur = info ? (sym(info.currency) || "¥") : "¥";
|
|
644
907
|
let rows = "";
|
|
645
908
|
if (envelope && envelope.error && !info) {
|
|
646
|
-
rows += '<div class="dshBalanceRow"><span
|
|
909
|
+
rows += '<div class="dshBalanceRow"><span>' + t("error") + "</span><span>" + String(envelope.error).slice(0, 80) + "</span></div>";
|
|
647
910
|
} else if (!info) {
|
|
648
|
-
rows += '<div class="dshBalanceRow"><span
|
|
911
|
+
rows += '<div class="dshBalanceRow"><span>' + t("balance") + "</span><span>—</span></div>";
|
|
649
912
|
} else {
|
|
650
913
|
const total = info.total_balance;
|
|
651
|
-
rows += '<div class="dshBalanceRow"><span
|
|
914
|
+
rows += '<div class="dshBalanceRow"><span>' + t("balance") + "</span><span>" + cur + (total !== void 0 ? total : "—") + "</span></div>";
|
|
652
915
|
}
|
|
653
916
|
// 累计消费(全部时间)——放在余额下面,不属于「本月消耗」区块
|
|
654
917
|
const usage = usageStateOf(usageEnvelope);
|
|
655
918
|
const usageSrc = usage.src;
|
|
656
919
|
if (usageSrc && usageSrc.cumulativeSpend) {
|
|
657
|
-
rows += '<div class="dshBalanceRow"><span
|
|
920
|
+
rows += '<div class="dshBalanceRow"><span>' + t("cumulativeSpend") + "</span><span>" + String(usageSrc.cumulativeSpend) + "</span></div>";
|
|
658
921
|
}
|
|
659
922
|
if (usageSrc && usageSrc.todaySpend) {
|
|
660
|
-
rows += '<div class="dshBalanceRow"><span
|
|
923
|
+
rows += '<div class="dshBalanceRow"><span>' + t("todaySpend") + "</span><span>" + fmtYuan(usageSrc.todaySpend) + "</span></div>";
|
|
661
924
|
}
|
|
662
925
|
if (usageSrc && usageSrc.todayTokens) {
|
|
663
|
-
rows += '<div class="dshBalanceRow"><span
|
|
926
|
+
rows += '<div class="dshBalanceRow"><span>' + t("todayTokens") + "</span><span>" + String(usageSrc.todayTokens) + "</span></div>";
|
|
664
927
|
}
|
|
665
928
|
return rows + usageHtmlOf(usageEnvelope);
|
|
666
929
|
}
|
|
@@ -676,28 +939,17 @@ window.__ModuleLoader__.load({
|
|
|
676
939
|
const s = JSON.stringify(v);
|
|
677
940
|
out.push([k, s.length > 48 ? s.slice(0, 48) + "…" : s]);
|
|
678
941
|
} else if (Array.isArray(v)) {
|
|
679
|
-
out.push([k, "[" + v.length + "
|
|
942
|
+
out.push([k, "[" + v.length + " " + t("items") + "]"]);
|
|
680
943
|
} else {
|
|
681
944
|
out.push([k, String(v)]);
|
|
682
945
|
}
|
|
683
946
|
}
|
|
684
947
|
}
|
|
685
948
|
|
|
686
|
-
/**
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
frozenBalance: "冻结余额",
|
|
691
|
-
overdraftLimit: "透支额度",
|
|
692
|
-
remainingOverdraftLimit: "剩余透支额度",
|
|
693
|
-
giftBalance: "赠送余额",
|
|
694
|
-
cashBalance: "现金余额",
|
|
695
|
-
rechargeBalance: "充值余额",
|
|
696
|
-
accountBalance: "账户余额",
|
|
697
|
-
walletBalance: "钱包余额",
|
|
698
|
-
plan: "套餐",
|
|
699
|
-
expiredAt: "过期时间"
|
|
700
|
-
};
|
|
949
|
+
/** Labels for known MiMo balance response fields (localized). */
|
|
950
|
+
function mimoFieldLabel(key) {
|
|
951
|
+
return (I18N[currentLang]?.mimoFieldLabels ?? I18N.zh.mimoFieldLabels)[key] || key;
|
|
952
|
+
}
|
|
701
953
|
|
|
702
954
|
/** Build the MiMo usage block (from /api/v1/usage): 与 DeepSeek「本月消耗」一模一样. */
|
|
703
955
|
function mimoUsageHtml(usage) {
|
|
@@ -710,9 +962,9 @@ window.__ModuleLoader__.load({
|
|
|
710
962
|
const tokens = token.totalToken !== void 0 ? fmtInt(token.totalToken) : "—";
|
|
711
963
|
if (totalSpend === "—" && tokens === "—") return "";
|
|
712
964
|
return '<div class="dshBalanceUsage">' +
|
|
713
|
-
'<div class="dshBalanceUsageTitle"><b
|
|
714
|
-
'<div class="dshBalanceRow"><span
|
|
715
|
-
'<div class="dshBalanceRow"><span>
|
|
965
|
+
'<div class="dshBalanceUsageTitle"><b>' + t("usageThisMonth") + "</b><span>" + t("all") + "</span></div>" +
|
|
966
|
+
'<div class="dshBalanceRow"><span>' + t("thisMonthSpend") + "</span><span>" + totalSpend + "</span></div>" +
|
|
967
|
+
'<div class="dshBalanceRow"><span>' + t("tokens") + "</span><span>" + tokens + "</span></div>" +
|
|
716
968
|
"</div>";
|
|
717
969
|
}
|
|
718
970
|
|
|
@@ -722,16 +974,16 @@ window.__ModuleLoader__.load({
|
|
|
722
974
|
const data = bal.data || {};
|
|
723
975
|
const cur = sym(data.currency) || "¥";
|
|
724
976
|
// 余额(首行,与 DeepSeek 一致)
|
|
725
|
-
let rows = '<div class="dshBalanceRow"><span
|
|
977
|
+
let rows = '<div class="dshBalanceRow"><span>' + t("balance") + "</span><span>" + cur + (data.total !== void 0 ? data.total : "—") + "</span></div>";
|
|
726
978
|
const usage = data.usage || null;
|
|
727
979
|
const cost = usage ? (usage.costUsage || {}) : {};
|
|
728
980
|
// 与 DeepSeek 同款:累计消费 + 今日消费/今日Token(来自 /usage/detail 逐日明细;无今日记录则显示 0)
|
|
729
981
|
if (cost.totalCost !== void 0) {
|
|
730
|
-
rows += '<div class="dshBalanceRow"><span
|
|
982
|
+
rows += '<div class="dshBalanceRow"><span>' + t("cumulativeSpend") + "</span><span>¥" + String(cost.totalCost) + "</span></div>";
|
|
731
983
|
}
|
|
732
984
|
const today = data.today || null;
|
|
733
|
-
rows += '<div class="dshBalanceRow"><span
|
|
734
|
-
rows += '<div class="dshBalanceRow"><span
|
|
985
|
+
rows += '<div class="dshBalanceRow"><span>' + t("todaySpend") + "</span><span>" + (today ? fmtYuan(today.cost) : "¥0.00") + "</span></div>";
|
|
986
|
+
rows += '<div class="dshBalanceRow"><span>' + t("todayTokens") + "</span><span>" + (today ? fmtInt(today.tokens) : "0") + "</span></div>";
|
|
735
987
|
// 本月消耗区块(与 DeepSeek 同款样式)
|
|
736
988
|
rows += mimoUsageHtml(usage);
|
|
737
989
|
return rows;
|
|
@@ -742,9 +994,9 @@ window.__ModuleLoader__.load({
|
|
|
742
994
|
if (!Array.isArray(daily) || daily.length === 0) return "";
|
|
743
995
|
// 接口按日期升序返回,全部展示(一个月最多 31 行)。
|
|
744
996
|
let html = '<div class="dshBalanceUsage">' +
|
|
745
|
-
'<div class="dshBalanceUsageTitle"><b
|
|
997
|
+
'<div class="dshBalanceUsageTitle"><b>' + t("dailyUsage") + "</b><span>MiMo</span></div>" +
|
|
746
998
|
'<div class="dshBalanceDaily">' +
|
|
747
|
-
'<div class="dshBalanceDailyHead"><span
|
|
999
|
+
'<div class="dshBalanceDailyHead"><span>' + t("date") + "</span><span>Token</span><span>" + t("requests") + "</span><span>" + t("spend") + "</span></div>";
|
|
748
1000
|
for (const d of daily) {
|
|
749
1001
|
const costStr = Number(d.cost) > 0 ? String(d.cost) : "0";
|
|
750
1002
|
html += '<div class="dshBalanceDailyRow">' +
|
|
@@ -765,7 +1017,7 @@ window.__ModuleLoader__.load({
|
|
|
765
1017
|
let balanceText = "—";
|
|
766
1018
|
let stateAttr = "";
|
|
767
1019
|
if (bal) {
|
|
768
|
-
if (bal.ok === false) { balanceText = bal.needCookie ? "
|
|
1020
|
+
if (bal.ok === false) { balanceText = bal.needCookie ? t("needSetup") : t("failed"); stateAttr = ' data-state="err"'; }
|
|
769
1021
|
else balanceText = formatProviderBalance(bal.data);
|
|
770
1022
|
}
|
|
771
1023
|
return '<button class="dshBalanceTab' + (isActive ? " active" : "") + '" type="button" data-tab="' + escapeHtml(p.id) + '">' +
|
|
@@ -781,16 +1033,16 @@ window.__ModuleLoader__.load({
|
|
|
781
1033
|
let actions = "";
|
|
782
1034
|
// 所有 provider 详情操作区统一只保留「× 移除」(AI 引导配置入口保留在添加表单里)。
|
|
783
1035
|
actions = '<div class="dshBalanceProvActions">' +
|
|
784
|
-
'<button class="dshBalanceProvRemove" type="button" data-remove="' + escapeHtml(p.id) + '" title="
|
|
1036
|
+
'<button class="dshBalanceProvRemove" type="button" data-remove="' + escapeHtml(p.id) + '" title="' + t("remove") + '">× ' + t("remove") + "</button></div>";
|
|
785
1037
|
|
|
786
1038
|
let errHint = "";
|
|
787
1039
|
if (bal && bal.ok === false) {
|
|
788
1040
|
if (bal.needCookie) {
|
|
789
|
-
errHint = '<div class="dshBalanceHint" style="margin-top:8px">
|
|
1041
|
+
errHint = '<div class="dshBalanceHint" style="margin-top:8px">' + t("mimoCookieHint") + "</div>";
|
|
790
1042
|
} else {
|
|
791
1043
|
const raw = bal.data && bal.data.raw ? String(bal.data.raw).slice(0, 260) : "";
|
|
792
|
-
errHint = '<div class="dshBalanceHint" style="margin-top:8px"
|
|
793
|
-
(raw ? '<br><span class="dshBalanceRawHint">' + escapeHtml(raw) + "</span>" : "") + "</div>";
|
|
1044
|
+
errHint = '<div class="dshBalanceHint" style="margin-top:8px">' + t("readFailed", String(bal.error || "").slice(0, 50)) +
|
|
1045
|
+
(raw ? '<br><span class="dshBalanceRawHint">' + t("rawPrefix") + escapeHtml(raw) + "</span>" : "") + "</div>";
|
|
794
1046
|
}
|
|
795
1047
|
}
|
|
796
1048
|
return detail + actions + errHint;
|
|
@@ -803,50 +1055,50 @@ window.__ModuleLoader__.load({
|
|
|
803
1055
|
const isMimo = addFormState.kind === "mimo";
|
|
804
1056
|
const isDeepseek = addFormState.kind === "deepseek";
|
|
805
1057
|
const aiGuide = (isMimo || isDeepseek)
|
|
806
|
-
? '<div class="dshBalanceField"><label
|
|
807
|
-
'<button class="dshBalanceBtn" type="button" data-ai-guide style="width:100%;justify-content:center">
|
|
1058
|
+
? '<div class="dshBalanceField"><label>' + t("configMethod") + "</label>" +
|
|
1059
|
+
'<button class="dshBalanceBtn" type="button" data-ai-guide style="width:100%;justify-content:center">' + t("aiGuide") + "</button></div>"
|
|
808
1060
|
: "";
|
|
809
1061
|
const note = (isMimo || isDeepseek)
|
|
810
|
-
? '<div class="dshBalanceHint"
|
|
811
|
-
: '<div class="dshBalanceHint"
|
|
1062
|
+
? '<div class="dshBalanceHint">' + t("aiGuideNote") + "</div>"
|
|
1063
|
+
: '<div class="dshBalanceHint">' + t("localKeyNote") + "</div>";
|
|
812
1064
|
return '<div class="dshBalanceAddForm">' +
|
|
813
|
-
'<div class="dshBalanceField"><label
|
|
814
|
-
'<option value="mimo"' + mimoSelected +
|
|
815
|
-
'<option value="deepseek"' + deepseekSelected +
|
|
816
|
-
'<div class="dshBalanceField"><label
|
|
1065
|
+
'<div class="dshBalanceField"><label>' + t("platform") + '</label><select class="dshBalanceInput" data-f-kind>' +
|
|
1066
|
+
'<option value="mimo"' + mimoSelected + ">" + t("mimoOption") + "</option>" +
|
|
1067
|
+
'<option value="deepseek"' + deepseekSelected + ">" + t("deepseekOption") + "</option></select></div>" +
|
|
1068
|
+
'<div class="dshBalanceField"><label>' + t("name") + "</label><input class=\"dshBalanceInput\" data-f-label value=\"" + escapeHtml(addFormState.label) + "\" placeholder=\"" + (addFormState.kind === "deepseek" ? t("namePlaceholderDeepseek") : t("namePlaceholderMimo")) + "\" /></div>" +
|
|
817
1069
|
aiGuide +
|
|
818
|
-
'<div class="dshBalanceFormBtns"><button class="dshBalanceBtn" type="button" data-save
|
|
819
|
-
'<button class="dshBalanceBtn" type="button" data-cancel
|
|
1070
|
+
'<div class="dshBalanceFormBtns"><button class="dshBalanceBtn" type="button" data-save>' + t("save") + "</button>" +
|
|
1071
|
+
'<button class="dshBalanceBtn" type="button" data-cancel>' + t("cancel") + "</button></div>" +
|
|
820
1072
|
note + "</div>";
|
|
821
1073
|
}
|
|
822
1074
|
|
|
823
1075
|
/** Build the chat-based AI guide panel for MiMo Cookie configuration. */
|
|
824
1076
|
function aiGuideCookiePanelHtml() {
|
|
825
1077
|
return '<div class="dshBalanceCookiePanel">' +
|
|
826
|
-
'<div class="dshBalanceHint" style="margin-bottom:4px;font-weight:600"
|
|
1078
|
+
'<div class="dshBalanceHint" style="margin-bottom:4px;font-weight:600">' + t("aiChatGuideTitle") + "</div>" +
|
|
827
1079
|
'<div class="dshBalanceWizard">' +
|
|
828
|
-
'<div class="dshBalanceWizardStep"><b>
|
|
829
|
-
'<span class="dshBalanceWizardLink" data-open-mimo
|
|
830
|
-
'<div class="dshBalanceWizardStep"><b>
|
|
831
|
-
'<span class="dshBalanceWizardLink" data-copy-step2
|
|
1080
|
+
'<div class="dshBalanceWizardStep"><b>' + t("step1") + "</b><span>" + t("step1Desc") + "</span>" +
|
|
1081
|
+
'<span class="dshBalanceWizardLink" data-open-mimo>' + t("openMimoBalance") + "</span></div>" +
|
|
1082
|
+
'<div class="dshBalanceWizardStep"><b>' + t("step2") + "</b><span>" + t("step2Desc") + "</span>" +
|
|
1083
|
+
'<span class="dshBalanceWizardLink" data-copy-step2>' + t("copyPromptToChat") + "</span></div>" +
|
|
832
1084
|
'</div>' +
|
|
833
|
-
'<div class="dshBalanceFormBtns"><button class="dshBalanceBtn" type="button" data-cookie-cancel
|
|
834
|
-
'<div style="margin-top:6px"><span class="dshBalanceWizardLink" data-cookie-manual
|
|
1085
|
+
'<div class="dshBalanceFormBtns"><button class="dshBalanceBtn" type="button" data-cookie-cancel>' + t("cancel") + "</button></div>" +
|
|
1086
|
+
'<div style="margin-top:6px"><span class="dshBalanceWizardLink" data-cookie-manual>' + t("manualPasteCookie") + "</span></div></div>";
|
|
835
1087
|
}
|
|
836
1088
|
|
|
837
1089
|
/** Build the manual fallback textarea for Cookie configuration. */
|
|
838
1090
|
function manualCookiePanelHtml() {
|
|
839
1091
|
const preview = cookieExtracted
|
|
840
1092
|
? '<code>' + escapeHtml(cookieExtracted.slice(0, 180) + (cookieExtracted.length > 180 ? '…' : '')) + '</code>'
|
|
841
|
-
:
|
|
1093
|
+
: t("cookieNotFound");
|
|
842
1094
|
const found = !!cookieExtracted;
|
|
843
1095
|
return '<div class="dshBalanceCookiePanel">' +
|
|
844
|
-
'<div class="dshBalanceHint" style="margin-bottom:4px;font-weight:600"
|
|
845
|
-
'<textarea class="dshBalanceInput dshBalanceTextarea" data-cookie-value placeholder="
|
|
846
|
-
'<div class="dshBalanceCookiePreview" data-found="' + found + '"><b
|
|
847
|
-
'<div class="dshBalanceFormBtns"><button class="dshBalanceBtn" type="button" data-cookie-save
|
|
848
|
-
'<button class="dshBalanceBtn" type="button" data-cookie-cancel
|
|
849
|
-
'<div style="margin-top:6px"><span class="dshBalanceWizardLink" data-cookie-guide
|
|
1096
|
+
'<div class="dshBalanceHint" style="margin-bottom:4px;font-weight:600">' + t("manualCookieTitle") + "</div>" +
|
|
1097
|
+
'<textarea class="dshBalanceInput dshBalanceTextarea" data-cookie-value placeholder="' + t("cookiePlaceholder") + '">' + escapeHtml(cookieEditValue) + '</textarea>' +
|
|
1098
|
+
'<div class="dshBalanceCookiePreview" data-found="' + found + '"><b>' + t("extractResult") + "</b>" + preview + '</div>' +
|
|
1099
|
+
'<div class="dshBalanceFormBtns"><button class="dshBalanceBtn" type="button" data-cookie-save>' + t("save") + '</button>' +
|
|
1100
|
+
'<button class="dshBalanceBtn" type="button" data-cookie-cancel>' + t("cancel") + '</button></div>' +
|
|
1101
|
+
'<div style="margin-top:6px"><span class="dshBalanceWizardLink" data-cookie-guide>' + t("backToAiGuide") + "</span></div></div>";
|
|
850
1102
|
}
|
|
851
1103
|
|
|
852
1104
|
/** Build the MiMo Cookie configuration panel (AI guide or manual fallback). */
|
|
@@ -858,17 +1110,17 @@ window.__ModuleLoader__.load({
|
|
|
858
1110
|
function syncHintHtml() {
|
|
859
1111
|
if (!lastSyncResult) return "";
|
|
860
1112
|
if (!lastSyncResult.ok) {
|
|
861
|
-
return '<div class="dshBalanceSyncHint" data-state="err"
|
|
1113
|
+
return '<div class="dshBalanceSyncHint" data-state="err">' + t("syncFailed", escapeHtml(String(lastSyncResult.error || "").slice(0, 60))) + "</div>";
|
|
862
1114
|
}
|
|
863
1115
|
if (lastSyncResult.added > 0) {
|
|
864
|
-
return '<div class="dshBalanceSyncHint" data-state="ok"
|
|
1116
|
+
return '<div class="dshBalanceSyncHint" data-state="ok">' + t("syncAddedN", lastSyncResult.added) + "</div>";
|
|
865
1117
|
}
|
|
866
1118
|
return "";
|
|
867
1119
|
}
|
|
868
1120
|
|
|
869
1121
|
/** Build the popover HTML from current state. */
|
|
870
1122
|
function popoverHtml() {
|
|
871
|
-
const header = '<h4>
|
|
1123
|
+
const header = '<h4>' + t("aiBalance") + '<button class="dshBalanceAddBtn" type="button" data-add>' + t("add") + "</button></h4>";
|
|
872
1124
|
const syncHint = showAddForm ? "" : syncHintHtml();
|
|
873
1125
|
let body;
|
|
874
1126
|
if (showAddForm) {
|
|
@@ -878,7 +1130,7 @@ window.__ModuleLoader__.load({
|
|
|
878
1130
|
} else {
|
|
879
1131
|
body = '<div class="dshBalanceProviders">';
|
|
880
1132
|
if (providers.length === 0) {
|
|
881
|
-
body += '<div class="dshBalanceHint"
|
|
1133
|
+
body += '<div class="dshBalanceHint">' + t("noProviders") + "</div>";
|
|
882
1134
|
} else {
|
|
883
1135
|
const activeP = providers.find((p) => p.id === activeProviderId) || activeProvider();
|
|
884
1136
|
body += '<div class="dshBalanceTabs">';
|
|
@@ -894,30 +1146,30 @@ window.__ModuleLoader__.load({
|
|
|
894
1146
|
return header + syncHint + body +
|
|
895
1147
|
'<div class="dshBalanceFoot">' + versionHtml +
|
|
896
1148
|
'<span style="display:flex;align-items:center;gap:8px">' +
|
|
897
|
-
'<
|
|
898
|
-
'<
|
|
1149
|
+
'<button class="dshBalanceBtn" type="button" data-refresh>' + t("refresh") + "</button>" +
|
|
1150
|
+
'<span>' + t("updatedAt") + updated + "</span></span></div>";
|
|
899
1151
|
}
|
|
900
1152
|
|
|
901
1153
|
/** Build the npm version snippet shown in the footer (always a clickable button). */
|
|
902
1154
|
function versionHtmlOf(versionEnvelope) {
|
|
903
1155
|
if (Date.now() < updateSuccessUntil) {
|
|
904
|
-
return '<span class="dshBalanceVersion" data-state="ok"
|
|
1156
|
+
return '<span class="dshBalanceVersion" data-state="ok">' + t("updateSuccess") + "</span>";
|
|
905
1157
|
}
|
|
906
1158
|
if (!versionEnvelope) {
|
|
907
|
-
return '<span class="dshBalanceVersion" data-state="ok"><button class="dshBalanceVersionBtn" type="button" data-update-cmd
|
|
1159
|
+
return '<span class="dshBalanceVersion" data-state="ok"><button class="dshBalanceVersionBtn" type="button" data-update-cmd>' + t("checkUpdate") + "</button></span>";
|
|
908
1160
|
}
|
|
909
1161
|
if (versionEnvelope.ok === false) {
|
|
910
|
-
return '<span class="dshBalanceVersion" data-state="err"><button class="dshBalanceVersionBtn" type="button" data-update-cmd
|
|
1162
|
+
return '<span class="dshBalanceVersion" data-state="err"><button class="dshBalanceVersionBtn" type="button" data-update-cmd>' + t("checkUpdate") + "</button></span>";
|
|
911
1163
|
}
|
|
912
1164
|
const local = versionEnvelope.local || "?";
|
|
913
1165
|
if (versionEnvelope.updateAvailable && versionEnvelope.latest) {
|
|
914
1166
|
return '<span class="dshBalanceVersion" data-state="update">' +
|
|
915
1167
|
'v' + local + " → v" + versionEnvelope.latest +
|
|
916
|
-
'<button class="dshBalanceVersionBtn" type="button" data-update-cmd
|
|
1168
|
+
'<button class="dshBalanceVersionBtn" type="button" data-update-cmd>' + t("update") + "</button>" +
|
|
917
1169
|
"</span>";
|
|
918
1170
|
}
|
|
919
1171
|
return '<span class="dshBalanceVersion" data-state="ok">' +
|
|
920
|
-
'v' + local + '<button class="dshBalanceVersionBtn" type="button" data-update-cmd
|
|
1172
|
+
'v' + local + '<button class="dshBalanceVersionBtn" type="button" data-update-cmd>' + t("checkUpdate") + "</button>" +
|
|
921
1173
|
"</span>";
|
|
922
1174
|
}
|
|
923
1175
|
|
|
@@ -927,11 +1179,11 @@ window.__ModuleLoader__.load({
|
|
|
927
1179
|
const kind = (popRef.querySelector("[data-f-kind]")?.value || "mimo");
|
|
928
1180
|
const label = (popRef.querySelector("[data-f-label]")?.value || "").trim();
|
|
929
1181
|
if (!label) {
|
|
930
|
-
window.alert("
|
|
1182
|
+
window.alert(t("fillName"));
|
|
931
1183
|
return;
|
|
932
1184
|
}
|
|
933
1185
|
const saveBtn = popRef.querySelector("[data-save]");
|
|
934
|
-
if (saveBtn !== null) { saveBtn.disabled = true; saveBtn.textContent = "
|
|
1186
|
+
if (saveBtn !== null) { saveBtn.disabled = true; saveBtn.textContent = t("saving"); }
|
|
935
1187
|
const body = { label, kind };
|
|
936
1188
|
const result = await saveProvider(body);
|
|
937
1189
|
if (result && result.ok) {
|
|
@@ -939,15 +1191,15 @@ window.__ModuleLoader__.load({
|
|
|
939
1191
|
showAddForm = false;
|
|
940
1192
|
await refresh(true);
|
|
941
1193
|
} else {
|
|
942
|
-
if (saveBtn !== null) { saveBtn.disabled = false; saveBtn.textContent = "
|
|
943
|
-
window.alert("
|
|
1194
|
+
if (saveBtn !== null) { saveBtn.disabled = false; saveBtn.textContent = t("save"); }
|
|
1195
|
+
window.alert(t("saveFailed", result && result.error ? result.error : "unknown"));
|
|
944
1196
|
}
|
|
945
1197
|
}
|
|
946
1198
|
|
|
947
1199
|
/** Handle the "remove" action for a provider. */
|
|
948
1200
|
async function onRemoveProvider(id) {
|
|
949
1201
|
if (id === "deepseek") return;
|
|
950
|
-
if (!window.confirm("
|
|
1202
|
+
if (!window.confirm(t("confirmRemove"))) return;
|
|
951
1203
|
await removeProvider(id);
|
|
952
1204
|
if (activeProviderId === id) activeProviderId = null;
|
|
953
1205
|
await refresh(true);
|
|
@@ -956,16 +1208,16 @@ window.__ModuleLoader__.load({
|
|
|
956
1208
|
/** Handle the "save cookie" action from the MiMo cookie panel. */
|
|
957
1209
|
async function onSaveCookie() {
|
|
958
1210
|
if (popRef === null || !cookieEditId) return;
|
|
959
|
-
if (!cookieExtracted) { window.alert("
|
|
1211
|
+
if (!cookieExtracted) { window.alert(t("pasteCookieHint")); return; }
|
|
960
1212
|
const saveBtn = popRef.querySelector("[data-cookie-save]");
|
|
961
|
-
if (saveBtn !== null) { saveBtn.disabled = true; saveBtn.textContent = "
|
|
1213
|
+
if (saveBtn !== null) { saveBtn.disabled = true; saveBtn.textContent = t("saving"); }
|
|
962
1214
|
const result = await saveProvider({ id: cookieEditId, kind: "mimo", cookie: cookieExtracted });
|
|
963
1215
|
if (result && result.ok) {
|
|
964
1216
|
cookieEditId = null; cookieEditValue = ""; cookieExtracted = "";
|
|
965
1217
|
await refresh(true);
|
|
966
1218
|
} else {
|
|
967
|
-
if (saveBtn !== null) { saveBtn.disabled = false; saveBtn.textContent = "
|
|
968
|
-
window.alert("
|
|
1219
|
+
if (saveBtn !== null) { saveBtn.disabled = false; saveBtn.textContent = t("save"); }
|
|
1220
|
+
window.alert(t("saveFailed", result && result.error ? result.error : "unknown"));
|
|
969
1221
|
}
|
|
970
1222
|
}
|
|
971
1223
|
|
|
@@ -1084,7 +1336,7 @@ window.__ModuleLoader__.load({
|
|
|
1084
1336
|
} catch (e) {
|
|
1085
1337
|
if (entryRef) {
|
|
1086
1338
|
const v = entryRef.querySelector(".dshBalanceValue");
|
|
1087
|
-
if (v) { v.dataset.state = "err"; v.textContent = "
|
|
1339
|
+
if (v) { v.dataset.state = "err"; v.textContent = t("failed"); }
|
|
1088
1340
|
}
|
|
1089
1341
|
lastEnvelope = { ok: false, error: String(e?.message ?? e) };
|
|
1090
1342
|
renderPopover();
|
|
@@ -1105,6 +1357,22 @@ window.__ModuleLoader__.load({
|
|
|
1105
1357
|
function apply(ctx) {
|
|
1106
1358
|
ensureStyle();
|
|
1107
1359
|
if (typeof document !== "undefined" && document.querySelector("[data-dsh-balance-entry]") !== null) return () => {};
|
|
1360
|
+
// 用 dsh 的 LocaleRuntime 服务读取/订阅语言(ctx.locale 由 @deepseek-ai/dsh-client-locale provide)。
|
|
1361
|
+
let offLocaleChange = null;
|
|
1362
|
+
try {
|
|
1363
|
+
const snap = ctx.locale && typeof ctx.locale.getLocale === "function" ? ctx.locale.getLocale() : null;
|
|
1364
|
+
if (snap && typeof snap.active === "string") currentLang = snap.active === "en" ? "en" : "zh";
|
|
1365
|
+
if (ctx.locale && typeof ctx.locale.subscribe === "function") {
|
|
1366
|
+
offLocaleChange = ctx.locale.subscribe(() => {
|
|
1367
|
+
const s = ctx.locale.getLocale();
|
|
1368
|
+
const next = s && s.active === "en" ? "en" : "zh";
|
|
1369
|
+
if (next !== currentLang) {
|
|
1370
|
+
currentLang = next;
|
|
1371
|
+
applyLangChange(next);
|
|
1372
|
+
}
|
|
1373
|
+
});
|
|
1374
|
+
}
|
|
1375
|
+
} catch { /* locale service optional */ }
|
|
1108
1376
|
const effect = ctx.effect(() => {
|
|
1109
1377
|
const entry = createEntry("compact");
|
|
1110
1378
|
entryRef = entry;
|
|
@@ -1156,6 +1424,7 @@ window.__ModuleLoader__.load({
|
|
|
1156
1424
|
return () => {
|
|
1157
1425
|
waitObserver.disconnect();
|
|
1158
1426
|
rootObserver.disconnect();
|
|
1427
|
+
if (offLocaleChange !== null) { try { offLocaleChange(); } catch { /* ignore */ } offLocaleChange = null; }
|
|
1159
1428
|
if (timer !== null) clearInterval(timer);
|
|
1160
1429
|
if (versionTimer !== null) clearInterval(versionTimer);
|
|
1161
1430
|
if (outsideHandler !== null) {
|
|
@@ -1173,7 +1442,8 @@ window.__ModuleLoader__.load({
|
|
|
1173
1442
|
}
|
|
1174
1443
|
|
|
1175
1444
|
exports.apply = apply;
|
|
1176
|
-
|
|
1445
|
+
/** Declare the locale service dependency so ctx.locale is available (dsh i18n). */
|
|
1446
|
+
exports.inject = ["locale"];
|
|
1177
1447
|
return module.exports;
|
|
1178
1448
|
}
|
|
1179
1449
|
});
|
package/lib/index.js
CHANGED
|
@@ -885,10 +885,6 @@ function apply(ctx) {
|
|
|
885
885
|
return;
|
|
886
886
|
}
|
|
887
887
|
const result = await queryBalance(key);
|
|
888
|
-
// 入口不展示货币符号:把币种字段清空,任何版本的前端都不会拼出 ¥。
|
|
889
|
-
if (result.ok && Array.isArray(result.data?.balance_infos)) {
|
|
890
|
-
for (const info of result.data.balance_infos) info.currency = "";
|
|
891
|
-
}
|
|
892
888
|
res.writeHead(result.ok ? 200 : 502, { "content-type": "application/json; charset=utf-8" });
|
|
893
889
|
res.end(JSON.stringify(result));
|
|
894
890
|
} catch (error) {
|
|
@@ -996,6 +992,40 @@ function apply(ctx) {
|
|
|
996
992
|
return dispose;
|
|
997
993
|
}, "deepseek-balance-widget: update route");
|
|
998
994
|
|
|
995
|
+
/** Read the user's locale from dsh settings.yaml (locale.preference). */
|
|
996
|
+
ctx.effect(() => {
|
|
997
|
+
const dispose = ctx.webServer.register({
|
|
998
|
+
kind: "prefix",
|
|
999
|
+
path: "/deepseek-balance-locale",
|
|
1000
|
+
handler: async (req, res) => {
|
|
1001
|
+
const url = new URL(req.url ?? "/", "http://x");
|
|
1002
|
+
if (req.method !== "GET" || url.pathname !== "/deepseek-balance-locale") {
|
|
1003
|
+
res.writeHead(404, { "content-type": "application/json; charset=utf-8" });
|
|
1004
|
+
res.end(JSON.stringify({ ok: false, error: "not found" }));
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
try {
|
|
1008
|
+
let lang = "zh";
|
|
1009
|
+
try {
|
|
1010
|
+
const text = await readFile(DSH_SETTINGS_PATH, "utf8");
|
|
1011
|
+
// 解析 YAML 顶层 locale: 段下的 preference: 字段(宽松匹配缩进)。
|
|
1012
|
+
const match = text.match(/^locale:\s*\r?\n(\s+preference:\s*(\S+))/m);
|
|
1013
|
+
if (match) {
|
|
1014
|
+
const value = match[2].replace(/^["']|["']$/g, "").toLowerCase();
|
|
1015
|
+
if (value === "en" || value === "zh") lang = value;
|
|
1016
|
+
}
|
|
1017
|
+
} catch { /* settings file missing/unreadable -> default zh */ }
|
|
1018
|
+
res.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
|
1019
|
+
res.end(JSON.stringify({ ok: true, lang }));
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
res.writeHead(500, { "content-type": "application/json; charset=utf-8" });
|
|
1022
|
+
res.end(JSON.stringify({ ok: false, error: String(error?.message ?? error) }));
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
});
|
|
1026
|
+
return dispose;
|
|
1027
|
+
}, "deepseek-balance-widget: locale route");
|
|
1028
|
+
|
|
999
1029
|
/** Provider list (GET), add/update (POST), remove (DELETE). */
|
|
1000
1030
|
ctx.effect(() => {
|
|
1001
1031
|
const dispose = ctx.webServer.register({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-deepseek-balance-widget",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.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": [
|