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.
- package/README.dev.md +434 -0
- package/README.md +50 -391
- package/README.zh.dev.md +371 -0
- package/README.zh.md +50 -328
- package/lib/auth/mimo-flow.d.ts +9 -0
- package/lib/auth/mimo-flow.js +171 -0
- package/lib/auth/registry.d.ts +10 -1
- package/lib/auth/registry.js +11 -1
- package/lib/auth/rpc.js +6 -0
- package/lib/client/SubscriptionsSection.js +96 -30
- package/lib/client/locales.d.ts +3 -1
- package/lib/client/locales.js +4 -2
- package/lib/client.js +230 -159
- package/lib/client.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/protocol.d.ts +13 -1
- package/lib/protocol.js +1 -0
- package/lib/providers/auto.js +3 -0
- package/lib/providers/grok.d.ts +2 -0
- package/lib/providers/grok.js +31 -21
- package/lib/providers/kimi-provider.js +2 -1
- package/lib/providers/kimi.js +18 -13
- package/lib/providers/mimo.d.ts +10 -0
- package/lib/providers/mimo.js +65 -1
- package/lib/settings.d.ts +8 -0
- package/lib/settings.js +19 -5
- package/package.json +1 -1
package/lib/auth/registry.d.ts
CHANGED
|
@@ -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
|
}
|
package/lib/auth/registry.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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,
|
|
123
|
+
borderRadius: 'var(--dsw-radius-xl, 16px)',
|
|
122
124
|
background: 'var(--dsw-alias-settings-card-fill, var(--dsw-alias-bg-layer-2))',
|
|
123
|
-
padding: '
|
|
125
|
+
padding: '14px 16px', display: 'flex', flexDirection: 'column', gap: 8,
|
|
126
|
+
transition: 'border-color 120ms ease, background 120ms ease',
|
|
124
127
|
},
|
|
125
|
-
|
|
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:
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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,
|
|
140
|
-
padding: '
|
|
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:
|
|
151
|
-
background: 'var(--dsw-alias-bg-layer-
|
|
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:
|
|
201
|
-
borderTop: '0.5px solid var(--dsw-alias-border-l2)', paddingTop:
|
|
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:
|
|
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
|
|
252
|
-
function
|
|
288
|
+
/** Status dot style for one provider's connection state. */
|
|
289
|
+
function dotStyle(status) {
|
|
253
290
|
if (status.busy)
|
|
254
|
-
return
|
|
291
|
+
return styles.dotBusy;
|
|
255
292
|
if (status.loggedIn)
|
|
256
|
-
return
|
|
257
|
-
return
|
|
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
|
|
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.
|
|
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:
|
|
474
|
-
|
|
475
|
-
|
|
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') }))] }))] })),
|
|
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') }),
|
|
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
|
}
|
package/lib/client/locales.d.ts
CHANGED
|
@@ -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
|
|
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;
|
package/lib/client/locales.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
/** English strings. */
|
|
9
9
|
export const en = {
|
|
10
10
|
nav: 'Subscriptions',
|
|
11
|
-
intro: 'Sign in with
|
|
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: '
|
|
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: '令牌已过期',
|