dsh-subscriptions 0.1.2 → 0.1.3
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 +28 -10
- package/lib/client/ExternalUsageCards.d.ts +11 -1
- package/lib/client/ExternalUsageCards.js +18 -3
- package/lib/client/SubscriptionUsageBadge.d.ts +19 -2
- package/lib/client/SubscriptionUsageBadge.js +60 -5
- package/lib/client/UsageBadgeDisplaySetting.js +2 -2
- package/lib/client/UsageMeter.js +11 -6
- package/lib/client/locales.d.ts +2 -0
- package/lib/client/locales.js +4 -2
- package/lib/client/usage-badge-preferences.d.ts +7 -2
- package/lib/client/usage-badge-preferences.js +3 -2
- package/lib/client/usage-pace.d.ts +14 -1
- package/lib/client/usage-pace.js +14 -1
- package/lib/client.js +101 -26
- package/lib/client.js.map +1 -1
- package/lib/index.js +32 -5
- package/lib/providers/minimax-usage.d.ts +22 -1
- package/lib/providers/minimax-usage.js +37 -7
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -190,8 +190,9 @@ const en = {
|
|
|
190
190
|
usageBadgeTitle: "Provider usage",
|
|
191
191
|
usageBadgeDisplay: "Status-bar quota display",
|
|
192
192
|
usageBadgeDisplayRecent: "Current / most recent provider with usage",
|
|
193
|
+
usageBadgeDisplayAlways: "Always show one provider, rotating through the rest",
|
|
193
194
|
usageBadgeDisplayHidden: "Hidden",
|
|
194
|
-
usageBadgeDisplayHint: "Saved automatically in this browser. One provider is shown at a time. Models without a quota source keep the most recent provider selected in this view, or show nothing. Hiding does not affect usage details here.",
|
|
195
|
+
usageBadgeDisplayHint: "Saved automatically in this browser. One provider is shown at a time. Models without a quota source keep the most recent provider selected in this view, or show nothing. Always show instead rotates through the providers with usage every 10 seconds while the current model has no quota source. Hiding does not affect usage details here.",
|
|
195
196
|
usageBadgeDisplaySaveFailed: "Could not save the display preference. Check browser storage permissions.",
|
|
196
197
|
usageBadgeCurrent: "current",
|
|
197
198
|
usageBadgeModelCount: "{count} model quotas",
|
|
@@ -361,8 +362,9 @@ const zh = {
|
|
|
361
362
|
usageBadgeTitle: "服务商用量",
|
|
362
363
|
usageBadgeDisplay: "状态栏额度显示",
|
|
363
364
|
usageBadgeDisplayRecent: "仅显示当前/最近使用的用量服务商",
|
|
365
|
+
usageBadgeDisplayAlways: "始终显示一家,其余服务商轮播",
|
|
364
366
|
usageBadgeDisplayHidden: "不显示",
|
|
365
|
-
usageBadgeDisplayHint: "
|
|
367
|
+
usageBadgeDisplayHint: "自动保存到当前浏览器。状态栏一次显示一家服务商;没有额度来源的模型沿用本视图最近选择的服务商,没有记录则隐藏。选择“始终显示”时,当前模型没有额度来源,就每 10 秒轮播一家有额度的服务商。关闭不影响本页的额度详情。",
|
|
366
368
|
usageBadgeDisplaySaveFailed: "显示设置保存失败,请检查浏览器存储权限。",
|
|
367
369
|
usageBadgeCurrent: "当前",
|
|
368
370
|
usageBadgeModelCount: "{count} 个模型配额",
|
|
@@ -1402,7 +1404,8 @@ const USAGE_BADGE_REFRESH_EVENT = "dsh:subscriptions:usage-refresh";
|
|
|
1402
1404
|
function readUsageBadgeMode() {
|
|
1403
1405
|
if (typeof window === "undefined") return "recent";
|
|
1404
1406
|
try {
|
|
1405
|
-
|
|
1407
|
+
const value = window.localStorage.getItem(MODE_KEY);
|
|
1408
|
+
return value === "hidden" || value === "always" ? value : "recent";
|
|
1406
1409
|
} catch (error) {
|
|
1407
1410
|
if (!(error instanceof DOMException) || error.name !== "SecurityError") throw error;
|
|
1408
1411
|
return "recent";
|
|
@@ -1410,7 +1413,7 @@ function readUsageBadgeMode() {
|
|
|
1410
1413
|
}
|
|
1411
1414
|
/** Save first, then notify; the settings control reports a rejected write. */
|
|
1412
1415
|
function setUsageBadgeMode(mode) {
|
|
1413
|
-
if (mode !== "recent" && mode !== "hidden") throw new TypeError("Invalid subscription usage display mode");
|
|
1416
|
+
if (mode !== "recent" && mode !== "always" && mode !== "hidden") throw new TypeError("Invalid subscription usage display mode");
|
|
1414
1417
|
window.localStorage.setItem(MODE_KEY, mode);
|
|
1415
1418
|
window.dispatchEvent(new Event(MODE_EVENT));
|
|
1416
1419
|
}
|
|
@@ -1466,10 +1469,23 @@ function resetCountdownParts(ms) {
|
|
|
1466
1469
|
}
|
|
1467
1470
|
].filter((part) => part.count > 0).slice(0, 2);
|
|
1468
1471
|
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Whether the reported percentage still describes the window now. Only a fresh
|
|
1474
|
+
* reading may be compared with the current time (pace warnings); an old one
|
|
1475
|
+
* keeps its absolute color. This does not decide whether the time cursor is
|
|
1476
|
+
* drawn: where the window stands in time never depends on the reading.
|
|
1477
|
+
*/
|
|
1469
1478
|
function isUsageFresh(window$1, observation, now = Date.now()) {
|
|
1470
1479
|
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
1480
|
}
|
|
1472
|
-
/**
|
|
1481
|
+
/**
|
|
1482
|
+
* How far through its window the clock is, from the window's own timing alone.
|
|
1483
|
+
* It ignores the percentage and how recently it was read, so an unused,
|
|
1484
|
+
* stale, aged or unobserved reading keeps its cursor whenever the timing
|
|
1485
|
+
* places it. Returns undefined when the timing cannot: only an explicit
|
|
1486
|
+
* interval or a provider-verified fixed duration supports linear pace, and no
|
|
1487
|
+
* start is invented from a window's kind or label.
|
|
1488
|
+
*/
|
|
1473
1489
|
function elapsedPercent(window$1, now = Date.now()) {
|
|
1474
1490
|
const end = window$1.resetsAt;
|
|
1475
1491
|
if (end === void 0 || !Number.isFinite(end) || end <= now) return void 0;
|
|
@@ -1548,17 +1564,18 @@ function UsageMeter({ window: quota, t, style, observedAt, stale }) {
|
|
|
1548
1564
|
observedAt,
|
|
1549
1565
|
stale
|
|
1550
1566
|
}, current);
|
|
1551
|
-
const elapsed =
|
|
1552
|
-
const
|
|
1553
|
-
const
|
|
1567
|
+
const elapsed = elapsedPercent(quota, current);
|
|
1568
|
+
const pace = fresh ? elapsed : void 0;
|
|
1569
|
+
const state = usageColorState(quota.usedPercent, pace, preset, fresh);
|
|
1570
|
+
const lead = pace === void 0 ? 0 : Math.round(used - pace);
|
|
1554
1571
|
const label = [
|
|
1555
1572
|
valid ? t("usageMeterUsed", {
|
|
1556
1573
|
used: Math.round(used),
|
|
1557
1574
|
remaining: Math.round(100 - used)
|
|
1558
1575
|
}) : t("usageMeterInvalid"),
|
|
1559
|
-
!valid ? "" : !fresh ? t("usageMeterStale") : state === "red" ? t("usageMeterNearLimit") :
|
|
1576
|
+
!valid ? "" : !fresh ? t("usageMeterStale") : state === "red" ? t("usageMeterNearLimit") : pace === void 0 && preset !== "remaining" ? t("usageMeterUnknownTime") : state === "yellow" ? t("usageMeterAhead") : t("usageMeterNoWarning"),
|
|
1560
1577
|
elapsed === void 0 ? "" : t("usageMeterPace", { elapsed: Math.round(elapsed) }),
|
|
1561
|
-
|
|
1578
|
+
pace === void 0 ? "" : lead > 0 ? t("usageMeterPaceAhead", { points: lead }) : lead < 0 ? t("usageMeterPaceBehind", { points: -lead }) : t("usageMeterPaceEven"),
|
|
1562
1579
|
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
1580
|
].filter(Boolean).join(" · ");
|
|
1564
1581
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -1579,7 +1596,7 @@ function UsageMeter({ window: quota, t, style, observedAt, stale }) {
|
|
|
1579
1596
|
height: "100%",
|
|
1580
1597
|
borderRadius: "inherit",
|
|
1581
1598
|
width: `${used}%`,
|
|
1582
|
-
background: usageBarColor(quota.usedPercent,
|
|
1599
|
+
background: usageBarColor(quota.usedPercent, pace, preset, fresh)
|
|
1583
1600
|
} }), elapsed !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1584
1601
|
"aria-hidden": "true",
|
|
1585
1602
|
"data-usage-time-marker": true,
|
|
@@ -1623,6 +1640,21 @@ const SOURCES = [
|
|
|
1623
1640
|
function errorText(error) {
|
|
1624
1641
|
return error instanceof Error ? error.message : String(error);
|
|
1625
1642
|
}
|
|
1643
|
+
/**
|
|
1644
|
+
* Row label of one window on an external usage card. A session or weekly
|
|
1645
|
+
* window appends the model it belongs to when the provider names one (MiniMax
|
|
1646
|
+
* reports a window per model), so two models' weekly rows stay distinguishable.
|
|
1647
|
+
* The monthly pool is a scope on an `other` window, not a model.
|
|
1648
|
+
* @param t - section translate.
|
|
1649
|
+
* @param window - the reported window.
|
|
1650
|
+
* @returns the localized label, e.g. "Weekly · general".
|
|
1651
|
+
*/
|
|
1652
|
+
function externalUsageWindowLabel(t, window$1) {
|
|
1653
|
+
const named = window$1.scope !== void 0 && window$1.scope !== "";
|
|
1654
|
+
if (window$1.kind === "other") return window$1.scope === "Monthly" ? t("usageMonthly") : named ? window$1.scope : t("usageWindow");
|
|
1655
|
+
const base = window$1.kind === "session" ? t("usageSession") : t("usageWeekly");
|
|
1656
|
+
return named ? `${base} · ${window$1.scope}` : base;
|
|
1657
|
+
}
|
|
1626
1658
|
/** Quota cards for API-key providers already available through DSH itself. */
|
|
1627
1659
|
function ExternalUsageCards({ rpc, t }) {
|
|
1628
1660
|
const [status, setStatus] = (0, react.useState)();
|
|
@@ -1755,7 +1787,7 @@ function ExternalUsageCards({ rpc, t }) {
|
|
|
1755
1787
|
}),
|
|
1756
1788
|
snapshot?.windows?.map((window$1, index) => {
|
|
1757
1789
|
const percent = displayUsedPercent(window$1.usedPercent);
|
|
1758
|
-
const label =
|
|
1790
|
+
const label = externalUsageWindowLabel(t, window$1);
|
|
1759
1791
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1760
1792
|
style: subscriptionCardStyles.usageRow,
|
|
1761
1793
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -2050,7 +2082,7 @@ function UsageBadgeDisplaySetting({ t }) {
|
|
|
2050
2082
|
value: mode,
|
|
2051
2083
|
onChange: (event) => {
|
|
2052
2084
|
const value = event.currentTarget.value;
|
|
2053
|
-
if (value !== "recent" && value !== "hidden") return;
|
|
2085
|
+
if (value !== "recent" && value !== "always" && value !== "hidden") return;
|
|
2054
2086
|
try {
|
|
2055
2087
|
setUsageBadgeMode(value);
|
|
2056
2088
|
setFailed(false);
|
|
@@ -2059,13 +2091,20 @@ function UsageBadgeDisplaySetting({ t }) {
|
|
|
2059
2091
|
setFailed(true);
|
|
2060
2092
|
}
|
|
2061
2093
|
},
|
|
2062
|
-
children: [
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2094
|
+
children: [
|
|
2095
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2096
|
+
value: "recent",
|
|
2097
|
+
children: t("usageBadgeDisplayRecent")
|
|
2098
|
+
}),
|
|
2099
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2100
|
+
value: "always",
|
|
2101
|
+
children: t("usageBadgeDisplayAlways")
|
|
2102
|
+
}),
|
|
2103
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
2104
|
+
value: "hidden",
|
|
2105
|
+
children: t("usageBadgeDisplayHidden")
|
|
2106
|
+
})
|
|
2107
|
+
]
|
|
2069
2108
|
})]
|
|
2070
2109
|
}),
|
|
2071
2110
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -3893,6 +3932,8 @@ const UsageBadgeIcon = usageBadgeIcon(__deepseek_ai_dsh_client_ui_primitives);
|
|
|
3893
3932
|
const USAGE_POLL_INTERVAL_MS = 15 * 6e4;
|
|
3894
3933
|
/** How often the badge re-reads the session's current model (model switches arrive only by asking). */
|
|
3895
3934
|
const MODEL_POLL_INTERVAL_MS = 3e3;
|
|
3935
|
+
/** How long the rotation holds one provider in `always` mode (a display cadence, not a data poll). */
|
|
3936
|
+
const BADGE_ROTATION_INTERVAL_MS = 1e4;
|
|
3896
3937
|
/** Distance between the trigger's top edge and the dialog's bottom (host stat dialogs use the same). */
|
|
3897
3938
|
const PANEL_GAP = 8;
|
|
3898
3939
|
/** Distance kept between the dialog and each viewport edge. */
|
|
@@ -4092,6 +4133,24 @@ function collapsedDisplays(displays, current) {
|
|
|
4092
4133
|
const match = displays.find((d) => d.provider === current);
|
|
4093
4134
|
return match === void 0 ? [] : [match];
|
|
4094
4135
|
}
|
|
4136
|
+
/**
|
|
4137
|
+
* `always` mode: the current model's provider while it reports usage,
|
|
4138
|
+
* otherwise the rotation steps through the providers that do, in display
|
|
4139
|
+
* order, wrapping around. The index is taken modulo the current provider
|
|
4140
|
+
* count, so providers appearing or dropping out stay inside range; the
|
|
4141
|
+
* sequence itself may shift when the roster changes. Unlike `recent`, the
|
|
4142
|
+
* retained subscription does not pin the pill — that retention is exactly
|
|
4143
|
+
* what this mode exists to escape.
|
|
4144
|
+
* @param displays - providers with usage, in display order.
|
|
4145
|
+
* @param current - the provider of the session's current model.
|
|
4146
|
+
* @param step - rotation tick; consecutive ticks advance one provider.
|
|
4147
|
+
*/
|
|
4148
|
+
function rotatingDisplay(displays, current, step) {
|
|
4149
|
+
if (displays.length === 0) return [];
|
|
4150
|
+
const match = displays.find((d) => d.provider === current);
|
|
4151
|
+
if (match !== void 0) return [match];
|
|
4152
|
+
return [displays[((Number.isFinite(step) ? Math.floor(step) : 0) % displays.length + displays.length) % displays.length]];
|
|
4153
|
+
}
|
|
4095
4154
|
/** Order for the expanded dialog: the current provider first, the rest in poll order. */
|
|
4096
4155
|
function expandedDisplays(displays, current) {
|
|
4097
4156
|
const match = displays.find((d) => d.provider === current);
|
|
@@ -4125,7 +4184,7 @@ function usageWindowLabel(t, window$1) {
|
|
|
4125
4184
|
* every provider's accounts and their windows. Returns null when no data is
|
|
4126
4185
|
* available.
|
|
4127
4186
|
*/
|
|
4128
|
-
function SubscriptionUsageBadge({ rpc, currentModel, t }) {
|
|
4187
|
+
function SubscriptionUsageBadge({ rpc, currentModel, t, rotationMs }) {
|
|
4129
4188
|
const translate = t ?? fallbackTranslate;
|
|
4130
4189
|
const [displays, setDisplays] = (0, react.useState)([]);
|
|
4131
4190
|
const [selection, setSelection] = (0, react.useState)(void 0);
|
|
@@ -4135,6 +4194,8 @@ function SubscriptionUsageBadge({ rpc, currentModel, t }) {
|
|
|
4135
4194
|
const badgeSelection = retainSubscriptionSelection(lastSubscription, selection);
|
|
4136
4195
|
const [open, setOpen] = (0, react.useState)(false);
|
|
4137
4196
|
const [hover, setHover] = (0, react.useState)(false);
|
|
4197
|
+
const [rotation, setRotation] = (0, react.useState)(0);
|
|
4198
|
+
const [dialogLead, setDialogLead] = (0, react.useState)(void 0);
|
|
4138
4199
|
const inflightRef = (0, react.useRef)(false);
|
|
4139
4200
|
const mountedRef = (0, react.useRef)(true);
|
|
4140
4201
|
const rootRef = (0, react.useRef)(null);
|
|
@@ -4298,18 +4359,32 @@ function SubscriptionUsageBadge({ rpc, currentModel, t }) {
|
|
|
4298
4359
|
(0, react.useEffect)(() => {
|
|
4299
4360
|
if (displayMode === "hidden") setOpen(false);
|
|
4300
4361
|
}, [displayMode]);
|
|
4362
|
+
(0, react.useEffect)(() => {
|
|
4363
|
+
if (displayMode !== "always") return;
|
|
4364
|
+
const interval = rotationMs !== void 0 && Number.isFinite(rotationMs) && rotationMs > 0 ? rotationMs : BADGE_ROTATION_INTERVAL_MS;
|
|
4365
|
+
const timer = setInterval(() => {
|
|
4366
|
+
setRotation((step) => step + 1);
|
|
4367
|
+
}, interval);
|
|
4368
|
+
return () => {
|
|
4369
|
+
clearInterval(timer);
|
|
4370
|
+
};
|
|
4371
|
+
}, [displayMode, rotationMs]);
|
|
4301
4372
|
const seat = /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
4302
4373
|
ref: seatRef,
|
|
4303
4374
|
style: styles.seat,
|
|
4304
4375
|
"aria-hidden": true
|
|
4305
4376
|
});
|
|
4306
|
-
const collapsed = collapsedDisplays(displays, badgeSelection?.provider);
|
|
4377
|
+
const collapsed = displayMode === "always" ? rotatingDisplay(displays, current, rotation) : collapsedDisplays(displays, badgeSelection?.provider);
|
|
4307
4378
|
if (displayMode === "hidden" || collapsed.length === 0) return seat;
|
|
4308
|
-
const
|
|
4309
|
-
const
|
|
4379
|
+
const pillModel = displayMode === "always" ? current === collapsed[0]?.provider ? selection?.model : void 0 : badgeSelection?.model;
|
|
4380
|
+
const label = compactSegment(collapsed[0], pillModel, translate);
|
|
4381
|
+
const dialogProvider = open && displayMode === "always" ? dialogLead : badgeSelection?.provider;
|
|
4382
|
+
const dialogModel = open && displayMode === "always" ? dialogLead === current ? selection?.model : void 0 : badgeSelection?.model;
|
|
4383
|
+
const expanded = expandedDisplays(displays, dialogProvider);
|
|
4310
4384
|
const title = translate("usageBadgeTitle");
|
|
4311
4385
|
const toggle = () => {
|
|
4312
4386
|
const next = !open;
|
|
4387
|
+
setDialogLead(collapsed[0]?.provider);
|
|
4313
4388
|
setOpen(next);
|
|
4314
4389
|
if (next) refresh();
|
|
4315
4390
|
};
|
|
@@ -4383,10 +4458,10 @@ function SubscriptionUsageBadge({ rpc, currentModel, t }) {
|
|
|
4383
4458
|
windows: account.windows,
|
|
4384
4459
|
observedAt: account.observedAt,
|
|
4385
4460
|
stale: account.stale,
|
|
4386
|
-
model: d.provider ===
|
|
4461
|
+
model: d.provider === dialogProvider ? dialogModel : void 0,
|
|
4387
4462
|
provider: d.provider,
|
|
4388
4463
|
translate
|
|
4389
|
-
}, `${d.provider}:${
|
|
4464
|
+
}, `${d.provider}:${dialogModel ?? ""}`)]
|
|
4390
4465
|
}, account.key))]
|
|
4391
4466
|
}, d.provider))
|
|
4392
4467
|
]
|