dsh-plugin-subscription-oauth 0.10.2 → 0.12.0

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.
@@ -8,6 +8,7 @@
8
8
  *
9
9
  * @module dsh-plugin-subscription-oauth/auth/registry
10
10
  */
11
+ import type { ModelPreferences } from '../settings.js';
11
12
  import type { LoginStart, ModelVariants, ProviderStatus, RefreshResult, SubscriptionStatus, UsageResult } from '../protocol.js';
12
13
  import type { LlmAdapter } from '@deepseek-ai/dsh-llm';
13
14
  import type { ProviderLoginStart, ProviderRefreshResult, ProviderUsageResult } from './provider-controller.js';
@@ -41,8 +42,9 @@ export interface ProviderRoute {
41
42
  * failure: the message names the routes that do exist.
42
43
  */
43
44
  export declare class SubscriptionRegistry {
45
+ private readonly preferences;
44
46
  private readonly byId;
45
- constructor(routes: readonly ProviderRoute[]);
47
+ constructor(routes: readonly ProviderRoute[], preferences: ModelPreferences);
46
48
  /** Route keys this registry serves, in display order. */
47
49
  ids(): readonly string[];
48
50
  /** Every route, in registration order. */
@@ -92,4 +94,11 @@ export declare class SubscriptionRegistry {
92
94
  reload(): Promise<SubscriptionStatus>;
93
95
  /** The fast and image variants for one model, as the route's discovery reported them. */
94
96
  modelVariants(provider: string, id: string): Promise<ModelVariants | undefined>;
97
+ /** Whether the `auto` provider is offered; flips it. */
98
+ autoState(): Promise<{
99
+ enabled: boolean;
100
+ }>;
101
+ setAutoOn(enabled: boolean): Promise<{
102
+ enabled: boolean;
103
+ }>;
95
104
  }
@@ -29,8 +29,10 @@ function displayRank(id) {
29
29
  * failure: the message names the routes that do exist.
30
30
  */
31
31
  export class SubscriptionRegistry {
32
+ preferences;
32
33
  byId;
33
- constructor(routes) {
34
+ constructor(routes, preferences) {
35
+ this.preferences = preferences;
34
36
  this.byId = new Map(routes.map((route) => [route.id, route]));
35
37
  }
36
38
  /** Route keys this registry serves, in display order. */
@@ -151,4 +153,12 @@ export class SubscriptionRegistry {
151
153
  const route = this.route(provider);
152
154
  return route.modelVariants?.(id);
153
155
  }
156
+ /** Whether the `auto` provider is offered; flips it. */
157
+ async autoState() {
158
+ return { enabled: (await this.preferences.autoPreferences()).enabled };
159
+ }
160
+ async setAutoOn(enabled) {
161
+ await this.preferences.setAutoOn(enabled);
162
+ return { enabled };
163
+ }
154
164
  }
package/lib/auth/rpc.js CHANGED
@@ -155,6 +155,12 @@ async function runEndpoint(registry, endpoint, payload, balance) {
155
155
  }
156
156
  case 'usage':
157
157
  return registry.usage(requiredProvider(payload, endpoint), booleanField(payload, 'force'));
158
+ case 'setAutoOn': {
159
+ const enabled = booleanField(payload, 'enabled');
160
+ if (enabled === undefined)
161
+ throw new ProviderError('setAutoOn requires an enabled flag', BAD_REQUEST);
162
+ return registry.setAutoOn(enabled);
163
+ }
158
164
  case 'deepseek-balance': {
159
165
  // The DeepSeek account's balance, read on the node half through the host's own
160
166
  // account service. The browser cannot call that service directly (it sits behind
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  /**
3
3
  * Subscriptions settings page.
4
4
  *
@@ -13,7 +13,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
13
13
  *
14
14
  * @module dsh-plugin-subscription-oauth/client/SubscriptionsSection
15
15
  */
16
- import { useCallback, useEffect, useRef, useState } from 'react';
16
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
17
17
  import { en } from './locales.js';
18
18
  import { subscriptionsCss } from './styles.js';
19
19
  import { SubscriptionsRpcError } from './subscriptions-rpc.js';
@@ -115,29 +115,48 @@ const styles = {
115
115
  display: 'flex', flexDirection: 'column', gap: 12, maxWidth: SECTION_WIDTH,
116
116
  color: 'var(--dsw-alias-label-primary)',
117
117
  },
118
- intro: { margin: 0, color: 'var(--dsw-alias-label-tertiary)', fontSize: 14, lineHeight: '22px' },
118
+ sectionHeader: { display: 'flex', flexDirection: 'column', gap: 4, marginBottom: 2 },
119
+ heading: { margin: 0, fontSize: 18, lineHeight: '26px', fontWeight: 600, color: 'var(--dsw-alias-label-primary)' },
120
+ intro: { margin: 0, color: 'var(--dsw-alias-label-tertiary)', fontSize: 13, lineHeight: '20px', maxWidth: 560 },
119
121
  card: {
120
122
  border: '0.5px solid var(--dsw-alias-settings-card-stroke, var(--dsw-alias-border-l4))',
121
- borderRadius: 'var(--dsw-radius-xl, 20px)',
123
+ borderRadius: 'var(--dsw-radius-xl, 16px)',
122
124
  background: 'var(--dsw-alias-settings-card-fill, var(--dsw-alias-bg-layer-2))',
123
- padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 6,
125
+ padding: '14px 16px', display: 'flex', flexDirection: 'column', gap: 8,
126
+ transition: 'border-color 120ms ease, background 120ms ease',
124
127
  },
125
- cardHeader: { display: 'flex', alignItems: 'center', gap: 8 },
128
+ cardOpen: {
129
+ borderColor: 'var(--dsw-alias-border-l2, var(--dsw-alias-border-l3))',
130
+ background: 'var(--dsw-alias-settings-card-fill-hover, var(--dsw-alias-settings-card-fill, var(--dsw-alias-bg-layer-2)))',
131
+ },
132
+ cardHeader: { display: 'flex', alignItems: 'center', gap: 10, cursor: 'pointer', userSelect: 'none' },
126
133
  /** Everything a folded card hides. */
127
- cardBody: { display: 'flex', flexDirection: 'column', gap: 6 },
134
+ cardBody: { display: 'flex', flexDirection: 'column', gap: 10, marginTop: 2 },
128
135
  /** The one-line state a folded card shows in place of its body. */
129
136
  cardSummary: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },
130
- dot: { width: 8, height: 8, borderRadius: '50%', flexShrink: 0 },
131
- name: { fontWeight: 500, fontSize: 14, lineHeight: '22px', color: 'var(--dsw-alias-label-primary)' },
132
- providerId: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-dimmed)' },
137
+ /** The status indicator: filled when live, a hollow ring when not. */
138
+ dot: {
139
+ width: 8, height: 8, borderRadius: '50%', flexShrink: 0,
140
+ border: '1.5px solid var(--dsw-alias-label-dimmed)',
141
+ background: 'transparent',
142
+ },
143
+ dotLive: {
144
+ borderColor: 'var(--dsw-alias-state-success-primary, #067647)',
145
+ background: 'var(--dsw-alias-state-success-primary, #067647)',
146
+ },
147
+ dotBusy: {
148
+ borderColor: 'var(--dsw-alias-state-warn-primary, #f5a524)',
149
+ background: 'var(--dsw-alias-state-warn-primary, #f5a524)',
150
+ },
151
+ name: { fontWeight: 600, fontSize: 14, lineHeight: '22px', color: 'var(--dsw-alias-label-primary)' },
133
152
  statusLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },
134
153
  errorLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' },
135
154
  successLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-success-primary)' },
136
155
  actions: { display: 'flex', gap: 8, marginTop: 4, alignItems: 'center', flexWrap: 'wrap' },
137
156
  accountRow: {
138
157
  display: 'flex', flexDirection: 'column', gap: 6,
139
- border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 'var(--dsw-radius-lg, 16px)',
140
- padding: '8px 10px', marginTop: 4,
158
+ border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 'var(--dsw-radius-lg, 12px)',
159
+ padding: '10px 12px', background: 'var(--dsw-alias-bg-layer-1)',
141
160
  },
142
161
  accountHeader: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' },
143
162
  starButton: {
@@ -147,10 +166,21 @@ const styles = {
147
166
  },
148
167
  accountName: { fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)', userSelect: 'all' },
149
168
  badge: {
150
- fontSize: 11, lineHeight: '16px', padding: '0 6px', borderRadius: 8,
151
- background: 'var(--dsw-alias-bg-layer-1)', border: '0.5px solid var(--dsw-alias-border-l2)',
169
+ fontSize: 10, lineHeight: '14px', padding: '1px 7px', borderRadius: 999, fontWeight: 500,
170
+ background: 'var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-1))',
171
+ border: '0.5px solid var(--dsw-alias-border-l2)',
152
172
  color: 'var(--dsw-alias-label-secondary)',
153
173
  },
174
+ badgeDefault: {
175
+ fontSize: 10, lineHeight: '14px', padding: '1px 7px', borderRadius: 999, fontWeight: 500,
176
+ background: 'var(--dsw-alias-state-success-primary, #067647)',
177
+ color: 'var(--dsw-alias-label-primary-foreground, #fff)',
178
+ },
179
+ badgeWarn: {
180
+ fontSize: 10, lineHeight: '14px', padding: '1px 7px', borderRadius: 999, fontWeight: 500,
181
+ background: 'var(--dsw-alias-state-warn-primary, #f5a524)',
182
+ color: 'var(--dsw-alias-label-primary-foreground, #fff)',
183
+ },
154
184
  pushRight: { marginLeft: 'auto', flexShrink: 0 },
155
185
  catalog: {
156
186
  display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4,
@@ -197,16 +227,23 @@ const styles = {
197
227
  fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-secondary)',
198
228
  },
199
229
  usage: {
200
- display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4,
201
- borderTop: '0.5px solid var(--dsw-alias-border-l2)', paddingTop: 8,
230
+ display: 'flex', flexDirection: 'column', gap: 8, marginTop: 2,
231
+ borderTop: '0.5px solid var(--dsw-alias-border-l2)', paddingTop: 10,
202
232
  },
203
233
  usageHeader: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' },
204
- usageRow: { display: 'flex', flexDirection: 'column', gap: 3 },
234
+ usageRow: { display: 'flex', flexDirection: 'column', gap: 4 },
205
235
  usageLine: { display: 'flex', alignItems: 'baseline', gap: 8, justifyContent: 'space-between' },
206
236
  usageMeter: {
207
237
  height: 6, borderRadius: 999, background: 'var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-2))',
208
- overflow: 'hidden',
238
+ overflow: 'hidden', boxShadow: 'inset 0 0 0 1px #8a8f9833',
239
+ },
240
+ usageAccount: {
241
+ display: 'flex', flexDirection: 'column', gap: 6,
242
+ padding: '8px 10px', borderRadius: 'var(--dsw-radius-md, 10px)',
243
+ background: 'var(--dsw-alias-bg-layer-1)', border: '0.5px solid var(--dsw-alias-border-l2)',
209
244
  },
245
+ /** The metered windows under one account row: no border, just the meters. */
246
+ usageInline: { display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4 },
210
247
  modelRow: {
211
248
  display: 'flex', alignItems: 'flex-start', gap: 8,
212
249
  cursor: 'pointer', fontSize: 13, lineHeight: '20px',
@@ -248,13 +285,13 @@ const styles = {
248
285
  modalActions: { display: 'flex', gap: 8, justifyContent: 'flex-end', marginTop: 2 },
249
286
  divider: { borderTop: '0.5px solid var(--dsw-alias-border-l2)' },
250
287
  };
251
- /** Status dot color for one provider's connection state. */
252
- function dotColor(status) {
288
+ /** Status dot style for one provider's connection state. */
289
+ function dotStyle(status) {
253
290
  if (status.busy)
254
- return 'var(--dsw-alias-state-warn-label)';
291
+ return styles.dotBusy;
255
292
  if (status.loggedIn)
256
- return 'var(--dsw-alias-state-success-primary)';
257
- return 'var(--dsw-alias-label-dimmed)';
293
+ return styles.dotLive;
294
+ return {};
258
295
  }
259
296
  /** Localized label for one provider-owned detail row. */
260
297
  function detailLabel(t, row) {
@@ -274,10 +311,17 @@ function windowAmount(window) {
274
311
  return `${String(window.used)}/${String(window.limit)}`;
275
312
  return '—';
276
313
  }
277
- /** One account's plan and metered allowances. */
314
+ /** One account's metered windows, rendered under its account row. */
315
+ function AccountUsage(props) {
316
+ const { entry, t } = props;
317
+ if (entry === undefined)
318
+ return null;
319
+ return (_jsxs("div", { style: styles.usageInline, children: [entry.windows.map((window) => (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageLine, children: [_jsxs("span", { style: styles.modelName, children: [windowLabel(t, window), ' · ', windowAmount(window)] }), _jsx("span", { style: styles.modelMeta, children: window.resetsAt === undefined ? '' : formatReset(window.resetsAt, t) })] }), window.usedPercent !== undefined && (_jsx("div", { className: "dsh-subscription-meter", children: _jsx("span", { style: { width: `${String(Math.min(100, Math.max(0, window.usedPercent)))}%` }, "data-exhausted": window.usedPercent >= 90 ? 'true' : 'false' }) }))] }, `${entry.account}-${window.label}`))), entry.windows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageNone') }))] }));
320
+ }
321
+ /** The plan-and-usage block for the whole route. */
278
322
  function UsageBlock(props) {
279
323
  const { usage, error, t, busyAction, onRefresh } = props;
280
- return (_jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.catalogTitle, children: t('usageTitle') }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: onRefresh, children: busyAction ? t('refreshing') : t('usageRefresh') })] }), error !== undefined && _jsx("p", { className: "dsh-subscription-error", children: t('usageError', { message: error }) }), usage.length === 0 && error === undefined && _jsx("p", { style: styles.statusLine, children: t('usageNone') }), usage.map((entry) => (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageLine, children: [_jsxs("span", { style: styles.modelMeta, children: [entry.plan === undefined ? entry.account : t('usagePlan', { plan: entry.plan }), entry.renewsAt === undefined ? '' : ` · ${t('usageRenews', { time: formatTime(entry.renewsAt) })}`] }), _jsx("span", { style: styles.modelMeta, children: t('usageFetched', { time: formatTime(entry.fetchedAt) }) })] }), entry.windows.map((window) => (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageLine, children: [_jsxs("span", { style: styles.modelName, children: [windowLabel(t, window), ' · ', windowAmount(window)] }), _jsx("span", { style: styles.modelMeta, children: window.resetsAt === undefined ? '' : formatReset(window.resetsAt, t) })] }), window.usedPercent !== undefined && (_jsx("div", { className: "dsh-subscription-meter", children: _jsx("span", { style: { width: `${String(Math.min(100, Math.max(0, window.usedPercent)))}%` }, "data-exhausted": window.usedPercent >= 90 ? 'true' : 'false' }) }))] }, `${entry.account}-${window.label}`)))] }, entry.account)))] }));
324
+ return (_jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.catalogTitle, children: t('usageTitle') }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: onRefresh, children: busyAction ? t('refreshing') : t('usageRefresh') })] }), error !== undefined && _jsx("p", { className: "dsh-subscription-error", children: t('usageError', { message: error }) }), usage.length === 0 && error === undefined && _jsx("p", { style: styles.statusLine, children: t('usageNone') }), usage.map((entry) => (_jsxs("div", { style: styles.usageAccount, children: [_jsxs("div", { style: styles.usageLine, children: [_jsxs("span", { style: styles.modelMeta, children: [entry.plan === undefined ? entry.account : t('usagePlan', { plan: entry.plan }), entry.renewsAt === undefined ? '' : ` · ${t('usageRenews', { time: formatTime(entry.renewsAt) })}`] }), _jsx("span", { style: styles.modelMeta, children: t('usageFetched', { time: formatTime(entry.fetchedAt) }) })] }), entry.windows.map((window) => (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageLine, children: [_jsxs("span", { style: styles.modelName, children: [windowLabel(t, window), ' · ', windowAmount(window)] }), _jsx("span", { style: styles.modelMeta, children: window.resetsAt === undefined ? '' : formatReset(window.resetsAt, t) })] }), window.usedPercent !== undefined && (_jsx("div", { className: "dsh-subscription-meter", children: _jsx("span", { style: { width: `${String(Math.min(100, Math.max(0, window.usedPercent)))}%` }, "data-exhausted": window.usedPercent >= 90 ? 'true' : 'false' }) }))] }, `${entry.account}-${window.label}`)))] }, entry.account)))] }));
281
325
  }
