dsh-connect-minimaxcode 0.6.6 → 0.6.7
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.en.md +10 -0
- package/README.md +10 -0
- package/lib/client.js +219 -76
- package/lib/index.js +21 -17
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -384,6 +384,15 @@ The OAuth token is valid for about an hour and is refreshed automatically by the
|
|
|
384
384
|
|
|
385
385
|
## Changelog
|
|
386
386
|
|
|
387
|
+
### 0.6.7 (2026-11-12)
|
|
388
|
+
|
|
389
|
+
- **Merged PR [#5](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/5) by [azsora](https://github.com/azsora)**:
|
|
390
|
+
- **The remaining percent the documented quota endpoint sends is read too**. Once a plan is held, the upstream renamed the window field from the share *used* (`"19%"`) to the share *remaining* (`90`); 0.6.5 already reads both shapes. This PR closes the other gap — when the documented endpoint answers **200 but carries a shape that cannot be read**, the older path is tried too, not only when the request throws.
|
|
391
|
+
- **The surfaces are reworked**: the pill detail and the plugin page card are now rounded sub-cards (balance, limit windows, check-in and expiring grants each in their own), and the generation panel wears the same shell.
|
|
392
|
+
- **Why there are no windows is now named**: instead of one "unavailable", each state is told apart — **not signed in / the sign-in expired / no subscription / the read failed** — and a failed read shows the upstream's own words. That is the part a free account needs most.
|
|
393
|
+
- **"Expiring soon" now covers three days**, and the count and the wording agree (the upstream's lots expire day by day over thirty-odd days; the window is a display choice, not an upstream fact).
|
|
394
|
+
- Together with the window fix in 0.6.5, a subscribed account now sees both its five-hour window and its weekly window read correctly.
|
|
395
|
+
|
|
387
396
|
### 0.6.6 (2026-11-12)
|
|
388
397
|
|
|
389
398
|
- **The README gained a "Feedback and contributions" section**: an email address (1628451685@qq.com), issues and pull requests, and a note that reports from **free accounts** are the ones most wanted — a subscribed account can read the limit windows and the plan tier, so some problems only show up on a free account and I may not be able to reproduce them here. It also lists what is worth including in a report, and invites a PR for anyone who fixes it themselves.
|
|
@@ -633,6 +642,7 @@ Fixing it yourself is even better — just open the PR. The format does not matt
|
|
|
633
642
|
- **[eghrhegpe](https://github.com/eghrhegpe)** — raised the request for a plugin-page icon and Chinese/English copy ([#1](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/issues/1)). It became 0.5.2: the plugin page has its own SVG artwork, the title and the introduction come in both languages, and the interface follows the host's language.
|
|
634
643
|
- **[azsora](https://github.com/azsora)** — found the plan's rate-limit endpoint in the desktop app's own traffic and wired the 5-hour and weekly usage windows into the credit pill and the plugin page ([#4](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/4)): the window length is derived from the response's own edges rather than hard-coded. It became 0.5.5.
|
|
635
644
|
- **[qzz19970504](https://github.com/qzz19970504)** — found and fixed the wiring gap that made every image upload fail ([#3](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/3)): the catalog declared the `image` modality, but the adapter never connected the host's durable attachment service, so pi-ai refused image requests before they were sent. The fix became 0.5.3.
|
|
645
|
+
- **[azsora](https://github.com/azsora)** — found that the official quota endpoint renamed its window field and inverted its meaning (the share used became the share remaining), closed the gap where a 200 with an unreadable shape never reached the older path, and reworked the pill detail and the page card into rounded sub-cards that say why there are no windows ([#5](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/5)). It became 0.6.7.
|
|
636
646
|
- Plugin author: **[JeffTsuiCUG](https://github.com/JeffTsuiCUG)**
|
|
637
647
|
|
|
638
648
|
## Acknowledgements
|
package/README.md
CHANGED
|
@@ -395,6 +395,15 @@ OAuth 令牌有效期约 1 小时,由客户端自动续期。插件**只在自
|
|
|
395
395
|
|
|
396
396
|
## 更新记录
|
|
397
397
|
|
|
398
|
+
### 0.6.7(2026-11-12)
|
|
399
|
+
|
|
400
|
+
- **并入 PR [#5](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/5)(作者 [azsora](https://github.com/azsora))**:
|
|
401
|
+
- **官方额度接口的「剩余百分比」也能读**。上游在有套餐后把窗口字段从「已用百分比」(`"19%"`)改成了「**剩余**百分比」(`90`),插件在 0.6.5 已能读这两种形态;本 PR 补上了另一处缺口 —— 官方接口回 **200 但形状读不懂**时也要回落旧路径,而不只是抛异常时才回落。
|
|
402
|
+
- **界面重排**:积分胶囊的详情与插件页卡片改为圆角分栏(余额、额度窗口、签到、到期批次各成一栏),生成面板沿用同样的外框。
|
|
403
|
+
- **没有窗口时说明原因**:不再只显示「不可用」,而是逐项区分**未登录 / 登录态已过期 / 未订阅 / 读取失败**,失败时把上游原话显示出来 —— 对免费账户最有用。
|
|
404
|
+
- **即将过期改为 3 天口径**:计数与文案一致(上游的批次按天到期、铺开三十多天,这个窗口是显示口径而非上游事实)。
|
|
405
|
+
- 本次合并与 0.6.5 的窗口修复一并生效:订阅账户的 5 小时窗口与周窗口现在都能正确显示。
|
|
406
|
+
|
|
398
407
|
### 0.6.6(2026-11-12)
|
|
399
408
|
|
|
400
409
|
- **README 新增「反馈与参与」**:留下邮箱(1628451685@qq.com)、issue 与 PR 三种联系方式,并说明**特别希望听到免费账户的反馈** —— 订阅账户能读到限额窗口与套餐档位,有些问题只有免费账户才会遇到、而我在本地未必能复现。附上了报问题时值得带上的几样信息,以及「自己修好直接提 PR」的邀请。
|
|
@@ -646,6 +655,7 @@ dsh plugin --profile desktop add dsh-qoder-connect
|
|
|
646
655
|
- **[eghrhegpe](https://github.com/eghrhegpe)** —— 提出了插件页图标与中英文文案的建议([#1](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/issues/1))。这条建议促成了 0.5.2:插件页有了自己的 SVG 图标,标题与简介有了中英两版,界面文案也开始跟随宿主语言。
|
|
647
656
|
- **[azsora](https://github.com/azsora)** —— 从桌面 App 的实际调用里找出了套餐的**限额窗口接口**,并把 5 小时与每周两个窗口的用量接进了积分胶囊与插件页卡片([#4](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/4)):窗口时长从响应自身的起止字段推导,不写死。该功能成为 0.5.5。
|
|
648
657
|
- **[qzz19970504](https://github.com/qzz19970504)** —— 定位并修复了「发图必然失败」的接线缺口([#3](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/3)):模型目录声明了 `image` 模态,但适配器没有把宿主的持久化附件服务接上,导致带图请求被 pi-ai 在发出前拒掉。该修复成为 0.5.3。
|
|
658
|
+
- **[azsora](https://github.com/azsora)** —— 找出官方额度接口改名并反转语义(「已用百分比」→「剩余百分比」)这件事,补上了「200 但形状读不懂时也要回落旧路径」的缺口,并把积分胶囊与插件页卡片重排为圆角分栏、让「为什么没有窗口」逐项有说法([#5](https://github.com/JeffTsuiCUG/dsh-connect-minimaxcode/pull/5))。该合并成为 0.6.7。
|
|
649
659
|
- 插件作者:**[JeffTsuiCUG](https://github.com/JeffTsuiCUG)**
|
|
650
660
|
|
|
651
661
|
## 致谢
|
package/lib/client.js
CHANGED
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
*/
|
|
31
31
|
const MINIMAX_PROVIDER_ID = 'minimaxcode';
|
|
32
32
|
const zh = {
|
|
33
|
-
title: 'MiniMax Code
|
|
33
|
+
title: 'MiniMax Code',
|
|
34
34
|
balance: '积分 {total}',
|
|
35
|
-
free: '
|
|
35
|
+
free: '签到积分',
|
|
36
36
|
purchased: '购买积分',
|
|
37
37
|
consumed: '上次读取后消耗',
|
|
38
38
|
tokens: '本机累计 {count} tokens',
|
|
@@ -44,9 +44,15 @@
|
|
|
44
44
|
signedOut: '未登录 MiniMax Code',
|
|
45
45
|
expiring: '即将过期',
|
|
46
46
|
expiringIn: '{amount} 积分 · {date} 到期',
|
|
47
|
-
expiringNone: '
|
|
47
|
+
expiringNone: '3 天内没有积分到期',
|
|
48
48
|
expiringSoon: '30 天内到期 {amount} 积分',
|
|
49
|
+
expiring3d: '3天内过期',
|
|
50
|
+
expiringAmount: '{amount} 积分',
|
|
49
51
|
nextExpiry: '最近到期日',
|
|
52
|
+
creditsBox: '积分',
|
|
53
|
+
planTitle: 'Plan 额度',
|
|
54
|
+
usageTitle: '本机用量',
|
|
55
|
+
usageTotal: '累计',
|
|
50
56
|
lots: '到期批次',
|
|
51
57
|
noLots: '没有待到期的积分批次',
|
|
52
58
|
loginTitle: '登录',
|
|
@@ -110,9 +116,9 @@
|
|
|
110
116
|
generateNoInput: '输入框不接受附件。',
|
|
111
117
|
};
|
|
112
118
|
const en = {
|
|
113
|
-
title: 'MiniMax Code
|
|
119
|
+
title: 'MiniMax Code',
|
|
114
120
|
balance: '{total} credits',
|
|
115
|
-
free: '
|
|
121
|
+
free: 'Check-in credits',
|
|
116
122
|
purchased: 'Purchased credits',
|
|
117
123
|
consumed: 'Spent since last read',
|
|
118
124
|
tokens: '{count} tokens used here',
|
|
@@ -124,9 +130,15 @@
|
|
|
124
130
|
signedOut: 'MiniMax Code not signed in',
|
|
125
131
|
expiring: 'Expiring soon',
|
|
126
132
|
expiringIn: '{amount} credits · expires {date}',
|
|
127
|
-
expiringNone: 'No credits expire within
|
|
133
|
+
expiringNone: 'No credits expire within 3 days',
|
|
128
134
|
expiringSoon: '{amount} credits expire within 30 days',
|
|
135
|
+
expiring3d: 'Expires in 3 days',
|
|
136
|
+
expiringAmount: '{amount} credits',
|
|
129
137
|
nextExpiry: 'Next expiry',
|
|
138
|
+
creditsBox: 'Credits',
|
|
139
|
+
planTitle: 'Plan allowance',
|
|
140
|
+
usageTitle: 'Local usage',
|
|
141
|
+
usageTotal: 'Total',
|
|
130
142
|
lots: 'Expiring grants',
|
|
131
143
|
noLots: 'No grant is waiting to expire',
|
|
132
144
|
loginTitle: 'Sign-in',
|
|
@@ -285,17 +297,22 @@
|
|
|
285
297
|
function row(label, value) {
|
|
286
298
|
return h('div', { style: rowStyle }, h('span', null, label), h('span', null, value));
|
|
287
299
|
}
|
|
288
|
-
/**
|
|
289
|
-
|
|
300
|
+
/**
|
|
301
|
+
* The expiry rows, shared by the pill's detail and the page card.
|
|
302
|
+
*
|
|
303
|
+
* The label names the window the count actually covers, and the value
|
|
304
|
+
* carries only the amount: when the lot falls due is the next row's
|
|
305
|
+
* business, not a suffix on this one.
|
|
306
|
+
*/
|
|
307
|
+
function expiryRows(t, credits_) {
|
|
290
308
|
if (credits_ === undefined)
|
|
291
|
-
return
|
|
309
|
+
return [];
|
|
292
310
|
const expiring = credits_.expiring ?? 0;
|
|
293
311
|
const next = credits_.nextExpiryAtMs;
|
|
294
|
-
return
|
|
295
|
-
? t('
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
: null, next !== undefined ? row(t('nextExpiry'), day(next)) : null);
|
|
312
|
+
return [
|
|
313
|
+
h('div', { style: rowStyle }, h('span', null, t('expiring3d')), h('span', { style: expiring > 0 ? warnStyle : undefined }, expiring > 0 ? t('expiringAmount', { amount: credits(expiring, 2) }) : t('expiringNone'))),
|
|
314
|
+
next !== undefined ? row(t('nextExpiry'), day(next)) : null,
|
|
315
|
+
];
|
|
299
316
|
}
|
|
300
317
|
/**
|
|
301
318
|
* A countdown to a window reset, coarse on purpose: minutes below an
|
|
@@ -337,42 +354,44 @@
|
|
|
337
354
|
* rather than written into the label: the plan's terms have changed
|
|
338
355
|
* before, and the display should follow whatever the server says.
|
|
339
356
|
*/
|
|
340
|
-
function
|
|
357
|
+
function rateLimitRows(t, rateLimit) {
|
|
341
358
|
if (rateLimit === undefined || rateLimit.state !== 'ok')
|
|
342
|
-
return
|
|
359
|
+
return [];
|
|
343
360
|
if (rateLimit.interval === undefined || rateLimit.weekly === undefined)
|
|
344
|
-
return
|
|
345
|
-
return
|
|
361
|
+
return [];
|
|
362
|
+
return [
|
|
363
|
+
row(t('usageWindow', { duration: windowLabel(rateLimit.interval) }), `${rateLimit.interval.usedPercent}%(${t('resetIn', { duration: countdown(rateLimit.interval.resetAtMs) })})`),
|
|
364
|
+
row(t('usageWindow', { duration: windowLabel(rateLimit.weekly) }), `${rateLimit.weekly.usedPercent}%(${t('resetIn', { duration: countdown(rateLimit.weekly.resetAtMs) })})`),
|
|
365
|
+
];
|
|
346
366
|
}
|
|
347
367
|
/**
|
|
348
|
-
* The account's own usage history.
|
|
368
|
+
* The account's own usage history, as label lines.
|
|
349
369
|
*
|
|
350
370
|
* This is not the token count of this host — the pill reports that
|
|
351
371
|
* separately — but the account's days, consumption and standing, which
|
|
352
|
-
* is what the upstream knows and nobody else does.
|
|
372
|
+
* is what the upstream knows and nobody else does. It belongs to the
|
|
373
|
+
* page card only, laid out in two columns.
|
|
353
374
|
*/
|
|
354
|
-
function
|
|
375
|
+
function usageProfileRows(t, profile) {
|
|
355
376
|
if (profile === undefined || profile.state !== 'ok')
|
|
356
|
-
return
|
|
357
|
-
const
|
|
377
|
+
return [];
|
|
378
|
+
const lines = [];
|
|
358
379
|
if (profile.consecutiveDays !== undefined && profile.consecutiveDays > 0) {
|
|
359
|
-
|
|
380
|
+
lines.push(t('usageStreak', { days: profile.consecutiveDays }));
|
|
360
381
|
}
|
|
361
382
|
if (profile.activeDays !== undefined) {
|
|
362
|
-
|
|
383
|
+
lines.push(t('usageActiveDays', { active: profile.activeDays, total: profile.totalDays ?? '' }));
|
|
363
384
|
}
|
|
364
385
|
if (profile.totalTokenConsumed !== undefined) {
|
|
365
|
-
|
|
386
|
+
lines.push(t('usageTotalTokens', { count: profile.totalTokenConsumed }));
|
|
366
387
|
}
|
|
367
388
|
if (profile.rankingPercent !== undefined) {
|
|
368
|
-
|
|
389
|
+
lines.push(t('usageRanking', { percent: profile.rankingPercent.toFixed(1) }));
|
|
369
390
|
}
|
|
370
391
|
if (profile.mostActiveDay?.date !== undefined) {
|
|
371
|
-
|
|
392
|
+
lines.push(t('usageBestDay', { date: profile.mostActiveDay.date }));
|
|
372
393
|
}
|
|
373
|
-
|
|
374
|
-
return null;
|
|
375
|
-
return h('div', { style: expiryBoxStyle, 'data-minimax-usage-profile': true }, ...rows);
|
|
394
|
+
return lines;
|
|
376
395
|
}
|
|
377
396
|
/**
|
|
378
397
|
* Why there are no usage windows, when there are none because the plan
|
|
@@ -387,6 +406,33 @@
|
|
|
387
406
|
return null;
|
|
388
407
|
return h('div', { style: mutedStyle, 'data-minimax-plan-notice': true }, t('planNoSubscription'));
|
|
389
408
|
}
|
|
409
|
+
/**
|
|
410
|
+
* What the page card's plan box says when it has no windows to show.
|
|
411
|
+
*
|
|
412
|
+
* An empty box would read as "nothing loaded yet" and invite a refresh
|
|
413
|
+
* that cannot help. Every state has a reason worth naming: no sign-in,
|
|
414
|
+
* a stale one, a plan without windows, or a read that failed — the
|
|
415
|
+
* upstream's own message is shown as it arrived, because it is the
|
|
416
|
+
* most specific answer available.
|
|
417
|
+
*/
|
|
418
|
+
function rateLimitFallback(t, rateLimit) {
|
|
419
|
+
if (rateLimit === undefined) {
|
|
420
|
+
return h('div', { style: mutedStyle }, t('unavailable'));
|
|
421
|
+
}
|
|
422
|
+
if (rateLimit.reason === 'no-subscription') {
|
|
423
|
+
return h('div', { style: mutedStyle, 'data-minimax-plan-notice': true }, t('planNoSubscription'));
|
|
424
|
+
}
|
|
425
|
+
if (rateLimit.state === 'signed-out') {
|
|
426
|
+
return h('div', { style: mutedStyle }, t('signedOut'));
|
|
427
|
+
}
|
|
428
|
+
if (rateLimit.state === 'expired') {
|
|
429
|
+
return h('div', { style: mutedStyle }, t('expired'));
|
|
430
|
+
}
|
|
431
|
+
if (rateLimit.error !== undefined && rateLimit.error.length > 0) {
|
|
432
|
+
return h('div', { style: mutedStyle }, rateLimit.error);
|
|
433
|
+
}
|
|
434
|
+
return h('div', { style: mutedStyle }, t('unavailable'));
|
|
435
|
+
}
|
|
390
436
|
/**
|
|
391
437
|
* The plan the account holds, when it holds one.
|
|
392
438
|
*
|
|
@@ -408,6 +454,13 @@
|
|
|
408
454
|
function CreditPill(props) {
|
|
409
455
|
const [status] = useStatus();
|
|
410
456
|
const [open, setOpen] = React.useState(false);
|
|
457
|
+
// Two ways to open, and they must not fight each other: hovering the
|
|
458
|
+
// pill shows the detail, and a click pins it so it stays after the
|
|
459
|
+
// cursor leaves. A pinned detail closes on the next click — even under
|
|
460
|
+
// the cursor, which is why the click also suppresses the hover that is
|
|
461
|
+
// still in progress until the cursor actually leaves.
|
|
462
|
+
const pinned = React.useRef(false);
|
|
463
|
+
const skipHover = React.useRef(false);
|
|
411
464
|
const t = props.t ?? ((key, params) => {
|
|
412
465
|
const template = zh[key] ?? key;
|
|
413
466
|
return template.replace(/\{(\w+)\}/g, (_match, name) => String(params?.[name] ?? ''));
|
|
@@ -434,19 +487,39 @@
|
|
|
434
487
|
? balance
|
|
435
488
|
: `${windowLabel(windows.interval)} ${windows.interval.usedPercent}%(${t('resetIn', { duration: countdown(windows.interval.resetAtMs) })})`;
|
|
436
489
|
const detail = available
|
|
437
|
-
? h('div', { style: detailStyle, 'data-minimax-credits-detail': true }, row(t('free'), credits(credits_.free, 3)), row(t('purchased'), credits(credits_.purchased, 3)), credits_.consumed !== undefined && credits_.consumed > 0
|
|
490
|
+
? h('div', { style: detailStyle, 'data-minimax-credits-detail': true }, h('div', { style: detailFlowStyle }, h('div', { style: boxStyle }, row(t('free'), credits(credits_.free, 3)), row(t('purchased'), credits(credits_.purchased, 3)), credits_.consumed !== undefined && credits_.consumed > 0
|
|
438
491
|
? row(t('consumed'), credits(credits_.consumed, 3))
|
|
439
|
-
: null,
|
|
440
|
-
? h('div', { style:
|
|
441
|
-
: null, credits_.updatedAtMs !== undefined
|
|
442
|
-
?
|
|
443
|
-
: null)
|
|
492
|
+
: null), h('div', { style: boxStyle, 'data-minimax-credits-expiry': true }, ...expiryRows(t, credits_)), h('div', { style: boxStyle, 'data-minimax-rate-limit': true }, ...rateLimitRows(t, status?.rateLimit)), usage !== undefined
|
|
493
|
+
? h('div', { style: boxStyle }, row(t('usageTotal'), `${tokens(usage.inputTokens + usage.outputTokens + usage.cacheReadTokens)} tokens(≈¥${money(usage.estimatedCny)})`))
|
|
494
|
+
: null, planNotice(t, status?.rateLimit), credits_.updatedAtMs !== undefined
|
|
495
|
+
? h('div', { style: mutedStyle }, t('updated', { time: new Date(credits_.updatedAtMs).toLocaleTimeString() }))
|
|
496
|
+
: null))
|
|
444
497
|
: null;
|
|
445
498
|
return h('div', { style: wrapStyle, 'data-minimax-credits': true }, h('button', {
|
|
446
499
|
type: 'button',
|
|
447
500
|
style: buttonStyle,
|
|
448
501
|
title: t('title'),
|
|
449
|
-
|
|
502
|
+
onMouseEnter: () => {
|
|
503
|
+
if (pinned.current || skipHover.current)
|
|
504
|
+
return;
|
|
505
|
+
setOpen(true);
|
|
506
|
+
},
|
|
507
|
+
onMouseLeave: () => {
|
|
508
|
+
skipHover.current = false;
|
|
509
|
+
if (!pinned.current)
|
|
510
|
+
setOpen(false);
|
|
511
|
+
},
|
|
512
|
+
onClick: () => {
|
|
513
|
+
if (pinned.current) {
|
|
514
|
+
pinned.current = false;
|
|
515
|
+
skipHover.current = open;
|
|
516
|
+
setOpen(false);
|
|
517
|
+
}
|
|
518
|
+
else {
|
|
519
|
+
pinned.current = true;
|
|
520
|
+
setOpen(true);
|
|
521
|
+
}
|
|
522
|
+
},
|
|
450
523
|
}, label), open ? detail : null);
|
|
451
524
|
}
|
|
452
525
|
/**
|
|
@@ -712,10 +785,10 @@
|
|
|
712
785
|
if (login === undefined)
|
|
713
786
|
return null;
|
|
714
787
|
if (login.state === 'signed-in') {
|
|
715
|
-
return h('div', { style:
|
|
788
|
+
return h('div', { style: boxStyle, 'data-minimax-login': true }, row(t('loginTitle'), login.source === 'plugin' ? t('loginPlugin') : t('loginDesktop')));
|
|
716
789
|
}
|
|
717
790
|
if (login.state === 'pending') {
|
|
718
|
-
return h('div', { style:
|
|
791
|
+
return h('div', { style: boxStyle, 'data-minimax-login': true }, row(t('loginTitle'), t('loginPending')), login.userCode !== undefined && login.userCode.length > 0
|
|
719
792
|
? row(t('loginCode'), login.userCode)
|
|
720
793
|
: null, login.verificationUriComplete !== undefined && login.verificationUriComplete.length > 0
|
|
721
794
|
? h('a', {
|
|
@@ -726,7 +799,7 @@
|
|
|
726
799
|
}, t('loginOpen'))
|
|
727
800
|
: null, h('button', { type: 'button', style: actionButtonStyle, onClick: () => { void act('login-poll'); } }, t('loginPolled')));
|
|
728
801
|
}
|
|
729
|
-
return h('div', { style:
|
|
802
|
+
return h('div', { style: boxStyle, 'data-minimax-login': true }, row(t('loginTitle'), t('loginSignedOut')), h('button', { type: 'button', style: actionButtonStyle, onClick: () => { void act('login-start'); } }, t('loginStart')));
|
|
730
803
|
}
|
|
731
804
|
/**
|
|
732
805
|
* The check-in block.
|
|
@@ -739,9 +812,15 @@
|
|
|
739
812
|
function signinBlock(t, signin, refresh) {
|
|
740
813
|
if (signin === undefined)
|
|
741
814
|
return null;
|
|
742
|
-
|
|
815
|
+
// The day's status sits at the title's right end: it is a property of
|
|
816
|
+
// the whole block, not of the day-count row below it.
|
|
817
|
+
return h('div', { style: boxStyle, 'data-minimax-signin': true }, h('div', { style: rowStyle }, h('span', { style: boxTitleStyle }, t('signinTitle')), h('span', { style: mutedStyle }, signin.today === undefined
|
|
818
|
+
? t('signinUnavailable')
|
|
819
|
+
: signin.today.claimed
|
|
820
|
+
? t('signinClaimed')
|
|
821
|
+
: signin.today.claimable ? t('signinClaimable') : t('signinUnavailable'))), signin.today === undefined
|
|
743
822
|
? h('div', { style: mutedStyle }, t('signinUnavailable'))
|
|
744
|
-
: h('div', { style: rowStyle }, h('span', null, t('signinToday', { day: signin.today.dayNo, points: signin.today.points }))
|
|
823
|
+
: h('div', { style: rowStyle }, h('span', null, t('signinToday', { day: signin.today.dayNo, points: signin.today.points }))), (signin.days ?? []).length === 0
|
|
745
824
|
? null
|
|
746
825
|
: h('div', { style: dayRowStyle }, ...(signin.days ?? []).map(day_ => h('span', {
|
|
747
826
|
key: `day-${day_.dayNo}`,
|
|
@@ -882,7 +961,7 @@
|
|
|
882
961
|
style: selectStyle,
|
|
883
962
|
onChange: (event) => onChange(event.target.value),
|
|
884
963
|
}, ...values.map(option => h('option', { key: option, value: option }, option)));
|
|
885
|
-
return h('div', { style:
|
|
964
|
+
return h('div', { style: genCardStyle, 'data-minimax-generation': true }, h('div', { style: cardTitleStyle }, t('generateTitle')), h('div', { style: rowStyle }, h('button', {
|
|
886
965
|
type: 'button',
|
|
887
966
|
style: mode === 'image' ? tabActiveStyle : actionButtonStyle,
|
|
888
967
|
onClick: () => { setMode('image'); setPicture(undefined); setVideo(undefined); },
|
|
@@ -934,13 +1013,47 @@
|
|
|
934
1013
|
// usable even when the balance cannot be read.
|
|
935
1014
|
h(GenerationPanel, props));
|
|
936
1015
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1016
|
+
// The page's rows of hairline sections are gone: the card now reads as
|
|
1017
|
+
// a two-column grid of soft sub-cards. Row one is the balance and the
|
|
1018
|
+
// plan's windows, row two the check-in cycle and the next grants due,
|
|
1019
|
+
// row three this host's own usage across both columns, row four the
|
|
1020
|
+
// account's usage history across both.
|
|
1021
|
+
const lots = [...(credits_.upcoming ?? [])].sort((a, b) => a.expireAtMs - b.expireAtMs);
|
|
1022
|
+
const windows = rateLimitRows(t, status?.rateLimit);
|
|
1023
|
+
const profileLines = usageProfileRows(t, status?.usageProfile);
|
|
1024
|
+
const updated = credits_.updatedAtMs !== undefined
|
|
1025
|
+
? t('updated', { time: new Date(credits_.updatedAtMs).toLocaleTimeString() })
|
|
1026
|
+
: undefined;
|
|
1027
|
+
const loginLine = login?.state === 'signed-in'
|
|
1028
|
+
? (login.source === 'plugin' ? t('loginPlugin') : t('loginDesktop'))
|
|
1029
|
+
: undefined;
|
|
1030
|
+
return h('div', { style: pageStyle, 'data-minimax-page': true }, h('div', { style: cardStyle, 'data-minimax-credits-card': true }, h('div', { style: cardHeadStyle }, h('span', { style: cardTitleStyle }, t('title')), h('span', { style: mutedStyle }, [loginLine, updated].filter(line => line !== undefined).join(' · '))), h('div', { style: gridStyle }, h('div', { style: boxStyle }, h('div', { style: boxTitleStyle }, t('creditsBox')), h('div', { style: bigNumberStyle }, credits(credits_.total, 2)), row(t('free'), credits(credits_.free, 3)), row(t('purchased'), credits(credits_.purchased, 3)), ...expiryRows(t, credits_).slice(0, 1)), h('div', { style: boxStyle, 'data-minimax-rate-limit': true }, h('div', { style: boxTitleStyle }, t('planTitle')), ...(windows.length > 0
|
|
1031
|
+
? [
|
|
1032
|
+
// The invisible number keeps the windows lined up with
|
|
1033
|
+
// the balance box's rows, which sit below its own big
|
|
1034
|
+
// number. With no windows to align, it is only noise.
|
|
1035
|
+
h('div', { style: { ...bigNumberStyle, visibility: 'hidden' }, 'aria-hidden': 'true' }, '—'),
|
|
1036
|
+
...windows,
|
|
1037
|
+
]
|
|
1038
|
+
: [rateLimitFallback(t, status?.rateLimit)])),
|
|
1039
|
+
// The tier and the day it ends, for an account that holds one.
|
|
1040
|
+
// An account without a plan has nothing to say here.
|
|
1041
|
+
planLine(t, status?.plan), signinBlock(t, signin, refresh), h('div', { style: boxStyle }, h('div', { style: boxTitleStyle }, t('lots')),
|
|
1042
|
+
// The upstream already caps the list at the five soonest lots
|
|
1043
|
+
// with credits left; the sort only keeps that promise honest.
|
|
1044
|
+
...(lots.length === 0
|
|
1045
|
+
? [h('div', { style: mutedStyle }, t('noLots'))]
|
|
1046
|
+
: lots.map((lot, index) => h('div', { key: `${lot.expireAtMs}-${index}`, style: rowStyle }, h('span', null, day(lot.expireAtMs)), h('span', null, credits(lot.amount, 2)))))), usage !== undefined
|
|
1047
|
+
? h('div', { style: { ...boxStyle, ...span2Style }, 'data-minimax-usage': true }, h('div', { style: boxTitleStyle }, t('usageTitle')), row(t('usageTotal'), `${tokens(usage.inputTokens + usage.outputTokens + usage.cacheReadTokens)} tokens(≈¥${money(usage.estimatedCny)})`), usage.requests > 0 ? row(t('requests', { count: usage.requests }), usage.lastModel ?? '') : null)
|
|
1048
|
+
: null, profileLines.length > 0
|
|
1049
|
+
? h('div', { style: { ...boxStyle, ...span2Style }, 'data-minimax-usage-profile': true }, h('div', { style: boxTitleStyle }, t('accountUsage')), h('div', { style: profileGridStyle }, ...profileLines.map((line, index) => h('div', { key: `profile-${index}` }, line))))
|
|
1050
|
+
: null, login !== undefined && login.state !== 'signed-in'
|
|
1051
|
+
? h('div', { style: span2Style }, loginBlock(t, login, refresh))
|
|
1052
|
+
: null)),
|
|
1053
|
+
// The generation panel does not need the balance: it spends the
|
|
1054
|
+
// account's generation allowance, not its credits, so it stays
|
|
1055
|
+
// usable even when the balance cannot be read.
|
|
1056
|
+
h(GenerationPanel, props));
|
|
944
1057
|
}
|
|
945
1058
|
const wrapStyle = {
|
|
946
1059
|
position: 'relative',
|
|
@@ -980,13 +1093,13 @@
|
|
|
980
1093
|
bottom: 'calc(100% + 6px)',
|
|
981
1094
|
left: 0,
|
|
982
1095
|
zIndex: 10,
|
|
983
|
-
minWidth: '
|
|
984
|
-
borderRadius: '
|
|
1096
|
+
minWidth: '260px',
|
|
1097
|
+
borderRadius: '14px',
|
|
985
1098
|
background: 'var(--dsh-panel-background, Canvas)',
|
|
986
1099
|
color: 'var(--dsh-panel-foreground, CanvasText)',
|
|
987
1100
|
border: '1px solid var(--dsh-panel-border, currentColor)',
|
|
988
1101
|
boxShadow: '0 6px 20px rgba(0,0,0,0.18)',
|
|
989
|
-
padding: '
|
|
1102
|
+
padding: '10px',
|
|
990
1103
|
fontSize: '12px',
|
|
991
1104
|
lineHeight: 1.6,
|
|
992
1105
|
textAlign: 'left',
|
|
@@ -996,19 +1109,51 @@
|
|
|
996
1109
|
justifyContent: 'space-between',
|
|
997
1110
|
gap: '16px',
|
|
998
1111
|
};
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1112
|
+
// The rounded sub-card every section of the detail and the page card
|
|
1113
|
+
// lives in. The fill is a translucent gray so one value works on both
|
|
1114
|
+
// themes; the host's own muted token wins when it offers one.
|
|
1115
|
+
const boxStyle = {
|
|
1116
|
+
background: 'var(--dsh-muted, rgba(127, 127, 127, 0.1))',
|
|
1117
|
+
borderRadius: '10px',
|
|
1118
|
+
padding: '7px 10px',
|
|
1119
|
+
display: 'flex',
|
|
1120
|
+
flexDirection: 'column',
|
|
1121
|
+
gap: '4px',
|
|
1004
1122
|
};
|
|
1123
|
+
const boxTitleStyle = { fontWeight: 600, fontSize: '12px', opacity: 0.85 };
|
|
1124
|
+
const detailFlowStyle = { display: 'flex', flexDirection: 'column', gap: '6px' };
|
|
1005
1125
|
const warnStyle = { color: 'var(--dsh-warning, #b45309)' };
|
|
1006
1126
|
const cardStyle = {
|
|
1007
1127
|
display: 'flex',
|
|
1008
1128
|
flexDirection: 'column',
|
|
1009
|
-
gap: '
|
|
1129
|
+
gap: '8px',
|
|
1010
1130
|
fontSize: '13px',
|
|
1011
1131
|
lineHeight: 1.6,
|
|
1132
|
+
background: 'var(--dsh-panel-background, Canvas)',
|
|
1133
|
+
border: '1px solid var(--dsh-panel-border, currentColor)',
|
|
1134
|
+
borderRadius: '14px',
|
|
1135
|
+
padding: '12px',
|
|
1136
|
+
};
|
|
1137
|
+
const cardHeadStyle = {
|
|
1138
|
+
display: 'flex',
|
|
1139
|
+
justifyContent: 'space-between',
|
|
1140
|
+
alignItems: 'baseline',
|
|
1141
|
+
gap: '8px',
|
|
1142
|
+
flexWrap: 'wrap',
|
|
1143
|
+
};
|
|
1144
|
+
// The page card's two-column grid: credits beside the plan, the check-in
|
|
1145
|
+
// beside the grants, and the wide rows spanning both columns below.
|
|
1146
|
+
const gridStyle = {
|
|
1147
|
+
display: 'grid',
|
|
1148
|
+
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
|
|
1149
|
+
gap: '8px',
|
|
1150
|
+
};
|
|
1151
|
+
const span2Style = { gridColumn: '1 / -1' };
|
|
1152
|
+
// The account usage box lays its lines out two across.
|
|
1153
|
+
const profileGridStyle = {
|
|
1154
|
+
display: 'grid',
|
|
1155
|
+
gridTemplateColumns: 'repeat(2, minmax(0, 1fr))',
|
|
1156
|
+
gap: '4px 16px',
|
|
1012
1157
|
};
|
|
1013
1158
|
// The page stacks the balance card and the generation panel.
|
|
1014
1159
|
const pageStyle = {
|
|
@@ -1016,19 +1161,23 @@
|
|
|
1016
1161
|
flexDirection: 'column',
|
|
1017
1162
|
gap: '10px',
|
|
1018
1163
|
};
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
const
|
|
1023
|
-
const listStyle = { listStyle: 'none', margin: 0, padding: 0 };
|
|
1024
|
-
const sectionBoxStyle = {
|
|
1025
|
-
marginTop: '10px',
|
|
1026
|
-
paddingTop: '8px',
|
|
1027
|
-
borderTop: '1px solid var(--dsh-panel-border, currentColor)',
|
|
1164
|
+
// The generation panel wears the same rounded shell as the balance
|
|
1165
|
+
// card, so the page reads as a stack of two cards rather than a card
|
|
1166
|
+
// above a bare section.
|
|
1167
|
+
const genCardStyle = {
|
|
1028
1168
|
display: 'flex',
|
|
1029
1169
|
flexDirection: 'column',
|
|
1030
|
-
gap: '
|
|
1170
|
+
gap: '8px',
|
|
1171
|
+
fontSize: '13px',
|
|
1172
|
+
lineHeight: 1.6,
|
|
1173
|
+
background: 'var(--dsh-panel-background, Canvas)',
|
|
1174
|
+
border: '1px solid var(--dsh-panel-border, currentColor)',
|
|
1175
|
+
borderRadius: '14px',
|
|
1176
|
+
padding: '12px',
|
|
1031
1177
|
};
|
|
1178
|
+
const cardTitleStyle = { fontWeight: 600 };
|
|
1179
|
+
const bigNumberStyle = { fontSize: '20px', fontWeight: 600, marginBottom: '2px' };
|
|
1180
|
+
const mutedStyle = { opacity: 0.7, fontSize: '12px' };
|
|
1032
1181
|
const actionButtonStyle = {
|
|
1033
1182
|
appearance: 'none',
|
|
1034
1183
|
background: 'transparent',
|
|
@@ -1057,12 +1206,6 @@
|
|
|
1057
1206
|
};
|
|
1058
1207
|
const dayTodayStyle = { opacity: 1, fontWeight: 600 };
|
|
1059
1208
|
const dayClaimedStyle = { background: 'var(--dsh-panel-border, currentColor)', opacity: 0.9 };
|
|
1060
|
-
const usageBoxStyle = {
|
|
1061
|
-
marginTop: '8px',
|
|
1062
|
-
paddingTop: '6px',
|
|
1063
|
-
borderTop: '1px solid var(--dsh-panel-border, currentColor)',
|
|
1064
|
-
opacity: 0.9,
|
|
1065
|
-
};
|
|
1066
1209
|
const tabActiveStyle = {
|
|
1067
1210
|
appearance: 'none',
|
|
1068
1211
|
padding: '2px 8px',
|
package/lib/index.js
CHANGED
|
@@ -1549,8 +1549,8 @@ function parseRateLimitWindows(document) {
|
|
|
1549
1549
|
const entry = remains.find((item) => typeof item === "object" && item !== null && item.model_name === "general") ?? remains[0];
|
|
1550
1550
|
if (typeof entry !== "object" || entry === null) return void 0;
|
|
1551
1551
|
const record = entry;
|
|
1552
|
-
const interval = readWindow(record.current_interval_used_percent, record.current_interval_remaining_percent, record.start_time, record.end_time, record.current_interval_status);
|
|
1553
|
-
const weekly = readWindow(record.current_weekly_used_percent, record.current_weekly_remaining_percent, record.weekly_start_time, record.weekly_end_time, record.current_weekly_status);
|
|
1552
|
+
const interval = readWindow(usedPercentFrom(record.current_interval_used_percent, record.current_interval_remaining_percent), record.start_time, record.end_time, record.current_interval_status);
|
|
1553
|
+
const weekly = readWindow(usedPercentFrom(record.current_weekly_used_percent, record.current_weekly_remaining_percent), record.weekly_start_time, record.weekly_end_time, record.current_weekly_status);
|
|
1554
1554
|
if (interval === void 0 || weekly === void 0) return void 0;
|
|
1555
1555
|
return {
|
|
1556
1556
|
interval,
|
|
@@ -1564,24 +1564,24 @@ function parseRateLimitWindows(document) {
|
|
|
1564
1564
|
*/
|
|
1565
1565
|
const UNLIMITED_STATUS = 3;
|
|
1566
1566
|
/**
|
|
1567
|
-
* The share of a window
|
|
1567
|
+
* The used share of a window, from either shape the upstream sends.
|
|
1568
1568
|
*
|
|
1569
|
-
* The
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
1572
|
-
*
|
|
1569
|
+
* The older path reports how much of the allowance went ("3%"); the documented
|
|
1570
|
+
* quota endpoint reports how much is left (96). Both describe the same window,
|
|
1571
|
+
* so a response that only carries the remaining share is turned into the used
|
|
1572
|
+
* one rather than treated as unreadable — the documented endpoint sends no
|
|
1573
|
+
* used field at all, which is what left the plan's windows off the card.
|
|
1573
1574
|
*/
|
|
1574
|
-
function
|
|
1575
|
-
const
|
|
1576
|
-
if (
|
|
1577
|
-
const
|
|
1578
|
-
|
|
1579
|
-
return 100 - remainingPercent;
|
|
1575
|
+
function usedPercentFrom(used, remaining) {
|
|
1576
|
+
const direct = parsePercent(used);
|
|
1577
|
+
if (direct !== void 0) return direct;
|
|
1578
|
+
const rest = parsePercent(remaining);
|
|
1579
|
+
return rest === void 0 ? void 0 : 100 - rest;
|
|
1580
1580
|
}
|
|
1581
1581
|
/** Assemble one window from its percent and edge fields; all must be present. */
|
|
1582
|
-
function readWindow(percent,
|
|
1582
|
+
function readWindow(percent, startAtMs, resetAtMs, status) {
|
|
1583
1583
|
if (status === UNLIMITED_STATUS) return void 0;
|
|
1584
|
-
const usedPercent =
|
|
1584
|
+
const usedPercent = parsePercent(percent);
|
|
1585
1585
|
const start = toNumber$1(startAtMs);
|
|
1586
1586
|
const reset = toNumber$1(resetAtMs);
|
|
1587
1587
|
if (usedPercent === void 0 || start === void 0 || reset === void 0) return void 0;
|
|
@@ -1745,7 +1745,7 @@ async function fetchTokenPlanWindows(apiKey, region, fetchImpl = fetch) {
|
|
|
1745
1745
|
function summarizeExpiry(lots, now = Date.now()) {
|
|
1746
1746
|
const alive = lots.filter((lot) => lot.remaining > 0 && lot.expireAtMs > now);
|
|
1747
1747
|
const within = (days) => alive.filter((lot) => lot.expireAtMs - now <= days * 864e5);
|
|
1748
|
-
const week = within(
|
|
1748
|
+
const week = within(3);
|
|
1749
1749
|
const month = within(30);
|
|
1750
1750
|
return {
|
|
1751
1751
|
expiring: week.reduce((sum, lot) => sum + lot.remaining, 0),
|
|
@@ -1930,7 +1930,11 @@ var MinimaxCredits = class {
|
|
|
1930
1930
|
const apiKey = this.#apiKey ?? await fetchTokenPlanApiKey(credential, region, fetchImpl);
|
|
1931
1931
|
this.#apiKey = apiKey;
|
|
1932
1932
|
if (apiKey !== void 0) try {
|
|
1933
|
-
|
|
1933
|
+
const read = await fetchTokenPlanWindows(apiKey, region, fetchImpl);
|
|
1934
|
+
if (read.error === void 0 || read.absence !== void 0) return read;
|
|
1935
|
+
const legacy = await fetchRateLimitWindows(credential, region, fetchImpl);
|
|
1936
|
+
if (legacy !== void 0) return { windows: legacy };
|
|
1937
|
+
return read;
|
|
1934
1938
|
} catch (error) {
|
|
1935
1939
|
const legacy = await fetchRateLimitWindows(credential, region, fetchImpl);
|
|
1936
1940
|
if (legacy !== void 0) return { windows: legacy };
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "dsh-connect-minimaxcode",
|
|
3
3
|
"displayName": "DSH Connect MiniMax Code",
|
|
4
4
|
"description": "把本机已登录的 MiniMax Code 桌面 App 模型接入 DeepSeek Harness:零配置、Anthropic Messages 兼容、动态模型目录,并显示积分余额与到期 — bring MiniMax Code desktop app models into DSH with a dynamic model catalog and a credit balance with expiry details.",
|
|
5
|
-
"version": "0.6.
|
|
5
|
+
"version": "0.6.7",
|
|
6
6
|
"author": "JeffTsuiCUG",
|
|
7
7
|
"contributors": [
|
|
8
8
|
{
|