@ruoyang/dsh-plugin-deepseek-balance 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -4
- package/client.js +140 -27
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ calls.
|
|
|
14
14
|
| Badge text | total balance in the primary currency, e.g. `¥4.94` |
|
|
15
15
|
| Hover panel | per-currency totals, granted vs topped-up split, whether the account is callable, and the query time |
|
|
16
16
|
| Interaction | click to refresh, drag to reposition, automatic refresh every 60 seconds |
|
|
17
|
+
| Language | every label comes from this plugin's own locale namespace, so the badge follows the UI language (English / Chinese) |
|
|
17
18
|
|
|
18
19
|
The badge itself is a pill pinned to the lower-right corner; drag it anywhere and it stays there for
|
|
19
20
|
the session.
|
|
@@ -102,9 +103,18 @@ A 404 on that path means the row did not mount — check that the bundle is enab
|
|
|
102
103
|
|
|
103
104
|
## Known limitations
|
|
104
105
|
|
|
105
|
-
- Visible badge text is currently Chinese only; it does not yet go through the Client locale service,
|
|
106
|
-
so an English UI still shows Chinese labels in the hover panel.
|
|
107
106
|
- The badge position resets on page reload; it is not persisted.
|
|
107
|
+
- Only the two languages the harness ships (English, Chinese) have dictionaries; a third language
|
|
108
|
+
falls back to the harness's own chain and then to the key, so add a dictionary before adding a
|
|
109
|
+
language.
|
|
110
|
+
|
|
111
|
+
## Language
|
|
112
|
+
|
|
113
|
+
Strings live in the plugin's own locale namespace and reach the component through the `t` seat the
|
|
114
|
+
slot framework injects for a registration that names a `locale` namespace, so switching
|
|
115
|
+
**Settings → General → Language** re-words the badge live. Host-side failures arrive as a
|
|
116
|
+
machine-readable `code` that the badge translates; an unrecognised code falls back to the host's own
|
|
117
|
+
message so a novel failure stays legible.
|
|
108
118
|
|
|
109
119
|
## 中文
|
|
110
120
|
|
|
@@ -139,8 +149,10 @@ curl "http://127.0.0.1:3080/deepseek-balance/api?force=1" # 绕过宿主半 20
|
|
|
139
149
|
返回 `NO_KEY` = 没配密钥,`HTTP_401` = 密钥被拒,`TRANSPORT` = 宿主机连不上
|
|
140
150
|
`api.deepseek.com`;404 = 行没挂上,去 **设置 → 插件** 看是否启用。
|
|
141
151
|
|
|
142
|
-
|
|
143
|
-
|
|
152
|
+
**已知限制**:徽标位置在刷新页面后重置(不做持久化);内置字典只有 harness 自带的两种语言
|
|
153
|
+
(中/英),加第三种语言前需要先补字典。
|
|
154
|
+
|
|
155
|
+
徽标文字走本插件自己的 locale 命名空间,**设置 → 通用 → 语言** 切换后会实时改文案。
|
|
144
156
|
|
|
145
157
|
## Releasing (maintainer)
|
|
146
158
|
|
package/client.js
CHANGED
|
@@ -10,8 +10,14 @@
|
|
|
10
10
|
* build step is involved. `id` must equal the package name. React comes from the
|
|
11
11
|
* browser module table via `require`.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Every visible string lives in this plugin's own locale namespace and reaches
|
|
14
|
+
* the component through the `t` seat the slot framework injects for a
|
|
15
|
+
* registration that names a `locale` namespace, so the badge follows the active
|
|
16
|
+
* language instead of being Chinese-only.
|
|
17
|
+
*
|
|
18
|
+
* The factory itself stays side-effect free: the stylesheet, the dictionaries,
|
|
19
|
+
* the interval and the slot registration are all created inside `apply` and
|
|
20
|
+
* owned by that fiber.
|
|
15
21
|
*/
|
|
16
22
|
window.__ModuleLoader__.load({
|
|
17
23
|
id: '@ruoyang/dsh-plugin-deepseek-balance',
|
|
@@ -26,6 +32,72 @@ window.__ModuleLoader__.load({
|
|
|
26
32
|
const SYMBOLS = { CNY: '¥', USD: '$' }
|
|
27
33
|
/** Id of the one style node this fiber owns. */
|
|
28
34
|
const STYLE_ID = 'dsh-balance-styles'
|
|
35
|
+
/**
|
|
36
|
+
* Locale namespace this plugin owns. It is also the namespace named on the
|
|
37
|
+
* slot registration, which is what makes the framework inject `t`.
|
|
38
|
+
*/
|
|
39
|
+
const NS = 'deepseek-balance'
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Dictionaries for every built-in locale. Registration requires the complete
|
|
43
|
+
* set, and both dictionaries carry the same keys.
|
|
44
|
+
*/
|
|
45
|
+
const DICTS = {
|
|
46
|
+
en: {
|
|
47
|
+
'pill.loading': 'Balance …',
|
|
48
|
+
'pill.unavailable': 'Balance unavailable',
|
|
49
|
+
'pill.title': 'DeepSeek balance · click to refresh · drag to move',
|
|
50
|
+
'panel.totalIn': '{currency} total',
|
|
51
|
+
'panel.grantSplit': ' granted / topped up',
|
|
52
|
+
'panel.balance': 'Balance',
|
|
53
|
+
'panel.noData': 'no data',
|
|
54
|
+
'panel.callable': 'Callable',
|
|
55
|
+
'panel.yes': 'yes',
|
|
56
|
+
'panel.no': 'no',
|
|
57
|
+
'panel.updated': 'Updated',
|
|
58
|
+
'panel.state': 'State',
|
|
59
|
+
'panel.querying': 'Querying …',
|
|
60
|
+
'panel.error': 'Error',
|
|
61
|
+
'panel.credential': 'Credential',
|
|
62
|
+
'hint': 'Click to refresh · drag to move',
|
|
63
|
+
'error.fallback': 'Balance temporarily unavailable',
|
|
64
|
+
'error.NO_KEY': 'No DEEPSEEK_API_KEY credential — configure it under Settings → Models',
|
|
65
|
+
'error.TRANSPORT': 'The host could not reach api.deepseek.com',
|
|
66
|
+
'error.BAD_JSON': 'The balance response was not valid JSON',
|
|
67
|
+
'error.TIMEOUT': 'The balance query timed out',
|
|
68
|
+
'error.NO_OUTPUT': 'The balance query returned nothing parseable',
|
|
69
|
+
'error.HTTP_401': 'The API key was rejected',
|
|
70
|
+
'error.HTTP_403': 'This API key is not permitted',
|
|
71
|
+
'error.HTTP_429': 'Rate limited — try again shortly',
|
|
72
|
+
},
|
|
73
|
+
zh: {
|
|
74
|
+
'pill.loading': '余额 …',
|
|
75
|
+
'pill.unavailable': '余额不可用',
|
|
76
|
+
'pill.title': 'DeepSeek 余额 · 点击刷新 · 拖动移动',
|
|
77
|
+
'panel.totalIn': '{currency} 总余额',
|
|
78
|
+
'panel.grantSplit': ' 赠金 / 充值',
|
|
79
|
+
'panel.balance': '余额',
|
|
80
|
+
'panel.noData': '无数据',
|
|
81
|
+
'panel.callable': '可调用',
|
|
82
|
+
'panel.yes': '是',
|
|
83
|
+
'panel.no': '否',
|
|
84
|
+
'panel.updated': '更新于',
|
|
85
|
+
'panel.state': '状态',
|
|
86
|
+
'panel.querying': '查询中 …',
|
|
87
|
+
'panel.error': '错误',
|
|
88
|
+
'panel.credential': '凭据',
|
|
89
|
+
'hint': '点击刷新 · 按住拖动可移动位置',
|
|
90
|
+
'error.fallback': '余额暂不可用',
|
|
91
|
+
'error.NO_KEY': '未找到 DEEPSEEK_API_KEY 凭据 —— 请在 设置 → Models 里配置',
|
|
92
|
+
'error.TRANSPORT': '宿主机连不上 api.deepseek.com',
|
|
93
|
+
'error.BAD_JSON': '余额响应不是合法 JSON',
|
|
94
|
+
'error.TIMEOUT': '余额查询超时',
|
|
95
|
+
'error.NO_OUTPUT': '余额查询没有返回可解析的结果',
|
|
96
|
+
'error.HTTP_401': 'API key 被拒绝',
|
|
97
|
+
'error.HTTP_403': '这个 API key 没有权限',
|
|
98
|
+
'error.HTTP_429': '被限流了 —— 稍后再试',
|
|
99
|
+
},
|
|
100
|
+
}
|
|
29
101
|
|
|
30
102
|
const CSS = [
|
|
31
103
|
'.dsh-balance-root{position:fixed;z-index:60;pointer-events:auto;user-select:none;',
|
|
@@ -92,6 +164,44 @@ window.__ModuleLoader__.load({
|
|
|
92
164
|
return pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds())
|
|
93
165
|
}
|
|
94
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Stand-in used only when the slot framework injects no `t` seat.
|
|
169
|
+
*
|
|
170
|
+
* The framework throws rather than omitting the seat for a registration that
|
|
171
|
+
* names a locale namespace, so this is a guard against a future change on a
|
|
172
|
+
* harness this plugin deliberately declares no version dependency on: a
|
|
173
|
+
* missing seat degrades the wording instead of blanking the badge.
|
|
174
|
+
*/
|
|
175
|
+
function fallbackTranslate(key) {
|
|
176
|
+
return DICTS.zh[key] !== undefined ? DICTS.zh[key] : key
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Take the injected translate seat, falling back when it is absent. */
|
|
180
|
+
function translatorOf(props) {
|
|
181
|
+
if (props !== null && typeof props === 'object' && typeof props.t === 'function') return props.t
|
|
182
|
+
return fallbackTranslate
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Wording for a failed snapshot.
|
|
187
|
+
*
|
|
188
|
+
* The host half already answers a machine-readable `code`; translating that
|
|
189
|
+
* keeps the badge in the active language, while an unrecognised code falls
|
|
190
|
+
* back to the host's own message so a novel failure is still legible. An
|
|
191
|
+
* unknown key resolves to the key itself, which is how the miss is detected.
|
|
192
|
+
*/
|
|
193
|
+
function failureText(t, payload) {
|
|
194
|
+
if (payload !== null && typeof payload === 'object' && typeof payload.code === 'string' && payload.code !== '') {
|
|
195
|
+
const key = 'error.' + payload.code
|
|
196
|
+
const text = t(key)
|
|
197
|
+
if (text !== key) return text
|
|
198
|
+
}
|
|
199
|
+
if (payload !== null && typeof payload === 'object' && typeof payload.message === 'string' && payload.message !== '') {
|
|
200
|
+
return payload.message
|
|
201
|
+
}
|
|
202
|
+
return t('error.fallback')
|
|
203
|
+
}
|
|
204
|
+
|
|
95
205
|
/** One key/value line of the hover panel. */
|
|
96
206
|
function row(key, value, extraClass) {
|
|
97
207
|
return React.createElement(
|
|
@@ -111,34 +221,33 @@ window.__ModuleLoader__.load({
|
|
|
111
221
|
* and freshness. The failure branch names the credential so a missing key is
|
|
112
222
|
* obvious without opening logs.
|
|
113
223
|
*/
|
|
114
|
-
function panelRows(data, failure, phase) {
|
|
224
|
+
function panelRows(t, data, failure, phase) {
|
|
115
225
|
const rows = []
|
|
116
226
|
if (data !== null) {
|
|
117
227
|
if (data.infos.length === 0) {
|
|
118
|
-
rows.push(row('
|
|
228
|
+
rows.push(row(t('panel.balance'), t('panel.noData')))
|
|
119
229
|
} else {
|
|
120
230
|
for (const info of data.infos) {
|
|
121
231
|
const symbol = symbolOf(info.currency)
|
|
122
|
-
rows.push(row(info.currency
|
|
123
|
-
rows.push(row('
|
|
232
|
+
rows.push(row(t('panel.totalIn', { currency: info.currency }), symbol + info.total))
|
|
233
|
+
rows.push(row(t('panel.grantSplit'), symbol + info.granted + ' / ' + symbol + info.toppedUp))
|
|
124
234
|
}
|
|
125
235
|
}
|
|
126
|
-
rows.push(row('
|
|
127
|
-
rows.push(row('
|
|
236
|
+
rows.push(row(t('panel.callable'), data.isAvailable === true ? t('panel.yes') : t('panel.no')))
|
|
237
|
+
rows.push(row(t('panel.updated'), formatTime(data.at)))
|
|
128
238
|
} else if (phase === 'loading') {
|
|
129
|
-
rows.push(row('
|
|
130
|
-
rows.push(row('
|
|
239
|
+
rows.push(row(t('panel.state'), t('panel.querying')))
|
|
240
|
+
rows.push(row(t('panel.credential'), 'DEEPSEEK_API_KEY'))
|
|
131
241
|
} else {
|
|
132
|
-
rows.push(row('
|
|
133
|
-
rows.push(row('
|
|
242
|
+
rows.push(row(t('panel.error'), failure === null ? t('error.fallback') : failure, 'dsh-balance-err'))
|
|
243
|
+
rows.push(row(t('panel.credential'), 'DEEPSEEK_API_KEY'))
|
|
134
244
|
}
|
|
135
|
-
rows.push(React.createElement('div', { className: 'dsh-balance-hint', key: 'hint' },
|
|
136
|
-
'点击刷新 · 按住拖动可移动位置'))
|
|
245
|
+
rows.push(React.createElement('div', { className: 'dsh-balance-hint', key: 'hint' }, t('hint')))
|
|
137
246
|
return rows
|
|
138
247
|
}
|
|
139
248
|
|
|
140
249
|
return {
|
|
141
|
-
inject: ['slots', 'timer'],
|
|
250
|
+
inject: ['slots', 'timer', 'locale'],
|
|
142
251
|
apply(ctx) {
|
|
143
252
|
ctx.effect(() => {
|
|
144
253
|
if (document.getElementById(STYLE_ID) === null) {
|
|
@@ -150,10 +259,13 @@ window.__ModuleLoader__.load({
|
|
|
150
259
|
return () => { document.getElementById(STYLE_ID)?.remove() }
|
|
151
260
|
}, 'deepseek-balance: styles')
|
|
152
261
|
|
|
262
|
+
ctx.effect(() => ctx.locale.register(NS, DICTS), 'deepseek-balance: dictionaries')
|
|
263
|
+
|
|
153
264
|
// Defined inside apply so the component closes over this fiber's context
|
|
154
265
|
// and can own its own interval. One mount per client run keeps the
|
|
155
266
|
// component identity stable across re-renders.
|
|
156
|
-
function BalanceBadge() {
|
|
267
|
+
function BalanceBadge(props) {
|
|
268
|
+
const t = translatorOf(props)
|
|
157
269
|
const [snapshot, setSnapshot] = React.useState({ phase: 'loading' })
|
|
158
270
|
const [busy, setBusy] = React.useState(false)
|
|
159
271
|
const [position, setPosition] = React.useState({ right: 22, bottom: 22 })
|
|
@@ -215,17 +327,16 @@ window.__ModuleLoader__.load({
|
|
|
215
327
|
const data = snapshot.phase === 'ready' && snapshot.data !== null && snapshot.data.ok === true
|
|
216
328
|
? snapshot.data
|
|
217
329
|
: null
|
|
218
|
-
const
|
|
219
|
-
? snapshot.message
|
|
220
|
-
: (snapshot.phase === 'ready' && snapshot.data !== null && snapshot.data.ok !== true
|
|
221
|
-
|
|
222
|
-
: null)
|
|
330
|
+
const failed = snapshot.phase === 'error'
|
|
331
|
+
? { message: snapshot.message }
|
|
332
|
+
: (snapshot.phase === 'ready' && snapshot.data !== null && snapshot.data.ok !== true ? snapshot.data : null)
|
|
333
|
+
const failure = failed === null ? null : failureText(t, failed)
|
|
223
334
|
|
|
224
335
|
let tone = 'idle'
|
|
225
|
-
let label = '
|
|
226
|
-
if (
|
|
336
|
+
let label = t('pill.loading')
|
|
337
|
+
if (failure !== null) {
|
|
227
338
|
tone = 'error'
|
|
228
|
-
label = '
|
|
339
|
+
label = t('pill.unavailable')
|
|
229
340
|
} else if (data !== null) {
|
|
230
341
|
tone = data.isAvailable === true ? 'ok' : 'warn'
|
|
231
342
|
label = symbolOf(data.currency) + data.total
|
|
@@ -237,12 +348,12 @@ window.__ModuleLoader__.load({
|
|
|
237
348
|
className: 'dsh-balance-root dsh-balance-tone-' + tone,
|
|
238
349
|
style: { right: position.right + 'px', bottom: position.bottom + 'px' },
|
|
239
350
|
},
|
|
240
|
-
React.createElement('div', { className: 'dsh-balance-panel' }, panelRows(data, failure, snapshot.phase)),
|
|
351
|
+
React.createElement('div', { className: 'dsh-balance-panel' }, panelRows(t, data, failure, snapshot.phase)),
|
|
241
352
|
React.createElement(
|
|
242
353
|
'div',
|
|
243
354
|
{
|
|
244
355
|
className: 'dsh-balance-pill',
|
|
245
|
-
title: '
|
|
356
|
+
title: t('pill.title'),
|
|
246
357
|
onPointerDown,
|
|
247
358
|
onPointerMove,
|
|
248
359
|
onPointerUp,
|
|
@@ -256,7 +367,9 @@ window.__ModuleLoader__.load({
|
|
|
256
367
|
}
|
|
257
368
|
|
|
258
369
|
ctx.effect(() => ctx.slots.inject('shell.overlay', () => ctx.slots.register(
|
|
259
|
-
|
|
370
|
+
// Naming the locale namespace is what makes the framework inject the
|
|
371
|
+
// `t` seat (and re-render every outlet when the language changes).
|
|
372
|
+
{ name: 'shell.overlay', id: 'deepseek-balance', order: 200, locale: NS },
|
|
260
373
|
BalanceBadge,
|
|
261
374
|
)), 'deepseek-balance: overlay badge')
|
|
262
375
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ruoyang/dsh-plugin-deepseek-balance",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Floating DeepSeek API balance badge for the DeepSeek Harness Web UI, backed by the harness's own DEEPSEEK_API_KEY credential",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"platform": "web",
|
|
43
43
|
"immediately": true,
|
|
44
44
|
"inject": [
|
|
45
|
+
"@deepseek-ai/dsh-client-locale",
|
|
45
46
|
"@deepseek-ai/dsh-client-ui-layout"
|
|
46
47
|
]
|
|
47
48
|
}
|