282
326
  /** The pending-login block: a device code, or a link to the sign-in page. */
283
327
  function PendingLogin(props) {
@@ -470,9 +514,15 @@ function ProviderCard(props) {
470
514
  : status.loggedIn
471
515
  ? t('statusConnected', { count: accounts.length })
472
516
  : t('statusSignedOut');
473
- return (_jsxs("div", { style: styles.card, children: [_jsxs("div", { style: styles.cardHeader, children: [_jsx("span", { style: { ...styles.dot, background: dotColor(status) }, "aria-hidden": "true" }), _jsx("span", { style: styles.name, children: status.displayName }), _jsx("span", { style: styles.providerId, children: status.provider }), _jsx("span", { style: styles.cardSummary, children: summaryLine }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, "aria-expanded": cardOpen, "aria-controls": `dsh-subscription-card-${status.provider}`, onClick: toggleCardOpen, children: cardOpen ? t('collapse') : t('edit') })] }), _jsxs("div", { id: `dsh-subscription-card-${status.provider}`, style: cardOpen ? styles.cardBody : { ...styles.cardBody, display: 'none' }, children: [connectable && !status.loggedIn && !busy && _jsx("p", { style: styles.statusLine, children: t('statusSignIn') }), status.lastError !== undefined && (_jsx("p", { className: "dsh-subscription-error", children: t('lastError', { message: status.lastError }) })), message?.tone === 'error' && _jsx("p", { className: "dsh-subscription-error", children: message.text }), message?.tone === 'success' && _jsx("p", { className: "dsh-subscription-success", children: message.text }), accounts.map((account) => (_jsx("div", { style: styles.accountRow, children: _jsxs("div", { style: styles.accountHeader, children: [_jsx("button", { type: "button", style: styles.starButton, title: account.isDefault ? t('defaultBadge') : t('makeDefault'), "aria-label": account.isDefault ? t('defaultBadge') : t('makeDefault'), disabled: busyAction || account.isDefault, onClick: () => { props.onSetDefault(account.account); }, children: account.isDefault ? '★' : '☆' }), _jsx("span", { style: styles.accountName, children: account.label }), account.isDefault && _jsx("span", { style: styles.badge, children: t('defaultBadge') }), account.needsLogin === true && (_jsx("span", { style: styles.badge, title: t('accountNeedsLoginHint'), children: t('accountNeedsLogin') })), _jsx("span", { style: styles.statusLine, children: account.expired
474
- ? t('accountExpired')
475
- : t('accountExpires', { time: formatTime(account.expiresAt) }) }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: () => { props.onLogout(account.account); }, children: t('logout') })] }) }, account.account))), status.loggedIn && (_jsxs("div", { style: styles.catalog, children: [_jsxs("div", { style: styles.catalogHeader, children: [_jsxs("button", { type: "button", className: "dsh-subscription-disclosure", style: styles.disclosure, "aria-expanded": modelsOpen, "aria-controls": `dsh-subscription-models-${status.provider}`, onClick: toggleModelsOpen, children: [_jsx("span", { style: styles.chevron, "aria-hidden": "true", children: modelsOpen ? '▾' : '▸' }), _jsx("span", { style: styles.catalogTitle, children: t('modelsTitle') })] }), _jsxs("span", { style: styles.catalogMeta, children: [t('modelsCount', { count: models.length }), ' · ', liveCatalog ? t('modelsLive') : t('modelsBuiltIn'), hiddenCount === 0 ? '' : ` ${t('modelsHiddenCount', { count: hiddenCount })}`] }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: props.onRefreshModels, children: busyAction ? t('refreshing') : t('refresh') }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", disabled: busyAction || !connectable, title: t('modelsAddHint'), onClick: () => { setAdding((open) => !open); }, children: t('modelsAdd') })] }), adding && (_jsxs("form", { style: styles.addRow, onSubmit: (event) => {
517
+ return (_jsxs("div", { style: cardOpen ? { ...styles.card, ...styles.cardOpen } : styles.card, children: [_jsxs("div", { style: styles.cardHeader, role: "button", tabIndex: 0, "aria-expanded": cardOpen, onClick: toggleCardOpen, onKeyDown: (event) => { if (event.key === 'Enter' || event.key === ' ') {
518
+ event.preventDefault();
519
+ toggleCardOpen();
520
+ } }, children: [_jsx("span", { style: { ...styles.dot, ...dotStyle(status) }, "aria-hidden": "true" }), _jsx("span", { style: styles.name, children: status.displayName }), status.loggedIn && _jsx("span", { style: styles.badgeDefault, children: t('badgeLive') }), _jsx("span", { style: styles.cardSummary, children: summaryLine }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, "aria-expanded": cardOpen, "aria-controls": `dsh-subscription-card-${status.provider}`, onClick: (event) => { event.stopPropagation(); toggleCardOpen(); }, children: cardOpen ? t('collapse') : t('edit') })] }), _jsxs("div", { id: `dsh-subscription-card-${status.provider}`, style: cardOpen ? styles.cardBody : { ...styles.cardBody, display: 'none' }, children: [connectable && !status.loggedIn && !busy && _jsx("p", { style: styles.statusLine, children: t('statusSignIn') }), status.lastError !== undefined && (_jsx("p", { className: "dsh-subscription-error", children: t('lastError', { message: status.lastError }) })), message?.tone === 'error' && _jsx("p", { className: "dsh-subscription-error", children: message.text }), message?.tone === 'success' && _jsx("p", { className: "dsh-subscription-success", children: message.text }), accounts.map((account) => {
521
+ const usageEntry = (status.usage ?? []).find((entry) => entry.account === account.account);
522
+ 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('makeDefault'), "aria-label": account.isDefault ? t('defaultBadge') : t('makeDefault'), disabled: busyAction || account.isDefault, onClick: () => { props.onSetDefault(account.account); }, children: account.isDefault ? '★' : '☆' }), _jsx("span", { style: styles.accountName, children: account.label }), account.isDefault && _jsx("span", { style: styles.badgeDefault, children: t('defaultBadge') }), account.needsLogin === true && (_jsx("span", { style: styles.badgeWarn, title: t('accountNeedsLoginHint'), children: t('accountNeedsLogin') })), usageEntry?.plan !== undefined && (_jsx("span", { style: styles.badge, children: t('usagePlan', { plan: usageEntry.plan }) })), _jsx("span", { style: styles.statusLine, children: account.expired
523
+ ? t('accountExpired')
524
+ : t('accountExpires', { time: formatTime(account.expiresAt) }) }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: () => { props.onLogout(account.account); }, children: t('logout') })] }), _jsx(AccountUsage, { entry: usageEntry, t: t })] }, account.account));
525
+ }), status.loggedIn && (_jsxs("div", { style: styles.catalog, children: [_jsxs("div", { style: styles.catalogHeader, children: [_jsxs("button", { type: "button", className: "dsh-subscription-disclosure", style: styles.disclosure, "aria-expanded": modelsOpen, "aria-controls": `dsh-subscription-models-${status.provider}`, onClick: toggleModelsOpen, children: [_jsx("span", { style: styles.chevron, "aria-hidden": "true", children: modelsOpen ? '▾' : '▸' }), _jsx("span", { style: styles.catalogTitle, children: t('modelsTitle') })] }), _jsxs("span", { style: styles.catalogMeta, children: [t('modelsCount', { count: models.length }), ' · ', liveCatalog ? t('modelsLive') : t('modelsBuiltIn'), hiddenCount === 0 ? '' : ` ${t('modelsHiddenCount', { count: hiddenCount })}`] }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.pushRight, disabled: busyAction, onClick: props.onRefreshModels, children: busyAction ? t('refreshing') : t('refresh') }), _jsx("button", { type: "button", className: "dsh-subscription-quiet", disabled: busyAction || !connectable, title: t('modelsAddHint'), onClick: () => { setAdding((open) => !open); }, children: t('modelsAdd') })] }), adding && (_jsxs("form", { style: styles.addRow, onSubmit: (event) => {
476
526
  event.preventDefault();
477
527
  const id = draft.trim();
478
528
  if (id.length === 0)
@@ -495,7 +545,7 @@ function ProviderCard(props) {
495
545
  ? { ...styles.modelRowGrow, cursor: 'pointer' }
496
546
  : { ...styles.modelRowGrow, cursor: 'pointer', color: 'var(--dsw-alias-label-dimmed)' }, title: model.visible ? t('modelsHidden') : t('modelsShow'), children: [_jsx("input", { type: "checkbox", className: "dsh-subscription-switch", role: "switch", checked: model.visible, disabled: busyAction, "aria-label": `${t('modelsShow')} · ${model.name}`, onChange: (event) => { props.onSetModelVisible(model.id, event.target.checked); } }), _jsxs("span", { style: styles.modelBody, children: [_jsxs("span", { style: styles.modelName, children: [model.name, model.source === undefined ? null : _jsx("span", { style: styles.badge, children: model.source })] }), _jsxs("span", { style: styles.modelMeta, children: [model.id, ' · ', t('modelsContext', { tokens: formatTokens(model.contextWindow) }), model.defaultEffort === undefined ? '' : ` · ${model.defaultEffort}`, model.inputModalities === undefined || model.inputModalities.length === 0
497
547
  ? ''
498
- : ` · ${model.inputModalities.join('/')}`] })] })] }), model.userAdded === true && (_jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.removeButton, disabled: busyAction, title: t('modelsRemove'), "aria-label": `${t('modelsRemove')} · ${model.name}`, onClick: () => { props.onRemoveModel(model.id); }, children: "\u00D7" }))] }, model.id))), models.length === 0 && (_jsx("p", { style: styles.statusLine, children: status.provider === 'auto' ? t('modelsAutoChainEmpty') : t('modelsEmpty') }))] }))] })), _jsx(UsageBlock, { usage: status.usage ?? [], ...status.usageError === undefined ? {} : { error: status.usageError }, t: t, busyAction: busyAction, onRefresh: props.onRefreshUsage }), connectable && (_jsxs("div", { style: styles.actions, children: [!busy && (_jsx("button", { type: "button", disabled: busyAction, onClick: props.onLogin, children: accounts.length === 0 ? t('connect') : t('addAccount') })), !busy && (_jsx("button", { type: "button", disabled: busyAction, onClick: () => { setManageOpen(true); }, children: t('manage') })), busy && (_jsx("button", { type: "button", disabled: busyAction, onClick: props.onCancel, children: t('cancel') }))] })), connectable && accounts.length > 0 && _jsx("p", { style: styles.statusLine, children: t('hint') }), busy && status.pending !== undefined && (_jsx(PendingLogin, { prompt: status.pending, t: t, copied: copied, onCopy: (code) => { void copyCode(code); } }))] }), _jsxs("dialog", { ref: dialog, className: "dsh-subscription-manager", "aria-labelledby": `dsh-subscription-manage-${status.provider}`,
548
+ : ` · ${model.inputModalities.join('/')}`] })] })] }), model.userAdded === true && (_jsx("button", { type: "button", className: "dsh-subscription-quiet", style: styles.removeButton, disabled: busyAction, title: t('modelsRemove'), "aria-label": `${t('modelsRemove')} · ${model.name}`, onClick: () => { props.onRemoveModel(model.id); }, children: "\u00D7" }))] }, model.id))), models.length === 0 && (_jsx("p", { style: styles.statusLine, children: status.provider === 'auto' ? t('modelsAutoChainEmpty') : t('modelsEmpty') }))] }))] })), connectable && (_jsxs("div", { style: styles.actions, children: [!busy && (_jsx("button", { type: "button", disabled: busyAction, onClick: props.onLogin, children: accounts.length === 0 ? t('connect') : t('addAccount') })), !busy && (_jsx("button", { type: "button", disabled: busyAction, onClick: () => { setManageOpen(true); }, children: t('manage') })), busy && (_jsx("button", { type: "button", disabled: busyAction, onClick: props.onCancel, children: t('cancel') }))] })), connectable && accounts.length > 0 && _jsx("p", { style: styles.statusLine, children: t('hint') }), busy && status.pending !== undefined && (_jsx(PendingLogin, { prompt: status.pending, t: t, copied: copied, onCopy: (code) => { void copyCode(code); } }))] }), _jsxs("dialog", { ref: dialog, className: "dsh-subscription-manager", "aria-labelledby": `dsh-subscription-manage-${status.provider}`,
499
549
  // A closed `<dialog>` is hidden by the user-agent stylesheet, and an
500
550
  // inline `display` would override that rule — so the open state has to
501
551
  // be restated here rather than left to `showModal()` alone.
@@ -614,6 +664,22 @@ export function SubscriptionsSection(props) {
614
664
  else
615
665
  stopPolling();
616
666
  }, [anyBusy, startPolling, stopPolling]);
667
+ /**
668
+ * Signed-in providers read first: a route with a live account is the one a
669
+ * reader most likely wants, and `auto` (which owns no account) leads the rest.
670
+ * The sort is stable, so equal ranks keep the host's configured order.
671
+ */
672
+ const orderedProviders = useMemo(() => {
673
+ const providers = status?.providers ?? [];
674
+ const rank = (provider) => {
675
+ if (provider.provider === 'auto')
676
+ return 0;
677
+ if (provider.loggedIn)
678
+ return 1;
679
+ return 2;
680
+ };
681
+ return [...providers].sort((left, right) => rank(left) - rank(right));
682
+ }, [status?.providers]);
617
683
  /** Run one action for one provider, reporting its own failure. */
618
684
  const act = useCallback(async (provider, run) => {
619
685
  setBusyAction(true);
@@ -758,5 +824,5 @@ export function SubscriptionsSection(props) {
758
824
  }, [act, call]);
759
825
  if (call === undefined)
760
826
  return _jsx("p", { style: styles.intro, children: t('unavailable') });
761
- return (_jsxs("div", { className: "dsh-subscriptions-settings", style: styles.section, children: [_jsx("style", { children: subscriptionsCss }), _jsx("p", { style: styles.intro, children: t('intro') }), error !== undefined && _jsx("p", { className: "dsh-subscription-error", children: error }), status === undefined && _jsx("p", { style: styles.statusLine, children: t('loading') }), status?.providers.map((provider) => (_jsx(ProviderCard, { status: provider, t: t, busyAction: busyAction, ...messages[provider.provider] === undefined ? {} : { message: messages[provider.provider] }, onLogin: () => { login(provider.provider); }, onCancel: () => { cancel(provider.provider); }, onLogout: (account) => { logout(provider.provider, account); }, onSetDefault: (account) => { setDefault(provider.provider, account); }, onRefreshToken: (account) => { refreshToken(provider.provider, account); }, onRefreshModels: () => { refreshModels(provider.provider); }, onSetModelVisible: (id, visible) => { setModelVisible(provider.provider, id, visible); }, onSetModelOrder: (ids) => { setModelOrder(provider.provider, ids); }, onAddModel: (id) => { addModel(provider.provider, id); }, onRemoveModel: (id) => { removeModel(provider.provider, id); }, onRefreshUsage: () => { refreshUsage(provider.provider); }, onReload: () => { reload(provider.provider); } }, provider.provider)))] }));
827
+ return (_jsxs("div", { className: "dsh-subscriptions-settings", style: styles.section, children: [_jsx("style", { children: subscriptionsCss }), _jsxs("div", { style: styles.sectionHeader, children: [_jsx("h2", { style: styles.heading, children: t('nav') }), _jsx("p", { style: styles.intro, children: t('intro') })] }), error !== undefined && _jsx("p", { className: "dsh-subscription-error", children: error }), status === undefined && _jsx("p", { style: styles.statusLine, children: t('loading') }), orderedProviders.map((provider) => (_jsx(ProviderCard, { status: provider, t: t, busyAction: busyAction, ...messages[provider.provider] === undefined ? {} : { message: messages[provider.provider] }, onLogin: () => { login(provider.provider); }, onCancel: () => { cancel(provider.provider); }, onLogout: (account) => { logout(provider.provider, account); }, onSetDefault: (account) => { setDefault(provider.provider, account); }, onRefreshToken: (account) => { refreshToken(provider.provider, account); }, onRefreshModels: () => { refreshModels(provider.provider); }, onSetModelVisible: (id, visible) => { setModelVisible(provider.provider, id, visible); }, onSetModelOrder: (ids) => { setModelOrder(provider.provider, ids); }, onAddModel: (id) => { addModel(provider.provider, id); }, onRemoveModel: (id) => { removeModel(provider.provider, id); }, onRefreshUsage: () => { refreshUsage(provider.provider); }, onReload: () => { reload(provider.provider); } }, provider.provider)))] }));
762
828
  }
@@ -8,7 +8,7 @@
8
8
  /** English strings. */
9
9
  export declare const en: {
10
10
  readonly nav: "Subscriptions";
11
- readonly intro: "Sign in with a subscription account — Kimi Code, Grok (X Premium) or Google Antigravity. DeepSeek Harness keeps the tokens on this machine and streams every request through the provider's own API.";
11
+ readonly intro: "Sign in with the AI subscriptions you already pay for — no API key. Pick a provider below, connect once, and its models join the model picker with live plan usage. Tokens stay on this machine.";
12
12
  readonly loading: "Checking subscriptions…";
13
13
  readonly unavailable: "The subscriptions page needs the web connection service, which this composition does not provide.";
14
14
  readonly statusConnected: "Connected · {count} account(s)";
@@ -26,6 +26,7 @@ export declare const en: {
26
26
  readonly logout: "Sign out";
27
27
  readonly makeDefault: "Use by default";
28
28
  readonly defaultBadge: "Default";
29
+ readonly badgeLive: "Live";
29
30
  readonly accountNeedsLogin: "Sign in again";
30
31
  readonly accountNeedsLoginHint: "The provider refused this account's grant, so its token cannot be renewed";
31
32
  readonly accountExpired: "token expired";
@@ -122,6 +123,7 @@ export declare const zh: {
122
123
  logout: string;
123
124
  makeDefault: string;
124
125
  defaultBadge: string;
126
+ badgeLive: string;
125
127
  accountNeedsLogin: string;
126
128
  accountNeedsLoginHint: string;
127
129
  accountExpired: string;
@@ -8,7 +8,7 @@
8
8
  /** English strings. */
9
9
  export const en = {
10
10
  nav: 'Subscriptions',
11
- intro: 'Sign in with a subscription account — Kimi Code, Grok (X Premium) or Google Antigravity. DeepSeek Harness keeps the tokens on this machine and streams every request through the provider\'s own API.',
11
+ intro: 'Sign in with the AI subscriptions you already pay for — no API key. Pick a provider below, connect once, and its models join the model picker with live plan usage. Tokens stay on this machine.',
12
12
  loading: 'Checking subscriptions…',
13
13
  unavailable: 'The subscriptions page needs the web connection service, which this composition does not provide.',
14
14
  statusConnected: 'Connected · {count} account(s)',
@@ -26,6 +26,7 @@ export const en = {
26
26
  logout: 'Sign out',
27
27
  makeDefault: 'Use by default',
28
28
  defaultBadge: 'Default',
29
+ badgeLive: 'Live',
29
30
  accountNeedsLogin: 'Sign in again',
30
31
  accountNeedsLoginHint: 'The provider refused this account\'s grant, so its token cannot be renewed',
31
32
  accountExpired: 'token expired',
@@ -102,7 +103,7 @@ export const en = {
102
103
  /** Chinese strings. */
103
104
  export const zh = {
104
105
  nav: '订阅',
105
- intro: '使用订阅账号登录 —— Kimi Code、Grok (X Premium) 或 Google Antigravity。DeepSeek Harness 将令牌保存在本机,并通过各服务自己的 API 发起请求。',
106
+ intro: '使用你已付费的 AI 订阅 —— 无需 API Key。在下方选择服务,连接一次,其模型即进入模型选择器并显示实时额度。令牌仅保存在本机。',
106
107
  loading: '正在检查订阅…',
107
108
  unavailable: '订阅页面需要 Web 连接服务,当前组合未提供该服务。',
108
109
  statusConnected: '已连接 {count} 个账号',
@@ -120,6 +121,7 @@ export const zh = {
120
121
  logout: '退出登录',
121
122
  makeDefault: '设为默认',
122
123
  defaultBadge: '默认',
124
+ badgeLive: '已连接',
123
125
  accountNeedsLogin: '需重新登录',
124
126
  accountNeedsLoginHint: '服务方已拒绝该账号的授权,令牌无法自动续期',
125
127
  accountExpired: '令牌已过期',