@goodandready/dsh-subscriptions 0.6.7 → 0.6.9
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 +18 -0
- package/README.ru.md +18 -0
- package/README.zh.md +18 -0
- package/lib/accounts.js +0 -17
- package/lib/client.js +502 -284
- package/lib/http.js +7 -0
- package/lib/relative-time.js +11 -3
- package/lib/routes/status.js +22 -14
- package/lib/routes.js +9 -2
- package/lib/updater.js +260 -0
- package/lib/usage.js +11 -11
- package/package.json +6 -6
package/lib/client.js
CHANGED
|
@@ -205,304 +205,460 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
205
205
|
|
|
206
206
|
const INSTRUCTIONS = {
|
|
207
207
|
codex: {
|
|
208
|
-
title:
|
|
209
|
-
stepsRu: [
|
|
210
|
-
'1. Авторизуйтесь через CLI на сервере: выполните "codex login" в терминале.',
|
|
211
|
-
'2. Нажмите кнопку "📥 Из CLI", чтобы сервер автоматически прочитал ~/.codex/auth.json.',
|
|
212
|
-
'3. Либо нажмите кнопку "Авторизация по коду" (Device Login) выше и введите код на auth.openai.com.',
|
|
213
|
-
],
|
|
208
|
+
title: "OpenAI / ChatGPT Codex",
|
|
214
209
|
stepsEn: [
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
210
|
+
"1. Log in via CLI on server: run \"codex login\" in terminal.",
|
|
211
|
+
"2. Click \"📥 From CLI\" to auto-import ~/.codex/auth.json.",
|
|
212
|
+
"3. Or click \"Device Login\" above and enter the code at auth.openai.com.",
|
|
213
|
+
],
|
|
214
|
+
stepsZh: [
|
|
215
|
+
"1. 在服务器上通过 CLI 登录:在终端运行 \"codex login\"。",
|
|
216
|
+
"2. 点击 \"📥 从 CLI 导入\" 自动读取 ~/.codex/auth.json。",
|
|
217
|
+
"3. 或者点击上方的 \"设备码登录\" (Device Login) 并在 auth.openai.com 输入代码。",
|
|
218
218
|
],
|
|
219
|
-
link:
|
|
219
|
+
link: "https://chatgpt.com",
|
|
220
220
|
},
|
|
221
221
|
claude: {
|
|
222
|
-
title:
|
|
223
|
-
stepsRu: [
|
|
224
|
-
'1. Выполните "claude login" в консоли сервера.',
|
|
225
|
-
'2. Нажмите "📥 Из CLI" — плагин подтянет токен из ~/.claude/credentials.json.',
|
|
226
|
-
'3. Либо скопируйте свой Session Token / API-ключ из консоли Anthropic и нажмите "Сохранить".',
|
|
227
|
-
],
|
|
222
|
+
title: "Claude Code",
|
|
228
223
|
stepsEn: [
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
224
|
+
"1. Run \"claude login\" in server console.",
|
|
225
|
+
"2. Click \"📥 From CLI\" — plugin will import ~/.claude/credentials.json.",
|
|
226
|
+
"3. Or paste your Session Token / API Key from Anthropic Console and click \"Save token\".",
|
|
232
227
|
],
|
|
233
|
-
|
|
228
|
+
stepsZh: [
|
|
229
|
+
"1. 在服务器控制台运行 \"claude login\"。",
|
|
230
|
+
"2. 点击 \"📥 从 CLI 导入\" — 插件将自动导入 ~/.claude/credentials.json。",
|
|
231
|
+
"3. 或者从 Anthropic 控制台复制您的 Session Token 或 API Key 并点击 \"保存令牌\"。",
|
|
232
|
+
],
|
|
233
|
+
link: "https://console.anthropic.com/settings/keys",
|
|
234
234
|
},
|
|
235
235
|
cursor: {
|
|
236
|
-
title:
|
|
237
|
-
stepsRu: [
|
|
238
|
-
'1. Авторизация через CLI: если Cursor установлен на сервере, нажмите "📥 Из CLI" (считывается из ~/.cursor/cli-config.json).',
|
|
239
|
-
'2. Вручную: откройте Настройки Cursor (кнопка "Подключить" или ссылка ниже), скопируйте WorkOS токен сессии или свой API ключ.',
|
|
240
|
-
'3. Вставьте его в поле ввода и нажмите кнопку "Сохранить".',
|
|
241
|
-
],
|
|
236
|
+
title: "Cursor IDE",
|
|
242
237
|
stepsEn: [
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
238
|
+
"1. Auto CLI: if Cursor is used on server, click \"📥 From CLI\" (reads from ~/.cursor/cli-config.json).",
|
|
239
|
+
"2. Manual: open Cursor Settings (Connect button or link below), copy your session token or API key.",
|
|
240
|
+
"3. Paste it into the input field and click \"Save token\".",
|
|
241
|
+
],
|
|
242
|
+
stepsZh: [
|
|
243
|
+
"1. CLI 自动导入:若服务器安装了 Cursor,点击 \"📥 从 CLI 导入\" (读取自 ~/.cursor/cli-config.json)。",
|
|
244
|
+
"2. 手动导入:打开 Cursor 设置页面(点击连接或下方链接),复制 WorkOS 会话令牌或 API 密钥。",
|
|
245
|
+
"3. 将其粘贴到输入框中并点击 \"保存令牌\"。",
|
|
246
246
|
],
|
|
247
|
-
link:
|
|
247
|
+
link: "https://cursor.com/settings",
|
|
248
248
|
},
|
|
249
249
|
antigravity: {
|
|
250
|
-
title:
|
|
251
|
-
stepsRu: [
|
|
252
|
-
'1. Самый простой способ: нажмите "📥 Из CLI" — на сервере уже есть авторизованная сессия (~/.gemini/antigravity-cli/antigravity-oauth-token).',
|
|
253
|
-
'2. Если токен обновлен в системе, нажатие "📥 Из CLI" мгновенно подхватит актуальный refresh_token.',
|
|
254
|
-
'3. Ручной ввод: вставьте JSON сессии или OAuth access_token/refresh_token в поле и нажмите "Сохранить".',
|
|
255
|
-
'Примечание: Кнопка "Подключить" для Google требует свой OAuth client_id в настройках плагина, поэтому для Antigravity рекомендуется использовать "📥 Из CLI"!',
|
|
256
|
-
],
|
|
250
|
+
title: "Google Antigravity / Gemini",
|
|
257
251
|
stepsEn: [
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
252
|
+
"1. Easiest way: click \"📥 From CLI\" — server already has active token in ~/.gemini/antigravity-cli/antigravity-oauth-token.",
|
|
253
|
+
"2. Clicking \"📥 From CLI\" instantly loads fresh credentials.",
|
|
254
|
+
"3. Manual: paste session JSON or OAuth tokens and click \"Save token\".",
|
|
255
|
+
"Note: \"Connect\" button requires custom Google client_id in plugin settings; use \"📥 From CLI\" for seamless login!",
|
|
256
|
+
],
|
|
257
|
+
stepsZh: [
|
|
258
|
+
"1. 推荐方式:点击 \"📥 从 CLI 导入\" — 服务器已保存 ~/.gemini/antigravity-cli/antigravity-oauth-token 会话。",
|
|
259
|
+
"2. 点击 \"📥 从 CLI 导入\" 可即时加载最新的凭据与 refresh_token。",
|
|
260
|
+
"3. 手动输入:将会话 JSON 或 OAuth access_token/refresh_token 粘贴至输入框并保存。",
|
|
261
|
+
"注:\"连接\" 按钮需要自定义 Google OAuth Client ID,推荐直接使用 \"📥 从 CLI 导入\"!",
|
|
262
262
|
],
|
|
263
|
-
link:
|
|
263
|
+
link: "https://antigravity.google",
|
|
264
264
|
},
|
|
265
265
|
kimi: {
|
|
266
|
-
title:
|
|
267
|
-
stepsRu: [
|
|
268
|
-
'1. Откройте консоль разработчика Moonshot (ссылка ниже или по кнопке "Подключить").',
|
|
269
|
-
'2. Создайте и скопируйте свой API-ключ (sk-...).',
|
|
270
|
-
'3. Вставьте ключ в поле слота и нажмите "Сохранить".',
|
|
271
|
-
'4. Если у вас настроен Kimi Code CLI, можно также нажать "📥 Из CLI" (~/.kimi-code).',
|
|
272
|
-
],
|
|
266
|
+
title: "Moonshot Kimi",
|
|
273
267
|
stepsEn: [
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
268
|
+
"1. Open Moonshot developer console (link below or click Connect).",
|
|
269
|
+
"2. Generate and copy your API Key (sk-...).",
|
|
270
|
+
"3. Paste it into the input field and click \"Save token\".",
|
|
271
|
+
"4. Or click \"📥 From CLI\" if ~/.kimi-code is configured.",
|
|
278
272
|
],
|
|
279
|
-
|
|
273
|
+
stepsZh: [
|
|
274
|
+
"1. 打开 Moonshot 开发者控制台(点击下方链接或点击连接)。",
|
|
275
|
+
"2. 创建并复制您的 API 密钥 (sk-...)。",
|
|
276
|
+
"3. 将密钥粘贴到槽位输入框中并点击 \"保存令牌\"。",
|
|
277
|
+
"4. 如果配置了 Kimi Code CLI,也可点击 \"📥 从 CLI 导入\" (~/.kimi-code)。",
|
|
278
|
+
],
|
|
279
|
+
link: "https://platform.moonshot.cn/console/api-keys",
|
|
280
280
|
},
|
|
281
281
|
glm: {
|
|
282
|
-
title:
|
|
283
|
-
stepsRu: [
|
|
284
|
-
'1. Откройте Центр разработчика Zhipu AI (ссылка ниже или по кнопке "Подключить").',
|
|
285
|
-
'2. Скопируйте ваш API-ключ GLM.',
|
|
286
|
-
'3. Вставьте его в поле ввода и нажмите "Сохранить".',
|
|
287
|
-
'4. Если установлен ZCode CLI (~/.zcode), нажмите "📥 Из CLI" для авто-импорта.',
|
|
288
|
-
],
|
|
282
|
+
title: "Zhipu GLM (Z.AI)",
|
|
289
283
|
stepsEn: [
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
284
|
+
"1. Open Zhipu BigModel API Keys center (link below or click Connect).",
|
|
285
|
+
"2. Copy your GLM API Key.",
|
|
286
|
+
"3. Paste it into the input field and click \"Save token\".",
|
|
287
|
+
"4. Or click \"📥 From CLI\" if ZCode CLI is installed (~/.zcode).",
|
|
294
288
|
],
|
|
295
|
-
|
|
289
|
+
stepsZh: [
|
|
290
|
+
"1. 打开智谱 AI 开放平台(点击下方链接或点击连接)。",
|
|
291
|
+
"2. 复制您的 GLM API 密钥。",
|
|
292
|
+
"3. 粘贴到输入框中并点击 \"保存令牌\"。",
|
|
293
|
+
"4. 若已安装 ZCode CLI (~/.zcode),点击 \"📥 从 CLI 导入\" 即可自动导入。",
|
|
294
|
+
],
|
|
295
|
+
link: "https://open.bigmodel.cn/usercenter/apikeys",
|
|
296
296
|
},
|
|
297
297
|
grok: {
|
|
298
|
-
title:
|
|
299
|
-
stepsRu: [
|
|
300
|
-
'1. Выполните вход через grok-cli или hermes на сервере.',
|
|
301
|
-
'2. Нажмите "📥 Из CLI" (~/.grok/auth.json).',
|
|
302
|
-
'3. Либо вставьте xAI API-ключ в поле и нажмите "Сохранить".',
|
|
303
|
-
],
|
|
298
|
+
title: "xAI Grok",
|
|
304
299
|
stepsEn: [
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
300
|
+
"1. Run grok-cli or hermes login on server.",
|
|
301
|
+
"2. Click \"📥 From CLI\" (~/.grok/auth.json).",
|
|
302
|
+
"3. Or paste your xAI API Key and click \"Save token\".",
|
|
303
|
+
],
|
|
304
|
+
stepsZh: [
|
|
305
|
+
"1. 在服务器上运行 grok-cli 或 hermes 登录。",
|
|
306
|
+
"2. 点击 \"📥 从 CLI 导入\" (~/.grok/auth.json)。",
|
|
307
|
+
"3. 或将 xAI API 密钥粘贴到输入框并点击 \"保存令牌\"。",
|
|
308
308
|
],
|
|
309
|
-
link:
|
|
309
|
+
link: "https://x.ai",
|
|
310
310
|
},
|
|
311
311
|
kiro: {
|
|
312
|
-
title:
|
|
313
|
-
stepsRu: [
|
|
314
|
-
'1. Нажмите "📥 Из CLI" для импорта из ~/.kiro/credentials.json или ~/.aws/sso.',
|
|
315
|
-
'2. Либо вставьте KIRO_API_KEY в поле и нажмите "Сохранить".',
|
|
316
|
-
],
|
|
312
|
+
title: "AWS Kiro",
|
|
317
313
|
stepsEn: [
|
|
318
|
-
|
|
319
|
-
|
|
314
|
+
"1. Click \"📥 From CLI\" to import from ~/.kiro/credentials.json.",
|
|
315
|
+
"2. Or paste KIRO_API_KEY and click \"Save token\".",
|
|
316
|
+
],
|
|
317
|
+
stepsZh: [
|
|
318
|
+
"1. 点击 \"📥 从 CLI 导入\" 从 ~/.kiro/credentials.json 或 ~/.aws/sso 导入凭据。",
|
|
319
|
+
"2. 或直接粘贴 KIRO_API_KEY 并点击 \"保存令牌\"。",
|
|
320
320
|
],
|
|
321
|
-
link:
|
|
321
|
+
link: "https://aws.amazon.com",
|
|
322
322
|
},
|
|
323
323
|
}
|
|
324
324
|
|
|
325
325
|
const en = {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
326
|
+
"notConnected": "Not connected",
|
|
327
|
+
"verifyAccount": "Verify account",
|
|
328
|
+
"coolingDown": "Cooling down",
|
|
329
|
+
"usageFull": "Usage 100%",
|
|
330
|
+
"connected": "Connected",
|
|
331
|
+
"title": "Subscriptions",
|
|
332
|
+
"cardIntro": "Subscription accounts, rotation and limits.",
|
|
333
|
+
"show": "Show",
|
|
334
|
+
"hide": "Hide",
|
|
335
|
+
"intro": "Log in with a consumer subscription. Tokens stay on the host credentials store. The browser never reads them back. After Connect, if the provider lands on localhost or a vendor page, paste the redirected URL or the code here.",
|
|
336
|
+
"useOrigin": "Use this Web UI origin as OAuth redirect_uri",
|
|
337
|
+
"useOriginHint": "Leave off unless you registered your own OAuth client for this origin. Vendor CLI clients usually require their published redirect URI plus the paste step.",
|
|
338
|
+
"closeModal": "Close",
|
|
339
|
+
"groupAuth": "Sign-in and redirect",
|
|
340
|
+
"groupPrivacy": "Privacy and model list",
|
|
341
|
+
"groupRotation": "Rotation and limits",
|
|
342
|
+
"groupProvider": "Provider behaviour",
|
|
343
|
+
"groupFallback": "Local fallback",
|
|
344
|
+
"privacyMask": "Mask emails and account identifiers",
|
|
345
|
+
"privacyMaskHint": "For demos and screen sharing: emails show as j***n@example.com.",
|
|
346
|
+
"autoLoopback": "Auto Loopback",
|
|
347
|
+
"autoLoopbackHint": "Listen on local callback port to catch OAuth tokens automatically without manual copy-paste.",
|
|
348
|
+
"hideDeprecated": "Hide deprecated models",
|
|
349
|
+
"hideDeprecatedHint": "Hide older or superseded model variants from model dropdowns.",
|
|
350
|
+
"composerQuota": "Composer quota indicator",
|
|
351
|
+
"composerQuotaHint": "Display subscription usage indicator directly in the composer toolbar.",
|
|
352
|
+
"composerQuotaOff": "Disabled",
|
|
353
|
+
"composerQuotaPercent": "Percentage",
|
|
354
|
+
"composerQuotaBar": "Progress Bar",
|
|
355
|
+
"composerQuotaForecast": "Remaining Forecast",
|
|
356
|
+
"expiryNotifyDays": "Expiry warning threshold (days)",
|
|
357
|
+
"expiryNotifyDaysHint": "Notify when subscription renewal or token expiration is within this many days (0 to disable).",
|
|
358
|
+
"codexFastMode": "Codex fast mode (1.5x)",
|
|
359
|
+
"codexFastModeHint": "Enable high-throughput mode for OpenAI Codex models if supported by subscription.",
|
|
360
|
+
"codexVerbosity": "Codex streaming verbosity",
|
|
361
|
+
"codexVerbosityHint": "Control streaming chunking detail level from the Codex provider.",
|
|
362
|
+
"codexVerbosityDefault": "Default",
|
|
363
|
+
"codexVerbosityLow": "Low",
|
|
364
|
+
"codexVerbosityMedium": "Medium",
|
|
365
|
+
"codexVerbosityHigh": "High",
|
|
366
|
+
"ollamaFallback": "Ollama local fallback",
|
|
367
|
+
"ollamaFallbackHint": "Automatically route queries to a local Ollama instance when cloud quota is exhausted.",
|
|
368
|
+
"ollamaBaseUrl": "Ollama Base URL",
|
|
369
|
+
"ollamaFallbackModel": "Ollama Fallback Model",
|
|
370
|
+
"advancedSettings": "Advanced Settings & Monitoring",
|
|
371
|
+
"cooldownMs": "Failure cooldown (minutes)",
|
|
372
|
+
"cooldownMsHint": "How long to pause a failing or rate-limited account before retrying.",
|
|
373
|
+
"probeIntervalMin": "Health probe interval (minutes)",
|
|
374
|
+
"probeIntervalMinHint": "Interval for background checks to refresh account status and quota balance.",
|
|
375
|
+
"notifyLimits": "Notify on rate limits",
|
|
376
|
+
"notifyLimitsHint": "Show notification alerts when account usage reaches quota ceiling.",
|
|
377
|
+
"hostOnline": "Host online",
|
|
378
|
+
"hostOffline": "Host unreachable",
|
|
379
|
+
"statsTitle": "Session Telemetry",
|
|
380
|
+
"statsRequests": "Successful / Total Requests",
|
|
381
|
+
"statsLatency": "Avg Response Latency",
|
|
382
|
+
"statsSuccessRate": "Session Success Rate",
|
|
383
|
+
"statsLastActivity": "Last Activity",
|
|
384
|
+
"smokeTitle": "Connection Smoke Test (Ping)",
|
|
385
|
+
"smokeDesc": "Test live upstream connectivity and measure round-trip latency to the subscription provider.",
|
|
386
|
+
"runSmoke": "⚡ Run Smoke Test (Ping)",
|
|
387
|
+
"smokeTesting": "Pinging…",
|
|
388
|
+
"smokeOk": "Smoke test passed",
|
|
389
|
+
"smokeFail": "Smoke test failed",
|
|
390
|
+
"modelsCatalogTitle": "Curated Subscription Models",
|
|
391
|
+
"modelsCatalogDesc": "Available model catalog across active subscriptions in DeepSeek Harness.",
|
|
392
|
+
"resetCredits": "Reset credits",
|
|
393
|
+
"resetCreditsHint": "ChatGPT reset cards: emergency reset for the 5-hour window. Requires a 5s confirmation.",
|
|
394
|
+
"resetCreditsAction": "Reset ChatGPT Limit",
|
|
395
|
+
"usageLimitsTitle": "Usage Limits",
|
|
396
|
+
"slotDetailsTitle": "Slot Details",
|
|
397
|
+
"resetAvailable": "Reset attempts available",
|
|
398
|
+
"resetExpires": "earliest expires",
|
|
399
|
+
"resetAck": "I understand one attempt will be consumed",
|
|
400
|
+
"resetWait": "confirm enabled in",
|
|
401
|
+
"resetReady": "ready",
|
|
402
|
+
"resetGo": "Reset now",
|
|
403
|
+
"resetBusy": "resetting…",
|
|
404
|
+
"resetDone": "quota reset",
|
|
405
|
+
"resetNothing": "server says nothing needs a reset (no attempt consumed)",
|
|
406
|
+
"resetNoCredit": "no usable credit (not consumed)",
|
|
407
|
+
"resetRedeemed": "already redeemed earlier",
|
|
408
|
+
"diagGenerate": "Generate diagnostics report",
|
|
409
|
+
"diagCopy": "Copy report",
|
|
410
|
+
"diagIssues": "Open issue tracker",
|
|
411
|
+
"diagHint": "The report has no tokens, emails, proxy addresses or personal data.",
|
|
412
|
+
"subsPill": "SUBS",
|
|
413
|
+
"subsModalTitle": "Subscription Hub",
|
|
414
|
+
"subsActiveHero": "Active Subscription",
|
|
415
|
+
"subsPoolTitle": "Provider Accounts",
|
|
416
|
+
"subsRefresh": "Refresh",
|
|
417
|
+
"subsOpenSettings": "All Settings →",
|
|
418
|
+
"subsFastMode": "Fast Mode 1.5x",
|
|
419
|
+
"subsHealthy": "Healthy",
|
|
420
|
+
"subsFree": "free",
|
|
421
|
+
"subsUsed": "used",
|
|
422
|
+
"subsModalClose": "Close",
|
|
423
|
+
"subsLogged": "connected",
|
|
424
|
+
"subsNotLogged": "not connected",
|
|
425
|
+
"subsCooldown": "cooldown",
|
|
426
|
+
"hudTitle": "Subscription balance",
|
|
427
|
+
"settingsLoading": "Loading settings…",
|
|
428
|
+
"settingsUnavailable": "Settings are unavailable",
|
|
429
|
+
"settingsRetry": "Retry",
|
|
430
|
+
"hudHint": "drag · edge docks · click refresh",
|
|
431
|
+
"fcCalibrating": "calibrating…",
|
|
432
|
+
"fcIdle": "no usage",
|
|
433
|
+
"fcHours": "h",
|
|
434
|
+
"fcMinutes": "m",
|
|
435
|
+
"reconnect": "Reconnect",
|
|
436
|
+
"connect": "Connect",
|
|
437
|
+
"disconnect": "Disconnect",
|
|
438
|
+
"removeSlot": "Remove slot",
|
|
439
|
+
"pastePlaceholder": "Paste redirected URL or code",
|
|
440
|
+
"submitCode": "Submit code",
|
|
441
|
+
"proxyPlaceholder": "Per-account proxy (http://, https://, socks5://) - optional",
|
|
442
|
+
"proxyCheck": "Check proxy",
|
|
443
|
+
"proxyOk": "proxy ok",
|
|
444
|
+
"proxyFail": "proxy fail",
|
|
445
|
+
"deviceLogin": "Device login",
|
|
446
|
+
"deviceHint": "Headless: open the link, enter the code, keep this tab open.",
|
|
447
|
+
"deviceCopy": "Copy code",
|
|
448
|
+
"devicePending": "Waiting for confirmation",
|
|
449
|
+
"deviceAuthorized": "Signed in",
|
|
450
|
+
"deviceExpired": "Expired - start again",
|
|
451
|
+
"verifyPrefix": "Google requires one-time account verification. ",
|
|
452
|
+
"verifyLink": "Open verification link",
|
|
453
|
+
"verifySuffix": " then reconnect.",
|
|
454
|
+
"addAccount": "+ Add account",
|
|
455
|
+
"save": "Save",
|
|
456
|
+
"saved": "Saved",
|
|
457
|
+
"loading": "Loading…",
|
|
458
|
+
"accountLabel": "Account",
|
|
459
|
+
"plan": "Plan",
|
|
460
|
+
"storedAs": "Stored as",
|
|
461
|
+
"switchLabel": "Provider",
|
|
462
|
+
"chipActive": "Subscriptions",
|
|
463
|
+
"expiryLabel": "expires",
|
|
464
|
+
"slashLogin": "Login to a subscription provider",
|
|
465
|
+
"slashLogout": "Log out a subscription provider",
|
|
466
|
+
"slashStatus": "Subscription status",
|
|
467
|
+
"none": "none",
|
|
468
|
+
"forecast": "≈",
|
|
469
|
+
"windowPrimary": "Primary window",
|
|
470
|
+
"windowSecondary": "Secondary window",
|
|
471
|
+
"resetLabel": "reset",
|
|
472
|
+
"check": "Check",
|
|
473
|
+
"checking": "Checking…",
|
|
474
|
+
"importToken": "Save token",
|
|
475
|
+
"importTokenPlace": "Paste token or API key",
|
|
476
|
+
"importLocalCli": "📥 From CLI",
|
|
477
|
+
"importLocalCliTitle": "Auto-import token from locally installed CLI on server",
|
|
478
|
+
"importLocalSuccess": "Successfully imported token from local CLI!",
|
|
479
|
+
"howToGetToken": "❓ Instructions",
|
|
480
|
+
"howToGetTokenTitle": "How to get token / API key for this provider",
|
|
481
|
+
"instructionsTitle": "How to obtain token",
|
|
482
|
+
"close": "Close",
|
|
483
|
+
"reconnectRequired": "Reconnect required",
|
|
484
|
+
"manualTitle": "If the browser did not come back",
|
|
485
|
+
"updateChecking": "Checking updates…",
|
|
486
|
+
"updateUpToDate": "Up to date",
|
|
487
|
+
"updateAvailable": "Update available",
|
|
488
|
+
"updateNow": "Update",
|
|
489
|
+
"updating": "Updating…",
|
|
490
|
+
"updateSuccessRestart": "Update complete. Restarting service…",
|
|
491
|
+
"updateFailed": "Update failed"
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
const zh = {
|
|
495
|
+
"notConnected": "未连接",
|
|
496
|
+
"verifyAccount": "需要验证账户",
|
|
497
|
+
"coolingDown": "冷却等待中",
|
|
498
|
+
"usageFull": "用量已达 100%",
|
|
499
|
+
"connected": "已连接",
|
|
500
|
+
"title": "个人订阅管理",
|
|
501
|
+
"cardIntro": "AI 服务订阅账户、自动轮换与速率限制管理。",
|
|
502
|
+
"show": "展开",
|
|
503
|
+
"hide": "收起",
|
|
504
|
+
"intro": "通过个人订阅登录。凭据安全保存在服务器端凭据库中,浏览器不回读敏感令牌。点击“连接”后,若页面跳转至 localhost 或服务商页面,请将重定向网址或授权代码粘贴到下方。",
|
|
505
|
+
"useOrigin": "将当前 Web UI 地址作为 OAuth 重定向地址 (redirect_uri)",
|
|
506
|
+
"useOriginHint": "除非您为当前域名申请了专属 OAuth 客户端,否则请保持关闭。官方 CLI 客户端通常需使用其公开重定向地址并配合粘贴代码流程。",
|
|
507
|
+
"closeModal": "关闭",
|
|
508
|
+
"groupAuth": "授权与重定向设置",
|
|
509
|
+
"groupPrivacy": "隐私与模型展示",
|
|
510
|
+
"groupRotation": "账户轮换与限制",
|
|
511
|
+
"groupProvider": "服务商特性设置",
|
|
512
|
+
"groupFallback": "本地兜底与容灾",
|
|
513
|
+
"privacyMask": "脱敏隐藏邮箱与账户标识",
|
|
514
|
+
"privacyMaskHint": "演示或录屏时自动将邮箱显示为 j***n@example.com。",
|
|
515
|
+
"autoLoopback": "自动回环监听 (Auto Loopback)",
|
|
516
|
+
"autoLoopbackHint": "在服务器本地回环端口监听以自动捕获 OAuth 令牌,无需手动复制粘贴。",
|
|
517
|
+
"hideDeprecated": "隐藏已废弃的旧模型",
|
|
518
|
+
"hideDeprecatedHint": "在模型下拉列表中隐藏早期或已被替代的旧版模型。",
|
|
519
|
+
"composerQuota": "输入框额度指示器",
|
|
520
|
+
"composerQuotaHint": "在聊天输入框工具栏直接显示当前订阅额度指示器。",
|
|
521
|
+
"composerQuotaOff": "已禁用",
|
|
522
|
+
"composerQuotaPercent": "百分比显示",
|
|
523
|
+
"composerQuotaBar": "进度条显示",
|
|
524
|
+
"composerQuotaForecast": "剩余耗尽预测",
|
|
525
|
+
"expiryNotifyDays": "订阅到期预警阈值(天)",
|
|
526
|
+
"expiryNotifyDaysHint": "当订阅续费或令牌将在指定天数内过期时弹出通知(设为 0 关闭)。",
|
|
527
|
+
"codexFastMode": "Codex 快速高吞吐模式 (1.5x)",
|
|
528
|
+
"codexFastModeHint": "对支持的 OpenAI Codex 订阅模型开启 1.5x 高速通道。",
|
|
529
|
+
"codexVerbosity": "Codex 流式输出详细级别",
|
|
530
|
+
"codexVerbosityHint": "调节 Codex 响应分块输出的详细程度。",
|
|
531
|
+
"codexVerbosityDefault": "默认",
|
|
532
|
+
"codexVerbosityLow": "低",
|
|
533
|
+
"codexVerbosityMedium": "中",
|
|
534
|
+
"codexVerbosityHigh": "高",
|
|
535
|
+
"ollamaFallback": "Ollama 本地模型自动兜底",
|
|
536
|
+
"ollamaFallbackHint": "当云端配额耗尽或服务商故障时,自动将请求转发给本地 Ollama 实例。",
|
|
537
|
+
"ollamaBaseUrl": "Ollama 服务基础 URL",
|
|
538
|
+
"ollamaFallbackModel": "Ollama 兜底模型名称",
|
|
539
|
+
"advancedSettings": "高级配置与监控",
|
|
540
|
+
"cooldownMs": "故障与限流冷却时间(分钟)",
|
|
541
|
+
"cooldownMsHint": "当账户遇到 429 或故障时暂停该账户的分钟数。",
|
|
542
|
+
"probeIntervalMin": "健康探测轮询间隔(分钟)",
|
|
543
|
+
"probeIntervalMinHint": "后台探活及更新账户配额剩余额度的轮询间隔。",
|
|
544
|
+
"notifyLimits": "配额耗尽时发送通知",
|
|
545
|
+
"notifyLimitsHint": "当账户使用量达到上限时触发系统通知提醒。",
|
|
546
|
+
"hostOnline": "主机在线",
|
|
547
|
+
"hostOffline": "主机无法连接",
|
|
548
|
+
"statsTitle": "会话遥测统计",
|
|
549
|
+
"statsRequests": "成功 / 总请求数",
|
|
550
|
+
"statsLatency": "平均响应延迟",
|
|
551
|
+
"statsSuccessRate": "会话成功率",
|
|
552
|
+
"statsLastActivity": "最近活跃时间",
|
|
553
|
+
"smokeTitle": "链路连通性冒烟测试 (Ping)",
|
|
554
|
+
"smokeDesc": "向当前订阅服务商发起实时探活并测量双向网络往返延迟 (RTT)。",
|
|
555
|
+
"runSmoke": "⚡ 执行冒烟探活 (Ping)",
|
|
556
|
+
"smokeTesting": "正在探活…",
|
|
557
|
+
"smokeOk": "冒烟测试成功",
|
|
558
|
+
"smokeFail": "冒烟测试失败",
|
|
559
|
+
"modelsCatalogTitle": "精选订阅模型",
|
|
560
|
+
"modelsCatalogDesc": "当前 DeepSeek Harness 活跃订阅下可用模型一览。",
|
|
561
|
+
"resetCredits": "重置 ChatGPT 额度",
|
|
562
|
+
"resetCreditsHint": "ChatGPT 重置卡:紧急重置 5 小时额度窗口。扣除前需等待 5 秒倒计时确认。",
|
|
563
|
+
"resetCreditsAction": "重置 ChatGPT 限制",
|
|
564
|
+
"usageLimitsTitle": "用量限制",
|
|
565
|
+
"slotDetailsTitle": "槽位详情",
|
|
566
|
+
"resetAvailable": "可用重置次数",
|
|
567
|
+
"resetExpires": "最早到期时间",
|
|
568
|
+
"resetAck": "我确认将消耗一次重置机会",
|
|
569
|
+
"resetWait": "确认倒计时",
|
|
570
|
+
"resetReady": "可以重置",
|
|
571
|
+
"resetGo": "立即重置额度",
|
|
572
|
+
"resetBusy": "正在重置…",
|
|
573
|
+
"resetDone": "配额已成功重置",
|
|
574
|
+
"resetNothing": "服务端指示当前无需重置(未消耗机会)",
|
|
575
|
+
"resetNoCredit": "无可用重置点数(未消耗机会)",
|
|
576
|
+
"resetRedeemed": "该点数先前已兑换使用",
|
|
577
|
+
"diagGenerate": "生成匿名诊断报告",
|
|
578
|
+
"diagCopy": "复制报告",
|
|
579
|
+
"diagIssues": "打开 Issue 反馈",
|
|
580
|
+
"diagHint": "诊断报告不包含令牌、邮箱、代理地址或任何个人隐私数据。",
|
|
581
|
+
"subsPill": "订阅",
|
|
582
|
+
"subsModalTitle": "订阅管理中心",
|
|
583
|
+
"subsActiveHero": "当前主用订阅",
|
|
584
|
+
"subsPoolTitle": "服务商账户池",
|
|
585
|
+
"subsRefresh": "刷新",
|
|
586
|
+
"subsOpenSettings": "全部设置 →",
|
|
587
|
+
"subsFastMode": "加速模式 1.5x",
|
|
588
|
+
"subsHealthy": "健康运行中",
|
|
589
|
+
"subsFree": "空闲可用",
|
|
590
|
+
"subsUsed": "已用",
|
|
591
|
+
"subsModalClose": "关闭",
|
|
592
|
+
"subsLogged": "已连接",
|
|
593
|
+
"subsNotLogged": "未连接",
|
|
594
|
+
"subsCooldown": "冷却中",
|
|
595
|
+
"hudTitle": "订阅额度余量",
|
|
596
|
+
"settingsLoading": "正在加载设置…",
|
|
597
|
+
"settingsUnavailable": "设置加载失败",
|
|
598
|
+
"settingsRetry": "重试",
|
|
599
|
+
"hudHint": "可拖拽 · 边缘停靠 · 点击刷新",
|
|
600
|
+
"fcCalibrating": "校准中…",
|
|
601
|
+
"fcIdle": "暂无消耗",
|
|
602
|
+
"fcHours": "小时",
|
|
603
|
+
"fcMinutes": "分",
|
|
604
|
+
"reconnect": "重新连接",
|
|
605
|
+
"connect": "连接",
|
|
606
|
+
"disconnect": "断开连接",
|
|
607
|
+
"removeSlot": "移除槽位",
|
|
608
|
+
"pastePlaceholder": "粘贴重定向 URL 或授权代码",
|
|
609
|
+
"submitCode": "提交授权代码",
|
|
610
|
+
"proxyPlaceholder": "单账户独立代理 (http://, https://, socks5://) - 可选",
|
|
611
|
+
"proxyCheck": "测试代理",
|
|
612
|
+
"proxyOk": "代理连接成功",
|
|
613
|
+
"proxyFail": "代理连接失败",
|
|
614
|
+
"deviceLogin": "设备码登录",
|
|
615
|
+
"deviceHint": "无头模式:打开下方链接,输入设备码,并保持当前标签页开启。",
|
|
616
|
+
"deviceCopy": "复制设备码",
|
|
617
|
+
"devicePending": "等待授权确认中",
|
|
618
|
+
"deviceAuthorized": "授权成功",
|
|
619
|
+
"deviceExpired": "设备码已过期,请重试",
|
|
620
|
+
"verifyPrefix": "Google 要求完成一次性账户验证。",
|
|
621
|
+
"verifyLink": "打开验证链接",
|
|
622
|
+
"verifySuffix": ",完成后点击重新连接。",
|
|
623
|
+
"addAccount": "+ 添加账户",
|
|
624
|
+
"save": "保存",
|
|
625
|
+
"saved": "已保存",
|
|
626
|
+
"loading": "加载中…",
|
|
627
|
+
"accountLabel": "账户",
|
|
628
|
+
"plan": "套餐方案",
|
|
629
|
+
"storedAs": "存储标识",
|
|
630
|
+
"switchLabel": "切换服务商",
|
|
631
|
+
"chipActive": "已启用订阅",
|
|
632
|
+
"expiryLabel": "到期时间",
|
|
633
|
+
"slashLogin": "登录订阅服务商",
|
|
634
|
+
"slashLogout": "登出订阅服务商",
|
|
635
|
+
"slashStatus": "查看订阅状态",
|
|
636
|
+
"none": "无",
|
|
637
|
+
"forecast": "≈",
|
|
638
|
+
"windowPrimary": "主配额窗口",
|
|
639
|
+
"windowSecondary": "次配额窗口",
|
|
640
|
+
"resetLabel": "重置倒计时",
|
|
641
|
+
"check": "检测",
|
|
642
|
+
"checking": "检测中…",
|
|
643
|
+
"importToken": "保存令牌",
|
|
644
|
+
"importTokenPlace": "粘贴令牌或 API Key",
|
|
645
|
+
"importLocalCli": "📥 从 CLI 导入",
|
|
646
|
+
"importLocalCliTitle": "从服务器已安装的本地 CLI 自动读取并导入令牌",
|
|
647
|
+
"importLocalSuccess": "已成功从本地 CLI 导入凭据!",
|
|
648
|
+
"howToGetToken": "❓ 获取指引",
|
|
649
|
+
"howToGetTokenTitle": "如何获取此服务商的令牌或 API 密钥",
|
|
650
|
+
"instructionsTitle": "获取令牌指引",
|
|
651
|
+
"close": "关闭",
|
|
652
|
+
"reconnectRequired": "需要重新连接",
|
|
653
|
+
"manualTitle": "若浏览器未能自动跳转回调",
|
|
654
|
+
"updateChecking": "检查更新中…",
|
|
655
|
+
"updateUpToDate": "已是最新版本",
|
|
656
|
+
"updateAvailable": "有新版本可用",
|
|
657
|
+
"updateNow": "立即更新",
|
|
658
|
+
"updating": "正在更新…",
|
|
659
|
+
"updateSuccessRestart": "更新成功,服务重启中…",
|
|
660
|
+
"updateFailed": "更新失败"
|
|
661
|
+
};
|
|
506
662
|
// #51: live countdown to the quota window reset (account.quota.resetAt).
|
|
507
663
|
function cleanErrorMessage(raw) {
|
|
508
664
|
if (!raw) return ''
|
|
@@ -549,17 +705,17 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
549
705
|
function formatRelativeReset(resetAt, lang, now) {
|
|
550
706
|
if (!resetAt || !Number.isFinite(resetAt) || resetAt <= 0) return ''
|
|
551
707
|
var delta = resetAt - (now || Date.now())
|
|
552
|
-
var
|
|
553
|
-
if (delta <= 0) return
|
|
708
|
+
var isZh = lang === 'zh'
|
|
709
|
+
if (delta <= 0) return isZh ? '刚刚' : 'just now'
|
|
554
710
|
var totalMinutes = Math.max(1, Math.round(delta / 60000))
|
|
555
711
|
var days = Math.floor(totalMinutes / 1440)
|
|
556
712
|
var hours = Math.floor((totalMinutes % 1440) / 60)
|
|
557
713
|
var minutes = totalMinutes % 60
|
|
558
714
|
var bits = []
|
|
559
|
-
if (days) bits.push(days + (
|
|
560
|
-
if (hours) bits.push(hours + (
|
|
561
|
-
if (minutes || !bits.length) bits.push(minutes + (
|
|
562
|
-
return (
|
|
715
|
+
if (days) bits.push(days + (isZh ? '天' : 'd'))
|
|
716
|
+
if (hours) bits.push(hours + (isZh ? '小时' : 'h'))
|
|
717
|
+
if (minutes || !bits.length) bits.push(minutes + (isZh ? '分' : 'm'))
|
|
718
|
+
return (isZh ? '' : 'in ') + bits.join(' ') + (isZh ? '后' : '')
|
|
563
719
|
}
|
|
564
720
|
|
|
565
721
|
function ResetCountdown(props) {
|
|
@@ -689,6 +845,43 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
689
845
|
const [hostPing, setHostPing] = React.useState({ ok: true, latencyMs: null })
|
|
690
846
|
const [telemetry, setTelemetry] = React.useState(null)
|
|
691
847
|
const [smoke, setSmoke] = React.useState({ testing: false, result: null })
|
|
848
|
+
const [updater, setUpdater] = React.useState({ checking: false, updating: false, status: null, message: '' })
|
|
849
|
+
|
|
850
|
+
const checkUpdater = () => {
|
|
851
|
+
setUpdater((u) => Object.assign({}, u, { checking: true, message: '' }))
|
|
852
|
+
return fetch('/dsh-subscriptions/update', { cache: 'no-store' })
|
|
853
|
+
.then((res) => res.json())
|
|
854
|
+
.then((data) => {
|
|
855
|
+
setUpdater((u) => Object.assign({}, u, { checking: false, status: data }))
|
|
856
|
+
return data
|
|
857
|
+
})
|
|
858
|
+
.catch((e) => {
|
|
859
|
+
setUpdater((u) => Object.assign({}, u, { checking: false, message: cleanErrorMessage(e.message || e) }))
|
|
860
|
+
})
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
const performUpdate = async () => {
|
|
864
|
+
setUpdater((u) => Object.assign({}, u, { updating: true, message: '' }))
|
|
865
|
+
try {
|
|
866
|
+
const res = await fetch('/dsh-subscriptions/update', {
|
|
867
|
+
method: 'POST',
|
|
868
|
+
headers: {
|
|
869
|
+
'Content-Type': 'application/json',
|
|
870
|
+
'x-dsh-plugin-update': '1',
|
|
871
|
+
},
|
|
872
|
+
})
|
|
873
|
+
const data = await res.json().catch(() => ({}))
|
|
874
|
+
if (!res.ok || !data.ok) {
|
|
875
|
+
throw new Error((data && data.error && (data.error.message || data.error.code)) || ('HTTP ' + res.status))
|
|
876
|
+
}
|
|
877
|
+
setUpdater((u) => Object.assign({}, u, { updating: false, message: t('updateSuccessRestart') }))
|
|
878
|
+
setTimeout(() => {
|
|
879
|
+
window.location.reload()
|
|
880
|
+
}, 3500)
|
|
881
|
+
} catch (e) {
|
|
882
|
+
setUpdater((u) => Object.assign({}, u, { updating: false, message: (t('updateFailed') + ': ' + cleanErrorMessage(e.message || e)) }))
|
|
883
|
+
}
|
|
884
|
+
}
|
|
692
885
|
|
|
693
886
|
const reloadTelemetry = () => {
|
|
694
887
|
fetch('/dsh-subscriptions/telemetry', { cache: 'no-store' })
|
|
@@ -744,6 +937,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
744
937
|
React.useEffect(() => {
|
|
745
938
|
let alive = true
|
|
746
939
|
reload().catch((e) => { if (alive) setErr(String(e && e.message ? e.message : e)) })
|
|
940
|
+
checkUpdater().catch(() => {})
|
|
747
941
|
return () => { alive = false }
|
|
748
942
|
}, [])
|
|
749
943
|
|
|
@@ -965,14 +1159,38 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
965
1159
|
React.createElement('span', {
|
|
966
1160
|
className: 'dsub-badge dsub-badge-neutral'
|
|
967
1161
|
}, 'Pool: ' + accounts.length + ' accounts'),
|
|
1162
|
+
updater.status && updater.status.updateAvailable
|
|
1163
|
+
? React.createElement('span', { className: 'dsub-badge dsub-badge-warn' },
|
|
1164
|
+
'⬆ ' + t('updateAvailable') + ' (v' + (updater.status.latestVersion || '') + ')'
|
|
1165
|
+
)
|
|
1166
|
+
: (updater.status && updater.status.currentVersion
|
|
1167
|
+
? React.createElement('span', { className: 'dsub-badge dsub-badge-neutral' },
|
|
1168
|
+
'v' + updater.status.currentVersion
|
|
1169
|
+
)
|
|
1170
|
+
: null
|
|
1171
|
+
),
|
|
1172
|
+
),
|
|
1173
|
+
React.createElement('div', { className: 'dsub-row', style: { marginTop: 0, gap: '8px' } },
|
|
1174
|
+
updater.status && updater.status.updateAvailable && updater.status.canAutoUpdate
|
|
1175
|
+
? React.createElement('button', {
|
|
1176
|
+
type: 'button',
|
|
1177
|
+
className: 'dsub-mini dsub-btn-primary',
|
|
1178
|
+
disabled: updater.updating,
|
|
1179
|
+
onClick: () => performUpdate(),
|
|
1180
|
+
}, updater.updating ? t('updating') : (t('updateNow') + ' → v' + updater.status.latestVersion))
|
|
1181
|
+
: null,
|
|
1182
|
+
React.createElement('button', {
|
|
1183
|
+
type: 'button',
|
|
1184
|
+
className: 'dsub-mini',
|
|
1185
|
+
disabled: smoke.testing,
|
|
1186
|
+
onClick: () => runSmokeTest(),
|
|
1187
|
+
}, smoke.testing ? t('smokeTesting') : t('runSmoke')),
|
|
968
1188
|
),
|
|
969
|
-
React.createElement('button', {
|
|
970
|
-
type: 'button',
|
|
971
|
-
className: 'dsub-mini dsub-btn-primary',
|
|
972
|
-
disabled: smoke.testing,
|
|
973
|
-
onClick: () => runSmokeTest(),
|
|
974
|
-
}, smoke.testing ? t('smokeTesting') : t('runSmoke')),
|
|
975
1189
|
),
|
|
1190
|
+
updater.message ? React.createElement('div', {
|
|
1191
|
+
className: updater.message.includes(t('updateSuccessRestart')) ? 'dsub-alert-ok' : 'dsub-alert-bad',
|
|
1192
|
+
style: { marginBottom: '12px' }
|
|
1193
|
+
}, updater.message) : null,
|
|
976
1194
|
smoke.result ? React.createElement('div', {
|
|
977
1195
|
className: smoke.result.ok ? 'dsub-alert-ok' : 'dsub-alert-bad',
|
|
978
1196
|
style: { marginBottom: '12px' }
|
|
@@ -1304,12 +1522,12 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1304
1522
|
const prov = row.slot.provider;
|
|
1305
1523
|
const info = (typeof INSTRUCTIONS !== 'undefined' && INSTRUCTIONS[prov]) || {
|
|
1306
1524
|
title: prov,
|
|
1307
|
-
|
|
1525
|
+
stepsZh: ['请在上方输入框中粘贴令牌或 API Key 并点击“保存令牌”。'],
|
|
1308
1526
|
stepsEn: ['Paste your token or API key into the field above and click "Save token".'],
|
|
1309
1527
|
};
|
|
1310
|
-
// Determine language safely from
|
|
1311
|
-
const
|
|
1312
|
-
const steps =
|
|
1528
|
+
// Determine language safely from current locale (fallback to zh if Chinese or en)
|
|
1529
|
+
const isZh = t('instructionsTitle').indexOf('获取') >= 0 || t('close') === '关闭';
|
|
1530
|
+
const steps = (isZh && info.stepsZh) ? info.stepsZh : (info.stepsEn || []);
|
|
1313
1531
|
return React.createElement('div', { className: 'dsub-helpBox' },
|
|
1314
1532
|
React.createElement('div', { className: 'dsub-helpHead' },
|
|
1315
1533
|
React.createElement('span', null, 'ℹ️ ' + info.title + ' — ' + t('instructionsTitle')),
|
|
@@ -1330,7 +1548,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1330
1548
|
rel: 'noopener noreferrer',
|
|
1331
1549
|
className: 'dsub-mini',
|
|
1332
1550
|
style: { display: 'inline-flex', alignItems: 'center', gap: 4, textDecoration: 'none' },
|
|
1333
|
-
}, '🔗 ' + (
|
|
1551
|
+
}, '🔗 ' + (isZh ? '打开服务商控制台' : 'Open provider console'))
|
|
1334
1552
|
) : null
|
|
1335
1553
|
);
|
|
1336
1554
|
})() : null),
|
|
@@ -1510,7 +1728,7 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1510
1728
|
var agoStr = null
|
|
1511
1729
|
if (at) {
|
|
1512
1730
|
var m = Math.round((Date.now() - at) / 60000)
|
|
1513
|
-
if (m >= 1) agoStr = m + 'm ' + (t('
|
|
1731
|
+
if (m >= 1) agoStr = m + 'm ' + (t('close') === '关闭' ? '前' : 'ago')
|
|
1514
1732
|
}
|
|
1515
1733
|
|
|
1516
1734
|
return React.createElement('div', { className: 'dsub-panel-box' },
|
|
@@ -1594,9 +1812,9 @@ const cssId = 'dsh-subscriptions/settings.module.css'
|
|
|
1594
1812
|
}
|
|
1595
1813
|
}
|
|
1596
1814
|
ctx.effect(() => {
|
|
1597
|
-
const undo = [addLocale('en', en)]
|
|
1815
|
+
const undo = [addLocale('en', en), addLocale('zh', zh)]
|
|
1598
1816
|
return () => { for (const off of undo) off() }
|
|
1599
|
-
}, 'dsh-subscriptions:
|
|
1817
|
+
}, 'dsh-subscriptions: locales')
|
|
1600
1818
|
// Labels outside a component take the translator bound to the namespace.
|
|
1601
1819
|
setT(ctx.locale.bind(NS))
|
|
1602
1820
|
// The canonical place is the Plugins tab: a card with its own header
|