@hystudio/dsh-quota-pill 0.2.6 → 0.2.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/client.js +10 -1
- package/package.json +1 -1
- package/src/adapters-apikey.js +37 -5
- package/src/adapters-cliproxy.js +13 -2
package/client.js
CHANGED
|
@@ -1295,7 +1295,14 @@ window.__ModuleLoader__.load({
|
|
|
1295
1295
|
h('div', { className: 'quota-pill-win-main' },
|
|
1296
1296
|
h('div', { className: 'quota-pill-win-text' },
|
|
1297
1297
|
h('span', { className: 'quota-pill-win-label', title: label }, label),
|
|
1298
|
-
|
|
1298
|
+
// Reserve the reset line even when there is nothing to show.
|
|
1299
|
+
// Conditionally omitting it made rows with a reset taller than those
|
|
1300
|
+
// without one, so the column of numbers did not line up.
|
|
1301
|
+
h('span', {
|
|
1302
|
+
className: 'quota-pill-win-reset',
|
|
1303
|
+
title: reset || undefined,
|
|
1304
|
+
style: reset ? undefined : { visibility: 'hidden' },
|
|
1305
|
+
}, reset || '\u00a0'),
|
|
1299
1306
|
),
|
|
1300
1307
|
h('div', { className: 'quota-pill-win-gauge' },
|
|
1301
1308
|
showCredits
|
|
@@ -1528,6 +1535,8 @@ window.__ModuleLoader__.load({
|
|
|
1528
1535
|
'.quota-pill-win-unit{flex:none;margin-left:3px;color:var(--dsw-alias-label-tertiary)}',
|
|
1529
1536
|
|
|
1530
1537
|
'.quota-pill-win-ring{flex:none;display:inline-flex}',
|
|
1538
|
+
// 固定行高:没有重置时间的行也要占住这一行,数字才对得齐
|
|
1539
|
+
'.quota-pill-win-reset{min-height:14px}',
|
|
1531
1540
|
// Sidecar not running: show the fix instead of a bare dash.
|
|
1532
1541
|
'.quota-pill-offline{display:flex;flex-direction:column;gap:6px;padding:10px 2px}',
|
|
1533
1542
|
'.quota-pill-offline-title{font-weight:600;color:var(--dsw-alias-label-primary,inherit)}',
|
package/package.json
CHANGED
package/src/adapters-apikey.js
CHANGED
|
@@ -11,6 +11,27 @@ import { getJson } from './http.js';
|
|
|
11
11
|
const clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
|
|
12
12
|
const round2 = (n) => Math.round(n * 100) / 100;
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Normalise a reset timestamp to ISO, or null when there is none.
|
|
16
|
+
*
|
|
17
|
+
* Upstreams are inconsistent: some send ISO strings, some epoch milliseconds,
|
|
18
|
+
* and some use `0` to mean "this window has no reset pending" (Command Code's
|
|
19
|
+
* five-hour window does exactly that). Treating 0 as a date would render 1970.
|
|
20
|
+
*/
|
|
21
|
+
function isoReset(v) {
|
|
22
|
+
if (v == null || v === '' || v === 0) return null;
|
|
23
|
+
if (typeof v === 'string') {
|
|
24
|
+
// Already ISO-ish: keep as-is so the client formats it consistently.
|
|
25
|
+
const t = Date.parse(v);
|
|
26
|
+
return Number.isFinite(t) ? new Date(t).toISOString() : null;
|
|
27
|
+
}
|
|
28
|
+
const n = Number(v);
|
|
29
|
+
if (!Number.isFinite(n) || n <= 0) return null;
|
|
30
|
+
// Seconds vs milliseconds: anything below ~year 33658 in ms is really seconds.
|
|
31
|
+
const ms = n < 1e11 ? n * 1000 : n;
|
|
32
|
+
return new Date(ms).toISOString();
|
|
33
|
+
}
|
|
34
|
+
|
|
14
35
|
/** Expand `products` (window → used%) into the `windows` rows the pill renders. */
|
|
15
36
|
function toWindows(products, resetByKey = {}) {
|
|
16
37
|
const out = [];
|
|
@@ -217,7 +238,7 @@ export async function commandcode() {
|
|
|
217
238
|
const pct = round2((Number(used) / Number(cap)) * 100);
|
|
218
239
|
products[label] = pct;
|
|
219
240
|
ratios.push(100 - pct);
|
|
220
|
-
if (tight === null || pct > tight[1]) tight = [key, pct, w.resetAt];
|
|
241
|
+
if (tight === null || pct > tight[1]) tight = [key, pct, isoReset(w.resetAt)];
|
|
221
242
|
}
|
|
222
243
|
|
|
223
244
|
const monthlyLeft = cred.monthlyCredits;
|
|
@@ -237,14 +258,21 @@ export async function commandcode() {
|
|
|
237
258
|
|
|
238
259
|
// Plan identity (GOAT / Pro / Max) lives on a sibling endpoint; failure there
|
|
239
260
|
// must not lose the quota we already have.
|
|
261
|
+
// Plan identity and the monthly period live on a sibling endpoint, nested under
|
|
262
|
+
// `data`. Getting this wrong is silent: the quota still renders, just with a
|
|
263
|
+
// null plan and a monthly window that cannot show when it renews.
|
|
240
264
|
let plan = null;
|
|
265
|
+
let monthlyReset = null;
|
|
241
266
|
try {
|
|
242
267
|
const sub = await getJson('https://api.commandcode.ai/alpha/billing/subscriptions', { token });
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
268
|
+
const d = sub.ok && sub.data ? (sub.data.data || {}) : {};
|
|
269
|
+
plan = d.planId || null;
|
|
270
|
+
// currentPeriodEnd is the real monthly renewal — better than deriving it
|
|
271
|
+
// from the weekly window, which was my earlier guess.
|
|
272
|
+
monthlyReset = d.currentPeriodEnd || null;
|
|
246
273
|
} catch {
|
|
247
274
|
plan = null;
|
|
275
|
+
monthlyReset = null;
|
|
248
276
|
}
|
|
249
277
|
|
|
250
278
|
const remaining = round2(Math.min(...ratios));
|
|
@@ -261,8 +289,12 @@ export async function commandcode() {
|
|
|
261
289
|
const r = {};
|
|
262
290
|
for (const [label, key] of [['5h', 'fiveHour'], ['weekly', 'weekly']]) {
|
|
263
291
|
const w = win[key] || {};
|
|
264
|
-
|
|
292
|
+
const iso = isoReset(w.resetAt);
|
|
293
|
+
if (iso) r[label] = iso;
|
|
265
294
|
}
|
|
295
|
+
// The monthly ceiling is derived, so its reset must come from the
|
|
296
|
+
// subscription period; the credits endpoint does not carry one.
|
|
297
|
+
if (monthlyReset) r.monthly = monthlyReset;
|
|
266
298
|
return r;
|
|
267
299
|
})()),
|
|
268
300
|
raw: { source: 'api.commandcode.ai/alpha/billing/credits', plan },
|
package/src/adapters-cliproxy.js
CHANGED
|
@@ -72,6 +72,14 @@ function isoFromEpoch(sec) {
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
|
|
75
|
+
/** Same reset for every product row — xAI reports one billing period for all. */
|
|
76
|
+
function fillReset(products, resetAt) {
|
|
77
|
+
const out = {};
|
|
78
|
+
if (!resetAt) return out;
|
|
79
|
+
for (const key of Object.keys(products || {})) out[key] = resetAt;
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
/**
|
|
76
84
|
* Expand `products` (window → used%) into the `windows` array the client
|
|
77
85
|
* renders. The client shows one row per window with its own reset time, so an
|
|
@@ -142,7 +150,10 @@ export async function xai(model) {
|
|
|
142
150
|
window: period.type || null,
|
|
143
151
|
resetAt: period.end || cfg.billingPeriodEnd || null,
|
|
144
152
|
products,
|
|
145
|
-
|
|
153
|
+
// Every per-product row shares the account's billing period, so they all
|
|
154
|
+
// carry the same reset. Leaving it empty made those rows look like they
|
|
155
|
+
// never expire while the summary right above them showed a date.
|
|
156
|
+
windows: toWindows(products, fillReset(products, period.end || cfg.billingPeriodEnd)),
|
|
146
157
|
raw: { source: 'billing?format=credits', config: cfg },
|
|
147
158
|
};
|
|
148
159
|
}
|
|
@@ -175,7 +186,7 @@ export async function xai(model) {
|
|
|
175
186
|
window: period.type || null,
|
|
176
187
|
resetAt: period.end || cfg.billingPeriodEnd || null,
|
|
177
188
|
products,
|
|
178
|
-
windows: toWindows(products,
|
|
189
|
+
windows: toWindows(products, fillReset(products, period.end || cfg.billingPeriodEnd)),
|
|
179
190
|
detail: {
|
|
180
191
|
tokens: `${rl.remaining_tokens}/${rl.limit_tokens}`,
|
|
181
192
|
requests: `${rl.remaining_requests}/${rl.limit_requests}`,
|