dsh-subscriptions 0.1.0 → 0.1.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 +99 -6
- package/icon.svg +28 -0
- package/lib/client/CursorCard.js +15 -11
- package/lib/client/ExternalUsageCards.js +11 -11
- package/lib/client/SubscriptionUsageBadge.d.ts +6 -2
- package/lib/client/SubscriptionUsageBadge.js +56 -24
- package/lib/client/SubscriptionsSection.d.ts +7 -2
- package/lib/client/SubscriptionsSection.js +9 -13
- package/lib/client/UsageBadgeDisplaySetting.js +18 -1
- package/lib/client/UsageMeter.d.ts +8 -0
- package/lib/client/UsageMeter.js +51 -0
- package/lib/client/host-contract.d.ts +1 -1
- package/lib/client/host-contract.js +1 -0
- package/lib/client/locales.d.ts +48 -0
- package/lib/client/locales.js +48 -0
- package/lib/client/subscription-card-styles.js +0 -5
- package/lib/client/usage-color-preferences.d.ts +6 -0
- package/lib/client/usage-color-preferences.js +38 -0
- package/lib/client/usage-pace.d.ts +29 -0
- package/lib/client/usage-pace.js +58 -0
- package/lib/client.js +396 -114
- package/lib/client.js.map +1 -1
- package/lib/index.js +225 -35
- package/lib/providers/claude.js +7 -0
- package/lib/providers/codex.js +5 -0
- package/lib/providers/common.d.ts +10 -0
- package/lib/providers/cursor-usage.js +6 -2
- package/lib/providers/external-usage-controller.d.ts +1 -1
- package/lib/providers/external-usage-controller.js +8 -3
- package/lib/providers/external-usage.d.ts +1 -1
- package/lib/providers/external-usage.js +62 -2
- package/lib/providers/grok.js +3 -1
- package/lib/providers/minimax-usage.d.ts +3 -0
- package/lib/providers/minimax-usage.js +55 -0
- package/lib/providers/pool-usage.js +6 -4
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +10 -6
package/lib/client.js
CHANGED
|
@@ -114,6 +114,30 @@ const en = {
|
|
|
114
114
|
usageMonthly: "Monthly",
|
|
115
115
|
usageWindow: "Window",
|
|
116
116
|
usageResets: "resets {date}",
|
|
117
|
+
usageMeterUsed: "Used {used}% · Remaining {remaining}%",
|
|
118
|
+
usageMeterPace: "Time elapsed {elapsed}% (vertical marker)",
|
|
119
|
+
usageMeterPaceAhead: "{points} points ahead of pace",
|
|
120
|
+
usageMeterPaceBehind: "{points} points behind pace",
|
|
121
|
+
usageMeterPaceEven: "On pace",
|
|
122
|
+
usageMeterInvalid: "Quota unavailable",
|
|
123
|
+
usageMeterStale: "Reading is stale or unavailable; refresh to confirm",
|
|
124
|
+
usageMeterNearLimit: "Near exhaustion",
|
|
125
|
+
usageMeterAhead: "Usage ahead of pace",
|
|
126
|
+
usageMeterNoWarning: "No quota warning",
|
|
127
|
+
usageMeterUnknownTime: "Time progress unknown",
|
|
128
|
+
usageMeterReset: "Resets in {duration}",
|
|
129
|
+
usageUnitDay: "{count} day",
|
|
130
|
+
usageUnitDays: "{count} days",
|
|
131
|
+
usageUnitHour: "{count} hour",
|
|
132
|
+
usageUnitHours: "{count} hours",
|
|
133
|
+
usageUnitMinute: "{count} minute",
|
|
134
|
+
usageUnitMinutes: "{count} minutes",
|
|
135
|
+
usageColorLabel: "Usage coloring",
|
|
136
|
+
usageColorStandard: "Standard (default · 10-point lead)",
|
|
137
|
+
usageColorRelaxed: "Relaxed (15-point lead)",
|
|
138
|
+
usageColorRemaining: "Remaining quota only (no yellow)",
|
|
139
|
+
usageColorPaceHint: "Yellow when usage leads elapsed time by {allowance} points; red at 90% used takes priority. Saved in this browser; only colors change.",
|
|
140
|
+
usageColorRemainingHint: "Red at 90% used; no yellow pace warnings. Reliable time markers remain visible. Saved in this browser.",
|
|
117
141
|
usagePlan: "Plan: {plan}",
|
|
118
142
|
externalUsageTitle: "Built-in provider usage",
|
|
119
143
|
externalUsageConnected: "API key configured",
|
|
@@ -261,6 +285,30 @@ const zh = {
|
|
|
261
285
|
usageMonthly: "每月",
|
|
262
286
|
usageWindow: "窗口",
|
|
263
287
|
usageResets: "{date} 重置",
|
|
288
|
+
usageMeterUsed: "已用 {used}% · 剩余 {remaining}%",
|
|
289
|
+
usageMeterPace: "时间已过 {elapsed}%(竖线标记)",
|
|
290
|
+
usageMeterPaceAhead: "领先时间进度 {points} 个百分点",
|
|
291
|
+
usageMeterPaceBehind: "落后时间进度 {points} 个百分点",
|
|
292
|
+
usageMeterPaceEven: "与时间进度一致",
|
|
293
|
+
usageMeterInvalid: "额度数据不可用",
|
|
294
|
+
usageMeterStale: "读数已陈旧或不可用,请刷新确认",
|
|
295
|
+
usageMeterNearLimit: "额度接近耗尽",
|
|
296
|
+
usageMeterAhead: "用量领先时间进度",
|
|
297
|
+
usageMeterNoWarning: "未触发额度警告",
|
|
298
|
+
usageMeterUnknownTime: "时间进度未知",
|
|
299
|
+
usageMeterReset: "{duration}后重置",
|
|
300
|
+
usageUnitDay: "{count} 天",
|
|
301
|
+
usageUnitDays: "{count} 天",
|
|
302
|
+
usageUnitHour: "{count} 小时",
|
|
303
|
+
usageUnitHours: "{count} 小时",
|
|
304
|
+
usageUnitMinute: "{count} 分钟",
|
|
305
|
+
usageUnitMinutes: "{count} 分钟",
|
|
306
|
+
usageColorLabel: "用量配色",
|
|
307
|
+
usageColorStandard: "标准(默认 · 领先 10 个百分点)",
|
|
308
|
+
usageColorRelaxed: "宽松(领先 15 个百分点)",
|
|
309
|
+
usageColorRemaining: "仅剩余额度(不显示黄色)",
|
|
310
|
+
usageColorPaceHint: "用量领先时间进度 {allowance} 个百分点时变黄;已用达到 90% 时优先变红。自动保存到当前浏览器,仅改变配色。",
|
|
311
|
+
usageColorRemainingHint: "已用达到 90% 时变红,不显示黄色节奏警告。可靠的时间标记仍保留,自动保存到当前浏览器。",
|
|
264
312
|
usagePlan: "计划:{plan}",
|
|
265
313
|
externalUsageTitle: "内置供应商用量",
|
|
266
314
|
externalUsageConnected: "已配置 API 密钥",
|
|
@@ -1342,17 +1390,6 @@ const subscriptionCardStyles = {
|
|
|
1342
1390
|
fontSize: 12,
|
|
1343
1391
|
lineHeight: "18px",
|
|
1344
1392
|
color: "var(--dsw-alias-label-tertiary)"
|
|
1345
|
-
},
|
|
1346
|
-
usageTrack: {
|
|
1347
|
-
height: 6,
|
|
1348
|
-
borderRadius: 3,
|
|
1349
|
-
overflow: "hidden",
|
|
1350
|
-
background: "var(--dsw-alias-bg-layer-1)",
|
|
1351
|
-
border: "1px solid var(--dsw-alias-border-l2)"
|
|
1352
|
-
},
|
|
1353
|
-
usageFill: {
|
|
1354
|
-
height: "100%",
|
|
1355
|
-
borderRadius: 3
|
|
1356
1393
|
}
|
|
1357
1394
|
};
|
|
1358
1395
|
|
|
@@ -1394,30 +1431,204 @@ function useUsageBadgeMode() {
|
|
|
1394
1431
|
return (0, react.useSyncExternalStore)(subscribeUsageBadgeMode, readUsageBadgeMode, () => "recent");
|
|
1395
1432
|
}
|
|
1396
1433
|
|
|
1434
|
+
//#endregion
|
|
1435
|
+
//#region src/client/usage-pace.ts
|
|
1436
|
+
const USAGE_FRESHNESS_MS = 5 * 6e4;
|
|
1437
|
+
function validUsage(used) {
|
|
1438
|
+
return Number.isFinite(used) && used >= 0 && used <= 100;
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* Rounded share for display, or undefined when the provider's number is
|
|
1442
|
+
* unusable. Callers must show the unavailable state rather than clamp an
|
|
1443
|
+
* out-of-range reading into 0% or 100%.
|
|
1444
|
+
*/
|
|
1445
|
+
function displayUsedPercent(used) {
|
|
1446
|
+
return validUsage(used) ? Math.round(used) : void 0;
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Reset countdown as the two largest nonzero whole units, rounded up so a
|
|
1450
|
+
* window never announces zero remaining time. Always at least one minute.
|
|
1451
|
+
*/
|
|
1452
|
+
function resetCountdownParts(ms) {
|
|
1453
|
+
const total = Math.max(1, Math.ceil(ms / 6e4));
|
|
1454
|
+
return [
|
|
1455
|
+
{
|
|
1456
|
+
unit: "day",
|
|
1457
|
+
count: Math.floor(total / 1440)
|
|
1458
|
+
},
|
|
1459
|
+
{
|
|
1460
|
+
unit: "hour",
|
|
1461
|
+
count: Math.floor(total % 1440 / 60)
|
|
1462
|
+
},
|
|
1463
|
+
{
|
|
1464
|
+
unit: "minute",
|
|
1465
|
+
count: total % 60
|
|
1466
|
+
}
|
|
1467
|
+
].filter((part) => part.count > 0).slice(0, 2);
|
|
1468
|
+
}
|
|
1469
|
+
function isUsageFresh(window$1, observation, now = Date.now()) {
|
|
1470
|
+
return validUsage(window$1.usedPercent) && observation.stale !== true && observation.observedAt !== void 0 && Number.isFinite(observation.observedAt) && observation.observedAt <= now && now - observation.observedAt <= USAGE_FRESHNESS_MS && (window$1.resetsAt === void 0 || Number.isFinite(window$1.resetsAt) && window$1.resetsAt > now);
|
|
1471
|
+
}
|
|
1472
|
+
/** Only explicit intervals or provider-verified fixed durations support linear pace. */
|
|
1473
|
+
function elapsedPercent(window$1, now = Date.now()) {
|
|
1474
|
+
const end = window$1.resetsAt;
|
|
1475
|
+
if (end === void 0 || !Number.isFinite(end) || end <= now) return void 0;
|
|
1476
|
+
const duration = window$1.windowDurationMs;
|
|
1477
|
+
const start = window$1.startsAt ?? (window$1.fixedWindow === true && duration !== void 0 && Number.isFinite(duration) && duration > 0 ? end - duration : void 0);
|
|
1478
|
+
if (start === void 0 || !Number.isFinite(start) || start >= end || start > now) return void 0;
|
|
1479
|
+
return Math.min(100, Math.max(0, (now - start) / (end - start) * 100));
|
|
1480
|
+
}
|
|
1481
|
+
function usageColorState(used, elapsed, preset, fresh) {
|
|
1482
|
+
if (!validUsage(used)) return "neutral";
|
|
1483
|
+
if (used >= 90) return "red";
|
|
1484
|
+
if (!fresh || preset === "remaining" || elapsed === void 0) return "green";
|
|
1485
|
+
return used - elapsed >= (preset === "relaxed" ? 15 : 10) ? "yellow" : "green";
|
|
1486
|
+
}
|
|
1487
|
+
function usageBarColor(used, elapsed, preset = "standard", fresh = true) {
|
|
1488
|
+
const state = usageColorState(used, elapsed, preset, fresh);
|
|
1489
|
+
return state === "red" ? "var(--dsw-alias-state-error-primary)" : state === "yellow" ? "var(--dsw-alias-state-warn-label)" : "var(--dsw-alias-state-success-primary)";
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
//#endregion
|
|
1493
|
+
//#region src/client/usage-color-preferences.ts
|
|
1494
|
+
const KEY = "dsh.subscriptions.usageColorPreset";
|
|
1495
|
+
const EVENT = "dsh:subscriptions:usage-color-preset";
|
|
1496
|
+
function isUsageColorPreset(value) {
|
|
1497
|
+
return value === "standard" || value === "relaxed" || value === "remaining";
|
|
1498
|
+
}
|
|
1499
|
+
function readUsageColorPreset() {
|
|
1500
|
+
if (typeof window === "undefined") return "standard";
|
|
1501
|
+
try {
|
|
1502
|
+
const value = window.localStorage.getItem(KEY);
|
|
1503
|
+
return isUsageColorPreset(value) ? value : "standard";
|
|
1504
|
+
} catch (error) {
|
|
1505
|
+
if (!(error instanceof DOMException) || error.name !== "SecurityError") throw error;
|
|
1506
|
+
return "standard";
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
function setUsageColorPreset(value) {
|
|
1510
|
+
if (!isUsageColorPreset(value)) throw new TypeError("Invalid usage coloring preset");
|
|
1511
|
+
window.localStorage.setItem(KEY, value);
|
|
1512
|
+
window.dispatchEvent(new Event(EVENT));
|
|
1513
|
+
}
|
|
1514
|
+
function subscribeUsageColorPreset(notify) {
|
|
1515
|
+
const storage = (event) => {
|
|
1516
|
+
if (event.key === KEY || event.key === null) notify();
|
|
1517
|
+
};
|
|
1518
|
+
window.addEventListener(EVENT, notify);
|
|
1519
|
+
window.addEventListener("storage", storage);
|
|
1520
|
+
return () => {
|
|
1521
|
+
window.removeEventListener(EVENT, notify);
|
|
1522
|
+
window.removeEventListener("storage", storage);
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
function useUsageColorPreset() {
|
|
1526
|
+
return (0, react.useSyncExternalStore)(subscribeUsageColorPreset, readUsageColorPreset, () => "standard");
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
//#endregion
|
|
1530
|
+
//#region src/client/UsageMeter.tsx
|
|
1531
|
+
/** Singular/plural locale keys per countdown unit (zh maps both to one string). */
|
|
1532
|
+
const UNIT_KEYS = {
|
|
1533
|
+
day: ["usageUnitDay", "usageUnitDays"],
|
|
1534
|
+
hour: ["usageUnitHour", "usageUnitHours"],
|
|
1535
|
+
minute: ["usageUnitMinute", "usageUnitMinutes"]
|
|
1536
|
+
};
|
|
1537
|
+
function UsageMeter({ window: quota, t, style, observedAt, stale }) {
|
|
1538
|
+
const preset = useUsageColorPreset();
|
|
1539
|
+
const [now, setNow] = (0, react.useState)(Date.now);
|
|
1540
|
+
(0, react.useEffect)(() => {
|
|
1541
|
+
const timer = setInterval(() => setNow(Date.now()), 6e4);
|
|
1542
|
+
return () => clearInterval(timer);
|
|
1543
|
+
}, []);
|
|
1544
|
+
const current = Math.max(now, Date.now());
|
|
1545
|
+
const valid = validUsage(quota.usedPercent);
|
|
1546
|
+
const used = valid ? quota.usedPercent : 0;
|
|
1547
|
+
const fresh = isUsageFresh(quota, {
|
|
1548
|
+
observedAt,
|
|
1549
|
+
stale
|
|
1550
|
+
}, current);
|
|
1551
|
+
const elapsed = fresh ? elapsedPercent(quota, current) : void 0;
|
|
1552
|
+
const state = usageColorState(quota.usedPercent, elapsed, preset, fresh);
|
|
1553
|
+
const lead = elapsed === void 0 ? 0 : Math.round(used - elapsed);
|
|
1554
|
+
const label = [
|
|
1555
|
+
valid ? t("usageMeterUsed", {
|
|
1556
|
+
used: Math.round(used),
|
|
1557
|
+
remaining: Math.round(100 - used)
|
|
1558
|
+
}) : t("usageMeterInvalid"),
|
|
1559
|
+
!valid ? "" : !fresh ? t("usageMeterStale") : state === "red" ? t("usageMeterNearLimit") : elapsed === void 0 && preset !== "remaining" ? t("usageMeterUnknownTime") : state === "yellow" ? t("usageMeterAhead") : t("usageMeterNoWarning"),
|
|
1560
|
+
elapsed === void 0 ? "" : t("usageMeterPace", { elapsed: Math.round(elapsed) }),
|
|
1561
|
+
elapsed === void 0 ? "" : lead > 0 ? t("usageMeterPaceAhead", { points: lead }) : lead < 0 ? t("usageMeterPaceBehind", { points: -lead }) : t("usageMeterPaceEven"),
|
|
1562
|
+
quota.resetsAt !== void 0 && quota.resetsAt > current ? t("usageMeterReset", { duration: resetCountdownParts(quota.resetsAt - current).map((part) => t(UNIT_KEYS[part.unit][part.count === 1 ? 0 : 1], { count: part.count })).join(" ") }) : ""
|
|
1563
|
+
].filter(Boolean).join(" · ");
|
|
1564
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1565
|
+
role: "img",
|
|
1566
|
+
"aria-label": label,
|
|
1567
|
+
title: label,
|
|
1568
|
+
"data-usage-color": state,
|
|
1569
|
+
style: {
|
|
1570
|
+
height: 6,
|
|
1571
|
+
borderRadius: 3,
|
|
1572
|
+
overflow: "hidden",
|
|
1573
|
+
background: "var(--dsw-alias-bg-layer-1)",
|
|
1574
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1575
|
+
...style,
|
|
1576
|
+
position: "relative"
|
|
1577
|
+
},
|
|
1578
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
|
|
1579
|
+
height: "100%",
|
|
1580
|
+
borderRadius: "inherit",
|
|
1581
|
+
width: `${used}%`,
|
|
1582
|
+
background: usageBarColor(quota.usedPercent, elapsed, preset, fresh)
|
|
1583
|
+
} }), elapsed !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1584
|
+
"aria-hidden": "true",
|
|
1585
|
+
"data-usage-time-marker": true,
|
|
1586
|
+
style: {
|
|
1587
|
+
position: "absolute",
|
|
1588
|
+
left: `clamp(0px, calc(${elapsed}% - 1px), calc(100% - 2px))`,
|
|
1589
|
+
top: 0,
|
|
1590
|
+
bottom: 0,
|
|
1591
|
+
width: 2,
|
|
1592
|
+
background: "var(--dsw-alias-label-primary)",
|
|
1593
|
+
boxShadow: "0 0 0 1px var(--dsw-alias-bg-layer-1)"
|
|
1594
|
+
}
|
|
1595
|
+
})]
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1397
1599
|
//#endregion
|
|
1398
1600
|
//#region src/client/ExternalUsageCards.tsx
|
|
1399
|
-
const SOURCES = [
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1601
|
+
const SOURCES = [
|
|
1602
|
+
{
|
|
1603
|
+
id: "opencode-go",
|
|
1604
|
+
name: "OpenCode Go",
|
|
1605
|
+
ref: "OPENCODE_GO_API_KEY"
|
|
1606
|
+
},
|
|
1607
|
+
{
|
|
1608
|
+
id: "kimi-code",
|
|
1609
|
+
name: "Kimi Code",
|
|
1610
|
+
ref: "KIMI_CODING_API_KEY"
|
|
1611
|
+
},
|
|
1612
|
+
{
|
|
1613
|
+
id: "minimax",
|
|
1614
|
+
name: "MiniMax",
|
|
1615
|
+
ref: "MINIMAX_API_KEY"
|
|
1616
|
+
},
|
|
1617
|
+
{
|
|
1618
|
+
id: "minimax-cn",
|
|
1619
|
+
name: "MiniMax CN",
|
|
1620
|
+
ref: "MINIMAX_CN_API_KEY"
|
|
1621
|
+
}
|
|
1622
|
+
];
|
|
1408
1623
|
function errorText(error) {
|
|
1409
1624
|
return error instanceof Error ? error.message : String(error);
|
|
1410
1625
|
}
|
|
1411
|
-
function barColor$1(percent) {
|
|
1412
|
-
if (percent >= 95) return "var(--dsw-alias-state-error-primary)";
|
|
1413
|
-
if (percent >= 80) return "var(--dsw-alias-state-warn-label)";
|
|
1414
|
-
return "var(--dsw-alias-state-success-primary)";
|
|
1415
|
-
}
|
|
1416
1626
|
/** Quota cards for API-key providers already available through DSH itself. */
|
|
1417
1627
|
function ExternalUsageCards({ rpc, t }) {
|
|
1418
1628
|
const [status, setStatus] = (0, react.useState)();
|
|
1419
1629
|
const [statusError, setStatusError] = (0, react.useState)();
|
|
1420
1630
|
const [usage, setUsage] = (0, react.useState)({});
|
|
1631
|
+
const [observedAt, setObservedAt] = (0, react.useState)({});
|
|
1421
1632
|
const [errors, setErrors] = (0, react.useState)({});
|
|
1422
1633
|
const [loading, setLoading] = (0, react.useState)({});
|
|
1423
1634
|
const refresh = (0, react.useCallback)(async (source) => {
|
|
@@ -1425,16 +1636,20 @@ function ExternalUsageCards({ rpc, t }) {
|
|
|
1425
1636
|
...prev,
|
|
1426
1637
|
[source]: true
|
|
1427
1638
|
}));
|
|
1428
|
-
setErrors((prev) => ({
|
|
1429
|
-
...prev,
|
|
1430
|
-
[source]: void 0
|
|
1431
|
-
}));
|
|
1432
1639
|
try {
|
|
1433
1640
|
const value = await callSubscriptionsAuth(rpc, "externalUsage", { source });
|
|
1434
1641
|
setUsage((prev) => ({
|
|
1435
1642
|
...prev,
|
|
1436
1643
|
[source]: value
|
|
1437
1644
|
}));
|
|
1645
|
+
setObservedAt((prev) => ({
|
|
1646
|
+
...prev,
|
|
1647
|
+
[source]: value.observedAt ?? Date.now()
|
|
1648
|
+
}));
|
|
1649
|
+
setErrors((prev) => ({
|
|
1650
|
+
...prev,
|
|
1651
|
+
[source]: void 0
|
|
1652
|
+
}));
|
|
1438
1653
|
window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
|
|
1439
1654
|
} catch (error) {
|
|
1440
1655
|
setErrors((prev) => ({
|
|
@@ -1539,24 +1754,18 @@ function ExternalUsageCards({ rpc, t }) {
|
|
|
1539
1754
|
children: t("usageEmpty")
|
|
1540
1755
|
}),
|
|
1541
1756
|
snapshot?.windows?.map((window$1, index) => {
|
|
1542
|
-
const percent =
|
|
1757
|
+
const percent = displayUsedPercent(window$1.usedPercent);
|
|
1543
1758
|
const label = window$1.kind === "session" ? t("usageSession") : window$1.kind === "weekly" ? t("usageWeekly") : window$1.scope === "Monthly" ? t("usageMonthly") : window$1.scope ?? t("usageWindow");
|
|
1544
1759
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1545
1760
|
style: subscriptionCardStyles.usageRow,
|
|
1546
1761
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1547
1762
|
style: subscriptionCardStyles.usageMeta,
|
|
1548
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
style: subscriptionCardStyles.usageTrack,
|
|
1555
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
|
|
1556
|
-
...subscriptionCardStyles.usageFill,
|
|
1557
|
-
width: `${percent}%`,
|
|
1558
|
-
background: barColor$1(percent)
|
|
1559
|
-
} })
|
|
1763
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [percent === void 0 ? t("usageMeterInvalid") : `${percent}%`, window$1.resetsAt === void 0 ? "" : ` · ${t("usageResets", { date: new Date(window$1.resetsAt).toLocaleString() })}`] })]
|
|
1764
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageMeter, {
|
|
1765
|
+
window: window$1,
|
|
1766
|
+
t,
|
|
1767
|
+
observedAt: observedAt[id],
|
|
1768
|
+
stale: errors[id] !== void 0 || snapshot?.stale === true
|
|
1560
1769
|
})]
|
|
1561
1770
|
}, index);
|
|
1562
1771
|
})
|
|
@@ -1584,15 +1793,12 @@ function ExternalUsageCards({ rpc, t }) {
|
|
|
1584
1793
|
function message(error) {
|
|
1585
1794
|
return error instanceof Error ? error.message : String(error);
|
|
1586
1795
|
}
|
|
1587
|
-
function barColor(percent) {
|
|
1588
|
-
if (percent >= 95) return "var(--dsw-alias-state-error-primary)";
|
|
1589
|
-
if (percent >= 80) return "var(--dsw-alias-state-warn-label)";
|
|
1590
|
-
return "var(--dsw-alias-state-success-primary)";
|
|
1591
|
-
}
|
|
1592
1796
|
/** Cursor's single connected account, rendered with the same card and Manage dialog as the other providers. */
|
|
1593
1797
|
function CursorCard({ rpc, t }) {
|
|
1594
1798
|
const [status, setStatus] = (0, react.useState)();
|
|
1595
1799
|
const [usage, setUsage] = (0, react.useState)();
|
|
1800
|
+
const [observedAt, setObservedAt] = (0, react.useState)();
|
|
1801
|
+
const [usageStale, setUsageStale] = (0, react.useState)(false);
|
|
1596
1802
|
const [authorizeUrl, setAuthorizeUrl] = (0, react.useState)();
|
|
1597
1803
|
const [error, setError] = (0, react.useState)();
|
|
1598
1804
|
const [loading, setLoading] = (0, react.useState)(false);
|
|
@@ -1615,11 +1821,15 @@ function CursorCard({ rpc, t }) {
|
|
|
1615
1821
|
const refreshUsage = (0, react.useCallback)(async () => {
|
|
1616
1822
|
setLoading(true);
|
|
1617
1823
|
try {
|
|
1618
|
-
|
|
1824
|
+
const value = await callSubscriptionsAuth(rpc, "cursorUsage", {});
|
|
1825
|
+
setUsage(value);
|
|
1826
|
+
setObservedAt(value.observedAt ?? Date.now());
|
|
1827
|
+
setUsageStale(value.stale === true);
|
|
1619
1828
|
setError(void 0);
|
|
1620
1829
|
window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
|
|
1621
1830
|
} catch (failure) {
|
|
1622
1831
|
setError(message(failure));
|
|
1832
|
+
setUsageStale(true);
|
|
1623
1833
|
} finally {
|
|
1624
1834
|
setLoading(false);
|
|
1625
1835
|
}
|
|
@@ -1665,6 +1875,8 @@ function CursorCard({ rpc, t }) {
|
|
|
1665
1875
|
try {
|
|
1666
1876
|
await callSubscriptionsAuth(rpc, "cursorLogout", {});
|
|
1667
1877
|
setUsage(void 0);
|
|
1878
|
+
setObservedAt(void 0);
|
|
1879
|
+
setUsageStale(false);
|
|
1668
1880
|
await refreshStatus();
|
|
1669
1881
|
} catch (failure) {
|
|
1670
1882
|
setError(message(failure));
|
|
@@ -1757,25 +1969,18 @@ function CursorCard({ rpc, t }) {
|
|
|
1757
1969
|
children: t("usageEmpty")
|
|
1758
1970
|
}),
|
|
1759
1971
|
usage?.windows?.map((window$1, index) => {
|
|
1760
|
-
const percent =
|
|
1972
|
+
const percent = displayUsedPercent(window$1.usedPercent);
|
|
1761
1973
|
const label = window$1.scope === "Included" ? t("cursorIncluded") : window$1.scope === "Cursor Models" ? t("cursorModels") : window$1.scope === "Other Models" ? t("cursorOtherModels") : window$1.scope === "Included requests" ? t("cursorIncludedRequests") : window$1.scope ?? t("usageWindow");
|
|
1762
1974
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1763
1975
|
style: subscriptionCardStyles.usageRow,
|
|
1764
1976
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1765
1977
|
style: subscriptionCardStyles.usageMeta,
|
|
1766
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
style: subscriptionCardStyles.usageTrack,
|
|
1773
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
|
|
1774
|
-
width: `${percent}%`,
|
|
1775
|
-
height: "100%",
|
|
1776
|
-
borderRadius: 3,
|
|
1777
|
-
background: barColor(percent)
|
|
1778
|
-
} })
|
|
1978
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [percent === void 0 ? t("usageMeterInvalid") : `${percent}%`, window$1.resetsAt === void 0 ? "" : ` · ${t("usageResets", { date: new Date(window$1.resetsAt).toLocaleString() })}`] })]
|
|
1979
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageMeter, {
|
|
1980
|
+
window: window$1,
|
|
1981
|
+
t,
|
|
1982
|
+
observedAt,
|
|
1983
|
+
stale: usageStale
|
|
1779
1984
|
})]
|
|
1780
1985
|
}, index);
|
|
1781
1986
|
})
|
|
@@ -1832,6 +2037,7 @@ function CursorCard({ rpc, t }) {
|
|
|
1832
2037
|
/** Browser-local display preference; never changes provider or account settings. */
|
|
1833
2038
|
function UsageBadgeDisplaySetting({ t }) {
|
|
1834
2039
|
const mode = useUsageBadgeMode();
|
|
2040
|
+
const preset = useUsageColorPreset();
|
|
1835
2041
|
const [failed, setFailed] = (0, react.useState)(false);
|
|
1836
2042
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1837
2043
|
style: styles$6.card,
|
|
@@ -1866,6 +2072,43 @@ function UsageBadgeDisplaySetting({ t }) {
|
|
|
1866
2072
|
style: styles$6.hint,
|
|
1867
2073
|
children: t("usageBadgeDisplayHint")
|
|
1868
2074
|
}),
|
|
2075
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
|
|
2076
|
+
style: styles$6.field,
|
|
2077
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("usageColorLabel") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
2078
|
+
style: styles$6.select,
|
|
2079
|
+
"aria-label": t("usageColorLabel"),
|
|
2080
|
+
value: preset,
|
|
2081
|
+
onChange: (event) => {
|
|
2082
|
+
const value = event.currentTarget.value;
|
|
2083
|
+
if (!isUsageColorPreset(value)) return;
|
|
2084
|
+
try {
|
|
2085
|
+
setUsageColorPreset(value);
|
|
2086
|
+
setFailed(false);
|
|
2087
|
+
} catch (error) {
|
|
2088
|
+
if (!(error instanceof DOMException) || !["SecurityError", "QuotaExceededError"].includes(error.name)) throw error;
|
|
2089
|
+
setFailed(true);
|
|
2090
|
+
}
|
|
2091
|
+
},
|
|
2092
|
+
children: [
|
|
2093
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2094
|
+
value: "standard",
|
|
2095
|
+
children: t("usageColorStandard")
|
|
2096
|
+
}),
|
|
2097
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2098
|
+
value: "relaxed",
|
|
2099
|
+
children: t("usageColorRelaxed")
|
|
2100
|
+
}),
|
|
2101
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2102
|
+
value: "remaining",
|
|
2103
|
+
children: t("usageColorRemaining")
|
|
2104
|
+
})
|
|
2105
|
+
]
|
|
2106
|
+
})]
|
|
2107
|
+
}),
|
|
2108
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2109
|
+
style: styles$6.hint,
|
|
2110
|
+
children: t(preset === "remaining" ? "usageColorRemainingHint" : "usageColorPaceHint", { allowance: preset === "relaxed" ? 15 : 10 })
|
|
2111
|
+
}),
|
|
1869
2112
|
failed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
1870
2113
|
role: "alert",
|
|
1871
2114
|
style: {
|
|
@@ -2017,8 +2260,6 @@ const styles$5 = {
|
|
|
2017
2260
|
cursor: "pointer",
|
|
2018
2261
|
color: "var(--dsw-alias-state-warn-label)"
|
|
2019
2262
|
},
|
|
2020
|
-
usageTrack: subscriptionCardStyles.usageTrack,
|
|
2021
|
-
usageFill: subscriptionCardStyles.usageFill,
|
|
2022
2263
|
deviceCode: {
|
|
2023
2264
|
marginTop: 4,
|
|
2024
2265
|
display: "flex",
|
|
@@ -2114,12 +2355,6 @@ function usageWindowLabel$1(t, window$1) {
|
|
|
2114
2355
|
const base = window$1.kind === "session" ? t("usageSession") : window$1.kind === "weekly" ? t("usageWeekly") : t("usageWindow");
|
|
2115
2356
|
return window$1.scope !== void 0 && window$1.scope !== "" ? `${base} · ${window$1.scope}` : base;
|
|
2116
2357
|
}
|
|
2117
|
-
/** Bar fill color: success normally, warn from 80%, error from 95%. Shared with the composer badge. */
|
|
2118
|
-
function usageBarColor(usedPercent$1) {
|
|
2119
|
-
if (usedPercent$1 >= 95) return "var(--dsw-alias-state-error-primary)";
|
|
2120
|
-
if (usedPercent$1 >= 80) return "var(--dsw-alias-state-warn-label)";
|
|
2121
|
-
return "var(--dsw-alias-state-success-primary)";
|
|
2122
|
-
}
|
|
2123
2358
|
/**
|
|
2124
2359
|
* The Subscriptions settings page component.
|
|
2125
2360
|
* @param props - the slot inject face ({@link SubscriptionsSectionInjected}).
|
|
@@ -2142,6 +2377,7 @@ function SubscriptionsSection(props) {
|
|
|
2142
2377
|
const [copiedCode, setCopiedCode] = (0, react.useState)(void 0);
|
|
2143
2378
|
/** Usage snapshots keyed `${provider}:${accountKey}` — every account tracks its own windows. */
|
|
2144
2379
|
const [usages, setUsages] = (0, react.useState)({});
|
|
2380
|
+
const [observedAt, setObservedAt] = (0, react.useState)({});
|
|
2145
2381
|
const [usageErrors, setUsageErrors] = (0, react.useState)({});
|
|
2146
2382
|
const [usageLoading, setUsageLoading] = (0, react.useState)({});
|
|
2147
2383
|
const mountedRef = (0, react.useRef)(true);
|
|
@@ -2242,6 +2478,10 @@ function SubscriptionsSection(props) {
|
|
|
2242
2478
|
...prev,
|
|
2243
2479
|
[key]: usage
|
|
2244
2480
|
}));
|
|
2481
|
+
setObservedAt((prev) => ({
|
|
2482
|
+
...prev,
|
|
2483
|
+
[key]: usage.observedAt ?? Date.now()
|
|
2484
|
+
}));
|
|
2245
2485
|
window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
|
|
2246
2486
|
setUsageErrors((prev) => {
|
|
2247
2487
|
const next = { ...prev };
|
|
@@ -2542,19 +2782,17 @@ function SubscriptionsSection(props) {
|
|
|
2542
2782
|
children: t("usageEmpty")
|
|
2543
2783
|
}),
|
|
2544
2784
|
(usage?.windows ?? []).map((window$1, index) => {
|
|
2545
|
-
const percent =
|
|
2785
|
+
const percent = displayUsedPercent(window$1.usedPercent);
|
|
2546
2786
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2547
2787
|
style: styles$5.usageRow,
|
|
2548
2788
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2549
2789
|
style: styles$5.usageMeta,
|
|
2550
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: usageWindowLabel$1(t, window$1) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [`${
|
|
2551
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
background: usageBarColor(percent)
|
|
2557
|
-
} })
|
|
2790
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: usageWindowLabel$1(t, window$1) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { children: [percent === void 0 ? t("usageMeterInvalid") : `${percent}%`, window$1.resetsAt !== void 0 && ` · ${t("usageResets", { date: new Date(window$1.resetsAt).toLocaleString() })}`] })]
|
|
2791
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageMeter, {
|
|
2792
|
+
window: window$1,
|
|
2793
|
+
t,
|
|
2794
|
+
observedAt: observedAt[usageKey],
|
|
2795
|
+
stale: usageError !== void 0 || usage.stale === true
|
|
2558
2796
|
})]
|
|
2559
2797
|
}, index);
|
|
2560
2798
|
})
|
|
@@ -2989,6 +3227,7 @@ const HOST_CONTRACT = {
|
|
|
2989
3227
|
"--dsw-alias-state-success-primary",
|
|
2990
3228
|
"--dsw-alias-state-warn-label",
|
|
2991
3229
|
"--dsw-elevation-prominent",
|
|
3230
|
+
"--dsw-menu-backdrop-filter",
|
|
2992
3231
|
"--dsw-specific-menu"
|
|
2993
3232
|
]
|
|
2994
3233
|
}
|
|
@@ -3670,7 +3909,9 @@ const PROVIDER_NAMES = {
|
|
|
3670
3909
|
antigravity: "Antigravity",
|
|
3671
3910
|
"cursor-subscription": "Cursor",
|
|
3672
3911
|
"opencode-go": "OpenCode Go",
|
|
3673
|
-
"kimi-coding": "Kimi Code"
|
|
3912
|
+
"kimi-coding": "Kimi Code",
|
|
3913
|
+
"minimax": "MiniMax",
|
|
3914
|
+
"minimax-cn": "MiniMax CN"
|
|
3674
3915
|
};
|
|
3675
3916
|
/** Key of one account's cached windows: `provider:accountKey`. */
|
|
3676
3917
|
function usageKeyOf(provider, account) {
|
|
@@ -3742,7 +3983,12 @@ async function loadBadgeRoster(rpc) {
|
|
|
3742
3983
|
}
|
|
3743
3984
|
});
|
|
3744
3985
|
}
|
|
3745
|
-
if (external.status === "fulfilled") for (const [source, provider] of [
|
|
3986
|
+
if (external.status === "fulfilled") for (const [source, provider] of [
|
|
3987
|
+
["opencode-go", "opencode-go"],
|
|
3988
|
+
["kimi-code", "kimi-coding"],
|
|
3989
|
+
["minimax", "minimax"],
|
|
3990
|
+
["minimax-cn", "minimax-cn"]
|
|
3991
|
+
]) {
|
|
3746
3992
|
refreshed.add(provider);
|
|
3747
3993
|
if (external.value[source]?.configured) roster.push({
|
|
3748
3994
|
provider,
|
|
@@ -3759,7 +4005,7 @@ async function loadBadgeRoster(rpc) {
|
|
|
3759
4005
|
}
|
|
3760
4006
|
async function usageOf(rpc, { provider, account }) {
|
|
3761
4007
|
if (provider === "cursor-subscription") return callSubscriptionsAuth(rpc, "cursorUsage", {});
|
|
3762
|
-
if (provider === "opencode-go" || provider === "kimi-coding") return callSubscriptionsAuth(rpc, "externalUsage", { source: provider === "kimi-coding" ? "kimi-code" : provider });
|
|
4008
|
+
if (provider === "opencode-go" || provider === "kimi-coding" || provider === "minimax" || provider === "minimax-cn") return callSubscriptionsAuth(rpc, "externalUsage", { source: provider === "kimi-coding" ? "kimi-code" : provider });
|
|
3763
4009
|
return callSubscriptionsAuth(rpc, "usage", {
|
|
3764
4010
|
provider,
|
|
3765
4011
|
account: account.key
|
|
@@ -3786,7 +4032,7 @@ function createCurrentModelReader(models, sessionId) {
|
|
|
3786
4032
|
* Falls back to the scope/kind abbreviation when no reset time is known.
|
|
3787
4033
|
*/
|
|
3788
4034
|
function windowLabel(w) {
|
|
3789
|
-
if (w.resetsAt === void 0) {
|
|
4035
|
+
if (w.resetsAt === void 0 || !Number.isFinite(w.resetsAt)) {
|
|
3790
4036
|
if (w.scope !== void 0 && w.scope !== "") return w.scope;
|
|
3791
4037
|
switch (w.kind) {
|
|
3792
4038
|
case "session": return "5h";
|
|
@@ -3802,9 +4048,13 @@ function windowLabel(w) {
|
|
|
3802
4048
|
if (hours > 0) return `${hours}h${minutes % 60}m`;
|
|
3803
4049
|
return `${Math.max(1, minutes)}m`;
|
|
3804
4050
|
}
|
|
3805
|
-
/**
|
|
3806
|
-
|
|
3807
|
-
|
|
4051
|
+
/**
|
|
4052
|
+
* One compact segment piece (`6d1h 25%`); an unusable provider percentage
|
|
4053
|
+
* renders as the no-data dash, consistent with the meter's unavailable state.
|
|
4054
|
+
*/
|
|
4055
|
+
function windowSegment(label, w) {
|
|
4056
|
+
const percent = displayUsedPercent(w.usedPercent);
|
|
4057
|
+
return percent === void 0 ? `${label} —` : `${label} ${percent}%`;
|
|
3808
4058
|
}
|
|
3809
4059
|
/** Keep model quotas separate: matching percentages do not imply a shared pool. */
|
|
3810
4060
|
function prioritizeWindows(windows, model) {
|
|
@@ -3826,10 +4076,10 @@ function compactSegment(d, model, t = fallbackTranslate) {
|
|
|
3826
4076
|
if (d.provider === "antigravity") {
|
|
3827
4077
|
const matching = model === void 0 ? [] : windows.filter((w) => w.scope === model);
|
|
3828
4078
|
if (matching.length === 0) return `${d.name} ${t(model === void 0 ? "usageBadgeModelCount" : "usageBadgeModelUnavailable", { count: new Set(windows.map((w) => w.scope).filter(Boolean)).size })}`;
|
|
3829
|
-
const parts$1 = matching.slice(0, 2).map((w) =>
|
|
4079
|
+
const parts$1 = matching.slice(0, 2).map((w) => windowSegment(w.kind === "weekly" ? t("usageWeekly") : t("usageWindow"), w));
|
|
3830
4080
|
return `${d.name} ${parts$1.join(" · ")}`;
|
|
3831
4081
|
}
|
|
3832
|
-
const parts = windows.slice(0, 2).map((w) =>
|
|
4082
|
+
const parts = windows.slice(0, 2).map((w) => windowSegment(windowLabel(w), w));
|
|
3833
4083
|
if (windows.length > 2) parts.push(`+${windows.length - 2}`);
|
|
3834
4084
|
return `${d.name} ${parts.join(" · ")}`;
|
|
3835
4085
|
}
|
|
@@ -3893,6 +4143,7 @@ function SubscriptionUsageBadge({ rpc, currentModel, t }) {
|
|
|
3893
4143
|
const currentRef = (0, react.useRef)(currentModel);
|
|
3894
4144
|
currentRef.current = currentModel;
|
|
3895
4145
|
const lastKnownRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
4146
|
+
const observationsRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
3896
4147
|
const refresh = (0, react.useCallback)(async () => {
|
|
3897
4148
|
if (rpc === void 0 || inflightRef.current) return;
|
|
3898
4149
|
inflightRef.current = true;
|
|
@@ -3901,7 +4152,10 @@ function SubscriptionUsageBadge({ rpc, currentModel, t }) {
|
|
|
3901
4152
|
if (!mountedRef.current) return;
|
|
3902
4153
|
const lastKnown = lastKnownRef.current;
|
|
3903
4154
|
const live = new Set(roster.map(({ provider, account }) => usageKeyOf(provider, account)));
|
|
3904
|
-
for (const key of lastKnown.keys()) if (refreshed.has(key.split(":", 1)[0]) && !live.has(key))
|
|
4155
|
+
for (const key of lastKnown.keys()) if (refreshed.has(key.split(":", 1)[0]) && !live.has(key)) {
|
|
4156
|
+
lastKnown.delete(key);
|
|
4157
|
+
observationsRef.current.delete(key);
|
|
4158
|
+
}
|
|
3905
4159
|
const results = await Promise.allSettled(roster.map(async ({ provider, account }) => {
|
|
3906
4160
|
return {
|
|
3907
4161
|
provider,
|
|
@@ -3913,6 +4167,13 @@ function SubscriptionUsageBadge({ rpc, currentModel, t }) {
|
|
|
3913
4167
|
};
|
|
3914
4168
|
}));
|
|
3915
4169
|
if (!mountedRef.current) return;
|
|
4170
|
+
for (const { provider, account } of roster) {
|
|
4171
|
+
const key = usageKeyOf(provider, account);
|
|
4172
|
+
observationsRef.current.set(key, {
|
|
4173
|
+
...observationsRef.current.get(key),
|
|
4174
|
+
stale: true
|
|
4175
|
+
});
|
|
4176
|
+
}
|
|
3916
4177
|
const plans = /* @__PURE__ */ new Map();
|
|
3917
4178
|
for (const r of results) {
|
|
3918
4179
|
if (r.status !== "fulfilled") continue;
|
|
@@ -3921,12 +4182,37 @@ function SubscriptionUsageBadge({ rpc, currentModel, t }) {
|
|
|
3921
4182
|
if (usage.plan !== void 0) plans.set(key, usage.plan);
|
|
3922
4183
|
if (!usage.supported || !usage.windows || usage.windows.length === 0) {
|
|
3923
4184
|
lastKnown.delete(key);
|
|
4185
|
+
observationsRef.current.delete(key);
|
|
3924
4186
|
continue;
|
|
3925
4187
|
}
|
|
3926
4188
|
lastKnown.set(key, usage.windows);
|
|
4189
|
+
observationsRef.current.set(key, {
|
|
4190
|
+
observedAt: usage.observedAt ?? Date.now(),
|
|
4191
|
+
stale: usage.stale === true
|
|
4192
|
+
});
|
|
3927
4193
|
}
|
|
3928
|
-
setDisplays((previous) => [...groupUsageDisplays(roster, lastKnown, plans)
|
|
3929
|
-
|
|
4194
|
+
setDisplays((previous) => [...groupUsageDisplays(roster, lastKnown, plans).map((display) => ({
|
|
4195
|
+
...display,
|
|
4196
|
+
accounts: display.accounts.map((account) => ({
|
|
4197
|
+
...account,
|
|
4198
|
+
...observationsRef.current.get(`${display.provider}:${account.key}`)
|
|
4199
|
+
}))
|
|
4200
|
+
})), ...previous.filter((display) => !refreshed.has(display.provider)).map((display) => ({
|
|
4201
|
+
...display,
|
|
4202
|
+
accounts: display.accounts.map((account) => ({
|
|
4203
|
+
...account,
|
|
4204
|
+
stale: true
|
|
4205
|
+
}))
|
|
4206
|
+
}))]);
|
|
4207
|
+
} catch {
|
|
4208
|
+
if (mountedRef.current) setDisplays((previous) => previous.map((display) => ({
|
|
4209
|
+
...display,
|
|
4210
|
+
accounts: display.accounts.map((account) => ({
|
|
4211
|
+
...account,
|
|
4212
|
+
stale: true
|
|
4213
|
+
}))
|
|
4214
|
+
})));
|
|
4215
|
+
} finally {
|
|
3930
4216
|
inflightRef.current = false;
|
|
3931
4217
|
}
|
|
3932
4218
|
}, [rpc]);
|
|
@@ -4095,6 +4381,8 @@ function SubscriptionUsageBadge({ rpc, currentModel, t }) {
|
|
|
4095
4381
|
})
|
|
4096
4382
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AccountWindows, {
|
|
4097
4383
|
windows: account.windows,
|
|
4384
|
+
observedAt: account.observedAt,
|
|
4385
|
+
stale: account.stale,
|
|
4098
4386
|
model: d.provider === badgeSelection?.provider ? badgeSelection.model : void 0,
|
|
4099
4387
|
provider: d.provider,
|
|
4100
4388
|
translate
|
|
@@ -4137,13 +4425,16 @@ function AccountMeta({ account, translate }) {
|
|
|
4137
4425
|
});
|
|
4138
4426
|
}
|
|
4139
4427
|
/** Preview each account independently; all remaining quotas stay accessible. */
|
|
4140
|
-
function AccountWindows({ windows, model, provider, translate }) {
|
|
4428
|
+
function AccountWindows({ windows, model, provider, translate, observedAt, stale }) {
|
|
4141
4429
|
const { shown, hidden } = previewWindows(windows, model, provider);
|
|
4142
4430
|
const rows = (items) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dl", {
|
|
4143
4431
|
style: styles.details,
|
|
4144
4432
|
children: items.map((w, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WindowRow, {
|
|
4145
4433
|
label: `${usageWindowLabel(translate, w)}${model !== void 0 && w.scope === model ? ` · ${translate("usageBadgeCurrent")}` : ""}`,
|
|
4146
|
-
window: w
|
|
4434
|
+
window: w,
|
|
4435
|
+
t: translate,
|
|
4436
|
+
observedAt,
|
|
4437
|
+
stale
|
|
4147
4438
|
}, i))
|
|
4148
4439
|
});
|
|
4149
4440
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [rows(shown), hidden.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
@@ -4155,8 +4446,8 @@ function AccountWindows({ windows, model, provider, translate }) {
|
|
|
4155
4446
|
})] });
|
|
4156
4447
|
}
|
|
4157
4448
|
/** One `dt`/`dd` pair: window name → `25% · 6d1h`, with the bar underneath. */
|
|
4158
|
-
function WindowRow({ label, window: w }) {
|
|
4159
|
-
const percent =
|
|
4449
|
+
function WindowRow({ label, window: w, t, observedAt, stale }) {
|
|
4450
|
+
const percent = displayUsedPercent(w.usedPercent);
|
|
4160
4451
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
4161
4452
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", {
|
|
4162
4453
|
style: styles.dt,
|
|
@@ -4164,23 +4455,17 @@ function WindowRow({ label, window: w }) {
|
|
|
4164
4455
|
}),
|
|
4165
4456
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dd", {
|
|
4166
4457
|
style: styles.dd,
|
|
4167
|
-
children: [
|
|
4168
|
-
|
|
4169
|
-
"
|
|
4170
|
-
|
|
4171
|
-
style: styles.reset,
|
|
4172
|
-
children: [" · ", windowLabel(w)]
|
|
4173
|
-
})
|
|
4174
|
-
]
|
|
4458
|
+
children: [percent === void 0 ? t("usageMeterInvalid") : `${percent}%`, w.resetsAt !== void 0 && Number.isFinite(w.resetsAt) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
4459
|
+
style: styles.reset,
|
|
4460
|
+
children: [" · ", windowLabel(w)]
|
|
4461
|
+
})]
|
|
4175
4462
|
}),
|
|
4176
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
4463
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UsageMeter, {
|
|
4464
|
+
window: w,
|
|
4465
|
+
t,
|
|
4177
4466
|
style: styles.bar,
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
...styles.barFill,
|
|
4181
|
-
width: `${percent}%`,
|
|
4182
|
-
background: usageBarColor(percent)
|
|
4183
|
-
} })
|
|
4467
|
+
observedAt,
|
|
4468
|
+
stale
|
|
4184
4469
|
})
|
|
4185
4470
|
] });
|
|
4186
4471
|
}
|
|
@@ -4232,6 +4517,7 @@ const styles = {
|
|
|
4232
4517
|
zIndex: 1100,
|
|
4233
4518
|
boxSizing: "border-box",
|
|
4234
4519
|
background: "var(--dsw-specific-menu)",
|
|
4520
|
+
backdropFilter: "var(--dsw-menu-backdrop-filter)",
|
|
4235
4521
|
width: "max-content",
|
|
4236
4522
|
minWidth: "min(300px, 100vw - 24px)",
|
|
4237
4523
|
maxWidth: "min(440px, 100vw - 24px)",
|
|
@@ -4339,10 +4625,6 @@ const styles = {
|
|
|
4339
4625
|
overflow: "hidden",
|
|
4340
4626
|
background: "var(--dsw-alias-border-l2)",
|
|
4341
4627
|
marginBottom: 2
|
|
4342
|
-
},
|
|
4343
|
-
barFill: {
|
|
4344
|
-
height: "100%",
|
|
4345
|
-
borderRadius: 2
|
|
4346
4628
|
}
|
|
4347
4629
|
};
|
|
4348
4630
|
|