dsh-consumption-badge 1.2.1 → 1.2.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 +82 -51
- package/lib/client.js +458 -136
- package/lib/index.js +26 -13
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -107,12 +107,263 @@ window.__ModuleLoader__.load({
|
|
|
107
107
|
|
|
108
108
|
// 同源取数:Host 半边的只读路由,失败一律转成 store 里的错误文案。
|
|
109
109
|
async function fetchJson(path) {
|
|
110
|
-
if (typeof fetch !== 'function') throw new Error('fetch 不可用')
|
|
110
|
+
if (typeof fetch !== 'function') throw new Error(tt('fetch 不可用'))
|
|
111
111
|
const res = await fetch(path, { headers: { accept: 'application/json' }, credentials: 'same-origin' })
|
|
112
112
|
if (!res.ok) throw new Error('HTTP ' + String(res.status))
|
|
113
113
|
return await res.json()
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
/* ============ i18n: follow the shell locale (zh / en) ============ */
|
|
117
|
+
// Design: the Chinese copy IS the dictionary key. Under `zh` no dictionary is
|
|
118
|
+
// needed (the key is the text); under `en` DICT_EN supplies the translation.
|
|
119
|
+
// Anything missing from DICT_EN falls back to the key, so a gap can only show
|
|
120
|
+
// Chinese — never an empty string.
|
|
121
|
+
//
|
|
122
|
+
// No manual re-render plumbing for slot-mounted UI: dsh-client-web-react's
|
|
123
|
+
// SlotOutlet subscribes to the locale face revision (useLocaleRevision), so
|
|
124
|
+
// every outlet re-renders on a language switch and these module-level calls
|
|
125
|
+
// pick up the new locale. Only the personalization page's own React root
|
|
126
|
+
// (rendered outside the slot system) subscribes explicitly.
|
|
127
|
+
const LOCALE_NS = 'consumption-badge'
|
|
128
|
+
const DICT_EN = {
|
|
129
|
+
/* badge + panel */
|
|
130
|
+
'本会话消耗(点击查看逐轮明细)': 'Session usage (click for per-turn details)',
|
|
131
|
+
'本会话消耗': 'Session usage',
|
|
132
|
+
'⛽ 本会话消耗': '⛽ Session usage',
|
|
133
|
+
'刷新余额': 'Refresh balance',
|
|
134
|
+
'Esc 关闭': 'Esc to close',
|
|
135
|
+
'累计 Token': 'Total tokens',
|
|
136
|
+
'估算金额': 'Estimated cost',
|
|
137
|
+
'当前时段': 'Current window',
|
|
138
|
+
'账户余额': 'Account balance',
|
|
139
|
+
'逐轮消耗趋势': 'Per-turn usage trend',
|
|
140
|
+
'命中 {hit} · 未命中 {miss} · 输出 {out}': 'hit {hit} · miss {miss} · output {out}',
|
|
141
|
+
'{id}(按 flash 估算)': '{id} (priced as flash)',
|
|
142
|
+
' · 峰时 ×2': ' · peak ×2',
|
|
143
|
+
' · 谷时': ' · off-peak ×1',
|
|
144
|
+
'高峰': 'Peak',
|
|
145
|
+
'空闲': 'Off-peak',
|
|
146
|
+
'峰': 'Peak',
|
|
147
|
+
'谷': 'Off-peak',
|
|
148
|
+
'峰时': 'Peak',
|
|
149
|
+
'谷时': 'Off-peak',
|
|
150
|
+
'北京时间 工作日 09:00-12:00 / 14:00-18:00': 'Beijing time, weekdays 09:00-12:00 / 14:00-18:00',
|
|
151
|
+
'金额按 DeepSeek 官方价估算(谷/峰 ×1/×2),仅供参考;数据源为本会话日志,刷新后自动恢复。': 'Costs are estimated from DeepSeek list prices (off-peak/peak ×1/×2) and are for reference only; the source is this session log, restored automatically after a refresh.',
|
|
152
|
+
/* chart tooltips */
|
|
153
|
+
'本会话还没有可统计的轮次': 'No turns to chart in this session yet',
|
|
154
|
+
'输入命中': 'Cache hit',
|
|
155
|
+
'输入未命中': 'Cache miss',
|
|
156
|
+
'输出': 'Output',
|
|
157
|
+
'本轮合计': 'Turn total',
|
|
158
|
+
'每轮 Token': 'Tokens per turn',
|
|
159
|
+
'每轮估算金额': 'Cost per turn',
|
|
160
|
+
'{n} 轮': '{n} turns',
|
|
161
|
+
'当日合计': 'Total',
|
|
162
|
+
'模型': 'Model',
|
|
163
|
+
/* balance + error copy */
|
|
164
|
+
'打开面板时请求一次': 'Fetched once per panel open',
|
|
165
|
+
'打开设置页时请求一次': 'Fetched once per settings open',
|
|
166
|
+
'读取中…': 'Loading…',
|
|
167
|
+
'获取失败': 'Failed',
|
|
168
|
+
'余额接口不可用': 'Balance API unavailable',
|
|
169
|
+
'余额不足': 'Insufficient balance',
|
|
170
|
+
'请前往 DeepSeek 开放平台充值': 'Top up at the DeepSeek open platform',
|
|
171
|
+
'赠送 ¥{v}': 'granted ¥{v}',
|
|
172
|
+
'充值 ¥{v}': 'topped up ¥{v}',
|
|
173
|
+
'DeepSeek 账户余额': 'DeepSeek account balance',
|
|
174
|
+
'空响应': 'Empty response',
|
|
175
|
+
'余额获取失败': 'Balance request failed',
|
|
176
|
+
'余额获取失败:{msg}': 'Balance request failed: {msg}',
|
|
177
|
+
'会话消耗读取失败:{msg}': 'Failed to read session usage: {msg}',
|
|
178
|
+
'fetch 不可用': 'fetch is not available',
|
|
179
|
+
/* host error codes (lib/index.js) → zh copy; DICT_EN supplies the English */
|
|
180
|
+
'credentials 服务不可用': 'The credentials service is unavailable.',
|
|
181
|
+
'读取 API Key 失败:{detail}': 'Failed to read the API key: {detail}',
|
|
182
|
+
'未配置 {detail}': 'No API key configured ({detail}).',
|
|
183
|
+
'subprocess 服务不可用': 'The subprocess service is unavailable.',
|
|
184
|
+
'curl 没有输出:{detail}': 'curl produced no output: {detail}',
|
|
185
|
+
'curl 启动失败:{detail}': 'curl could not be started: {detail}',
|
|
186
|
+
'响应解析失败:{detail}': 'Could not parse the response: {detail}',
|
|
187
|
+
'余额接口请求失败': 'The balance request failed.',
|
|
188
|
+
'缺少 sessionId': 'Missing sessionId.',
|
|
189
|
+
'会话日志不可用': 'The session log is unavailable.',
|
|
190
|
+
/* settings page */
|
|
191
|
+
'消耗统计': 'Usage statistics',
|
|
192
|
+
'本地估算,非官方账单': 'Local estimate, not an official bill',
|
|
193
|
+
'今天': 'Today',
|
|
194
|
+
'近一周': 'Last 7 days',
|
|
195
|
+
'近一月': 'Last 30 days',
|
|
196
|
+
'近一年': 'Last 12 months',
|
|
197
|
+
'全部历史 · 含所有工作区': 'All history · every workspace',
|
|
198
|
+
' · 全部历史逐轮计价': ' · per-turn pricing over all history',
|
|
199
|
+
'时间范围': 'Time range',
|
|
200
|
+
'自定义区间': 'Custom range',
|
|
201
|
+
'工作区': 'Workspace',
|
|
202
|
+
'全部工作区': 'All workspaces',
|
|
203
|
+
'未归属工作区': 'Unassigned',
|
|
204
|
+
'日历选择(覆盖时间范围)': 'Calendar (overrides the range)',
|
|
205
|
+
'清除': 'Clear',
|
|
206
|
+
'数据': 'Data',
|
|
207
|
+
'刷新': 'Refresh',
|
|
208
|
+
'估算金额(右轴)': 'Estimated cost (right axis)',
|
|
209
|
+
'Token(左轴,默认隐藏)': 'Tokens (left axis, hidden by default)',
|
|
210
|
+
'该范围内暂无消耗记录': 'No usage recorded in this range',
|
|
211
|
+
'总消耗 ': 'Total cost ',
|
|
212
|
+
'总 Token ': 'Total tokens ',
|
|
213
|
+
'记录 ': 'Records ',
|
|
214
|
+
' 条': '',
|
|
215
|
+
'峰时 ': 'Peak ',
|
|
216
|
+
'谷时 ': 'Off-peak ',
|
|
217
|
+
'粒度 ': 'Bucket ',
|
|
218
|
+
'按月': 'monthly',
|
|
219
|
+
'按日': 'daily',
|
|
220
|
+
'按小时': 'hourly',
|
|
221
|
+
'数据源:本机逐轮记录(localStorage {prefix}<sessionId>:<turn>);按北京时间聚合(今天与单日区间逐小时,多日逐日,近一年逐月){worker}。金额按 DeepSeek 官方价估算(谷/峰 ×1/×2),仅供参考。': 'Source: local per-turn records (localStorage {prefix}<sessionId>:<turn>), aggregated in Beijing time (hourly for today and single-day ranges, daily for multi-day ranges, monthly for the last 12 months){worker}. Costs are estimated from DeepSeek list prices (off-peak/peak ×1/×2) and are for reference only.',
|
|
222
|
+
';聚合已在 Web Worker 中完成': '; aggregation ran in a Web Worker',
|
|
223
|
+
/* personalization page block */
|
|
224
|
+
'消耗弹窗': 'Usage popup',
|
|
225
|
+
'背景': 'Background',
|
|
226
|
+
'点击对话里的 ⛽ 胶囊弹出的消耗面板': 'The usage panel opened by the ⛽ pill in the chat',
|
|
227
|
+
'消耗弹窗背景': 'Usage popup background',
|
|
228
|
+
'默认': 'Default',
|
|
229
|
+
'毛玻璃': 'Glass',
|
|
230
|
+
'自定义背景': 'Custom image',
|
|
231
|
+
'纯色': 'Solid color',
|
|
232
|
+
'弹窗底色;文字颜色会按明暗自动切换': 'Popup base color; text color adapts automatically to lightness',
|
|
233
|
+
'正在保存…': 'Saving…',
|
|
234
|
+
'已保存到本机': 'Saved on this device',
|
|
235
|
+
'取回失败,请重新选择': 'Could not read it back — please pick again',
|
|
236
|
+
'当前:本地图片({status})': 'Using: local image ({status})',
|
|
237
|
+
'当前:本地图片(已保存到本机)': 'Using: local image (saved on this device)',
|
|
238
|
+
'当前:图片 URL': 'Using: image URL',
|
|
239
|
+
'未设置图片(此时按默认背景显示)': 'No image set (falls back to the default background)',
|
|
240
|
+
'图片': 'Image',
|
|
241
|
+
';可选 URL 或本地图片,不限大小': '; a URL or a local image, any size',
|
|
242
|
+
'更换图片…': 'Change image…',
|
|
243
|
+
'选择图片…': 'Choose image…',
|
|
244
|
+
'已使用本地图片,输入 URL 可替换': 'Local image in use — enter a URL to replace it',
|
|
245
|
+
'不透明度': 'Opacity',
|
|
246
|
+
'背景图片自身的透明度(0% 完全不可见,100% 完整显示)': 'Opacity of the background image itself (0% invisible, 100% fully visible)',
|
|
247
|
+
'把背景图片磨砂化(对图片做真模糊)': 'Frost the background image (a real blur on the image)',
|
|
248
|
+
'底色半透明并模糊背后的页面': 'Translucent base that blurs the page behind it',
|
|
249
|
+
'毛玻璃强度': 'Glass strength',
|
|
250
|
+
'模糊半径(0–48px)': 'Blur radius (0–48px)',
|
|
251
|
+
'消耗徽章弹层的背景样式;不影响对话区与侧边栏背景。{tag}': 'Background style of the badge popup; it does not affect the chat or sidebar background. {tag}',
|
|
252
|
+
'重置本栏': 'Reset this section',
|
|
253
|
+
'消耗弹窗设置需要 react-dom,当前环境不可用。': 'Popup settings need react-dom, which is not available here.',
|
|
254
|
+
'error: IndexedDB 与 FileReader 都不可用': 'error: IndexedDB and FileReader are both unavailable',
|
|
255
|
+
'IndexedDB 与 FileReader 都不可用': 'IndexedDB and FileReader are both unavailable',
|
|
256
|
+
/* update notice */
|
|
257
|
+
'消耗徽章有新版本': 'A new version of the usage badge is available',
|
|
258
|
+
'当前 v{current} → 最新 v{latest}': 'current v{current} → latest v{latest}',
|
|
259
|
+
'复制更新命令': 'Copy update command',
|
|
260
|
+
'已复制': 'Copied',
|
|
261
|
+
'忽略此版本': 'Ignore this version',
|
|
262
|
+
'稍后提醒': 'Remind me later',
|
|
263
|
+
'更新后需重启 dsh 进程': 'Restart the dsh process after updating',
|
|
264
|
+
'版本来自 npm registry(24h 缓存);「忽略此版本」会记住,直到出现更高版本。': 'Versions come from the npm registry (24h cache); “Ignore this version” is remembered until a higher version appears.',
|
|
265
|
+
/* slot labels */
|
|
266
|
+
'消耗': 'Usage',
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let boundTranslate = null
|
|
270
|
+
let localeService = null
|
|
271
|
+
let appCtx = null
|
|
272
|
+
|
|
273
|
+
/** Fill `{name}` placeholders (same template syntax as the locale service). */
|
|
274
|
+
function applyParams(template, params) {
|
|
275
|
+
if (!params) return String(template)
|
|
276
|
+
return String(template).replace(/\{(\w+)\}/g, function (all, name) {
|
|
277
|
+
return Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : all
|
|
278
|
+
})
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Bind the shell's locale service when the composition provides one.
|
|
283
|
+
* Optional dependency on purpose: without the service every string falls
|
|
284
|
+
* back to its Chinese key, so the plugin still works standalone.
|
|
285
|
+
* @param ctx client cordis context.
|
|
286
|
+
*/
|
|
287
|
+
function attachLocale(ctx) {
|
|
288
|
+
if (localeService !== null) return
|
|
289
|
+
let service = null
|
|
290
|
+
try {
|
|
291
|
+
// ctx.get(name, strict = true) throws when the service is missing: this is an
|
|
292
|
+
// optional dependency, so read it non-strictly. The try/catch also covers
|
|
293
|
+
// contexts without a get() (older shells, test doubles).
|
|
294
|
+
if (ctx && typeof ctx.get === 'function') service = ctx.get('locale', false) || null
|
|
295
|
+
} catch (e) {
|
|
296
|
+
service = null
|
|
297
|
+
}
|
|
298
|
+
if (service === null || typeof service.register !== 'function' || typeof service.bind !== 'function') return
|
|
299
|
+
try {
|
|
300
|
+
// Only the `en` side is registered: the key already is the `zh` copy, and
|
|
301
|
+
// the locale service resolves a miss to the key itself.
|
|
302
|
+
service.register(LOCALE_NS, 'en', DICT_EN)
|
|
303
|
+
} catch (e) {
|
|
304
|
+
// Already registered (hot re-mount of the same fiber) — binding is enough.
|
|
305
|
+
}
|
|
306
|
+
boundTranslate = service.bind(LOCALE_NS)
|
|
307
|
+
localeService = service
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Late-binding guard: the locale service may be provided after this plugin's
|
|
312
|
+
* apply() ran (boot order differs between compositions), so the first render
|
|
313
|
+
* retries the bind. Costs one ctx.get per tt() call while unbound.
|
|
314
|
+
*/
|
|
315
|
+
function ensureLocaleBound() {
|
|
316
|
+
if (localeService !== null || appCtx === null) return
|
|
317
|
+
attachLocale(appCtx)
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Translate a key with the active locale; falls back to the key itself. */
|
|
321
|
+
function tt(key, params) {
|
|
322
|
+
if (localeService === null) ensureLocaleBound()
|
|
323
|
+
if (boundTranslate !== null) {
|
|
324
|
+
try {
|
|
325
|
+
return boundTranslate(key, params)
|
|
326
|
+
} catch (e) { /* fall through to the key */ }
|
|
327
|
+
}
|
|
328
|
+
return applyParams(key, params)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** Subscribe to locale switches (only the self-built React root needs this). */
|
|
332
|
+
function subscribeLocale(fn) {
|
|
333
|
+
const service = localeService
|
|
334
|
+
if (service === null || typeof service.subscribe !== 'function') return function () {}
|
|
335
|
+
try {
|
|
336
|
+
const off = service.subscribe(fn)
|
|
337
|
+
return typeof off === 'function' ? off : function () {}
|
|
338
|
+
} catch (e) {
|
|
339
|
+
return function () {}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/* Host error codes (lib/index.js) → the Chinese key tt() translates. */
|
|
344
|
+
const HOST_ERROR_KEYS = {
|
|
345
|
+
'no-credentials': 'credentials 服务不可用',
|
|
346
|
+
'key-read-failed': '读取 API Key 失败:{detail}',
|
|
347
|
+
'no-key': '未配置 {detail}',
|
|
348
|
+
'no-subprocess': 'subprocess 服务不可用',
|
|
349
|
+
'curl-empty': 'curl 没有输出:{detail}',
|
|
350
|
+
'spawn-failed': 'curl 启动失败:{detail}',
|
|
351
|
+
'bad-response': '响应解析失败:{detail}',
|
|
352
|
+
'balance-request-failed': '余额接口请求失败',
|
|
353
|
+
'missing-session-id': '缺少 sessionId',
|
|
354
|
+
'log-unavailable': '会话日志不可用',
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Render a host error payload in the active locale. Unknown codes fall back
|
|
359
|
+
* to the host's own (English) message so nothing is ever swallowed.
|
|
360
|
+
*/
|
|
361
|
+
function hostErrorText(code, detail, fallback) {
|
|
362
|
+
const key = typeof code === 'string' ? HOST_ERROR_KEYS[code] : undefined
|
|
363
|
+
if (key === undefined) return typeof fallback === 'string' ? fallback : ''
|
|
364
|
+
return tt(key, { detail: typeof detail === 'string' ? detail : '' })
|
|
365
|
+
}
|
|
366
|
+
|
|
116
367
|
/* local persistence: one record per <sessionId>:<turn> */
|
|
117
368
|
function lsGet(key) {
|
|
118
369
|
try {
|
|
@@ -203,10 +454,10 @@ window.__ModuleLoader__.load({
|
|
|
203
454
|
|
|
204
455
|
/* ================= 消耗弹窗背景样式(与个性化插件联动) ================= */
|
|
205
456
|
const MODAL_STYLE_KEY = 'dsh-consumption:modal-style'
|
|
206
|
-
const BUILD_TAG = 'dcb-1.2.
|
|
457
|
+
const BUILD_TAG = 'dcb-1.2.2'
|
|
207
458
|
// 本地版本:本包是零构建的手写客户端包,无法 import package.json,因此把版本号作为常量维护,
|
|
208
459
|
// 并由 check.mjs 守卫与 package.json 严格一致(发布前不一致直接失败)。
|
|
209
|
-
const PLUGIN_VERSION = '1.2.
|
|
460
|
+
const PLUGIN_VERSION = '1.2.2'
|
|
210
461
|
const PKG_NAME = 'dsh-consumption-badge'
|
|
211
462
|
const UPDATE_REGISTRY_URL = 'https://registry.npmjs.org/' + PKG_NAME + '/latest'
|
|
212
463
|
const UPDATE_COMMAND = 'dsh plugin --profile web update ' + PKG_NAME
|
|
@@ -219,6 +470,7 @@ window.__ModuleLoader__.load({
|
|
|
219
470
|
// opacity:背景图片自身的不透明度(0–100,与个性化插件其它背景栏同口径);
|
|
220
471
|
// 旧版的 scrim(压暗遮罩)在读取时折算成 100 - scrim,老配置不会丢。
|
|
221
472
|
const DEFAULT_MODAL_STYLE = { mode: 'default', color: '#17181c', image: '', imageKey: '', opacity: 85, glass: false, blur: 22 }
|
|
473
|
+
// label 是词典 key(中文原文),渲染时统一走 tt(mode.label),所以这里不能直接显示。
|
|
222
474
|
const MODAL_MODES = [
|
|
223
475
|
{ value: 'default', label: '默认' },
|
|
224
476
|
{ value: 'glass', label: '毛玻璃' },
|
|
@@ -521,7 +773,7 @@ window.__ModuleLoader__.load({
|
|
|
521
773
|
return latest !== '' ? latest : null
|
|
522
774
|
})
|
|
523
775
|
.catch(function (err) {
|
|
524
|
-
console.warn('[consumption-badge]
|
|
776
|
+
console.warn('[consumption-badge] update check failed (silent): ' + (err && err.message ? err.message : String(err)))
|
|
525
777
|
return null
|
|
526
778
|
})
|
|
527
779
|
if (controller === null) return request
|
|
@@ -580,6 +832,11 @@ window.__ModuleLoader__.load({
|
|
|
580
832
|
turns: [],
|
|
581
833
|
turnsState: 'idle',
|
|
582
834
|
turnsError: '',
|
|
835
|
+
// Error copy is stored language-neutral: the kind names the sentence and
|
|
836
|
+
// the raw detail rides turnsError, so a locale switch re-renders the text.
|
|
837
|
+
turnsErrorKind: '',
|
|
838
|
+
turnsErrorCode: '',
|
|
839
|
+
turnsErrorFallback: '',
|
|
583
840
|
account: null,
|
|
584
841
|
accountState: 'idle',
|
|
585
842
|
integrations: null,
|
|
@@ -595,6 +852,9 @@ window.__ModuleLoader__.load({
|
|
|
595
852
|
balance: null,
|
|
596
853
|
balanceState: 'idle',
|
|
597
854
|
balanceError: '',
|
|
855
|
+
balanceErrorKind: '',
|
|
856
|
+
balanceErrorCode: '',
|
|
857
|
+
balanceErrorFallback: '',
|
|
598
858
|
balanceAt: 0,
|
|
599
859
|
}
|
|
600
860
|
const listeners = new Set()
|
|
@@ -663,13 +923,26 @@ window.__ModuleLoader__.load({
|
|
|
663
923
|
}
|
|
664
924
|
state.turns = restoreTurns(sessionId, normalized)
|
|
665
925
|
state.turnsState = res && res.ok === false ? 'error' : 'ready'
|
|
666
|
-
|
|
926
|
+
if (res && res.ok === false) {
|
|
927
|
+
state.turnsErrorKind = 'host'
|
|
928
|
+
state.turnsErrorCode = typeof res.code === 'string' ? res.code : ''
|
|
929
|
+
state.turnsError = typeof res.detail === 'string' ? res.detail : ''
|
|
930
|
+
state.turnsErrorFallback = typeof res.error === 'string' ? res.error : ''
|
|
931
|
+
} else {
|
|
932
|
+
state.turnsErrorKind = ''
|
|
933
|
+
state.turnsErrorCode = ''
|
|
934
|
+
state.turnsError = ''
|
|
935
|
+
state.turnsErrorFallback = ''
|
|
936
|
+
}
|
|
667
937
|
persistTurns(sessionId, normalized)
|
|
668
938
|
emit()
|
|
669
939
|
})
|
|
670
940
|
.catch(function (e) {
|
|
671
941
|
state.turnsState = 'error'
|
|
672
|
-
state.
|
|
942
|
+
state.turnsErrorKind = 'read'
|
|
943
|
+
state.turnsErrorCode = ''
|
|
944
|
+
state.turnsError = msgOf(e)
|
|
945
|
+
state.turnsErrorFallback = ''
|
|
673
946
|
emit()
|
|
674
947
|
})
|
|
675
948
|
.then(function () { turnsInFlight = null })
|
|
@@ -684,17 +957,35 @@ window.__ModuleLoader__.load({
|
|
|
684
957
|
balanceInFlight = Promise.resolve()
|
|
685
958
|
.then(function () { return fetchJson(API + '/balance' + (force ? '?force=1' : '')) })
|
|
686
959
|
.then(function (res) {
|
|
687
|
-
const value = res && typeof res === 'object' ? res : { ok: false
|
|
960
|
+
const value = res && typeof res === 'object' ? res : { ok: false }
|
|
688
961
|
state.balance = value
|
|
689
962
|
state.balanceAt = Date.now()
|
|
690
963
|
state.balanceState = 'ready'
|
|
691
|
-
|
|
964
|
+
if (!res || typeof res !== 'object') {
|
|
965
|
+
state.balanceErrorKind = 'empty'
|
|
966
|
+
state.balanceError = ''
|
|
967
|
+
state.balanceErrorCode = ''
|
|
968
|
+
state.balanceErrorFallback = ''
|
|
969
|
+
} else if (value.ok) {
|
|
970
|
+
state.balanceErrorKind = ''
|
|
971
|
+
state.balanceError = ''
|
|
972
|
+
state.balanceErrorCode = ''
|
|
973
|
+
state.balanceErrorFallback = ''
|
|
974
|
+
} else {
|
|
975
|
+
state.balanceErrorKind = 'host'
|
|
976
|
+
state.balanceErrorCode = typeof value.code === 'string' ? value.code : ''
|
|
977
|
+
state.balanceError = typeof value.detail === 'string' ? value.detail : ''
|
|
978
|
+
state.balanceErrorFallback = typeof value.error === 'string' ? value.error : ''
|
|
979
|
+
}
|
|
692
980
|
emit()
|
|
693
981
|
})
|
|
694
982
|
.catch(function (e) {
|
|
695
983
|
state.balance = null
|
|
696
984
|
state.balanceState = 'error'
|
|
697
|
-
state.
|
|
985
|
+
state.balanceErrorKind = 'fetch'
|
|
986
|
+
state.balanceErrorCode = ''
|
|
987
|
+
state.balanceError = msgOf(e)
|
|
988
|
+
state.balanceErrorFallback = ''
|
|
698
989
|
emit()
|
|
699
990
|
})
|
|
700
991
|
.then(function () { balanceInFlight = null })
|
|
@@ -767,10 +1058,10 @@ window.__ModuleLoader__.load({
|
|
|
767
1058
|
const key = 'modal-bg-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8)
|
|
768
1059
|
return idbPutImage(key, file).then(function (stored) {
|
|
769
1060
|
if (stored !== true) {
|
|
770
|
-
console.warn('[consumption-badge] IndexedDB
|
|
1061
|
+
console.warn('[consumption-badge] IndexedDB write failed, falling back to a data URL')
|
|
771
1062
|
return readAsDataUrl(file).then(function (dataUrl) {
|
|
772
1063
|
if (!dataUrl) {
|
|
773
|
-
state.modalImageStatus = 'error: IndexedDB 与 FileReader 都不可用'
|
|
1064
|
+
state.modalImageStatus = 'error: ' + tt('IndexedDB 与 FileReader 都不可用')
|
|
774
1065
|
emit()
|
|
775
1066
|
return
|
|
776
1067
|
}
|
|
@@ -785,7 +1076,7 @@ window.__ModuleLoader__.load({
|
|
|
785
1076
|
if (typeof URL !== 'undefined' && typeof URL.createObjectURL === 'function' && blob) {
|
|
786
1077
|
setResolvedImageUrl(URL.createObjectURL(blob))
|
|
787
1078
|
} else if (blob === null) {
|
|
788
|
-
console.warn('[consumption-badge]
|
|
1079
|
+
console.warn('[consumption-badge] image stored but could not be read back', key)
|
|
789
1080
|
}
|
|
790
1081
|
if (prevKey && prevKey !== key) idbDeleteImage(prevKey)
|
|
791
1082
|
state.modalImageStatus = 'saved'
|
|
@@ -793,7 +1084,7 @@ window.__ModuleLoader__.load({
|
|
|
793
1084
|
})
|
|
794
1085
|
}).catch(function (err) {
|
|
795
1086
|
const text = err && err.message ? err.message : String(err)
|
|
796
|
-
console.error('[consumption-badge]
|
|
1087
|
+
console.error('[consumption-badge] failed to save the image: ' + text)
|
|
797
1088
|
state.modalImageStatus = 'error: ' + text
|
|
798
1089
|
emit()
|
|
799
1090
|
})
|
|
@@ -931,7 +1222,7 @@ window.__ModuleLoader__.load({
|
|
|
931
1222
|
const holder = React.useState(-1)
|
|
932
1223
|
const hover = holder[0]
|
|
933
1224
|
const setHover = holder[1]
|
|
934
|
-
if (!series.length) return h('div', { className: 'dcb-empty' }, '本会话还没有可统计的轮次')
|
|
1225
|
+
if (!series.length) return h('div', { className: 'dcb-empty' }, tt('本会话还没有可统计的轮次'))
|
|
935
1226
|
const innerW = CHART_W - CHART_PAD.l - CHART_PAD.r
|
|
936
1227
|
const innerH = CHART_H - CHART_PAD.t - CHART_PAD.b
|
|
937
1228
|
let maxToken = 1
|
|
@@ -998,18 +1289,18 @@ window.__ModuleLoader__.load({
|
|
|
998
1289
|
h('div', { className: 'dcb-tooltip-title' },
|
|
999
1290
|
'#' + String(hovered.turn),
|
|
1000
1291
|
h('span', { className: 'dcb-tooltip-model' }, hovered.model || DEFAULT_MODEL),
|
|
1001
|
-
h('span', { className: hovered.peak ? 'dcb-tag is-peak' : 'dcb-tag is-valley' }, hovered.peak ? '峰时' : '谷时')),
|
|
1002
|
-
h('div', { className: 'dcb-tooltip-row' }, h('span', null, '输入命中'), h('b', null, fmtTokens(hovered.hit))),
|
|
1003
|
-
h('div', { className: 'dcb-tooltip-row' }, h('span', null, '输入未命中'), h('b', null, fmtTokens(hovered.miss))),
|
|
1004
|
-
h('div', { className: 'dcb-tooltip-row' }, h('span', null, '输出'), h('b', null, fmtTokens(hovered.out))),
|
|
1005
|
-
h('div', { className: 'dcb-tooltip-row dcb-tooltip-total' }, h('span', null, '本轮合计'), h('b', null, fmtTokens(hovered.tokens))),
|
|
1006
|
-
h('div', { className: 'dcb-tooltip-row dcb-tooltip-total' }, h('span', null, '估算金额'), h('b', null, '≈¥' + fmtYuan(hovered.cost))))
|
|
1292
|
+
h('span', { className: hovered.peak ? 'dcb-tag is-peak' : 'dcb-tag is-valley' }, tt(hovered.peak ? '峰时' : '谷时'))),
|
|
1293
|
+
h('div', { className: 'dcb-tooltip-row' }, h('span', null, tt('输入命中')), h('b', null, fmtTokens(hovered.hit))),
|
|
1294
|
+
h('div', { className: 'dcb-tooltip-row' }, h('span', null, tt('输入未命中')), h('b', null, fmtTokens(hovered.miss))),
|
|
1295
|
+
h('div', { className: 'dcb-tooltip-row' }, h('span', null, tt('输出')), h('b', null, fmtTokens(hovered.out))),
|
|
1296
|
+
h('div', { className: 'dcb-tooltip-row dcb-tooltip-total' }, h('span', null, tt('本轮合计')), h('b', null, fmtTokens(hovered.tokens))),
|
|
1297
|
+
h('div', { className: 'dcb-tooltip-row dcb-tooltip-total' }, h('span', null, tt('估算金额')), h('b', null, '≈¥' + fmtYuan(hovered.cost))))
|
|
1007
1298
|
: null
|
|
1008
1299
|
|
|
1009
1300
|
return h('div', { className: 'dcb-chart-wrap' },
|
|
1010
1301
|
h('div', { className: 'dcb-chart-legend' },
|
|
1011
|
-
h('span', { className: 'dcb-legend-item' }, h('i', { className: 'dcb-legend-dot' }), '每轮 Token'),
|
|
1012
|
-
h('span', { className: 'dcb-legend-item' }, h('i', { className: 'dcb-legend-dot dcb-dot-cost' }), '每轮估算金额')),
|
|
1302
|
+
h('span', { className: 'dcb-legend-item' }, h('i', { className: 'dcb-legend-dot' }), tt('每轮 Token')),
|
|
1303
|
+
h('span', { className: 'dcb-legend-item' }, h('i', { className: 'dcb-legend-dot dcb-dot-cost' }), tt('每轮估算金额'))),
|
|
1013
1304
|
h('div', { className: 'dcb-chart-body' },
|
|
1014
1305
|
h('svg', {
|
|
1015
1306
|
className: 'dcb-chart-svg',
|
|
@@ -1031,6 +1322,24 @@ window.__ModuleLoader__.load({
|
|
|
1031
1322
|
|
|
1032
1323
|
/* components: one store per plugin instance */
|
|
1033
1324
|
const store = createStore()
|
|
1325
|
+
|
|
1326
|
+
/* Error copy is composed at render time (not at fetch time) so it follows the locale. */
|
|
1327
|
+
function turnsErrorText() {
|
|
1328
|
+
const st = store.state
|
|
1329
|
+
if (st.turnsErrorKind === 'host') return hostErrorText(st.turnsErrorCode, st.turnsError, st.turnsErrorFallback)
|
|
1330
|
+
if (st.turnsErrorKind === 'read') return tt('会话消耗读取失败:{msg}', { msg: st.turnsError })
|
|
1331
|
+
return st.turnsError
|
|
1332
|
+
}
|
|
1333
|
+
function balanceErrorText() {
|
|
1334
|
+
const st = store.state
|
|
1335
|
+
if (st.balanceErrorKind === 'empty') return tt('空响应')
|
|
1336
|
+
if (st.balanceErrorKind === 'host') {
|
|
1337
|
+
const text = hostErrorText(st.balanceErrorCode, st.balanceError, st.balanceErrorFallback)
|
|
1338
|
+
return text === '' ? tt('余额获取失败') : text
|
|
1339
|
+
}
|
|
1340
|
+
if (st.balanceErrorKind === 'fetch') return tt('余额获取失败:{msg}', { msg: st.balanceError })
|
|
1341
|
+
return st.balanceError
|
|
1342
|
+
}
|
|
1034
1343
|
// Assigned by the overlay's callback ref, so Esc can reach a focused node without useRef.
|
|
1035
1344
|
let panelRootEl = null
|
|
1036
1345
|
|
|
@@ -1084,7 +1393,7 @@ window.__ModuleLoader__.load({
|
|
|
1084
1393
|
return h('button', {
|
|
1085
1394
|
type: 'button',
|
|
1086
1395
|
className: 'dcb-pill',
|
|
1087
|
-
title: '本会话消耗(点击查看逐轮明细)',
|
|
1396
|
+
title: tt('本会话消耗(点击查看逐轮明细)'),
|
|
1088
1397
|
onClick: function (event) {
|
|
1089
1398
|
try {
|
|
1090
1399
|
event.preventDefault()
|
|
@@ -1098,7 +1407,7 @@ window.__ModuleLoader__.load({
|
|
|
1098
1407
|
h('span', { className: 'dcb-pill-sep' }, '|'),
|
|
1099
1408
|
h('span', null, pending ? '…' : '≈¥' + fmtYuan(cost)),
|
|
1100
1409
|
h('span', { className: 'dcb-pill-sep' }, '|'),
|
|
1101
|
-
h('span', { className: peak ? 'dcb-tag is-peak' : 'dcb-tag is-valley' }, peak ? '峰' : '谷'))
|
|
1410
|
+
h('span', { className: peak ? 'dcb-tag is-peak' : 'dcb-tag is-valley' }, tt(peak ? '峰' : '谷')))
|
|
1102
1411
|
}
|
|
1103
1412
|
|
|
1104
1413
|
function ConsumptionPanel() {
|
|
@@ -1138,27 +1447,28 @@ window.__ModuleLoader__.load({
|
|
|
1138
1447
|
const totalCost = costOf(aggregated, price.table, peak)
|
|
1139
1448
|
const balance = state.balance
|
|
1140
1449
|
const loading = state.balanceState === 'loading'
|
|
1450
|
+
const balanceErr = balanceErrorText()
|
|
1141
1451
|
let balanceValue = '—'
|
|
1142
|
-
let balanceHint =
|
|
1452
|
+
let balanceHint = balanceErr || tt('打开面板时请求一次')
|
|
1143
1453
|
let balanceClass = 'dcb-card-value'
|
|
1144
1454
|
if (loading && !balance) {
|
|
1145
|
-
balanceValue = '读取中…'
|
|
1455
|
+
balanceValue = tt('读取中…')
|
|
1146
1456
|
balanceHint = 'GET /user/balance'
|
|
1147
1457
|
} else if (balance && balance.ok === false) {
|
|
1148
|
-
balanceValue = '获取失败'
|
|
1149
|
-
balanceHint =
|
|
1458
|
+
balanceValue = tt('获取失败')
|
|
1459
|
+
balanceHint = balanceErr || tt('余额接口不可用')
|
|
1150
1460
|
balanceClass = 'dcb-card-value dcb-bad'
|
|
1151
1461
|
} else if (balance && balance.available === false) {
|
|
1152
|
-
balanceValue = '余额不足'
|
|
1153
|
-
balanceHint = '请前往 DeepSeek 开放平台充值'
|
|
1462
|
+
balanceValue = tt('余额不足')
|
|
1463
|
+
balanceHint = tt('请前往 DeepSeek 开放平台充值')
|
|
1154
1464
|
balanceClass = 'dcb-card-value dcb-bad'
|
|
1155
1465
|
} else if (balance) {
|
|
1156
1466
|
const symbol = balance.currency && balance.currency !== 'CNY' ? String(balance.currency) + ' ' : '¥'
|
|
1157
1467
|
balanceValue = symbol + fmtYuan(balance.total)
|
|
1158
1468
|
const parts = []
|
|
1159
|
-
if (typeof balance.granted === 'number') parts.push('赠送 ¥'
|
|
1160
|
-
if (typeof balance.toppedUp === 'number') parts.push('充值 ¥'
|
|
1161
|
-
balanceHint = parts.length ? parts.join(' · ') : 'DeepSeek 账户余额'
|
|
1469
|
+
if (typeof balance.granted === 'number') parts.push(tt('赠送 ¥{v}', { v: fmtYuan(balance.granted) }))
|
|
1470
|
+
if (typeof balance.toppedUp === 'number') parts.push(tt('充值 ¥{v}', { v: fmtYuan(balance.toppedUp) }))
|
|
1471
|
+
balanceHint = parts.length ? parts.join(' · ') : tt('DeepSeek 账户余额')
|
|
1162
1472
|
}
|
|
1163
1473
|
|
|
1164
1474
|
function close() {
|
|
@@ -1186,27 +1496,27 @@ window.__ModuleLoader__.load({
|
|
|
1186
1496
|
h('div', {
|
|
1187
1497
|
className: surface.className,
|
|
1188
1498
|
role: 'dialog',
|
|
1189
|
-
'aria-label': '本会话消耗',
|
|
1499
|
+
'aria-label': tt('本会话消耗'),
|
|
1190
1500
|
style: surface.style,
|
|
1191
1501
|
},
|
|
1192
1502
|
h('div', { className: 'dcb-head' },
|
|
1193
|
-
h('div', { className: 'dcb-head-title' }, '⛽ 本会话消耗'),
|
|
1503
|
+
h('div', { className: 'dcb-head-title' }, tt('⛽ 本会话消耗')),
|
|
1194
1504
|
h('div', { className: 'dcb-head-actions' },
|
|
1195
|
-
h('button', { type: 'button', className: 'dcb-btn', onClick: function () { store.ensureBalance(true) } }, '刷新余额'),
|
|
1196
|
-
h('button', { type: 'button', className: 'dcb-btn dcb-btn-close', onClick: close, title: 'Esc 关闭' }, '✕'))),
|
|
1505
|
+
h('button', { type: 'button', className: 'dcb-btn', onClick: function () { store.ensureBalance(true) } }, tt('刷新余额')),
|
|
1506
|
+
h('button', { type: 'button', className: 'dcb-btn dcb-btn-close', onClick: close, title: tt('Esc 关闭') }, '✕'))),
|
|
1197
1507
|
h('div', { className: 'dcb-cards' },
|
|
1198
|
-
h(Card, { label: '累计 Token', value: fmtTokens(tokens), hint: '命中 '
|
|
1199
|
-
h(Card, { label: '估算金额', value: '≈¥' + fmtYuan(totalCost), hint: (price.known ? price.id :
|
|
1200
|
-
h(Card, { label: '当前时段', value: peak ? '高峰' : '空闲', hint: '北京时间 工作日 09:00-12:00 / 14:00-18:00' }),
|
|
1508
|
+
h(Card, { label: tt('累计 Token'), value: fmtTokens(tokens), hint: tt('命中 {hit} · 未命中 {miss} · 输出 {out}', { hit: fmtTokens(aggregated.hit), miss: fmtTokens(aggregated.miss), out: fmtTokens(aggregated.out) }) }),
|
|
1509
|
+
h(Card, { label: tt('估算金额'), value: '≈¥' + fmtYuan(totalCost), hint: (price.known ? price.id : tt('{id}(按 flash 估算)', { id: price.id })) + tt(peak ? ' · 峰时 ×2' : ' · 谷时') }),
|
|
1510
|
+
h(Card, { label: tt('当前时段'), value: tt(peak ? '高峰' : '空闲'), hint: tt('北京时间 工作日 09:00-12:00 / 14:00-18:00') }),
|
|
1201
1511
|
h('div', { className: 'dcb-card' },
|
|
1202
|
-
h('div', { className: 'dcb-card-label' }, '账户余额'),
|
|
1512
|
+
h('div', { className: 'dcb-card-label' }, tt('账户余额')),
|
|
1203
1513
|
h('div', { className: balanceClass }, balanceValue),
|
|
1204
1514
|
h('div', { className: 'dcb-card-hint' }, balanceHint))),
|
|
1205
|
-
h('div', { className: 'dcb-section-title' }, '逐轮消耗趋势'),
|
|
1515
|
+
h('div', { className: 'dcb-section-title' }, tt('逐轮消耗趋势')),
|
|
1206
1516
|
h(UsageChart, { series: series }),
|
|
1207
1517
|
h('div', { className: 'dcb-foot' },
|
|
1208
|
-
state.turnsError ? h('span', { className: 'dcb-foot-warn' },
|
|
1209
|
-
'金额按 DeepSeek 官方价估算(谷/峰 ×1/×2),仅供参考;数据源为本会话日志,刷新后自动恢复。')))
|
|
1518
|
+
state.turnsError ? h('span', { className: 'dcb-foot-warn' }, turnsErrorText() + ' ') : null,
|
|
1519
|
+
tt('金额按 DeepSeek 官方价估算(谷/峰 ×1/×2),仅供参考;数据源为本会话日志,刷新后自动恢复。'))))
|
|
1210
1520
|
}
|
|
1211
1521
|
|
|
1212
1522
|
/* ===================== settings section: history page ===================== */
|
|
@@ -1624,12 +1934,12 @@ window.__ModuleLoader__.load({
|
|
|
1624
1934
|
? h('div', { className: 'dcb-tooltip', style: { left: String((xAt(hover) / HIST_W) * 100) + '%' } },
|
|
1625
1935
|
h('div', { className: 'dcb-tooltip-title' },
|
|
1626
1936
|
hovered.label,
|
|
1627
|
-
h('span', { className: 'dcb-tooltip-model' }, hovered.records
|
|
1628
|
-
h('div', { className: 'dcb-tooltip-row dcb-tooltip-total' }, h('span', null, '当日合计'), h('b', null, '≈¥' + fmtYuan(hovered.cost))),
|
|
1937
|
+
h('span', { className: 'dcb-tooltip-model' }, tt('{n} 轮', { n: hovered.records }))),
|
|
1938
|
+
h('div', { className: 'dcb-tooltip-row dcb-tooltip-total' }, h('span', null, tt('当日合计')), h('b', null, '≈¥' + fmtYuan(hovered.cost))),
|
|
1629
1939
|
h('div', { className: 'dcb-tooltip-row' }, h('span', null, 'Token'), h('b', null, fmtTokens(hovered.tokens))),
|
|
1630
|
-
h('div', { className: 'dcb-tooltip-row' }, h('span', null, '峰时'), h('b', null, '≈¥' + fmtYuan(hovered.peakCost) + ' / ' + fmtTokens(hovered.peakTokens))),
|
|
1631
|
-
h('div', { className: 'dcb-tooltip-row' }, h('span', null, '谷时'), h('b', null, '≈¥' + fmtYuan(hovered.valleyCost) + ' / ' + fmtTokens(hovered.valleyTokens))),
|
|
1632
|
-
h('div', { className: 'dcb-tooltip-row' }, h('span', null, '模型'), h('b', null, hovered.models.length ? hovered.models.join(', ') : '—')))
|
|
1940
|
+
h('div', { className: 'dcb-tooltip-row' }, h('span', null, tt('峰时')), h('b', null, '≈¥' + fmtYuan(hovered.peakCost) + ' / ' + fmtTokens(hovered.peakTokens))),
|
|
1941
|
+
h('div', { className: 'dcb-tooltip-row' }, h('span', null, tt('谷时')), h('b', null, '≈¥' + fmtYuan(hovered.valleyCost) + ' / ' + fmtTokens(hovered.valleyTokens))),
|
|
1942
|
+
h('div', { className: 'dcb-tooltip-row' }, h('span', null, tt('模型')), h('b', null, hovered.models.length ? hovered.models.join(', ') : '—')))
|
|
1633
1943
|
: null
|
|
1634
1944
|
|
|
1635
1945
|
return h('div', { className: 'dcb-chart-body' },
|
|
@@ -1779,69 +2089,70 @@ window.__ModuleLoader__.load({
|
|
|
1779
2089
|
const nowPeak = isPeakAt(Date.now())
|
|
1780
2090
|
const balance = st.balance
|
|
1781
2091
|
const balanceLoading = st.balanceState === 'loading'
|
|
2092
|
+
const balanceErr = balanceErrorText()
|
|
1782
2093
|
let balanceValue = '—'
|
|
1783
|
-
let balanceHint =
|
|
2094
|
+
let balanceHint = balanceErr || tt('打开设置页时请求一次')
|
|
1784
2095
|
let balanceClass = 'dcb-card-value'
|
|
1785
2096
|
if (balanceLoading && !balance) {
|
|
1786
|
-
balanceValue = '读取中…'
|
|
2097
|
+
balanceValue = tt('读取中…')
|
|
1787
2098
|
balanceHint = 'GET /user/balance'
|
|
1788
2099
|
} else if (balance && balance.ok === false) {
|
|
1789
|
-
balanceValue = '获取失败'
|
|
1790
|
-
balanceHint =
|
|
2100
|
+
balanceValue = tt('获取失败')
|
|
2101
|
+
balanceHint = balanceErr || tt('余额接口不可用')
|
|
1791
2102
|
balanceClass = 'dcb-card-value dcb-bad'
|
|
1792
2103
|
} else if (balance && balance.available === false) {
|
|
1793
|
-
balanceValue = '余额不足'
|
|
1794
|
-
balanceHint = '请前往 DeepSeek 开放平台充值'
|
|
2104
|
+
balanceValue = tt('余额不足')
|
|
2105
|
+
balanceHint = tt('请前往 DeepSeek 开放平台充值')
|
|
1795
2106
|
balanceClass = 'dcb-card-value dcb-bad'
|
|
1796
2107
|
} else if (balance) {
|
|
1797
2108
|
const symbol = balance.currency && balance.currency !== 'CNY' ? String(balance.currency) + ' ' : '¥'
|
|
1798
2109
|
balanceValue = symbol + fmtYuan(balance.total)
|
|
1799
2110
|
const parts = []
|
|
1800
|
-
if (typeof balance.granted === 'number') parts.push('赠送 ¥'
|
|
1801
|
-
if (typeof balance.toppedUp === 'number') parts.push('充值 ¥'
|
|
1802
|
-
balanceHint = parts.length ? parts.join(' · ') : 'DeepSeek 账户余额'
|
|
2111
|
+
if (typeof balance.granted === 'number') parts.push(tt('赠送 ¥{v}', { v: fmtYuan(balance.granted) }))
|
|
2112
|
+
if (typeof balance.toppedUp === 'number') parts.push(tt('充值 ¥{v}', { v: fmtYuan(balance.toppedUp) }))
|
|
2113
|
+
balanceHint = parts.length ? parts.join(' · ') : tt('DeepSeek 账户余额')
|
|
1803
2114
|
}
|
|
1804
2115
|
|
|
1805
2116
|
const index = buildWorkspaceIndex(wsSig)
|
|
1806
2117
|
const points = stats ? stats.points : []
|
|
1807
2118
|
const hasData = !!stats && stats.count > 0
|
|
1808
2119
|
const rangeOptions = [
|
|
1809
|
-
{ id: 'today', label: '今天' },
|
|
1810
|
-
{ id: 'week', label: '近一周' },
|
|
1811
|
-
{ id: 'month', label: '近一月' },
|
|
1812
|
-
{ id: 'year', label: '近一年' },
|
|
2120
|
+
{ id: 'today', label: tt('今天') },
|
|
2121
|
+
{ id: 'week', label: tt('近一周') },
|
|
2122
|
+
{ id: 'month', label: tt('近一月') },
|
|
2123
|
+
{ id: 'year', label: tt('近一年') },
|
|
1813
2124
|
]
|
|
1814
2125
|
|
|
1815
2126
|
return h('div', { className: 'dcb-set' },
|
|
1816
2127
|
h('div', { className: 'dcb-set-head' },
|
|
1817
|
-
h('div', { className: 'dcb-set-title' }, '消耗统计'),
|
|
1818
|
-
h('div', { className: 'dcb-set-note' }, '本地估算,非官方账单')),
|
|
2128
|
+
h('div', { className: 'dcb-set-title' }, tt('消耗统计')),
|
|
2129
|
+
h('div', { className: 'dcb-set-note' }, tt('本地估算,非官方账单'))),
|
|
1819
2130
|
// 顶部四卡:累计(全部历史,不受下面筛选影响)+ 当前时段 + 账户余额
|
|
1820
2131
|
h('div', { className: 'dcb-cards dcb-cards-set' },
|
|
1821
2132
|
h(Card, {
|
|
1822
|
-
label: '累计 Token',
|
|
2133
|
+
label: tt('累计 Token'),
|
|
1823
2134
|
value: allStats ? fmtTokens(allStats.totalTokens) : '—',
|
|
1824
2135
|
hint: allStats
|
|
1825
|
-
? ('命中 '
|
|
1826
|
-
: '全部历史 · 含所有工作区',
|
|
2136
|
+
? tt('命中 {hit} · 未命中 {miss} · 输出 {out}', { hit: fmtTokens(allStats.totalHit), miss: fmtTokens(allStats.totalMiss), out: fmtTokens(allStats.totalOut) })
|
|
2137
|
+
: tt('全部历史 · 含所有工作区'),
|
|
1827
2138
|
}),
|
|
1828
2139
|
h(Card, {
|
|
1829
|
-
label: '估算金额',
|
|
2140
|
+
label: tt('估算金额'),
|
|
1830
2141
|
value: allStats ? '≈¥' + fmtYuan(allStats.totalCost) : '—',
|
|
1831
|
-
hint: (accountPrice.known ? accountPrice.id :
|
|
2142
|
+
hint: (accountPrice.known ? accountPrice.id : tt('{id}(按 flash 估算)', { id: accountPrice.id })) + tt(' · 全部历史逐轮计价'),
|
|
1832
2143
|
}),
|
|
1833
2144
|
h(Card, {
|
|
1834
|
-
label: '当前时段',
|
|
1835
|
-
value: nowPeak ? '高峰' : '空闲',
|
|
1836
|
-
hint: '北京时间 工作日 09:00-12:00 / 14:00-18:00',
|
|
2145
|
+
label: tt('当前时段'),
|
|
2146
|
+
value: tt(nowPeak ? '高峰' : '空闲'),
|
|
2147
|
+
hint: tt('北京时间 工作日 09:00-12:00 / 14:00-18:00'),
|
|
1837
2148
|
}),
|
|
1838
2149
|
h('div', { className: 'dcb-card' },
|
|
1839
|
-
h('div', { className: 'dcb-card-label' }, '账户余额'),
|
|
2150
|
+
h('div', { className: 'dcb-card-label' }, tt('账户余额')),
|
|
1840
2151
|
h('div', { className: balanceClass }, balanceValue),
|
|
1841
2152
|
h('div', { className: 'dcb-card-hint' }, balanceHint))),
|
|
1842
2153
|
h('div', { className: 'dcb-set-controls' },
|
|
1843
2154
|
h('div', { className: 'dcb-field' },
|
|
1844
|
-
h('label', { className: 'dcb-field-label', htmlFor: 'dcb-range-select' }, '时间范围'),
|
|
2155
|
+
h('label', { className: 'dcb-field-label', htmlFor: 'dcb-range-select' }, tt('时间范围')),
|
|
1845
2156
|
h('select', {
|
|
1846
2157
|
id: 'dcb-range-select',
|
|
1847
2158
|
className: 'dcb-select',
|
|
@@ -1853,20 +2164,20 @@ window.__ModuleLoader__.load({
|
|
|
1853
2164
|
},
|
|
1854
2165
|
},
|
|
1855
2166
|
rangeOptions.map(function (opt) { return h('option', { key: opt.id, value: opt.id }, opt.label) }),
|
|
1856
|
-
(calFrom || calTo) ? h('option', { key: 'custom', value: 'custom' }, '自定义区间') : null)),
|
|
2167
|
+
(calFrom || calTo) ? h('option', { key: 'custom', value: 'custom' }, tt('自定义区间')) : null)),
|
|
1857
2168
|
h('div', { className: 'dcb-field' },
|
|
1858
|
-
h('label', { className: 'dcb-field-label', htmlFor: 'dcb-workspace-select' }, '工作区'),
|
|
2169
|
+
h('label', { className: 'dcb-field-label', htmlFor: 'dcb-workspace-select' }, tt('工作区')),
|
|
1859
2170
|
h('select', {
|
|
1860
2171
|
id: 'dcb-workspace-select',
|
|
1861
2172
|
className: 'dcb-select',
|
|
1862
2173
|
value: wid,
|
|
1863
2174
|
onChange: function (event) { setWid(event.target.value) },
|
|
1864
2175
|
},
|
|
1865
|
-
h('option', { key: 'all', value: '' }, '全部工作区'),
|
|
2176
|
+
h('option', { key: 'all', value: '' }, tt('全部工作区')),
|
|
1866
2177
|
index.list.map(function (it) { return h('option', { key: it.id, value: it.id }, it.title) }),
|
|
1867
|
-
h('option', { key: 'none', value: UNKNOWN_WORKSPACE }, '未归属工作区'))),
|
|
2178
|
+
h('option', { key: 'none', value: UNKNOWN_WORKSPACE }, tt('未归属工作区')))),
|
|
1868
2179
|
h('div', { className: 'dcb-field' },
|
|
1869
|
-
h('span', { className: 'dcb-field-label' }, '日历选择(覆盖时间范围)'),
|
|
2180
|
+
h('span', { className: 'dcb-field-label' }, tt('日历选择(覆盖时间范围)')),
|
|
1870
2181
|
h('div', { className: 'dcb-range' },
|
|
1871
2182
|
h('input', {
|
|
1872
2183
|
type: 'date',
|
|
@@ -1882,36 +2193,36 @@ window.__ModuleLoader__.load({
|
|
|
1882
2193
|
onChange: function (event) { setCalTo(event.target.value) },
|
|
1883
2194
|
}),
|
|
1884
2195
|
(calFrom || calTo)
|
|
1885
|
-
? h('button', { type: 'button', className: 'dcb-btn', onClick: function () { setCalFrom(''); setCalTo('') } }, '清除')
|
|
2196
|
+
? h('button', { type: 'button', className: 'dcb-btn', onClick: function () { setCalFrom(''); setCalTo('') } }, tt('清除'))
|
|
1886
2197
|
: null)),
|
|
1887
2198
|
h('div', { className: 'dcb-field' },
|
|
1888
|
-
h('span', { className: 'dcb-field-label' }, '数据'),
|
|
1889
|
-
h('button', { type: 'button', className: 'dcb-btn', onClick: function () { setReloadTick(function (v) { return v + 1 }) } }, '刷新'))),
|
|
2199
|
+
h('span', { className: 'dcb-field-label' }, tt('数据')),
|
|
2200
|
+
h('button', { type: 'button', className: 'dcb-btn', onClick: function () { setReloadTick(function (v) { return v + 1 }) } }, tt('刷新')))),
|
|
1890
2201
|
h('div', { className: 'dcb-set-body' },
|
|
1891
2202
|
h('div', { className: 'dcb-hist-legend' },
|
|
1892
|
-
h('span', { className: 'dcb-tog dcb-tog-cost is-on' }, h('i', null), '估算金额(右轴)'),
|
|
2203
|
+
h('span', { className: 'dcb-tog dcb-tog-cost is-on' }, h('i', null), tt('估算金额(右轴)')),
|
|
1893
2204
|
h('button', {
|
|
1894
2205
|
type: 'button',
|
|
1895
2206
|
className: showTokens ? 'dcb-tog is-on' : 'dcb-tog',
|
|
1896
2207
|
onClick: function () { setShowTokens(function (v) { return v === true ? false : true }) },
|
|
1897
|
-
}, h('i', null), 'Token(左轴,默认隐藏)')),
|
|
2208
|
+
}, h('i', null), tt('Token(左轴,默认隐藏)'))),
|
|
1898
2209
|
hasData
|
|
1899
2210
|
? h(HistoryChart, { points: points, showTokens: showTokens })
|
|
1900
|
-
: h('div', { className: 'dcb-hist-empty' }, '该范围内暂无消耗记录')),
|
|
2211
|
+
: h('div', { className: 'dcb-hist-empty' }, tt('该范围内暂无消耗记录'))),
|
|
1901
2212
|
hasData
|
|
1902
2213
|
? h('div', { className: 'dcb-sum' },
|
|
1903
|
-
h('span', null, '总消耗 ', h('b', null, '≈¥' + fmtYuan(stats.totalCost))),
|
|
1904
|
-
h('span', null, '总 Token ', h('b', null, fmtTokens(stats.totalTokens))),
|
|
1905
|
-
h('span', null, '记录 ', h('b', null, String(stats.count)), ' 条'),
|
|
1906
|
-
h('span', null, '峰时 ', h('b', null, '≈¥' + fmtYuan(stats.peakCost))),
|
|
1907
|
-
h('span', null, '谷时 ', h('b', null, '≈¥' + fmtYuan(stats.valleyCost))),
|
|
1908
|
-
h('span', null, '粒度 ', h('b', null, stats.granularity === 'month' ? '按月' : (stats.granularity === 'hour' ? '按小时' : '按日'))))
|
|
2214
|
+
h('span', null, tt('总消耗 '), h('b', null, '≈¥' + fmtYuan(stats.totalCost))),
|
|
2215
|
+
h('span', null, tt('总 Token '), h('b', null, fmtTokens(stats.totalTokens))),
|
|
2216
|
+
h('span', null, tt('记录 '), h('b', null, String(stats.count)), tt(' 条')),
|
|
2217
|
+
h('span', null, tt('峰时 '), h('b', null, '≈¥' + fmtYuan(stats.peakCost))),
|
|
2218
|
+
h('span', null, tt('谷时 '), h('b', null, '≈¥' + fmtYuan(stats.valleyCost))),
|
|
2219
|
+
h('span', null, tt('粒度 '), h('b', null, tt(stats.granularity === 'month' ? '按月' : (stats.granularity === 'hour' ? '按小时' : '按日')))))
|
|
1909
2220
|
: null,
|
|
1910
2221
|
h('div', { className: 'dcb-hist-aside' },
|
|
1911
|
-
'数据源:本机逐轮记录(localStorage
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
2222
|
+
tt('数据源:本机逐轮记录(localStorage {prefix}<sessionId>:<turn>);按北京时间聚合(今天与单日区间逐小时,多日逐日,近一年逐月){worker}。金额按 DeepSeek 官方价估算(谷/峰 ×1/×2),仅供参考。', {
|
|
2223
|
+
prefix: LS_PREFIX,
|
|
2224
|
+
worker: stats && stats.via === 'worker' ? tt(';聚合已在 Web Worker 中完成') : '',
|
|
2225
|
+
})))
|
|
1915
2226
|
}
|
|
1916
2227
|
|
|
1917
2228
|
/* ============ 与 dsh-plugin-personalization 联动:个性化页内新增「消耗弹窗」栏 ============ */
|
|
@@ -1943,6 +2254,11 @@ window.__ModuleLoader__.load({
|
|
|
1943
2254
|
React.useEffect(function () {
|
|
1944
2255
|
return store.subscribe(function () { setTick(function (v) { return v + 1 }) })
|
|
1945
2256
|
}, [])
|
|
2257
|
+
// This block renders in its own React root outside the slot system, so it
|
|
2258
|
+
// subscribes to the locale face itself (slot outlets do that for us).
|
|
2259
|
+
React.useEffect(function () {
|
|
2260
|
+
return subscribeLocale(function () { setTick(function (v) { return v + 1 }) })
|
|
2261
|
+
}, [])
|
|
1946
2262
|
const cfg = store.state.modalStyle
|
|
1947
2263
|
|
|
1948
2264
|
function patch(next) {
|
|
@@ -1978,23 +2294,23 @@ window.__ModuleLoader__.load({
|
|
|
1978
2294
|
const rows = []
|
|
1979
2295
|
rows.push(h('div', { className: 'pp-field', key: 'mode' },
|
|
1980
2296
|
h('div', { className: 'pp-fieldLabelWrap' },
|
|
1981
|
-
h('span', { className: 'pp-fieldLabel' }, '背景'),
|
|
1982
|
-
h('span', { className: 'pp-fieldHint' }, '点击对话里的 ⛽ 胶囊弹出的消耗面板')),
|
|
2297
|
+
h('span', { className: 'pp-fieldLabel' }, tt('背景')),
|
|
2298
|
+
h('span', { className: 'pp-fieldHint' }, tt('点击对话里的 ⛽ 胶囊弹出的消耗面板'))),
|
|
1983
2299
|
h('div', { className: 'pp-fieldControl' },
|
|
1984
|
-
h('div', { className: 'pp-modes', role: 'radiogroup', 'aria-label': '消耗弹窗背景' },
|
|
2300
|
+
h('div', { className: 'pp-modes', role: 'radiogroup', 'aria-label': tt('消耗弹窗背景') },
|
|
1985
2301
|
MODAL_MODES.map(function (mode) {
|
|
1986
2302
|
return h(ModalPill, {
|
|
1987
2303
|
key: mode.value,
|
|
1988
2304
|
active: cfg.mode === mode.value,
|
|
1989
2305
|
onClick: function () { patch({ mode: mode.value }) },
|
|
1990
|
-
}, mode.label)
|
|
2306
|
+
}, tt(mode.label))
|
|
1991
2307
|
})))))
|
|
1992
2308
|
|
|
1993
2309
|
if (cfg.mode === 'solid') {
|
|
1994
2310
|
rows.push(h('div', { className: 'pp-field', key: 'solid' },
|
|
1995
2311
|
h('div', { className: 'pp-fieldLabelWrap' },
|
|
1996
|
-
h('span', { className: 'pp-fieldLabel' }, '纯色'),
|
|
1997
|
-
h('span', { className: 'pp-fieldHint' }, '弹窗底色;文字颜色会按明暗自动切换')),
|
|
2312
|
+
h('span', { className: 'pp-fieldLabel' }, tt('纯色')),
|
|
2313
|
+
h('span', { className: 'pp-fieldHint' }, tt('弹窗底色;文字颜色会按明暗自动切换'))),
|
|
1998
2314
|
h('div', { className: 'pp-fieldControl' },
|
|
1999
2315
|
h('div', { className: 'pp-colorRow' },
|
|
2000
2316
|
h('input', {
|
|
@@ -2021,17 +2337,17 @@ window.__ModuleLoader__.load({
|
|
|
2021
2337
|
const previewSrc = localImage ? store.state.modalImageUrl : cfg.image
|
|
2022
2338
|
const status = store.state.modalImageStatus
|
|
2023
2339
|
let statusText = ''
|
|
2024
|
-
if (status === 'saving') statusText = '正在保存…'
|
|
2025
|
-
else if (status === 'saved') statusText = '已保存到本机'
|
|
2340
|
+
if (status === 'saving') statusText = tt('正在保存…')
|
|
2341
|
+
else if (status === 'saved') statusText = tt('已保存到本机')
|
|
2026
2342
|
else if (typeof status === 'string' && status.indexOf('error:') === 0) statusText = status
|
|
2027
|
-
else if (localImage && !store.state.modalImageUrl) statusText = '取回失败,请重新选择'
|
|
2343
|
+
else if (localImage && !store.state.modalImageUrl) statusText = tt('取回失败,请重新选择')
|
|
2028
2344
|
const summary = localImage
|
|
2029
|
-
? (
|
|
2030
|
-
: (urlImage ? '当前:图片 URL' : '未设置图片(此时按默认背景显示)')
|
|
2345
|
+
? (statusText ? tt('当前:本地图片({status})', { status: statusText }) : tt('当前:本地图片(已保存到本机)'))
|
|
2346
|
+
: (urlImage ? tt('当前:图片 URL') : tt('未设置图片(此时按默认背景显示)'))
|
|
2031
2347
|
rows.push(h('div', { className: 'pp-field', key: 'image' },
|
|
2032
2348
|
h('div', { className: 'pp-fieldLabelWrap' },
|
|
2033
|
-
h('span', { className: 'pp-fieldLabel' }, '图片'),
|
|
2034
|
-
h('span', { className: 'pp-fieldHint' }, summary + ';可选 URL 或本地图片,不限大小')),
|
|
2349
|
+
h('span', { className: 'pp-fieldLabel' }, tt('图片')),
|
|
2350
|
+
h('span', { className: 'pp-fieldHint' }, summary + tt(';可选 URL 或本地图片,不限大小'))),
|
|
2035
2351
|
h('div', { className: 'pp-fieldControl' },
|
|
2036
2352
|
h('div', { className: 'pp-urlRow' },
|
|
2037
2353
|
// 原生 file 输入隐藏起来:它在保存后必然显示"未选择文件",容易误判成没存上。
|
|
@@ -2049,13 +2365,13 @@ window.__ModuleLoader__.load({
|
|
|
2049
2365
|
const el = fileRef.current
|
|
2050
2366
|
if (el && typeof el.click === 'function') el.click()
|
|
2051
2367
|
},
|
|
2052
|
-
}, hasImage ? '更换图片…' : '选择图片…'),
|
|
2368
|
+
}, hasImage ? tt('更换图片…') : tt('选择图片…')),
|
|
2053
2369
|
h('button', {
|
|
2054
2370
|
type: 'button',
|
|
2055
2371
|
className: 'pp-blockReset',
|
|
2056
2372
|
disabled: !hasImage,
|
|
2057
2373
|
onClick: clearImage,
|
|
2058
|
-
}, '清除'),
|
|
2374
|
+
}, tt('清除')),
|
|
2059
2375
|
previewSrc
|
|
2060
2376
|
? h('img', { className: 'pp-thumb', src: previewSrc, alt: '' })
|
|
2061
2377
|
: null,
|
|
@@ -2063,14 +2379,14 @@ window.__ModuleLoader__.load({
|
|
|
2063
2379
|
type: 'text',
|
|
2064
2380
|
className: 'dcb-date',
|
|
2065
2381
|
style: { flex: '1 1 160px', minWidth: '0' },
|
|
2066
|
-
placeholder: localImage ? '已使用本地图片,输入 URL 可替换' : 'https://…',
|
|
2382
|
+
placeholder: localImage ? tt('已使用本地图片,输入 URL 可替换') : 'https://…',
|
|
2067
2383
|
value: localImage ? '' : cfg.image,
|
|
2068
2384
|
onChange: function (event) { patch({ image: event.target.value, imageKey: '' }) },
|
|
2069
2385
|
})))))
|
|
2070
2386
|
rows.push(h('div', { className: 'pp-field', key: 'opacity' },
|
|
2071
2387
|
h('div', { className: 'pp-fieldLabelWrap' },
|
|
2072
|
-
h('span', { className: 'pp-fieldLabel' }, '不透明度'),
|
|
2073
|
-
h('span', { className: 'pp-fieldHint' }, '背景图片自身的透明度(0% 完全不可见,100% 完整显示)')),
|
|
2388
|
+
h('span', { className: 'pp-fieldLabel' }, tt('不透明度')),
|
|
2389
|
+
h('span', { className: 'pp-fieldHint' }, tt('背景图片自身的透明度(0% 完全不可见,100% 完整显示)'))),
|
|
2074
2390
|
h('div', { className: 'pp-fieldControl' },
|
|
2075
2391
|
h('div', { className: 'pp-opacityRow' },
|
|
2076
2392
|
h('input', {
|
|
@@ -2079,7 +2395,7 @@ window.__ModuleLoader__.load({
|
|
|
2079
2395
|
min: '0',
|
|
2080
2396
|
max: '100',
|
|
2081
2397
|
step: '1',
|
|
2082
|
-
'aria-label': '不透明度',
|
|
2398
|
+
'aria-label': tt('不透明度'),
|
|
2083
2399
|
value: String(typeof cfg.opacity === 'number' ? cfg.opacity : DEFAULT_MODAL_STYLE.opacity),
|
|
2084
2400
|
onChange: function (event) { patch({ opacity: Number(event.target.value) }) },
|
|
2085
2401
|
}),
|
|
@@ -2091,9 +2407,9 @@ window.__ModuleLoader__.load({
|
|
|
2091
2407
|
if (cfg.mode === 'image' || cfg.mode === 'solid') {
|
|
2092
2408
|
rows.push(h('div', { className: 'pp-field', key: 'glass' },
|
|
2093
2409
|
h('div', { className: 'pp-fieldLabelWrap' },
|
|
2094
|
-
h('span', { className: 'pp-fieldLabel' }, '毛玻璃'),
|
|
2410
|
+
h('span', { className: 'pp-fieldLabel' }, tt('毛玻璃')),
|
|
2095
2411
|
h('span', { className: 'pp-fieldHint' },
|
|
2096
|
-
cfg.mode === 'image' ? '把背景图片磨砂化(对图片做真模糊)' : '底色半透明并模糊背后的页面')),
|
|
2412
|
+
tt(cfg.mode === 'image' ? '把背景图片磨砂化(对图片做真模糊)' : '底色半透明并模糊背后的页面'))),
|
|
2097
2413
|
h('div', { className: 'pp-fieldControl' },
|
|
2098
2414
|
h('button', {
|
|
2099
2415
|
type: 'button',
|
|
@@ -2108,8 +2424,8 @@ window.__ModuleLoader__.load({
|
|
|
2108
2424
|
if (cfg.mode === 'glass' || cfg.glass === true) {
|
|
2109
2425
|
rows.push(h('div', { className: 'pp-field', key: 'blur' },
|
|
2110
2426
|
h('div', { className: 'pp-fieldLabelWrap' },
|
|
2111
|
-
h('span', { className: 'pp-fieldLabel' }, '毛玻璃强度'),
|
|
2112
|
-
h('span', { className: 'pp-fieldHint' }, '模糊半径(0–48px
|
|
2427
|
+
h('span', { className: 'pp-fieldLabel' }, tt('毛玻璃强度')),
|
|
2428
|
+
h('span', { className: 'pp-fieldHint' }, tt('模糊半径(0–48px)'))),
|
|
2113
2429
|
h('div', { className: 'pp-fieldControl' },
|
|
2114
2430
|
h('div', { className: 'pp-opacityRow' },
|
|
2115
2431
|
h('input', {
|
|
@@ -2126,8 +2442,8 @@ window.__ModuleLoader__.load({
|
|
|
2126
2442
|
return h('section', { className: 'pp-block', 'data-section': 'dcb-consumption', 'data-dcb-build': BUILD_TAG },
|
|
2127
2443
|
h('header', { className: 'pp-blockHead' },
|
|
2128
2444
|
h('div', { className: 'pp-blockHeadMain' },
|
|
2129
|
-
h('h3', { className: 'pp-blockTitle' }, '消耗弹窗'),
|
|
2130
|
-
h('p', { className: 'pp-blockDesc' }, '消耗徽章弹层的背景样式;不影响对话区与侧边栏背景。'
|
|
2445
|
+
h('h3', { className: 'pp-blockTitle' }, tt('消耗弹窗')),
|
|
2446
|
+
h('p', { className: 'pp-blockDesc' }, tt('消耗徽章弹层的背景样式;不影响对话区与侧边栏背景。{tag}', { tag: BUILD_TAG }))),
|
|
2131
2447
|
h('button', {
|
|
2132
2448
|
type: 'button',
|
|
2133
2449
|
className: 'pp-blockReset',
|
|
@@ -2140,7 +2456,7 @@ window.__ModuleLoader__.load({
|
|
|
2140
2456
|
} catch (e) { /* ignore */ }
|
|
2141
2457
|
}
|
|
2142
2458
|
},
|
|
2143
|
-
}, '重置本栏')),
|
|
2459
|
+
}, tt('重置本栏'))),
|
|
2144
2460
|
h('div', { className: 'pp-blockBody' }, rows))
|
|
2145
2461
|
}
|
|
2146
2462
|
|
|
@@ -2173,7 +2489,7 @@ window.__ModuleLoader__.load({
|
|
|
2173
2489
|
root = reactDom.createRoot(container)
|
|
2174
2490
|
root.render(h(ConsumptionModalSettings, null))
|
|
2175
2491
|
} else {
|
|
2176
|
-
container.textContent = '消耗弹窗设置需要 react-dom,当前环境不可用。'
|
|
2492
|
+
container.textContent = tt('消耗弹窗设置需要 react-dom,当前环境不可用。')
|
|
2177
2493
|
}
|
|
2178
2494
|
return container
|
|
2179
2495
|
}
|
|
@@ -2189,7 +2505,7 @@ window.__ModuleLoader__.load({
|
|
|
2189
2505
|
if (node.parentNode !== page) page.appendChild(node)
|
|
2190
2506
|
if (!announced) {
|
|
2191
2507
|
announced = true
|
|
2192
|
-
console.log('[consumption-badge]
|
|
2508
|
+
console.log('[consumption-badge] dsh-plugin-personalization detected: injected the usage popup section into its settings page')
|
|
2193
2509
|
}
|
|
2194
2510
|
}
|
|
2195
2511
|
|
|
@@ -2259,43 +2575,43 @@ window.__ModuleLoader__.load({
|
|
|
2259
2575
|
function copyCommand() {
|
|
2260
2576
|
const clipboard = typeof navigator !== 'undefined' && navigator !== null ? navigator.clipboard : null
|
|
2261
2577
|
if (clipboard === null || clipboard === undefined || typeof clipboard.writeText !== 'function') {
|
|
2262
|
-
console.warn('[consumption-badge]
|
|
2578
|
+
console.warn('[consumption-badge] clipboard unavailable, copy the command manually')
|
|
2263
2579
|
return
|
|
2264
2580
|
}
|
|
2265
2581
|
try {
|
|
2266
2582
|
const pending = clipboard.writeText(UPDATE_COMMAND)
|
|
2267
2583
|
if (pending && typeof pending.then === 'function') {
|
|
2268
2584
|
pending.then(function () { setCopied(true) }, function () {
|
|
2269
|
-
console.warn('[consumption-badge]
|
|
2585
|
+
console.warn('[consumption-badge] clipboard write failed')
|
|
2270
2586
|
})
|
|
2271
2587
|
return
|
|
2272
2588
|
}
|
|
2273
2589
|
setCopied(true)
|
|
2274
2590
|
} catch (e) {
|
|
2275
|
-
console.warn('[consumption-badge]
|
|
2591
|
+
console.warn('[consumption-badge] clipboard write threw')
|
|
2276
2592
|
}
|
|
2277
2593
|
}
|
|
2278
2594
|
|
|
2279
2595
|
return h('div', { className: 'dcb-notice', role: 'status', 'aria-live': 'polite' },
|
|
2280
2596
|
h('div', { className: 'dcb-notice-head' },
|
|
2281
|
-
h('span', { className: 'dcb-notice-title' }, '消耗徽章有新版本'),
|
|
2597
|
+
h('span', { className: 'dcb-notice-title' }, tt('消耗徽章有新版本')),
|
|
2282
2598
|
h('span', { className: 'dcb-notice-versions' },
|
|
2283
|
-
'当前 v'
|
|
2599
|
+
tt('当前 v{current} → 最新 v{latest}', { current: String(info.currentVersion), latest: String(info.latestVersion) })),
|
|
2284
2600
|
h('button', {
|
|
2285
2601
|
type: 'button',
|
|
2286
2602
|
className: 'dcb-notice-x',
|
|
2287
|
-
title: '稍后提醒',
|
|
2603
|
+
title: tt('稍后提醒'),
|
|
2288
2604
|
onClick: function () { store.dismissUpdate('later') },
|
|
2289
2605
|
}, '✕')),
|
|
2290
2606
|
h('div', { className: 'dcb-notice-cmd', title: UPDATE_COMMAND }, UPDATE_COMMAND),
|
|
2291
2607
|
h('div', { className: 'dcb-notice-actions' },
|
|
2292
2608
|
h('button', { type: 'button', className: 'dcb-btn dcb-btn-primary', onClick: copyCommand },
|
|
2293
|
-
copied ? '已复制' : '复制更新命令'),
|
|
2294
|
-
h('button', { type: 'button', className: 'dcb-btn', onClick: function () { store.dismissUpdate('ignore') } }, '忽略此版本'),
|
|
2295
|
-
h('button', { type: 'button', className: 'dcb-btn', onClick: function () { store.dismissUpdate('later') } }, '稍后提醒'),
|
|
2296
|
-
h('span', { className: 'dcb-notice-hint' }, '更新后需重启 dsh 进程')),
|
|
2609
|
+
copied ? tt('已复制') : tt('复制更新命令')),
|
|
2610
|
+
h('button', { type: 'button', className: 'dcb-btn', onClick: function () { store.dismissUpdate('ignore') } }, tt('忽略此版本')),
|
|
2611
|
+
h('button', { type: 'button', className: 'dcb-btn', onClick: function () { store.dismissUpdate('later') } }, tt('稍后提醒')),
|
|
2612
|
+
h('span', { className: 'dcb-notice-hint' }, tt('更新后需重启 dsh 进程'))),
|
|
2297
2613
|
h('div', { className: 'dcb-notice-foot' },
|
|
2298
|
-
'版本来自 npm registry(24h 缓存);「忽略此版本」会记住,直到出现更高版本。'))
|
|
2614
|
+
tt('版本来自 npm registry(24h 缓存);「忽略此版本」会记住,直到出现更高版本。')))
|
|
2299
2615
|
}
|
|
2300
2616
|
|
|
2301
2617
|
/* styles + registration */
|
|
@@ -2429,9 +2745,15 @@ window.__ModuleLoader__.load({
|
|
|
2429
2745
|
|
|
2430
2746
|
exports.apply = function apply(ctx) {
|
|
2431
2747
|
insertCss(ctx)
|
|
2748
|
+
// Bind the shell locale first: every render below reads through tt().
|
|
2749
|
+
appCtx = ctx
|
|
2750
|
+
attachLocale(ctx)
|
|
2751
|
+
// Slot labels are thunks so the settings nav / action row re-read the copy
|
|
2752
|
+
// after a language switch (the slot system resolves labels at read time).
|
|
2753
|
+
const labelThunk = function () { return tt('消耗') }
|
|
2432
2754
|
ctx.slots.inject('conversation.chat.assistant-actions', function () {
|
|
2433
2755
|
const dispose = ctx.slots.register(
|
|
2434
|
-
{ name: 'conversation.chat.assistant-actions', id: 'consumption', order: 20, label:
|
|
2756
|
+
{ name: 'conversation.chat.assistant-actions', id: 'consumption', order: 20, label: labelThunk },
|
|
2435
2757
|
function (props) { return h(ConsumptionBadge, props) },
|
|
2436
2758
|
)
|
|
2437
2759
|
return function () { dispose() }
|
|
@@ -2446,7 +2768,7 @@ window.__ModuleLoader__.load({
|
|
|
2446
2768
|
// 设置页整页:settings.section(不是 settings.plugin.item 的卡片位)。
|
|
2447
2769
|
ctx.slots.inject('settings.section', function () {
|
|
2448
2770
|
const dispose = ctx.slots.register(
|
|
2449
|
-
{ name: 'settings.section', id: 'balance-dsh-consumption', order: 26, label:
|
|
2771
|
+
{ name: 'settings.section', id: 'balance-dsh-consumption', order: 26, label: labelThunk },
|
|
2450
2772
|
function (props) { return h(ConsumptionSettings, props) },
|
|
2451
2773
|
)
|
|
2452
2774
|
return function () { dispose() }
|