dsh-plugin-subscription-oauth 0.10.1 → 0.11.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 +12 -0
- package/lib/auth/mimo-flow.js +106 -0
- package/lib/client/SubscriptionsSection.js +66 -20
- package/lib/client/locales.d.ts +3 -1
- package/lib/client/locales.js +4 -2
- package/lib/client.js +134 -36
- package/lib/client.js.map +1 -1
- package/lib/providers/mimo-provider.js +17 -2
- package/package.json +1 -1
|
@@ -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,40 @@ 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
|
-
dot: { width: 8, height: 8, borderRadius: '50%', flexShrink: 0 },
|
|
131
|
-
name: { fontWeight:
|
|
132
|
-
providerId: {
|
|
137
|
+
dot: { width: 8, height: 8, borderRadius: '50%', flexShrink: 0, boxShadow: '0 0 0 3px var(--dsw-alias-state-success-primary, transparent)' },
|
|
138
|
+
name: { fontWeight: 600, fontSize: 14, lineHeight: '22px', color: 'var(--dsw-alias-label-primary)' },
|
|
139
|
+
providerId: {
|
|
140
|
+
fontSize: 11, lineHeight: '16px', color: 'var(--dsw-alias-label-dimmed)',
|
|
141
|
+
padding: '1px 6px', borderRadius: 999, background: 'var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-1))',
|
|
142
|
+
fontFamily: 'monospace',
|
|
143
|
+
},
|
|
133
144
|
statusLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },
|
|
134
145
|
errorLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-error-primary)' },
|
|
135
146
|
successLine: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-state-success-primary)' },
|
|
136
147
|
actions: { display: 'flex', gap: 8, marginTop: 4, alignItems: 'center', flexWrap: 'wrap' },
|
|
137
148
|
accountRow: {
|
|
138
149
|
display: 'flex', flexDirection: 'column', gap: 6,
|
|
139
|
-
border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 'var(--dsw-radius-lg,
|
|
140
|
-
padding: '
|
|
150
|
+
border: '0.5px solid var(--dsw-alias-border-l2)', borderRadius: 'var(--dsw-radius-lg, 12px)',
|
|
151
|
+
padding: '10px 12px', background: 'var(--dsw-alias-bg-layer-1)',
|
|
141
152
|
},
|
|
142
153
|
accountHeader: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' },
|
|
143
154
|
starButton: {
|
|
@@ -147,10 +158,21 @@ const styles = {
|
|
|
147
158
|
},
|
|
148
159
|
accountName: { fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)', userSelect: 'all' },
|
|
149
160
|
badge: {
|
|
150
|
-
fontSize:
|
|
151
|
-
background: 'var(--dsw-alias-bg-layer-
|
|
161
|
+
fontSize: 10, lineHeight: '14px', padding: '1px 7px', borderRadius: 999, fontWeight: 500,
|
|
162
|
+
background: 'var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-1))',
|
|
163
|
+
border: '0.5px solid var(--dsw-alias-border-l2)',
|
|
152
164
|
color: 'var(--dsw-alias-label-secondary)',
|
|
153
165
|
},
|
|
166
|
+
badgeDefault: {
|
|
167
|
+
fontSize: 10, lineHeight: '14px', padding: '1px 7px', borderRadius: 999, fontWeight: 500,
|
|
168
|
+
background: 'var(--dsw-alias-state-success-primary, #067647)',
|
|
169
|
+
color: 'var(--dsw-alias-label-primary-foreground, #fff)',
|
|
170
|
+
},
|
|
171
|
+
badgeWarn: {
|
|
172
|
+
fontSize: 10, lineHeight: '14px', padding: '1px 7px', borderRadius: 999, fontWeight: 500,
|
|
173
|
+
background: 'var(--dsw-alias-state-warn-primary, #f5a524)',
|
|
174
|
+
color: 'var(--dsw-alias-label-primary-foreground, #fff)',
|
|
175
|
+
},
|
|
154
176
|
pushRight: { marginLeft: 'auto', flexShrink: 0 },
|
|
155
177
|
catalog: {
|
|
156
178
|
display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4,
|
|
@@ -197,15 +219,20 @@ const styles = {
|
|
|
197
219
|
fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-secondary)',
|
|
198
220
|
},
|
|
199
221
|
usage: {
|
|
200
|
-
display: 'flex', flexDirection: 'column', gap:
|
|
201
|
-
borderTop: '0.5px solid var(--dsw-alias-border-l2)', paddingTop:
|
|
222
|
+
display: 'flex', flexDirection: 'column', gap: 8, marginTop: 2,
|
|
223
|
+
borderTop: '0.5px solid var(--dsw-alias-border-l2)', paddingTop: 10,
|
|
202
224
|
},
|
|
203
225
|
usageHeader: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' },
|
|
204
|
-
usageRow: { display: 'flex', flexDirection: 'column', gap:
|
|
226
|
+
usageRow: { display: 'flex', flexDirection: 'column', gap: 4 },
|
|
205
227
|
usageLine: { display: 'flex', alignItems: 'baseline', gap: 8, justifyContent: 'space-between' },
|
|
206
228
|
usageMeter: {
|
|
207
229
|
height: 6, borderRadius: 999, background: 'var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-2))',
|
|
208
|
-
overflow: 'hidden',
|
|
230
|
+
overflow: 'hidden', boxShadow: 'inset 0 0 0 1px #8a8f9833',
|
|
231
|
+
},
|
|
232
|
+
usageAccount: {
|
|
233
|
+
display: 'flex', flexDirection: 'column', gap: 6,
|
|
234
|
+
padding: '8px 10px', borderRadius: 'var(--dsw-radius-md, 10px)',
|
|
235
|
+
background: 'var(--dsw-alias-bg-layer-1)', border: '0.5px solid var(--dsw-alias-border-l2)',
|
|
209
236
|
},
|
|
210
237
|
modelRow: {
|
|
211
238
|
display: 'flex', alignItems: 'flex-start', gap: 8,
|
|
@@ -277,7 +304,7 @@ function windowAmount(window) {
|
|
|
277
304
|
/** One account's plan and metered allowances. */
|
|
278
305
|
function UsageBlock(props) {
|
|
279
306
|
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.
|
|
307
|
+
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
308
|
}
|
|
282
309
|
/** The pending-login block: a device code, or a link to the sign-in page. */
|
|
283
310
|
function PendingLogin(props) {
|
|
@@ -470,7 +497,10 @@ function ProviderCard(props) {
|
|
|
470
497
|
: status.loggedIn
|
|
471
498
|
? t('statusConnected', { count: accounts.length })
|
|
472
499
|
: t('statusSignedOut');
|
|
473
|
-
return (_jsxs("div", { style:
|
|
500
|
+
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 === ' ') {
|
|
501
|
+
event.preventDefault();
|
|
502
|
+
toggleCardOpen();
|
|
503
|
+
} }, 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 }), 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) => (_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.badgeDefault, children: t('defaultBadge') }), account.needsLogin === true && (_jsx("span", { style: styles.badgeWarn, title: t('accountNeedsLoginHint'), children: t('accountNeedsLogin') })), _jsx("span", { style: styles.statusLine, children: account.expired
|
|
474
504
|
? t('accountExpired')
|
|
475
505
|
: 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) => {
|
|
476
506
|
event.preventDefault();
|
|
@@ -614,6 +644,22 @@ export function SubscriptionsSection(props) {
|
|
|
614
644
|
else
|
|
615
645
|
stopPolling();
|
|
616
646
|
}, [anyBusy, startPolling, stopPolling]);
|
|
647
|
+
/**
|
|
648
|
+
* Signed-in providers read first: a route with a live account is the one a
|
|
649
|
+
* reader most likely wants, and `auto` (which owns no account) leads the rest.
|
|
650
|
+
* The sort is stable, so equal ranks keep the host's configured order.
|
|
651
|
+
*/
|
|
652
|
+
const orderedProviders = useMemo(() => {
|
|
653
|
+
const providers = status?.providers ?? [];
|
|
654
|
+
const rank = (provider) => {
|
|
655
|
+
if (provider.provider === 'auto')
|
|
656
|
+
return 0;
|
|
657
|
+
if (provider.loggedIn)
|
|
658
|
+
return 1;
|
|
659
|
+
return 2;
|
|
660
|
+
};
|
|
661
|
+
return [...providers].sort((left, right) => rank(left) - rank(right));
|
|
662
|
+
}, [status?.providers]);
|
|
617
663
|
/** Run one action for one provider, reporting its own failure. */
|
|
618
664
|
const act = useCallback(async (provider, run) => {
|
|
619
665
|
setBusyAction(true);
|
|
@@ -758,5 +804,5 @@ export function SubscriptionsSection(props) {
|
|
|
758
804
|
}, [act, call]);
|
|
759
805
|
if (call === undefined)
|
|
760
806
|
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') }),
|
|
807
|
+
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
808
|
}
|
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: '令牌已过期',
|
package/lib/client.js
CHANGED
|
@@ -39,7 +39,7 @@ react_jsx_runtime = __toESM(react_jsx_runtime);
|
|
|
39
39
|
/** English strings. */
|
|
40
40
|
const en = {
|
|
41
41
|
nav: "Subscriptions",
|
|
42
|
-
intro: "Sign in with
|
|
42
|
+
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.",
|
|
43
43
|
loading: "Checking subscriptions…",
|
|
44
44
|
unavailable: "The subscriptions page needs the web connection service, which this composition does not provide.",
|
|
45
45
|
statusConnected: "Connected · {count} account(s)",
|
|
@@ -57,6 +57,7 @@ const en = {
|
|
|
57
57
|
logout: "Sign out",
|
|
58
58
|
makeDefault: "Use by default",
|
|
59
59
|
defaultBadge: "Default",
|
|
60
|
+
badgeLive: "Live",
|
|
60
61
|
accountNeedsLogin: "Sign in again",
|
|
61
62
|
accountNeedsLoginHint: "The provider refused this account's grant, so its token cannot be renewed",
|
|
62
63
|
accountExpired: "token expired",
|
|
@@ -133,7 +134,7 @@ const en = {
|
|
|
133
134
|
/** Chinese strings. */
|
|
134
135
|
const zh = {
|
|
135
136
|
nav: "订阅",
|
|
136
|
-
intro: "
|
|
137
|
+
intro: "使用你已付费的 AI 订阅 —— 无需 API Key。在下方选择服务,连接一次,其模型即进入模型选择器并显示实时额度。令牌仅保存在本机。",
|
|
137
138
|
loading: "正在检查订阅…",
|
|
138
139
|
unavailable: "订阅页面需要 Web 连接服务,当前组合未提供该服务。",
|
|
139
140
|
statusConnected: "已连接 {count} 个账号",
|
|
@@ -151,6 +152,7 @@ const zh = {
|
|
|
151
152
|
logout: "退出登录",
|
|
152
153
|
makeDefault: "设为默认",
|
|
153
154
|
defaultBadge: "默认",
|
|
155
|
+
badgeLive: "已连接",
|
|
154
156
|
accountNeedsLogin: "需重新登录",
|
|
155
157
|
accountNeedsLoginHint: "服务方已拒绝该账号的授权,令牌无法自动续期",
|
|
156
158
|
accountExpired: "令牌已过期",
|
|
@@ -877,30 +879,52 @@ const styles = {
|
|
|
877
879
|
maxWidth: SECTION_WIDTH,
|
|
878
880
|
color: "var(--dsw-alias-label-primary)"
|
|
879
881
|
},
|
|
882
|
+
sectionHeader: {
|
|
883
|
+
display: "flex",
|
|
884
|
+
flexDirection: "column",
|
|
885
|
+
gap: 4,
|
|
886
|
+
marginBottom: 2
|
|
887
|
+
},
|
|
888
|
+
heading: {
|
|
889
|
+
margin: 0,
|
|
890
|
+
fontSize: 18,
|
|
891
|
+
lineHeight: "26px",
|
|
892
|
+
fontWeight: 600,
|
|
893
|
+
color: "var(--dsw-alias-label-primary)"
|
|
894
|
+
},
|
|
880
895
|
intro: {
|
|
881
896
|
margin: 0,
|
|
882
897
|
color: "var(--dsw-alias-label-tertiary)",
|
|
883
|
-
fontSize:
|
|
884
|
-
lineHeight: "
|
|
898
|
+
fontSize: 13,
|
|
899
|
+
lineHeight: "20px",
|
|
900
|
+
maxWidth: 560
|
|
885
901
|
},
|
|
886
902
|
card: {
|
|
887
903
|
border: "0.5px solid var(--dsw-alias-settings-card-stroke, var(--dsw-alias-border-l4))",
|
|
888
|
-
borderRadius: "var(--dsw-radius-xl,
|
|
904
|
+
borderRadius: "var(--dsw-radius-xl, 16px)",
|
|
889
905
|
background: "var(--dsw-alias-settings-card-fill, var(--dsw-alias-bg-layer-2))",
|
|
890
|
-
padding: "
|
|
906
|
+
padding: "14px 16px",
|
|
891
907
|
display: "flex",
|
|
892
908
|
flexDirection: "column",
|
|
893
|
-
gap:
|
|
909
|
+
gap: 8,
|
|
910
|
+
transition: "border-color 120ms ease, background 120ms ease"
|
|
911
|
+
},
|
|
912
|
+
cardOpen: {
|
|
913
|
+
borderColor: "var(--dsw-alias-border-l2, var(--dsw-alias-border-l3))",
|
|
914
|
+
background: "var(--dsw-alias-settings-card-fill-hover, var(--dsw-alias-settings-card-fill, var(--dsw-alias-bg-layer-2)))"
|
|
894
915
|
},
|
|
895
916
|
cardHeader: {
|
|
896
917
|
display: "flex",
|
|
897
918
|
alignItems: "center",
|
|
898
|
-
gap:
|
|
919
|
+
gap: 10,
|
|
920
|
+
cursor: "pointer",
|
|
921
|
+
userSelect: "none"
|
|
899
922
|
},
|
|
900
923
|
cardBody: {
|
|
901
924
|
display: "flex",
|
|
902
925
|
flexDirection: "column",
|
|
903
|
-
gap:
|
|
926
|
+
gap: 10,
|
|
927
|
+
marginTop: 2
|
|
904
928
|
},
|
|
905
929
|
cardSummary: {
|
|
906
930
|
fontSize: 12,
|
|
@@ -911,18 +935,23 @@ const styles = {
|
|
|
911
935
|
width: 8,
|
|
912
936
|
height: 8,
|
|
913
937
|
borderRadius: "50%",
|
|
914
|
-
flexShrink: 0
|
|
938
|
+
flexShrink: 0,
|
|
939
|
+
boxShadow: "0 0 0 3px var(--dsw-alias-state-success-primary, transparent)"
|
|
915
940
|
},
|
|
916
941
|
name: {
|
|
917
|
-
fontWeight:
|
|
942
|
+
fontWeight: 600,
|
|
918
943
|
fontSize: 14,
|
|
919
944
|
lineHeight: "22px",
|
|
920
945
|
color: "var(--dsw-alias-label-primary)"
|
|
921
946
|
},
|
|
922
947
|
providerId: {
|
|
923
|
-
fontSize:
|
|
924
|
-
lineHeight: "
|
|
925
|
-
color: "var(--dsw-alias-label-dimmed)"
|
|
948
|
+
fontSize: 11,
|
|
949
|
+
lineHeight: "16px",
|
|
950
|
+
color: "var(--dsw-alias-label-dimmed)",
|
|
951
|
+
padding: "1px 6px",
|
|
952
|
+
borderRadius: 999,
|
|
953
|
+
background: "var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-1))",
|
|
954
|
+
fontFamily: "monospace"
|
|
926
955
|
},
|
|
927
956
|
statusLine: {
|
|
928
957
|
margin: 0,
|
|
@@ -954,9 +983,9 @@ const styles = {
|
|
|
954
983
|
flexDirection: "column",
|
|
955
984
|
gap: 6,
|
|
956
985
|
border: "0.5px solid var(--dsw-alias-border-l2)",
|
|
957
|
-
borderRadius: "var(--dsw-radius-lg,
|
|
958
|
-
padding: "
|
|
959
|
-
|
|
986
|
+
borderRadius: "var(--dsw-radius-lg, 12px)",
|
|
987
|
+
padding: "10px 12px",
|
|
988
|
+
background: "var(--dsw-alias-bg-layer-1)"
|
|
960
989
|
},
|
|
961
990
|
accountHeader: {
|
|
962
991
|
display: "flex",
|
|
@@ -982,14 +1011,33 @@ const styles = {
|
|
|
982
1011
|
userSelect: "all"
|
|
983
1012
|
},
|
|
984
1013
|
badge: {
|
|
985
|
-
fontSize:
|
|
986
|
-
lineHeight: "
|
|
987
|
-
padding: "
|
|
988
|
-
borderRadius:
|
|
989
|
-
|
|
1014
|
+
fontSize: 10,
|
|
1015
|
+
lineHeight: "14px",
|
|
1016
|
+
padding: "1px 7px",
|
|
1017
|
+
borderRadius: 999,
|
|
1018
|
+
fontWeight: 500,
|
|
1019
|
+
background: "var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-1))",
|
|
990
1020
|
border: "0.5px solid var(--dsw-alias-border-l2)",
|
|
991
1021
|
color: "var(--dsw-alias-label-secondary)"
|
|
992
1022
|
},
|
|
1023
|
+
badgeDefault: {
|
|
1024
|
+
fontSize: 10,
|
|
1025
|
+
lineHeight: "14px",
|
|
1026
|
+
padding: "1px 7px",
|
|
1027
|
+
borderRadius: 999,
|
|
1028
|
+
fontWeight: 500,
|
|
1029
|
+
background: "var(--dsw-alias-state-success-primary, #067647)",
|
|
1030
|
+
color: "var(--dsw-alias-label-primary-foreground, #fff)"
|
|
1031
|
+
},
|
|
1032
|
+
badgeWarn: {
|
|
1033
|
+
fontSize: 10,
|
|
1034
|
+
lineHeight: "14px",
|
|
1035
|
+
padding: "1px 7px",
|
|
1036
|
+
borderRadius: 999,
|
|
1037
|
+
fontWeight: 500,
|
|
1038
|
+
background: "var(--dsw-alias-state-warn-primary, #f5a524)",
|
|
1039
|
+
color: "var(--dsw-alias-label-primary-foreground, #fff)"
|
|
1040
|
+
},
|
|
993
1041
|
pushRight: {
|
|
994
1042
|
marginLeft: "auto",
|
|
995
1043
|
flexShrink: 0
|
|
@@ -1097,10 +1145,10 @@ const styles = {
|
|
|
1097
1145
|
usage: {
|
|
1098
1146
|
display: "flex",
|
|
1099
1147
|
flexDirection: "column",
|
|
1100
|
-
gap:
|
|
1101
|
-
marginTop:
|
|
1148
|
+
gap: 8,
|
|
1149
|
+
marginTop: 2,
|
|
1102
1150
|
borderTop: "0.5px solid var(--dsw-alias-border-l2)",
|
|
1103
|
-
paddingTop:
|
|
1151
|
+
paddingTop: 10
|
|
1104
1152
|
},
|
|
1105
1153
|
usageHeader: {
|
|
1106
1154
|
display: "flex",
|
|
@@ -1111,7 +1159,7 @@ const styles = {
|
|
|
1111
1159
|
usageRow: {
|
|
1112
1160
|
display: "flex",
|
|
1113
1161
|
flexDirection: "column",
|
|
1114
|
-
gap:
|
|
1162
|
+
gap: 4
|
|
1115
1163
|
},
|
|
1116
1164
|
usageLine: {
|
|
1117
1165
|
display: "flex",
|
|
@@ -1123,7 +1171,17 @@ const styles = {
|
|
|
1123
1171
|
height: 6,
|
|
1124
1172
|
borderRadius: 999,
|
|
1125
1173
|
background: "var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-2))",
|
|
1126
|
-
overflow: "hidden"
|
|
1174
|
+
overflow: "hidden",
|
|
1175
|
+
boxShadow: "inset 0 0 0 1px #8a8f9833"
|
|
1176
|
+
},
|
|
1177
|
+
usageAccount: {
|
|
1178
|
+
display: "flex",
|
|
1179
|
+
flexDirection: "column",
|
|
1180
|
+
gap: 6,
|
|
1181
|
+
padding: "8px 10px",
|
|
1182
|
+
borderRadius: "var(--dsw-radius-md, 10px)",
|
|
1183
|
+
background: "var(--dsw-alias-bg-layer-1)",
|
|
1184
|
+
border: "0.5px solid var(--dsw-alias-border-l2)"
|
|
1127
1185
|
},
|
|
1128
1186
|
modelRow: {
|
|
1129
1187
|
display: "flex",
|
|
@@ -1284,7 +1342,7 @@ function UsageBlock(props) {
|
|
|
1284
1342
|
children: t("usageNone")
|
|
1285
1343
|
}),
|
|
1286
1344
|
usage.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1287
|
-
style: styles.
|
|
1345
|
+
style: styles.usageAccount,
|
|
1288
1346
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1289
1347
|
style: styles.usageLine,
|
|
1290
1348
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
@@ -1522,10 +1580,23 @@ function ProviderCard(props) {
|
|
|
1522
1580
|
*/
|
|
1523
1581
|
const summaryLine = !connectable ? t("statusAuto", { count: models.filter((model) => model.visible).length }) : busy ? t("statusBusy") : status.loggedIn ? t("statusConnected", { count: accounts.length }) : t("statusSignedOut");
|
|
1524
1582
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1525
|
-
style:
|
|
1583
|
+
style: cardOpen ? {
|
|
1584
|
+
...styles.card,
|
|
1585
|
+
...styles.cardOpen
|
|
1586
|
+
} : styles.card,
|
|
1526
1587
|
children: [
|
|
1527
1588
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1528
1589
|
style: styles.cardHeader,
|
|
1590
|
+
role: "button",
|
|
1591
|
+
tabIndex: 0,
|
|
1592
|
+
"aria-expanded": cardOpen,
|
|
1593
|
+
onClick: toggleCardOpen,
|
|
1594
|
+
onKeyDown: (event) => {
|
|
1595
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
1596
|
+
event.preventDefault();
|
|
1597
|
+
toggleCardOpen();
|
|
1598
|
+
}
|
|
1599
|
+
},
|
|
1529
1600
|
children: [
|
|
1530
1601
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1531
1602
|
style: {
|
|
@@ -1542,6 +1613,10 @@ function ProviderCard(props) {
|
|
|
1542
1613
|
style: styles.providerId,
|
|
1543
1614
|
children: status.provider
|
|
1544
1615
|
}),
|
|
1616
|
+
status.loggedIn && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1617
|
+
style: styles.badgeDefault,
|
|
1618
|
+
children: t("badgeLive")
|
|
1619
|
+
}),
|
|
1545
1620
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1546
1621
|
style: styles.cardSummary,
|
|
1547
1622
|
children: summaryLine
|
|
@@ -1552,7 +1627,10 @@ function ProviderCard(props) {
|
|
|
1552
1627
|
style: styles.pushRight,
|
|
1553
1628
|
"aria-expanded": cardOpen,
|
|
1554
1629
|
"aria-controls": `dsh-subscription-card-${status.provider}`,
|
|
1555
|
-
onClick:
|
|
1630
|
+
onClick: (event) => {
|
|
1631
|
+
event.stopPropagation();
|
|
1632
|
+
toggleCardOpen();
|
|
1633
|
+
},
|
|
1556
1634
|
children: cardOpen ? t("collapse") : t("edit")
|
|
1557
1635
|
})
|
|
1558
1636
|
]
|
|
@@ -1601,11 +1679,11 @@ function ProviderCard(props) {
|
|
|
1601
1679
|
children: account.label
|
|
1602
1680
|
}),
|
|
1603
1681
|
account.isDefault && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1604
|
-
style: styles.
|
|
1682
|
+
style: styles.badgeDefault,
|
|
1605
1683
|
children: t("defaultBadge")
|
|
1606
1684
|
}),
|
|
1607
1685
|
account.needsLogin === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
1608
|
-
style: styles.
|
|
1686
|
+
style: styles.badgeWarn,
|
|
1609
1687
|
title: t("accountNeedsLoginHint"),
|
|
1610
1688
|
children: t("accountNeedsLogin")
|
|
1611
1689
|
}),
|
|
@@ -2079,6 +2157,20 @@ function SubscriptionsSection(props) {
|
|
|
2079
2157
|
startPolling,
|
|
2080
2158
|
stopPolling
|
|
2081
2159
|
]);
|
|
2160
|
+
/**
|
|
2161
|
+
* Signed-in providers read first: a route with a live account is the one a
|
|
2162
|
+
* reader most likely wants, and `auto` (which owns no account) leads the rest.
|
|
2163
|
+
* The sort is stable, so equal ranks keep the host's configured order.
|
|
2164
|
+
*/
|
|
2165
|
+
const orderedProviders = (0, react.useMemo)(() => {
|
|
2166
|
+
const providers = status?.providers ?? [];
|
|
2167
|
+
const rank = (provider) => {
|
|
2168
|
+
if (provider.provider === "auto") return 0;
|
|
2169
|
+
if (provider.loggedIn) return 1;
|
|
2170
|
+
return 2;
|
|
2171
|
+
};
|
|
2172
|
+
return [...providers].sort((left, right) => rank(left) - rank(right));
|
|
2173
|
+
}, [status?.providers]);
|
|
2082
2174
|
/** Run one action for one provider, reporting its own failure. */
|
|
2083
2175
|
const act = (0, react.useCallback)(async (provider, run) => {
|
|
2084
2176
|
setBusyAction(true);
|
|
@@ -2248,9 +2340,15 @@ function SubscriptionsSection(props) {
|
|
|
2248
2340
|
style: styles.section,
|
|
2249
2341
|
children: [
|
|
2250
2342
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: subscriptionsCss }),
|
|
2251
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
2252
|
-
style: styles.
|
|
2253
|
-
children:
|
|
2343
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2344
|
+
style: styles.sectionHeader,
|
|
2345
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
2346
|
+
style: styles.heading,
|
|
2347
|
+
children: t("nav")
|
|
2348
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2349
|
+
style: styles.intro,
|
|
2350
|
+
children: t("intro")
|
|
2351
|
+
})]
|
|
2254
2352
|
}),
|
|
2255
2353
|
error !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2256
2354
|
className: "dsh-subscription-error",
|
|
@@ -2260,7 +2358,7 @@ function SubscriptionsSection(props) {
|
|
|
2260
2358
|
style: styles.statusLine,
|
|
2261
2359
|
children: t("loading")
|
|
2262
2360
|
}),
|
|
2263
|
-
|
|
2361
|
+
orderedProviders.map((provider) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProviderCard, {
|
|
2264
2362
|
status: provider,
|
|
2265
2363
|
t,
|
|
2266
2364
|
busyAction,
|