dsh-subscriptions 0.2.0 → 0.2.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 +220 -180
- package/lib/auth/claude-code-creds.js +31 -9
- package/lib/auth/device-flow.js +16 -9
- package/lib/auth/oauth-flow.js +13 -10
- package/lib/auth/rpc.d.ts +8 -4
- package/lib/auth/rpc.js +89 -25
- package/lib/auth/store.js +80 -31
- package/lib/client/CursorCard.d.ts +1 -1
- package/lib/client/CursorCard.js +50 -16
- package/lib/client/ExternalUsageCards.d.ts +1 -1
- package/lib/client/ExternalUsageCards.js +37 -16
- package/lib/client/ImageGallery.d.ts +2 -2
- package/lib/client/ImageGallery.js +92 -33
- package/lib/client/ImageGenerateToolview.js +50 -14
- package/lib/client/OllamaCard.d.ts +1 -1
- package/lib/client/OllamaCard.js +26 -8
- package/lib/client/OllamaSettingsManager.js +113 -30
- package/lib/client/ProviderAccountManager.d.ts +1 -1
- package/lib/client/ProviderAccountManager.js +164 -49
- package/lib/client/ProviderModelEditor.js +137 -34
- package/lib/client/SpeedSelect.d.ts +62 -14
- package/lib/client/SpeedSelect.js +261 -49
- package/lib/client/SubscriptionUsageBadge.d.ts +4 -10
- package/lib/client/SubscriptionUsageBadge.js +300 -112
- package/lib/client/SubscriptionsSection.d.ts +2 -0
- package/lib/client/SubscriptionsSection.js +265 -85
- package/lib/client/UsageBadgeDisplaySetting.js +35 -12
- package/lib/client/UsageMeter.d.ts +1 -1
- package/lib/client/UsageMeter.js +62 -24
- package/lib/client/VideoGenerateToolview.js +69 -17
- package/lib/client/account-preferences.d.ts +8 -0
- package/lib/client/account-preferences.js +20 -5
- package/lib/client/host-contract.d.ts +11 -2
- package/lib/client/host-contract.js +20 -3
- package/lib/client/index.d.ts +5 -5
- package/lib/client/index.js +34 -12
- package/lib/client/locales.d.ts +22 -0
- package/lib/client/locales.js +73 -51
- package/lib/client/model-filter.d.ts +37 -0
- package/lib/client/model-filter.js +45 -0
- package/lib/client/ollama-draft.js +21 -12
- package/lib/client/reset-action.js +6 -4
- package/lib/client/reset-credits-view.d.ts +1 -1
- package/lib/client/reset-credits-view.js +66 -17
- package/lib/client/subscription-card-styles.js +94 -24
- package/lib/client/usage-color-preferences.js +4 -2
- package/lib/client/usage-pace.js +30 -12
- package/lib/client/usage-window-filter.d.ts +7 -0
- package/lib/client/usage-window-filter.js +12 -0
- package/lib/client.js +480 -136
- package/lib/client.js.map +1 -1
- package/lib/http.js +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.js +167 -76
- package/lib/model-defaults.js +4 -2
- package/lib/provider-settings.d.ts +17 -0
- package/lib/provider-settings.js +79 -16
- package/lib/providers/account-preferences.js +51 -22
- package/lib/providers/accounts.js +15 -9
- package/lib/providers/antigravity-oauth-client.js +2 -2
- package/lib/providers/antigravity.js +141 -68
- package/lib/providers/catalog-store.js +120 -41
- package/lib/providers/claude-cli-version.js +3 -3
- package/lib/providers/claude.d.ts +9 -2
- package/lib/providers/claude.js +153 -90
- package/lib/providers/codex-client-version.js +3 -3
- package/lib/providers/codex-search.js +40 -13
- package/lib/providers/codex.js +196 -112
- package/lib/providers/common.js +71 -39
- package/lib/providers/copilot.js +143 -82
- package/lib/providers/cursor-adapter.js +42 -18
- package/lib/providers/cursor-auth.d.ts +1 -1
- package/lib/providers/cursor-auth.js +69 -24
- package/lib/providers/cursor-model-settings.js +14 -5
- package/lib/providers/cursor-usage.js +27 -13
- package/lib/providers/external-usage-controller.js +14 -5
- package/lib/providers/external-usage.js +59 -23
- package/lib/providers/grok.js +152 -89
- package/lib/providers/image-pool.js +29 -11
- package/lib/providers/minimax-usage.js +31 -11
- package/lib/providers/npm-cli-version.js +10 -3
- package/lib/providers/ollama-settings.js +24 -8
- package/lib/providers/ollama-web.js +29 -11
- package/lib/providers/ollama.js +129 -66
- package/lib/providers/pool-family.js +11 -3
- package/lib/providers/pool-health.js +28 -8
- package/lib/providers/pool-usage.js +16 -9
- package/lib/providers/pool.d.ts +8 -0
- package/lib/providers/pool.js +81 -42
- package/lib/providers/rate-limit.js +8 -2
- package/lib/providers/replay.js +40 -17
- package/lib/providers/reset-redemption.js +18 -6
- package/lib/tools/image-generate.js +123 -65
- package/lib/tools/registration.js +5 -2
- package/lib/tools/video-generate.js +51 -31
- package/lib/tools/x-search.js +50 -22
- package/lib/translate/anthropic.js +85 -37
- package/lib/translate/antigravity-schema.js +71 -28
- package/lib/translate/antigravity-thinking.js +22 -9
- package/lib/translate/antigravity.js +151 -52
- package/lib/translate/chat-completions.js +67 -23
- package/lib/translate/resolved.js +55 -21
- package/lib/translate/responses.js +69 -29
- package/lib/translate/sse.js +4 -1
- package/package.json +27 -4
|
@@ -13,6 +13,8 @@ export interface AccountStatus {
|
|
|
13
13
|
}
|
|
14
14
|
/** One provider's login state as answered by the `status` endpoint. */
|
|
15
15
|
export interface ProviderStatus {
|
|
16
|
+
/** Picker selection; absent follows discovery, [] hides all model quotas. */
|
|
17
|
+
visibleModels?: string[];
|
|
16
18
|
busy: boolean;
|
|
17
19
|
accounts: AccountStatus[];
|
|
18
20
|
detail?: string;
|
|
@@ -15,18 +15,19 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
15
15
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
16
16
|
import { en } from './locales.js';
|
|
17
17
|
import { resetActionBlock } from './reset-action.js';
|
|
18
|
+
import { visibleUsageWindows } from './usage-window-filter.js';
|
|
18
19
|
import { ProviderAccountManager } from './ProviderAccountManager.js';
|
|
19
20
|
import { providerSettingsCss } from './provider-settings-styles.js';
|
|
20
21
|
import { ExternalUsageCards } from './ExternalUsageCards.js';
|
|
21
22
|
import { CursorCard } from './CursorCard.js';
|
|
22
23
|
import { OllamaCard } from './OllamaCard.js';
|
|
23
24
|
import { UsageMeter } from './UsageMeter.js';
|
|
24
|
-
import { ResetCreditsDisclosure, ResetCreditsErrorLine, showsResetCredits } from './reset-credits-view.js';
|
|
25
|
+
import { ResetCreditsDisclosure, ResetCreditsErrorLine, showsResetCredits, } from './reset-credits-view.js';
|
|
25
26
|
import { displayUsedPercent } from './usage-pace.js';
|
|
26
27
|
import { UsageBadgeDisplaySetting } from './UsageBadgeDisplaySetting.js';
|
|
27
28
|
import { USAGE_BADGE_REFRESH_EVENT } from './usage-badge-preferences.js';
|
|
28
29
|
import { subscriptionCardStyles as cardStyles } from './subscription-card-styles.js';
|
|
29
|
-
import { callSubscriptionsAuth, SubscriptionsAuthError } from './subscriptions-rpc.js';
|
|
30
|
+
import { callSubscriptionsAuth, SubscriptionsAuthError, } from './subscriptions-rpc.js';
|
|
30
31
|
export { callSubscriptionsAuth } from './subscriptions-rpc.js';
|
|
31
32
|
/** Poll cadence while a provider login attempt is busy. */
|
|
32
33
|
const POLL_INTERVAL_MS = 2000;
|
|
@@ -50,7 +51,7 @@ function messageOf(error) {
|
|
|
50
51
|
}
|
|
51
52
|
/** Copy a keyed map without the entries whose key is not in `live`. */
|
|
52
53
|
function dropStale(map, live) {
|
|
53
|
-
const stale = Object.keys(map).filter(key => !live.has(key));
|
|
54
|
+
const stale = Object.keys(map).filter((key) => !live.has(key));
|
|
54
55
|
if (stale.length === 0)
|
|
55
56
|
return map;
|
|
56
57
|
const next = { ...map };
|
|
@@ -74,61 +75,128 @@ function fallbackTranslate(key, params) {
|
|
|
74
75
|
}
|
|
75
76
|
const styles = {
|
|
76
77
|
section: {
|
|
77
|
-
display: 'flex',
|
|
78
|
+
display: 'flex',
|
|
79
|
+
flexDirection: 'column',
|
|
80
|
+
gap: 12,
|
|
81
|
+
maxWidth: 560,
|
|
78
82
|
color: 'var(--dsw-alias-label-primary)',
|
|
79
83
|
},
|
|
80
|
-
intro: {
|
|
81
|
-
|
|
84
|
+
intro: {
|
|
85
|
+
margin: 0,
|
|
86
|
+
color: 'var(--dsw-alias-label-tertiary)',
|
|
87
|
+
fontSize: 14,
|
|
88
|
+
lineHeight: '22px',
|
|
89
|
+
},
|
|
90
|
+
card: {
|
|
91
|
+
...cardStyles.card,
|
|
82
92
|
border: '0.5px solid var(--dsw-alias-settings-card-stroke, var(--dsw-alias-border-l4))',
|
|
83
|
-
borderRadius: 'var(--dsw-radius-xl, 20px)',
|
|
93
|
+
borderRadius: 'var(--dsw-radius-xl, 20px)',
|
|
94
|
+
background: 'var(--dsw-alias-settings-card-fill, var(--dsw-alias-bg-layer-2))',
|
|
84
95
|
},
|
|
85
96
|
cardHeader: cardStyles.header,
|
|
86
97
|
dot: cardStyles.dot,
|
|
87
98
|
name: cardStyles.name,
|
|
88
99
|
statusLine: cardStyles.status,
|
|
89
|
-
clientVersion: {
|
|
100
|
+
clientVersion: {
|
|
101
|
+
fontSize: 12,
|
|
102
|
+
lineHeight: '18px',
|
|
103
|
+
color: 'var(--dsw-alias-label-tertiary)',
|
|
104
|
+
fontVariantNumeric: 'tabular-nums',
|
|
105
|
+
},
|
|
90
106
|
errorLine: cardStyles.error,
|
|
91
107
|
actions: cardStyles.actions,
|
|
92
|
-
usage: {
|
|
108
|
+
usage: {
|
|
109
|
+
...cardStyles.usage,
|
|
110
|
+
borderTop: '0.5px solid var(--dsw-alias-border-l2)',
|
|
111
|
+
},
|
|
93
112
|
usageHeader: cardStyles.usageHeader,
|
|
94
113
|
usageTitle: cardStyles.usageTitle,
|
|
95
114
|
usagePlan: cardStyles.usagePlan,
|
|
96
|
-
usageRefresh: {
|
|
115
|
+
usageRefresh: {
|
|
116
|
+
minHeight: 22,
|
|
117
|
+
height: 22,
|
|
118
|
+
padding: '0 8px',
|
|
119
|
+
marginLeft: 'auto',
|
|
120
|
+
},
|
|
97
121
|
usageRow: cardStyles.usageRow,
|
|
98
122
|
usageMeta: cardStyles.usageMeta,
|
|
99
|
-
accountRow: {
|
|
123
|
+
accountRow: {
|
|
124
|
+
...cardStyles.account,
|
|
125
|
+
border: '0.5px solid var(--dsw-alias-border-l2)',
|
|
126
|
+
borderRadius: 'var(--dsw-radius-lg, 16px)',
|
|
127
|
+
},
|
|
100
128
|
accountHeader: cardStyles.accountHeader,
|
|
101
129
|
accountName: cardStyles.accountName,
|
|
102
130
|
starButton: {
|
|
103
|
-
border: 'none',
|
|
104
|
-
|
|
131
|
+
border: 'none',
|
|
132
|
+
padding: 0,
|
|
133
|
+
width: 28,
|
|
134
|
+
font: 'inherit',
|
|
135
|
+
fontSize: 14,
|
|
136
|
+
lineHeight: '20px',
|
|
137
|
+
cursor: 'pointer',
|
|
105
138
|
color: 'var(--dsw-alias-state-warn-label)',
|
|
106
139
|
},
|
|
107
140
|
deviceCode: {
|
|
108
|
-
marginTop: 4,
|
|
109
|
-
|
|
110
|
-
|
|
141
|
+
marginTop: 4,
|
|
142
|
+
display: 'flex',
|
|
143
|
+
flexDirection: 'column',
|
|
144
|
+
gap: 6,
|
|
145
|
+
border: '0.5px solid var(--dsw-alias-border-l2)',
|
|
146
|
+
borderRadius: 'var(--dsw-radius-lg, 16px)',
|
|
147
|
+
padding: '10px 12px',
|
|
148
|
+
background: 'var(--dsw-alias-bg-layer-1)',
|
|
111
149
|
},
|
|
112
150
|
deviceCodeText: {
|
|
113
|
-
fontFamily: 'monospace',
|
|
114
|
-
|
|
151
|
+
fontFamily: 'monospace',
|
|
152
|
+
fontSize: 18,
|
|
153
|
+
lineHeight: '24px',
|
|
154
|
+
letterSpacing: 2,
|
|
155
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
156
|
+
userSelect: 'all',
|
|
115
157
|
},
|
|
116
158
|
manual: { fontSize: 13, lineHeight: '20px' },
|
|
117
|
-
manualRow: {
|
|
159
|
+
manualRow: {
|
|
160
|
+
display: 'flex',
|
|
161
|
+
flexWrap: 'wrap',
|
|
162
|
+
gap: 8,
|
|
163
|
+
alignItems: 'center',
|
|
164
|
+
marginTop: 8,
|
|
165
|
+
},
|
|
118
166
|
manualInput: { flex: '1 1 200px', minWidth: 0 },
|
|
119
167
|
modalOverlay: {
|
|
120
|
-
position: 'fixed',
|
|
121
|
-
|
|
122
|
-
|
|
168
|
+
position: 'fixed',
|
|
169
|
+
inset: 0,
|
|
170
|
+
zIndex: 1000,
|
|
171
|
+
display: 'flex',
|
|
172
|
+
alignItems: 'center',
|
|
173
|
+
justifyContent: 'center',
|
|
174
|
+
padding: 16,
|
|
175
|
+
background: 'var(--dsw-alias-bg-mask-1, #0006)',
|
|
176
|
+
backdropFilter: 'var(--dsw-mask-blur, none)',
|
|
123
177
|
},
|
|
124
178
|
modal: {
|
|
125
|
-
width: 460,
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
179
|
+
width: 460,
|
|
180
|
+
maxWidth: '100%',
|
|
181
|
+
maxHeight: '90vh',
|
|
182
|
+
overflowY: 'auto',
|
|
183
|
+
boxSizing: 'border-box',
|
|
184
|
+
display: 'flex',
|
|
185
|
+
flexDirection: 'column',
|
|
186
|
+
gap: 12,
|
|
187
|
+
padding: '16px 18px',
|
|
188
|
+
borderRadius: 'var(--dsw-radius-panel, 28px)',
|
|
189
|
+
background: 'var(--dsw-alias-bg-layer-2)',
|
|
190
|
+
border: 0,
|
|
191
|
+
boxShadow: 'var(--dsw-elevation-prominent)',
|
|
129
192
|
},
|
|
130
193
|
modalHeader: { display: 'flex', alignItems: 'center', gap: 8 },
|
|
131
|
-
modalTitle: {
|
|
194
|
+
modalTitle: {
|
|
195
|
+
fontWeight: 500,
|
|
196
|
+
fontSize: 16,
|
|
197
|
+
lineHeight: '24px',
|
|
198
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
199
|
+
},
|
|
132
200
|
};
|
|
133
201
|
/** Status dot color for one provider state. */
|
|
134
202
|
function dotColor(status) {
|
|
@@ -138,16 +206,6 @@ function dotColor(status) {
|
|
|
138
206
|
return 'var(--dsw-alias-state-success-primary)';
|
|
139
207
|
return 'var(--dsw-alias-label-dimmed)';
|
|
140
208
|
}
|
|
141
|
-
/**
|
|
142
|
-
* Whether a provider has at least one connected account. Multi-account made
|
|
143
|
-
* "logged in" a property of the account list rather than a flag, so every
|
|
144
|
-
* logged-in test goes through this one predicate.
|
|
145
|
-
* @param status - the provider's last reported state, possibly absent.
|
|
146
|
-
* @returns true when the provider serves at least one account.
|
|
147
|
-
*/
|
|
148
|
-
function hasAccount(status) {
|
|
149
|
-
return (status?.accounts.length ?? 0) > 0;
|
|
150
|
-
}
|
|
151
209
|
/**
|
|
152
210
|
* One-line status text for one provider state.
|
|
153
211
|
* @param t - section translate.
|
|
@@ -172,7 +230,10 @@ function statusText(t, status) {
|
|
|
172
230
|
export function clientVersionText(t, clientVersion) {
|
|
173
231
|
if (clientVersion === undefined)
|
|
174
232
|
return '';
|
|
175
|
-
return t('clientVersion', {
|
|
233
|
+
return t('clientVersion', {
|
|
234
|
+
version: clientVersion.version,
|
|
235
|
+
source: t(CLIENT_VERSION_SOURCES[clientVersion.source]),
|
|
236
|
+
});
|
|
176
237
|
}
|
|
177
238
|
/**
|
|
178
239
|
* Localized label of one usage window (kind, plus the model scope when named).
|
|
@@ -183,8 +244,12 @@ export function clientVersionText(t, clientVersion) {
|
|
|
183
244
|
function usageWindowLabel(t, window) {
|
|
184
245
|
const base = window.kind === 'session'
|
|
185
246
|
? t('usageSession')
|
|
186
|
-
: window.kind === 'weekly'
|
|
187
|
-
|
|
247
|
+
: window.kind === 'weekly'
|
|
248
|
+
? t('usageWeekly')
|
|
249
|
+
: t('usageWindow');
|
|
250
|
+
return window.scope !== undefined && window.scope !== ''
|
|
251
|
+
? `${base} · ${window.scope}`
|
|
252
|
+
: base;
|
|
188
253
|
}
|
|
189
254
|
/** Meter fill color lives with the pace logic: fresh 90%+ is red, pace presets add a yellow lead threshold. */
|
|
190
255
|
export { usageBarColor } from './usage-pace.js';
|
|
@@ -199,7 +264,11 @@ export function SubscriptionsSection(props) {
|
|
|
199
264
|
const [statuses, setStatuses] = useState({});
|
|
200
265
|
const [errors, setErrors] = useState({});
|
|
201
266
|
const [manualDrafts, setManualDrafts] = useState({
|
|
202
|
-
codex: '',
|
|
267
|
+
codex: '',
|
|
268
|
+
claude: '',
|
|
269
|
+
grok: '',
|
|
270
|
+
copilot: '',
|
|
271
|
+
antigravity: '',
|
|
203
272
|
});
|
|
204
273
|
/** Pending device-flow codes (copilot), shown while the attempt polls. */
|
|
205
274
|
const [deviceCodes, setDeviceCodes] = useState({});
|
|
@@ -269,7 +338,8 @@ export function SubscriptionsSection(props) {
|
|
|
269
338
|
return;
|
|
270
339
|
setStatuses(response.providers);
|
|
271
340
|
const signature = PROVIDERS.map(({ id }) => `${id}:${response.providers[id].accounts
|
|
272
|
-
.map(account => `${account.key}:${account.isDefault}`)
|
|
341
|
+
.map((account) => `${account.key}:${account.isDefault}`)
|
|
342
|
+
.join(',')}`).join(';');
|
|
273
343
|
if (signature !== usageRosterSignatureRef.current) {
|
|
274
344
|
usageRosterSignatureRef.current = signature;
|
|
275
345
|
window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
|
|
@@ -295,7 +365,9 @@ export function SubscriptionsSection(props) {
|
|
|
295
365
|
const startPolling = useCallback((provider) => {
|
|
296
366
|
if (pollersRef.current.has(provider))
|
|
297
367
|
return;
|
|
298
|
-
pollersRef.current.set(provider, setInterval(() => {
|
|
368
|
+
pollersRef.current.set(provider, setInterval(() => {
|
|
369
|
+
void refresh();
|
|
370
|
+
}, POLL_INTERVAL_MS));
|
|
299
371
|
}, [refresh]);
|
|
300
372
|
// Initial load; every busy provider (e.g. an attempt started before a page
|
|
301
373
|
// reload) resumes polling. Teardown clears pollers and the mounted guard.
|
|
@@ -314,6 +386,9 @@ export function SubscriptionsSection(props) {
|
|
|
314
386
|
});
|
|
315
387
|
return () => {
|
|
316
388
|
mountedRef.current = false;
|
|
389
|
+
// The ref holds pollers started after this effect began. Copying .current
|
|
390
|
+
// at setup would clear an empty set and leave those intervals running.
|
|
391
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- read the live set at unmount
|
|
317
392
|
for (const poller of pollersRef.current.values())
|
|
318
393
|
clearInterval(poller);
|
|
319
394
|
pollersRef.current.clear();
|
|
@@ -337,13 +412,16 @@ export function SubscriptionsSection(props) {
|
|
|
337
412
|
if (rpc === undefined || usageInflightRef.current.has(key))
|
|
338
413
|
return;
|
|
339
414
|
const read = (async () => {
|
|
340
|
-
setUsageLoading(prev => ({ ...prev, [key]: true }));
|
|
415
|
+
setUsageLoading((prev) => ({ ...prev, [key]: true }));
|
|
341
416
|
try {
|
|
342
|
-
const usage = await callSubscriptionsAuth(rpc, 'usage', { provider, account, ...force ? { force: true } : {} });
|
|
417
|
+
const usage = await callSubscriptionsAuth(rpc, 'usage', { provider, account, ...(force ? { force: true } : {}) });
|
|
343
418
|
if (!mountedRef.current)
|
|
344
419
|
return;
|
|
345
|
-
setUsages(prev => ({ ...prev, [key]: usage }));
|
|
346
|
-
setObservedAt(prev => ({
|
|
420
|
+
setUsages((prev) => ({ ...prev, [key]: usage }));
|
|
421
|
+
setObservedAt((prev) => ({
|
|
422
|
+
...prev,
|
|
423
|
+
[key]: usage.observedAt ?? Date.now(),
|
|
424
|
+
}));
|
|
347
425
|
window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
|
|
348
426
|
setUsageErrors((prev) => {
|
|
349
427
|
const next = { ...prev };
|
|
@@ -353,12 +431,12 @@ export function SubscriptionsSection(props) {
|
|
|
353
431
|
}
|
|
354
432
|
catch (error) {
|
|
355
433
|
if (mountedRef.current)
|
|
356
|
-
setUsageErrors(prev => ({ ...prev, [key]: messageOf(error) }));
|
|
434
|
+
setUsageErrors((prev) => ({ ...prev, [key]: messageOf(error) }));
|
|
357
435
|
}
|
|
358
436
|
finally {
|
|
359
437
|
usageInflightRef.current.delete(key);
|
|
360
438
|
if (mountedRef.current)
|
|
361
|
-
setUsageLoading(prev => ({ ...prev, [key]: false }));
|
|
439
|
+
setUsageLoading((prev) => ({ ...prev, [key]: false }));
|
|
362
440
|
}
|
|
363
441
|
})();
|
|
364
442
|
usageInflightRef.current.set(key, read);
|
|
@@ -386,7 +464,10 @@ export function SubscriptionsSection(props) {
|
|
|
386
464
|
}
|
|
387
465
|
}
|
|
388
466
|
async function consumeReset() {
|
|
389
|
-
if (!rpc ||
|
|
467
|
+
if (!rpc ||
|
|
468
|
+
!resetConfirmation ||
|
|
469
|
+
!resetAcknowledged ||
|
|
470
|
+
resetInflight.current)
|
|
390
471
|
return;
|
|
391
472
|
const { account, ticket } = resetConfirmation;
|
|
392
473
|
resetInflight.current = true;
|
|
@@ -418,8 +499,8 @@ export function SubscriptionsSection(props) {
|
|
|
418
499
|
void loadUsage(id, account.key);
|
|
419
500
|
}
|
|
420
501
|
}
|
|
421
|
-
setUsages(prev => dropStale(prev, live));
|
|
422
|
-
setUsageErrors(prev => dropStale(prev, live));
|
|
502
|
+
setUsages((prev) => dropStale(prev, live));
|
|
503
|
+
setUsageErrors((prev) => dropStale(prev, live));
|
|
423
504
|
}, [statuses, usages, usageErrors, loadUsage]);
|
|
424
505
|
const login = useCallback(async (provider, method) => {
|
|
425
506
|
if (rpc === undefined)
|
|
@@ -428,9 +509,10 @@ export function SubscriptionsSection(props) {
|
|
|
428
509
|
try {
|
|
429
510
|
const response = await callSubscriptionsAuth(rpc, 'login', {
|
|
430
511
|
provider,
|
|
431
|
-
...method === undefined ? {} : { method },
|
|
512
|
+
...(method === undefined ? {} : { method }),
|
|
432
513
|
});
|
|
433
|
-
if (typeof response.authorizeUrl === 'string' &&
|
|
514
|
+
if (typeof response.authorizeUrl === 'string' &&
|
|
515
|
+
response.authorizeUrl === '') {
|
|
434
516
|
// Instant login (e.g. imported from Claude Code credentials)
|
|
435
517
|
await refresh();
|
|
436
518
|
return;
|
|
@@ -441,14 +523,25 @@ export function SubscriptionsSection(props) {
|
|
|
441
523
|
if (!mountedRef.current)
|
|
442
524
|
return;
|
|
443
525
|
// Optimistic busy so Cancel and the manual fallback appear before the first poll tick.
|
|
444
|
-
setStatuses(prev => ({
|
|
526
|
+
setStatuses((prev) => ({
|
|
445
527
|
...prev,
|
|
446
|
-
[provider]: {
|
|
528
|
+
[provider]: {
|
|
529
|
+
accounts: prev[provider]?.accounts ?? [],
|
|
530
|
+
...prev[provider],
|
|
531
|
+
busy: true,
|
|
532
|
+
},
|
|
447
533
|
}));
|
|
448
|
-
if (typeof response.userCode === 'string' &&
|
|
534
|
+
if (typeof response.userCode === 'string' &&
|
|
535
|
+
response.userCode.length > 0) {
|
|
449
536
|
// Device flow: show the code card instead of opening the page blind —
|
|
450
537
|
// the user copies the code first, then opens the verification page.
|
|
451
|
-
setDeviceCodes(prev => ({
|
|
538
|
+
setDeviceCodes((prev) => ({
|
|
539
|
+
...prev,
|
|
540
|
+
[provider]: {
|
|
541
|
+
userCode: response.userCode,
|
|
542
|
+
verificationUrl: response.authorizeUrl,
|
|
543
|
+
},
|
|
544
|
+
}));
|
|
452
545
|
}
|
|
453
546
|
else {
|
|
454
547
|
window.open(response.authorizeUrl, '_blank', 'noopener');
|
|
@@ -458,7 +551,7 @@ export function SubscriptionsSection(props) {
|
|
|
458
551
|
catch (error) {
|
|
459
552
|
setProviderError(provider, messageOf(error));
|
|
460
553
|
}
|
|
461
|
-
}, [rpc, t, setProviderError, startPolling]);
|
|
554
|
+
}, [rpc, t, setProviderError, startPolling, refresh]);
|
|
462
555
|
const cancel = useCallback(async (provider) => {
|
|
463
556
|
if (rpc === undefined)
|
|
464
557
|
return;
|
|
@@ -479,9 +572,12 @@ export function SubscriptionsSection(props) {
|
|
|
479
572
|
return;
|
|
480
573
|
setProviderError(provider, undefined);
|
|
481
574
|
try {
|
|
482
|
-
await callSubscriptionsAuth(rpc, 'manual', {
|
|
575
|
+
await callSubscriptionsAuth(rpc, 'manual', {
|
|
576
|
+
provider,
|
|
577
|
+
input,
|
|
578
|
+
});
|
|
483
579
|
if (mountedRef.current)
|
|
484
|
-
setManualDrafts(prev => ({ ...prev, [provider]: '' }));
|
|
580
|
+
setManualDrafts((prev) => ({ ...prev, [provider]: '' }));
|
|
485
581
|
}
|
|
486
582
|
catch (error) {
|
|
487
583
|
setProviderError(provider, messageOf(error));
|
|
@@ -495,7 +591,10 @@ export function SubscriptionsSection(props) {
|
|
|
495
591
|
return;
|
|
496
592
|
setProviderError(provider, undefined);
|
|
497
593
|
try {
|
|
498
|
-
await callSubscriptionsAuth(rpc, 'logout', {
|
|
594
|
+
await callSubscriptionsAuth(rpc, 'logout', {
|
|
595
|
+
provider,
|
|
596
|
+
account,
|
|
597
|
+
});
|
|
499
598
|
}
|
|
500
599
|
catch (error) {
|
|
501
600
|
setProviderError(provider, messageOf(error));
|
|
@@ -507,7 +606,10 @@ export function SubscriptionsSection(props) {
|
|
|
507
606
|
return;
|
|
508
607
|
setProviderError(provider, undefined);
|
|
509
608
|
try {
|
|
510
|
-
await callSubscriptionsAuth(rpc, 'setDefault', {
|
|
609
|
+
await callSubscriptionsAuth(rpc, 'setDefault', {
|
|
610
|
+
provider,
|
|
611
|
+
account,
|
|
612
|
+
});
|
|
511
613
|
}
|
|
512
614
|
catch (error) {
|
|
513
615
|
setProviderError(provider, messageOf(error));
|
|
@@ -515,16 +617,19 @@ export function SubscriptionsSection(props) {
|
|
|
515
617
|
await refresh();
|
|
516
618
|
}, [rpc, setProviderError, refresh]);
|
|
517
619
|
const copyDeviceCode = useCallback((provider, userCode) => {
|
|
518
|
-
void navigator.clipboard
|
|
620
|
+
void navigator.clipboard
|
|
621
|
+
?.writeText(userCode)
|
|
622
|
+
.then(() => {
|
|
519
623
|
if (!mountedRef.current)
|
|
520
624
|
return;
|
|
521
625
|
setCopiedCode(provider);
|
|
522
626
|
setTimeout(() => {
|
|
523
627
|
if (mountedRef.current) {
|
|
524
|
-
setCopiedCode(current => current === provider ? undefined : current);
|
|
628
|
+
setCopiedCode((current) => current === provider ? undefined : current);
|
|
525
629
|
}
|
|
526
630
|
}, 1500);
|
|
527
|
-
})
|
|
631
|
+
})
|
|
632
|
+
.catch(() => undefined);
|
|
528
633
|
}, []);
|
|
529
634
|
if (rpc === undefined) {
|
|
530
635
|
return _jsx("p", { style: styles.intro, children: t('unavailable') });
|
|
@@ -534,34 +639,109 @@ export function SubscriptionsSection(props) {
|
|
|
534
639
|
const busy = status?.busy === true;
|
|
535
640
|
const deviceCode = deviceCodes[id];
|
|
536
641
|
const accounts = status?.accounts ?? [];
|
|
537
|
-
return (_jsxs("div", { style: styles.card, children: [_jsxs("div", { style: styles.cardHeader, children: [_jsx("span", { style: { ...styles.dot, background: dotColor(status) } }), _jsx("span", { style: styles.name, children: name }), status?.clientVersion !== undefined && (_jsx("span", { style: styles.clientVersion, title: t('clientVersionHint'), children: clientVersionText(t, status.clientVersion) }))] }), _jsx("p", { style: styles.statusLine, children: statusText(t, status) }), status?.detail !== undefined && status.detail !== '' && (_jsx("p", { style: styles.statusLine, children: status.detail })), errors[id] !== undefined && _jsx("p", { style: styles.errorLine, children: errors[id] }), accounts.map((account) => {
|
|
642
|
+
return (_jsxs("div", { style: styles.card, children: [_jsxs("div", { style: styles.cardHeader, children: [_jsx("span", { style: { ...styles.dot, background: dotColor(status) } }), _jsx("span", { style: styles.name, children: name }), status?.clientVersion !== undefined && (_jsx("span", { style: styles.clientVersion, title: t('clientVersionHint'), children: clientVersionText(t, status.clientVersion) }))] }), _jsx("p", { style: styles.statusLine, children: statusText(t, status) }), status?.detail !== undefined && status.detail !== '' && (_jsx("p", { style: styles.statusLine, children: status.detail })), errors[id] !== undefined && (_jsx("p", { style: styles.errorLine, children: errors[id] })), accounts.map((account) => {
|
|
538
643
|
const usageKey = `${id}:${account.key}`;
|
|
539
644
|
const usage = usages[usageKey];
|
|
645
|
+
const shownWindows = visibleUsageWindows(id, usage?.windows, status?.visibleModels);
|
|
540
646
|
const usageError = usageErrors[usageKey];
|
|
541
647
|
const display = account.account ?? account.key;
|
|
542
648
|
const sortedCredits = [...(usage?.resetCredits ?? [])].sort((a, b) => (a.expiresAt ?? Infinity) - (b.expiresAt ?? Infinity));
|
|
543
|
-
const nextReset = sortedCredits.find(c => c.expiresAt === undefined || c.expiresAt > Date.now());
|
|
544
|
-
const resetBlock = usage
|
|
649
|
+
const nextReset = sortedCredits.find((c) => c.expiresAt === undefined || c.expiresAt > Date.now());
|
|
650
|
+
const resetBlock = usage
|
|
651
|
+
? resetActionBlock(usage)
|
|
652
|
+
: 'resetUseRefresh';
|
|
545
653
|
const resetDisabled = resetBusy || resetBlock !== undefined;
|
|
546
654
|
// Providers without a usage endpoint answer supported:false — no block.
|
|
547
|
-
const showUsage = usage?.supported !== false
|
|
548
|
-
|
|
655
|
+
const showUsage = usage?.supported !== false &&
|
|
656
|
+
(usage !== undefined ||
|
|
657
|
+
usageError !== undefined ||
|
|
658
|
+
usageLoading[usageKey] === true);
|
|
549
659
|
return (_jsxs("div", { style: styles.accountRow, children: [_jsxs("div", { style: styles.accountHeader, children: [_jsx("button", { type: "button", style: styles.starButton, title: account.isDefault ? t('defaultBadge') : t('setDefault'), onClick: () => {
|
|
550
660
|
if (!account.isDefault)
|
|
551
661
|
void setDefault(id, account.key);
|
|
552
|
-
}, children: account.isDefault ? '★' : '☆' }), _jsx("span", { style: styles.accountName, children: display }), account.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: account.plan })), account.expiresAt !== undefined && (_jsx("span", { style: styles.statusLine, children: t('accountExpires', {
|
|
662
|
+
}, children: account.isDefault ? '★' : '☆' }), _jsx("span", { style: styles.accountName, children: display }), account.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: account.plan })), account.expiresAt !== undefined && (_jsx("span", { style: styles.statusLine, children: t('accountExpires', {
|
|
663
|
+
date: new Date(account.expiresAt).toLocaleString(),
|
|
664
|
+
}) })), _jsx("button", { type: "button", style: { marginLeft: 'auto', flexShrink: 0 }, onClick: () => {
|
|
665
|
+
void logout(id, account.key, display, name);
|
|
666
|
+
}, children: t('logout') })] }), showUsage && (_jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.usageTitle, children: t('usageTitle') }), usage?.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: t('usagePlan', { plan: usage.plan }) })), _jsx("button", { type: "button", style: styles.usageRefresh, disabled: usageLoading[usageKey] === true, onClick: () => {
|
|
667
|
+
void loadUsage(id, account.key, true);
|
|
668
|
+
}, children: t('usageRefresh') })] }), usage === undefined && usageError === undefined && (_jsx("p", { style: styles.statusLine, children: t('usageLoading') })), usageError !== undefined && (_jsx("p", { style: styles.errorLine, children: t('usageError', { message: usageError }) })), usage?.windows !== undefined &&
|
|
669
|
+
usage.windows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageEmpty') })), usage?.windows !== undefined &&
|
|
670
|
+
usage.windows.length > 0 &&
|
|
671
|
+
shownWindows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageNoVisibleModels') })), shownWindows.map((window, index) => {
|
|
553
672
|
const percent = displayUsedPercent(window.usedPercent);
|
|
554
|
-
return (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageMeta, children: [_jsx("span", { children: usageWindowLabel(t, window) }), _jsxs("span", { children: [percent === undefined
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
673
|
+
return (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageMeta, children: [_jsx("span", { children: usageWindowLabel(t, window) }), _jsxs("span", { children: [percent === undefined
|
|
674
|
+
? t('usageMeterInvalid')
|
|
675
|
+
: `${percent}%`, window.resetsAt !== undefined &&
|
|
676
|
+
` · ${t('usageResets', { date: new Date(window.resetsAt).toLocaleString() })}`] })] }), _jsx(UsageMeter, { window: window, t: t, observedAt: observedAt[usageKey], stale: usageError !== undefined || usage.stale === true })] }, index));
|
|
677
|
+
}), (id === 'codex' || id === 'claude') &&
|
|
678
|
+
usage?.resetCreditsError !== undefined && (_jsx(ResetCreditsErrorLine, { provider: id, message: usage.resetCreditsError, t: t })), (id === 'codex' || id === 'claude') &&
|
|
679
|
+
showsResetCredits(id, usage?.resetCredits) && (_jsx(ResetCreditsDisclosure, { mode: id === 'claude' ? 'grants' : 'credits', credits: sortedCredits, t: t, ...(nextReset !== undefined
|
|
680
|
+
? { nextCredit: nextReset }
|
|
681
|
+
: {}), ...(id === 'codex'
|
|
682
|
+
? {
|
|
683
|
+
useAction: {
|
|
684
|
+
accountKey: account.key,
|
|
685
|
+
busy: resetBusy && resetAccount === account.key,
|
|
686
|
+
disabled: resetDisabled,
|
|
687
|
+
title: t(resetBusy
|
|
688
|
+
? 'resetUseChecking'
|
|
689
|
+
: (resetBlock ?? 'resetUseReady')),
|
|
690
|
+
label: t('resetUseButton'),
|
|
691
|
+
onUse: () => {
|
|
692
|
+
if (!resetDisabled)
|
|
693
|
+
void prepareReset(account.key);
|
|
694
|
+
},
|
|
695
|
+
},
|
|
696
|
+
}
|
|
697
|
+
: {}), ...(resetAccount === account.key &&
|
|
698
|
+
resetMessage !== ''
|
|
699
|
+
? { statusMessage: resetMessage }
|
|
700
|
+
: {}) }))] }))] }, account.key));
|
|
701
|
+
}), _jsxs("div", { style: styles.actions, children: [!busy && accounts.length === 0 && (_jsx("button", { type: "button", onClick: () => {
|
|
702
|
+
void login(id);
|
|
703
|
+
}, children: t('login') })), !busy && accounts.length > 0 && id === 'claude' && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", onClick: () => {
|
|
704
|
+
void login(id, 'oauth');
|
|
705
|
+
}, children: t('addAccountOAuth') }), _jsx("button", { type: "button", onClick: () => {
|
|
706
|
+
void login(id, 'keychain');
|
|
707
|
+
}, children: t('addAccountKeychain') })] })), !busy && accounts.length > 0 && id !== 'claude' && (_jsx("button", { type: "button", onClick: () => {
|
|
708
|
+
void login(id);
|
|
709
|
+
}, children: t('addAccount') })), !busy && (_jsx("button", { type: "button", "aria-haspopup": "dialog", onClick: () => setManagedProvider({ id, name }), children: t('accountsManage') })), busy && (_jsx("button", { type: "button", onClick: () => {
|
|
710
|
+
void cancel(id);
|
|
711
|
+
}, children: t('cancel') }))] }), !busy && accounts.length > 0 && (_jsx("p", { style: styles.statusLine, children: t('addAccountHint') })), busy && deviceCode !== undefined && (_jsxs("div", { style: styles.deviceCode, children: [_jsx("span", { style: styles.statusLine, children: t('deviceCodePrompt') }), _jsx("span", { style: styles.deviceCodeText, children: deviceCode.userCode }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { type: "button", onClick: () => {
|
|
712
|
+
copyDeviceCode(id, deviceCode.userCode);
|
|
713
|
+
}, children: copiedCode === id
|
|
714
|
+
? t('deviceCodeCopied')
|
|
715
|
+
: t('deviceCodeCopy') }), _jsx("button", { type: "button", onClick: () => {
|
|
716
|
+
window.open(deviceCode.verificationUrl, '_blank', 'noopener');
|
|
717
|
+
}, children: t('deviceCodeOpenPage') })] })] })), busy && deviceCode === undefined && (_jsxs("details", { style: styles.manual, children: [_jsx("summary", { children: t('manualSummary') }), _jsxs("div", { style: styles.manualRow, children: [_jsx("input", { style: styles.manualInput, value: manualDrafts[id], placeholder: t('manualPlaceholder'), onChange: (event) => setManualDrafts((prev) => ({
|
|
718
|
+
...prev,
|
|
719
|
+
[id]: event.target.value,
|
|
720
|
+
})) }), _jsx("button", { type: "button", onClick: () => {
|
|
721
|
+
void submitManual(id);
|
|
722
|
+
}, children: t('submit') })] })] }))] }, id));
|
|
723
|
+
}), _jsx(CursorCard, { rpc: rpc, t: t }), _jsx(OllamaCard, { rpc: rpc, t: t }), _jsx(ExternalUsageCards, { rpc: rpc, t: t }), managedProvider && (_jsx(ProviderAccountManager, { provider: managedProvider.id, name: managedProvider.name, rpc: rpc, t: t, onClose: () => {
|
|
724
|
+
setManagedProvider(undefined);
|
|
725
|
+
void refresh();
|
|
726
|
+
} })), _jsx("style", { children: '.subscriptions-reset-dialog::backdrop { background: var(--dsw-alias-bg-mask-1, #0006); backdrop-filter: var(--dsw-mask-blur, none); }' }), _jsx("dialog", { ref: resetDialogRef, className: "subscriptions-reset-dialog", "aria-labelledby": "subscriptions-reset-title", onCancel: () => setResetConfirmation(undefined), onClose: () => setResetConfirmation(undefined), style: {
|
|
727
|
+
...styles.modal,
|
|
728
|
+
display: resetConfirmation ? 'flex' : 'none',
|
|
729
|
+
margin: 'auto',
|
|
730
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
731
|
+
}, children: resetConfirmation && (_jsxs(_Fragment, { children: [_jsx("h3", { id: "subscriptions-reset-title", style: { ...styles.modalTitle, margin: 0 }, children: t('resetUseConfirmTitle') }), _jsx("p", { style: { ...styles.statusLine, overflowWrap: 'anywhere' }, children: statuses.codex?.accounts.find((a) => a.key === resetConfirmation.account)?.account ?? resetConfirmation.account }), _jsxs("div", { style: { ...styles.usageMeta, gap: 12 }, children: [_jsx("span", { children: t('usageWeekly') }), _jsxs("strong", { children: [Math.round(resetConfirmation.weeklyUsedPercent), "%"] })] }), _jsx("div", { style: styles.usageMeta, children: _jsx("span", { children: resetConfirmation.creditExpiresAt === undefined
|
|
732
|
+
? t('resetCreditExpiryUnknown')
|
|
733
|
+
: t('resetCreditExpires', {
|
|
734
|
+
date: new Date(resetConfirmation.creditExpiresAt).toLocaleString(),
|
|
735
|
+
}) }) }), _jsx("p", { style: { ...styles.statusLine, margin: '4px 0' }, children: t('resetUseWarning') }), _jsxs("label", { style: {
|
|
736
|
+
display: 'flex',
|
|
737
|
+
gap: 8,
|
|
738
|
+
alignItems: 'flex-start',
|
|
739
|
+
fontSize: 12,
|
|
740
|
+
}, children: [_jsx("input", { type: "checkbox", checked: resetAcknowledged, onChange: (e) => setResetAcknowledged(e.target.checked) }), t('resetUseAcknowledge')] }), _jsxs("div", { className: "dsh-subscription-dialog-actions", style: {
|
|
741
|
+
...styles.actions,
|
|
742
|
+
justifyContent: 'flex-end',
|
|
743
|
+
marginTop: 8,
|
|
744
|
+
}, children: [_jsx("button", { type: "button", autoFocus: true, onClick: () => setResetConfirmation(undefined), children: t('cancel') }), _jsx("button", { type: "button", className: "dsh-subscription-primary", disabled: !resetAcknowledged || resetBusy, onClick: () => {
|
|
745
|
+
void consumeReset();
|
|
746
|
+
}, children: t('resetUseConfirm') })] })] })) })] }));
|
|
567
747
|
}
|