dsh-subscriptions 0.1.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/LICENSE +21 -0
- package/README.md +129 -0
- package/cordis.patch.yml +12 -0
- package/lib/auth/claude-code-creds.d.ts +23 -0
- package/lib/auth/claude-code-creds.js +172 -0
- package/lib/auth/device-flow.d.ts +55 -0
- package/lib/auth/device-flow.js +177 -0
- package/lib/auth/jwt.d.ts +10 -0
- package/lib/auth/jwt.js +25 -0
- package/lib/auth/oauth-flow.d.ts +91 -0
- package/lib/auth/oauth-flow.js +227 -0
- package/lib/auth/pkce.d.ts +31 -0
- package/lib/auth/pkce.js +35 -0
- package/lib/auth/rpc.d.ts +209 -0
- package/lib/auth/rpc.js +386 -0
- package/lib/auth/store.d.ts +196 -0
- package/lib/auth/store.js +468 -0
- package/lib/client/CursorCard.d.ts +9 -0
- package/lib/client/CursorCard.js +113 -0
- package/lib/client/ExternalUsageCards.d.ts +9 -0
- package/lib/client/ExternalUsageCards.js +72 -0
- package/lib/client/ImageGallery.d.ts +54 -0
- package/lib/client/ImageGallery.js +112 -0
- package/lib/client/ImageGenerateToolview.d.ts +48 -0
- package/lib/client/ImageGenerateToolview.js +133 -0
- package/lib/client/ProviderAccountManager.d.ts +13 -0
- package/lib/client/ProviderAccountManager.js +147 -0
- package/lib/client/ProviderModelEditor.d.ts +39 -0
- package/lib/client/ProviderModelEditor.js +107 -0
- package/lib/client/SpeedSelect.d.ts +71 -0
- package/lib/client/SpeedSelect.js +177 -0
- package/lib/client/SubscriptionUsageBadge.d.ts +116 -0
- package/lib/client/SubscriptionUsageBadge.js +543 -0
- package/lib/client/SubscriptionsSection.d.ts +140 -0
- package/lib/client/SubscriptionsSection.js +544 -0
- package/lib/client/UsageBadgeDisplaySetting.d.ts +7 -0
- package/lib/client/UsageBadgeDisplaySetting.js +36 -0
- package/lib/client/VideoGenerateToolview.d.ts +42 -0
- package/lib/client/VideoGenerateToolview.js +175 -0
- package/lib/client/account-preferences.d.ts +24 -0
- package/lib/client/account-preferences.js +21 -0
- package/lib/client/fast-command.d.ts +2 -0
- package/lib/client/fast-command.js +4 -0
- package/lib/client/host-contract.d.ts +90 -0
- package/lib/client/host-contract.js +101 -0
- package/lib/client/host-icons.d.ts +24 -0
- package/lib/client/host-icons.js +21 -0
- package/lib/client/index.d.ts +27 -0
- package/lib/client/index.js +136 -0
- package/lib/client/locales.d.ts +297 -0
- package/lib/client/locales.js +295 -0
- package/lib/client/subscription-card-styles.d.ts +3 -0
- package/lib/client/subscription-card-styles.js +53 -0
- package/lib/client/subscriptions-rpc.d.ts +14 -0
- package/lib/client/subscriptions-rpc.js +34 -0
- package/lib/client/usage-badge-preferences.d.ts +11 -0
- package/lib/client/usage-badge-preferences.js +41 -0
- package/lib/client.js +4472 -0
- package/lib/client.js.map +1 -0
- package/lib/compat.d.ts +36 -0
- package/lib/compat.js +20 -0
- package/lib/http.d.ts +6 -0
- package/lib/http.js +15 -0
- package/lib/index.d.ts +80 -0
- package/lib/index.js +14018 -0
- package/lib/model-defaults.d.ts +23 -0
- package/lib/model-defaults.js +237 -0
- package/lib/provider-settings.d.ts +39 -0
- package/lib/provider-settings.js +151 -0
- package/lib/providers/account-preferences.d.ts +41 -0
- package/lib/providers/account-preferences.js +153 -0
- package/lib/providers/accounts.d.ts +112 -0
- package/lib/providers/accounts.js +142 -0
- package/lib/providers/antigravity-oauth-client.d.ts +7 -0
- package/lib/providers/antigravity-oauth-client.js +7 -0
- package/lib/providers/antigravity.d.ts +110 -0
- package/lib/providers/antigravity.js +516 -0
- package/lib/providers/catalog-store.d.ts +48 -0
- package/lib/providers/catalog-store.js +243 -0
- package/lib/providers/claude-cli-version.d.ts +15 -0
- package/lib/providers/claude-cli-version.js +24 -0
- package/lib/providers/claude.d.ts +181 -0
- package/lib/providers/claude.js +680 -0
- package/lib/providers/codex-client-version.d.ts +8 -0
- package/lib/providers/codex-client-version.js +17 -0
- package/lib/providers/codex-search.d.ts +29 -0
- package/lib/providers/codex-search.js +198 -0
- package/lib/providers/codex.d.ts +229 -0
- package/lib/providers/codex.js +821 -0
- package/lib/providers/common.d.ts +389 -0
- package/lib/providers/common.js +656 -0
- package/lib/providers/copilot.d.ts +344 -0
- package/lib/providers/copilot.js +888 -0
- package/lib/providers/cursor-adapter.d.ts +15 -0
- package/lib/providers/cursor-adapter.js +39 -0
- package/lib/providers/cursor-auth.d.ts +43 -0
- package/lib/providers/cursor-auth.js +169 -0
- package/lib/providers/cursor-model-settings.d.ts +15 -0
- package/lib/providers/cursor-model-settings.js +51 -0
- package/lib/providers/cursor-usage.d.ts +9 -0
- package/lib/providers/cursor-usage.js +112 -0
- package/lib/providers/external-usage-controller.d.ts +19 -0
- package/lib/providers/external-usage-controller.js +32 -0
- package/lib/providers/external-usage.d.ts +8 -0
- package/lib/providers/external-usage.js +82 -0
- package/lib/providers/grok.d.ts +205 -0
- package/lib/providers/grok.js +713 -0
- package/lib/providers/image-pool.d.ts +30 -0
- package/lib/providers/image-pool.js +89 -0
- package/lib/providers/npm-cli-version.d.ts +56 -0
- package/lib/providers/npm-cli-version.js +98 -0
- package/lib/providers/pool-family.d.ts +56 -0
- package/lib/providers/pool-family.js +45 -0
- package/lib/providers/pool-health.d.ts +74 -0
- package/lib/providers/pool-health.js +150 -0
- package/lib/providers/pool-usage.d.ts +86 -0
- package/lib/providers/pool-usage.js +223 -0
- package/lib/providers/pool.d.ts +108 -0
- package/lib/providers/pool.js +368 -0
- package/lib/providers/rate-limit.d.ts +192 -0
- package/lib/providers/rate-limit.js +338 -0
- package/lib/tools/image-generate.d.ts +128 -0
- package/lib/tools/image-generate.js +438 -0
- package/lib/tools/registration.d.ts +15 -0
- package/lib/tools/registration.js +24 -0
- package/lib/tools/video-generate.d.ts +89 -0
- package/lib/tools/video-generate.js +257 -0
- package/lib/tools/x-search.d.ts +58 -0
- package/lib/tools/x-search.js +197 -0
- package/lib/translate/anthropic.d.ts +161 -0
- package/lib/translate/anthropic.js +515 -0
- package/lib/translate/antigravity-schema.d.ts +4 -0
- package/lib/translate/antigravity-schema.js +145 -0
- package/lib/translate/antigravity-thinking.d.ts +5 -0
- package/lib/translate/antigravity-thinking.js +44 -0
- package/lib/translate/antigravity.d.ts +114 -0
- package/lib/translate/antigravity.js +366 -0
- package/lib/translate/chat-completions.d.ts +120 -0
- package/lib/translate/chat-completions.js +382 -0
- package/lib/translate/resolved.d.ts +80 -0
- package/lib/translate/resolved.js +118 -0
- package/lib/translate/responses.d.ts +179 -0
- package/lib/translate/responses.js +408 -0
- package/lib/translate/sse.d.ts +21 -0
- package/lib/translate/sse.js +56 -0
- package/package.json +114 -0
- package/vendor/cursor/LICENSE +21 -0
- package/vendor/cursor/index.d.ts +18 -0
- package/vendor/cursor/index.js +3574 -0
- package/vendor/cursor/proto.js +254 -0
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Subscriptions settings section: one card per subscription provider with an
|
|
4
|
+
* OAuth login/logout flow driven by the node half's `/subscriptions-auth` RPC
|
|
5
|
+
* channel. Login state lives server-side; the page polls `status` only while
|
|
6
|
+
* a login attempt is busy, so an idle page never polls. All state is local
|
|
7
|
+
* React state — the page has no store.
|
|
8
|
+
*
|
|
9
|
+
* Every color resolves through a `--dsw-alias-*` design token (the ui-theme
|
|
10
|
+
* design-platform.css values flip under `body[data-ds-dark-theme]`), and
|
|
11
|
+
* every user-visible string goes through the locale-bound `t` of the
|
|
12
|
+
* 'settings.subscriptions' namespace. Buttons and inputs take the
|
|
13
|
+
* ModelsSection vocabulary minus hover rules, which inline styles cannot
|
|
14
|
+
* express.
|
|
15
|
+
*/
|
|
16
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
17
|
+
import { en } from './locales.js';
|
|
18
|
+
import { ProviderAccountManager } from './ProviderAccountManager.js';
|
|
19
|
+
import { ExternalUsageCards } from './ExternalUsageCards.js';
|
|
20
|
+
import { CursorCard } from './CursorCard.js';
|
|
21
|
+
import { UsageBadgeDisplaySetting } from './UsageBadgeDisplaySetting.js';
|
|
22
|
+
import { USAGE_BADGE_REFRESH_EVENT } from './usage-badge-preferences.js';
|
|
23
|
+
import { subscriptionCardStyles as cardStyles } from './subscription-card-styles.js';
|
|
24
|
+
import { callSubscriptionsAuth, SubscriptionsAuthError } from './subscriptions-rpc.js';
|
|
25
|
+
export { callSubscriptionsAuth } from './subscriptions-rpc.js';
|
|
26
|
+
/** Poll cadence while a provider login attempt is busy. */
|
|
27
|
+
const POLL_INTERVAL_MS = 2000;
|
|
28
|
+
/**
|
|
29
|
+
* Model count above which the expanded default-effort list also offers a name
|
|
30
|
+
* filter; below it the list is short enough to scan.
|
|
31
|
+
*/
|
|
32
|
+
const MODEL_FILTER_THRESHOLD = 8;
|
|
33
|
+
const CLIENT_VERSION_SOURCES = {
|
|
34
|
+
npm: 'clientVersionNpm',
|
|
35
|
+
local: 'clientVersionLocal',
|
|
36
|
+
fallback: 'clientVersionFallback',
|
|
37
|
+
config: 'clientVersionConfig',
|
|
38
|
+
};
|
|
39
|
+
/** Card display metadata, in page order (names are brand names, not translated). */
|
|
40
|
+
const PROVIDERS = [
|
|
41
|
+
{ id: 'codex', name: 'Codex (ChatGPT)' },
|
|
42
|
+
{ id: 'claude', name: 'Claude' },
|
|
43
|
+
{ id: 'grok', name: 'Grok (X Premium)' },
|
|
44
|
+
{ id: 'copilot', name: 'GitHub Copilot' },
|
|
45
|
+
{ id: 'antigravity', name: 'Google Antigravity' },
|
|
46
|
+
];
|
|
47
|
+
/** Human text of an action failure, SubscriptionsAuthError or not. */
|
|
48
|
+
function messageOf(error) {
|
|
49
|
+
return error instanceof Error ? error.message : String(error);
|
|
50
|
+
}
|
|
51
|
+
/** Copy a keyed map without the entries whose key is not in `live`. */
|
|
52
|
+
function dropStale(map, live) {
|
|
53
|
+
const stale = Object.keys(map).filter(key => !live.has(key));
|
|
54
|
+
if (stale.length === 0)
|
|
55
|
+
return map;
|
|
56
|
+
const next = { ...map };
|
|
57
|
+
for (const key of stale)
|
|
58
|
+
delete next[key];
|
|
59
|
+
return next;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* English-dictionary fallback for a missing inject `t` (standalone renders);
|
|
63
|
+
* the slot inject always supplies the locale-bound one.
|
|
64
|
+
* @param key - dictionary key.
|
|
65
|
+
* @param params - `{name}` template params.
|
|
66
|
+
* @returns the template with params substituted.
|
|
67
|
+
*/
|
|
68
|
+
function fallbackTranslate(key, params) {
|
|
69
|
+
let text = en[key];
|
|
70
|
+
for (const [name, value] of Object.entries(params ?? {})) {
|
|
71
|
+
text = text.replaceAll(`{${name}}`, String(value));
|
|
72
|
+
}
|
|
73
|
+
return text;
|
|
74
|
+
}
|
|
75
|
+
const styles = {
|
|
76
|
+
section: {
|
|
77
|
+
display: 'flex', flexDirection: 'column', gap: 12, maxWidth: 560,
|
|
78
|
+
color: 'var(--dsw-alias-label-primary)',
|
|
79
|
+
},
|
|
80
|
+
intro: { margin: 0, color: 'var(--dsw-alias-label-tertiary)', fontSize: 14, lineHeight: '22px' },
|
|
81
|
+
card: cardStyles.card,
|
|
82
|
+
cardHeader: cardStyles.header,
|
|
83
|
+
dot: cardStyles.dot,
|
|
84
|
+
name: cardStyles.name,
|
|
85
|
+
statusLine: cardStyles.status,
|
|
86
|
+
clientVersion: { fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)', fontVariantNumeric: 'tabular-nums' },
|
|
87
|
+
errorLine: cardStyles.error,
|
|
88
|
+
actions: cardStyles.actions,
|
|
89
|
+
button: cardStyles.button,
|
|
90
|
+
usage: cardStyles.usage,
|
|
91
|
+
usageHeader: cardStyles.usageHeader,
|
|
92
|
+
usageTitle: cardStyles.usageTitle,
|
|
93
|
+
usagePlan: cardStyles.usagePlan,
|
|
94
|
+
usageRefresh: cardStyles.usageRefresh,
|
|
95
|
+
usageRow: cardStyles.usageRow,
|
|
96
|
+
usageMeta: cardStyles.usageMeta,
|
|
97
|
+
accountRow: cardStyles.account,
|
|
98
|
+
accountHeader: cardStyles.accountHeader,
|
|
99
|
+
accountName: cardStyles.accountName,
|
|
100
|
+
starButton: {
|
|
101
|
+
border: 'none', background: 'transparent', padding: 0,
|
|
102
|
+
font: 'inherit', fontSize: 14, lineHeight: '20px', cursor: 'pointer',
|
|
103
|
+
color: 'var(--dsw-alias-state-warn-label)',
|
|
104
|
+
},
|
|
105
|
+
usageTrack: cardStyles.usageTrack,
|
|
106
|
+
usageFill: cardStyles.usageFill,
|
|
107
|
+
deviceCode: {
|
|
108
|
+
marginTop: 4, display: 'flex', flexDirection: 'column', gap: 6,
|
|
109
|
+
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
110
|
+
padding: '10px 12px', background: 'var(--dsw-alias-bg-layer-1)',
|
|
111
|
+
},
|
|
112
|
+
deviceCodeText: {
|
|
113
|
+
fontFamily: 'monospace', fontSize: 18, lineHeight: '24px', letterSpacing: 2,
|
|
114
|
+
color: 'var(--dsw-alias-label-primary)', userSelect: 'all',
|
|
115
|
+
},
|
|
116
|
+
modalOverlay: {
|
|
117
|
+
position: 'fixed', inset: 0, zIndex: 1000,
|
|
118
|
+
display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 16,
|
|
119
|
+
background: 'rgba(0, 0, 0, 0.45)',
|
|
120
|
+
},
|
|
121
|
+
modal: {
|
|
122
|
+
width: 460, maxWidth: '100%', maxHeight: '90vh', overflowY: 'auto',
|
|
123
|
+
boxSizing: 'border-box', display: 'flex', flexDirection: 'column', gap: 12,
|
|
124
|
+
padding: '16px 18px', borderRadius: 12,
|
|
125
|
+
background: 'var(--dsw-alias-bg-layer-1)', border: '1px solid var(--dsw-alias-border-l2)',
|
|
126
|
+
},
|
|
127
|
+
modalHeader: { display: 'flex', alignItems: 'center', gap: 8 },
|
|
128
|
+
modalTitle: { fontWeight: 600, fontSize: 15, lineHeight: '22px', color: 'var(--dsw-alias-label-primary)' },
|
|
129
|
+
};
|
|
130
|
+
/** Status dot color for one provider state. */
|
|
131
|
+
function dotColor(status) {
|
|
132
|
+
if (status?.busy === true)
|
|
133
|
+
return 'var(--dsw-alias-state-warn-label)';
|
|
134
|
+
if ((status?.accounts.length ?? 0) > 0)
|
|
135
|
+
return 'var(--dsw-alias-state-success-primary)';
|
|
136
|
+
return 'var(--dsw-alias-label-dimmed)';
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Whether a provider has at least one connected account. Multi-account made
|
|
140
|
+
* "logged in" a property of the account list rather than a flag, so every
|
|
141
|
+
* logged-in test goes through this one predicate.
|
|
142
|
+
* @param status - the provider's last reported state, possibly absent.
|
|
143
|
+
* @returns true when the provider serves at least one account.
|
|
144
|
+
*/
|
|
145
|
+
function hasAccount(status) {
|
|
146
|
+
return (status?.accounts.length ?? 0) > 0;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* One-line status text for one provider state.
|
|
150
|
+
* @param t - section translate.
|
|
151
|
+
* @param status - the provider's last reported state.
|
|
152
|
+
* @returns the localized status line.
|
|
153
|
+
*/
|
|
154
|
+
function statusText(t, status) {
|
|
155
|
+
if (status === undefined)
|
|
156
|
+
return t('checking');
|
|
157
|
+
if (status.busy)
|
|
158
|
+
return t('loginInProgress');
|
|
159
|
+
if (status.accounts.length > 0)
|
|
160
|
+
return t('loggedInCount', { count: status.accounts.length });
|
|
161
|
+
return t('notLoggedIn');
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The CLI version a route presents, beside the provider name; nothing for
|
|
165
|
+
* routes without one. The source word (npm / local / built-in / configured)
|
|
166
|
+
* is the fix for upstream issue #108: a failed npm lookup must read as a
|
|
167
|
+
* fallback, never as a plan limit.
|
|
168
|
+
*/
|
|
169
|
+
export function clientVersionText(t, clientVersion) {
|
|
170
|
+
if (clientVersion === undefined)
|
|
171
|
+
return '';
|
|
172
|
+
return t('clientVersion', { version: clientVersion.version, source: t(CLIENT_VERSION_SOURCES[clientVersion.source]) });
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Localized label of one usage window (kind, plus the model scope when named).
|
|
176
|
+
* @param t - section translate.
|
|
177
|
+
* @param window - the reported window.
|
|
178
|
+
* @returns e.g. "5-hour window" or "Weekly · Opus".
|
|
179
|
+
*/
|
|
180
|
+
function usageWindowLabel(t, window) {
|
|
181
|
+
const base = window.kind === 'session'
|
|
182
|
+
? t('usageSession')
|
|
183
|
+
: window.kind === 'weekly' ? t('usageWeekly') : t('usageWindow');
|
|
184
|
+
return window.scope !== undefined && window.scope !== '' ? `${base} · ${window.scope}` : base;
|
|
185
|
+
}
|
|
186
|
+
/** Bar fill color: success normally, warn from 80%, error from 95%. Shared with the composer badge. */
|
|
187
|
+
export function usageBarColor(usedPercent) {
|
|
188
|
+
if (usedPercent >= 95)
|
|
189
|
+
return 'var(--dsw-alias-state-error-primary)';
|
|
190
|
+
if (usedPercent >= 80)
|
|
191
|
+
return 'var(--dsw-alias-state-warn-label)';
|
|
192
|
+
return 'var(--dsw-alias-state-success-primary)';
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Derive one provider's default-effort section from its catalog and filter.
|
|
196
|
+
* Pure so the collapsed-header counts and the filter stay testable without a
|
|
197
|
+
* DOM: rows come only from models that advertise levels, the count of the rest
|
|
198
|
+
* rides as one line, and the filter matches display name or model id.
|
|
199
|
+
* @param models - the provider's catalog models, or undefined while loading.
|
|
200
|
+
* @param filter - the raw filter input (trimmed and lowercased here).
|
|
201
|
+
* @returns the section's rows and header counts.
|
|
202
|
+
*/
|
|
203
|
+
export function deriveModelDefaultsView(models, filter) {
|
|
204
|
+
const all = models ?? [];
|
|
205
|
+
const withEfforts = all.filter(model => model.efforts.length > 0);
|
|
206
|
+
const query = filter.trim().toLowerCase();
|
|
207
|
+
const shown = query === ''
|
|
208
|
+
? withEfforts
|
|
209
|
+
: withEfforts.filter(model => model.name.toLowerCase().includes(query)
|
|
210
|
+
|| model.id.toLowerCase().includes(query));
|
|
211
|
+
return {
|
|
212
|
+
shown,
|
|
213
|
+
total: withEfforts.length,
|
|
214
|
+
overridden: withEfforts.filter(model => model.configured !== undefined).length,
|
|
215
|
+
withoutEfforts: all.length - withEfforts.length,
|
|
216
|
+
showFilter: withEfforts.length > MODEL_FILTER_THRESHOLD,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Whether the default-effort catalog needs (re)fetching.
|
|
221
|
+
*
|
|
222
|
+
* Fetching is gated on an *attempt* signature rather than on the payload
|
|
223
|
+
* being empty: an empty answer is a legitimate result (a narrowed
|
|
224
|
+
* `config.providers`, or a catalog that is momentarily unavailable), and
|
|
225
|
+
* treating it as "not loaded yet" re-ran this effect forever. The signature
|
|
226
|
+
* also covers the accounts, so logging a second provider in refetches
|
|
227
|
+
* instead of leaving that card on the previous answer.
|
|
228
|
+
* @param input - the decision inputs.
|
|
229
|
+
* @returns true when the caller should start a fetch.
|
|
230
|
+
*/
|
|
231
|
+
export function shouldFetchModelDefaults(input) {
|
|
232
|
+
if (input.failed)
|
|
233
|
+
return false;
|
|
234
|
+
if (input.loggedIn.length === 0)
|
|
235
|
+
return false;
|
|
236
|
+
// Only an open disclosure pays for the per-model live resolve.
|
|
237
|
+
if (!input.open.some(provider => input.loggedIn.includes(provider)))
|
|
238
|
+
return false;
|
|
239
|
+
return input.loadedFor !== input.signature;
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Stable signature of the accounts a catalog answer depends on. A change
|
|
243
|
+
* means a previous answer is stale (an account arrived or left), so the next
|
|
244
|
+
* open disclosure refetches.
|
|
245
|
+
* @param statuses - the per-provider status snapshot.
|
|
246
|
+
* @returns a signature string, stable across renders with equal accounts.
|
|
247
|
+
*/
|
|
248
|
+
export function modelDefaultsSignature(statuses) {
|
|
249
|
+
return PROVIDERS
|
|
250
|
+
.map(({ id }) => `${id}:${(statuses[id]?.accounts ?? []).map(account => account.key).sort().join(',')}`)
|
|
251
|
+
.join('|');
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* The Subscriptions settings page component.
|
|
255
|
+
* @param props - the slot inject face ({@link SubscriptionsSectionInjected}).
|
|
256
|
+
* @returns the section body, or a notice while the RPC face is absent.
|
|
257
|
+
*/
|
|
258
|
+
export function SubscriptionsSection(props) {
|
|
259
|
+
const { rpc } = props;
|
|
260
|
+
const t = props.t ?? fallbackTranslate;
|
|
261
|
+
const [statuses, setStatuses] = useState({});
|
|
262
|
+
const [errors, setErrors] = useState({});
|
|
263
|
+
const [manualDrafts, setManualDrafts] = useState({
|
|
264
|
+
codex: '', claude: '', grok: '', copilot: '', antigravity: '',
|
|
265
|
+
});
|
|
266
|
+
/** Pending device-flow codes (copilot), shown while the attempt polls. */
|
|
267
|
+
const [deviceCodes, setDeviceCodes] = useState({});
|
|
268
|
+
const [copiedCode, setCopiedCode] = useState(undefined);
|
|
269
|
+
/** Usage snapshots keyed `${provider}:${accountKey}` — every account tracks its own windows. */
|
|
270
|
+
const [usages, setUsages] = useState({});
|
|
271
|
+
const [usageErrors, setUsageErrors] = useState({});
|
|
272
|
+
const [usageLoading, setUsageLoading] = useState({});
|
|
273
|
+
const mountedRef = useRef(true);
|
|
274
|
+
const pollersRef = useRef(new Map());
|
|
275
|
+
/** Accounts with a `usage` call in flight; guards the auto-fetch effect against re-entry. */
|
|
276
|
+
const usageInflightRef = useRef(new Set());
|
|
277
|
+
const usageRosterSignatureRef = useRef();
|
|
278
|
+
const [managedProvider, setManagedProvider] = useState();
|
|
279
|
+
const setProviderError = useCallback((provider, message) => {
|
|
280
|
+
if (!mountedRef.current)
|
|
281
|
+
return;
|
|
282
|
+
setErrors((prev) => {
|
|
283
|
+
const next = { ...prev };
|
|
284
|
+
if (message === undefined)
|
|
285
|
+
delete next[provider];
|
|
286
|
+
else
|
|
287
|
+
next[provider] = message;
|
|
288
|
+
return next;
|
|
289
|
+
});
|
|
290
|
+
}, []);
|
|
291
|
+
const stopPolling = useCallback((provider) => {
|
|
292
|
+
const poller = pollersRef.current.get(provider);
|
|
293
|
+
if (poller !== undefined) {
|
|
294
|
+
clearInterval(poller);
|
|
295
|
+
pollersRef.current.delete(provider);
|
|
296
|
+
}
|
|
297
|
+
}, []);
|
|
298
|
+
/** Refetch every provider's status; stop a provider's poller once its attempt settles. */
|
|
299
|
+
const refresh = useCallback(async () => {
|
|
300
|
+
if (rpc === undefined)
|
|
301
|
+
return;
|
|
302
|
+
let response;
|
|
303
|
+
try {
|
|
304
|
+
response = await callSubscriptionsAuth(rpc, 'status', {});
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
// A failed poll must not kill the page; busy providers keep polling and
|
|
308
|
+
// the action paths report their own errors. But staying silent turns a
|
|
309
|
+
// persistent failure into an endless "Checking…" — show it instead.
|
|
310
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
311
|
+
for (const { id } of PROVIDERS)
|
|
312
|
+
setProviderError(id, message);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (!mountedRef.current)
|
|
316
|
+
return;
|
|
317
|
+
setStatuses(response.providers);
|
|
318
|
+
const signature = PROVIDERS.map(({ id }) => `${id}:${response.providers[id].accounts
|
|
319
|
+
.map(account => `${account.key}:${account.isDefault}`).join(',')}`).join(';');
|
|
320
|
+
if (signature !== usageRosterSignatureRef.current) {
|
|
321
|
+
usageRosterSignatureRef.current = signature;
|
|
322
|
+
window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
|
|
323
|
+
}
|
|
324
|
+
// The poll recovered: drop any error line a previous failed poll left.
|
|
325
|
+
for (const { id } of PROVIDERS)
|
|
326
|
+
setProviderError(id, undefined);
|
|
327
|
+
for (const { id } of PROVIDERS) {
|
|
328
|
+
const status = response.providers[id];
|
|
329
|
+
if (status.accounts.length > 0 || !status.busy) {
|
|
330
|
+
stopPolling(id);
|
|
331
|
+
// The attempt settled (success, timeout, or cancel): drop the code card.
|
|
332
|
+
setDeviceCodes((prev) => {
|
|
333
|
+
if (prev[id] === undefined)
|
|
334
|
+
return prev;
|
|
335
|
+
const next = { ...prev };
|
|
336
|
+
delete next[id];
|
|
337
|
+
return next;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}, [rpc, stopPolling, setProviderError]);
|
|
342
|
+
const startPolling = useCallback((provider) => {
|
|
343
|
+
if (pollersRef.current.has(provider))
|
|
344
|
+
return;
|
|
345
|
+
pollersRef.current.set(provider, setInterval(() => { void refresh(); }, POLL_INTERVAL_MS));
|
|
346
|
+
}, [refresh]);
|
|
347
|
+
// Initial load; every busy provider (e.g. an attempt started before a page
|
|
348
|
+
// reload) resumes polling. Teardown clears pollers and the mounted guard.
|
|
349
|
+
useEffect(() => {
|
|
350
|
+
mountedRef.current = true;
|
|
351
|
+
void refresh().then(() => {
|
|
352
|
+
if (!mountedRef.current)
|
|
353
|
+
return;
|
|
354
|
+
setStatuses((current) => {
|
|
355
|
+
for (const { id } of PROVIDERS) {
|
|
356
|
+
if (current[id]?.busy === true)
|
|
357
|
+
startPolling(id);
|
|
358
|
+
}
|
|
359
|
+
return current;
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
return () => {
|
|
363
|
+
mountedRef.current = false;
|
|
364
|
+
for (const poller of pollersRef.current.values())
|
|
365
|
+
clearInterval(poller);
|
|
366
|
+
pollersRef.current.clear();
|
|
367
|
+
};
|
|
368
|
+
}, [refresh, startPolling]);
|
|
369
|
+
const loadUsage = useCallback(async (provider, account, force = false) => {
|
|
370
|
+
const key = `${provider}:${account}`;
|
|
371
|
+
if (rpc === undefined || usageInflightRef.current.has(key))
|
|
372
|
+
return;
|
|
373
|
+
usageInflightRef.current.add(key);
|
|
374
|
+
setUsageLoading(prev => ({ ...prev, [key]: true }));
|
|
375
|
+
try {
|
|
376
|
+
const usage = await callSubscriptionsAuth(rpc, 'usage', { provider, account, ...force ? { force: true } : {} });
|
|
377
|
+
if (!mountedRef.current)
|
|
378
|
+
return;
|
|
379
|
+
setUsages(prev => ({ ...prev, [key]: usage }));
|
|
380
|
+
window.dispatchEvent(new Event(USAGE_BADGE_REFRESH_EVENT));
|
|
381
|
+
setUsageErrors((prev) => {
|
|
382
|
+
const next = { ...prev };
|
|
383
|
+
delete next[key];
|
|
384
|
+
return next;
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
if (mountedRef.current)
|
|
389
|
+
setUsageErrors(prev => ({ ...prev, [key]: messageOf(error) }));
|
|
390
|
+
}
|
|
391
|
+
finally {
|
|
392
|
+
usageInflightRef.current.delete(key);
|
|
393
|
+
if (mountedRef.current)
|
|
394
|
+
setUsageLoading(prev => ({ ...prev, [key]: false }));
|
|
395
|
+
}
|
|
396
|
+
}, [rpc]);
|
|
397
|
+
// Fetch usage once an account is logged in; drop the snapshots of accounts
|
|
398
|
+
// that vanished so a re-login refetches. A failed lookup does not auto-retry
|
|
399
|
+
// — the per-account Refresh button is the retry path.
|
|
400
|
+
useEffect(() => {
|
|
401
|
+
const live = new Set();
|
|
402
|
+
for (const { id } of PROVIDERS) {
|
|
403
|
+
for (const account of statuses[id]?.accounts ?? []) {
|
|
404
|
+
const key = `${id}:${account.key}`;
|
|
405
|
+
live.add(key);
|
|
406
|
+
if (usages[key] === undefined && usageErrors[key] === undefined)
|
|
407
|
+
void loadUsage(id, account.key);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
setUsages(prev => dropStale(prev, live));
|
|
411
|
+
setUsageErrors(prev => dropStale(prev, live));
|
|
412
|
+
}, [statuses, usages, usageErrors, loadUsage]);
|
|
413
|
+
const login = useCallback(async (provider, method) => {
|
|
414
|
+
if (rpc === undefined)
|
|
415
|
+
return;
|
|
416
|
+
setProviderError(provider, undefined);
|
|
417
|
+
try {
|
|
418
|
+
const response = await callSubscriptionsAuth(rpc, 'login', {
|
|
419
|
+
provider,
|
|
420
|
+
...method === undefined ? {} : { method },
|
|
421
|
+
});
|
|
422
|
+
if (typeof response.authorizeUrl === 'string' && response.authorizeUrl === '') {
|
|
423
|
+
// Instant login (e.g. imported from Claude Code credentials)
|
|
424
|
+
await refresh();
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
if (typeof response.authorizeUrl !== 'string') {
|
|
428
|
+
throw new SubscriptionsAuthError(t('loginMissingUrl'));
|
|
429
|
+
}
|
|
430
|
+
if (!mountedRef.current)
|
|
431
|
+
return;
|
|
432
|
+
// Optimistic busy so Cancel and the manual fallback appear before the first poll tick.
|
|
433
|
+
setStatuses(prev => ({
|
|
434
|
+
...prev,
|
|
435
|
+
[provider]: { accounts: prev[provider]?.accounts ?? [], ...prev[provider], busy: true },
|
|
436
|
+
}));
|
|
437
|
+
if (typeof response.userCode === 'string' && response.userCode.length > 0) {
|
|
438
|
+
// Device flow: show the code card instead of opening the page blind —
|
|
439
|
+
// the user copies the code first, then opens the verification page.
|
|
440
|
+
setDeviceCodes(prev => ({ ...prev, [provider]: { userCode: response.userCode, verificationUrl: response.authorizeUrl } }));
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
window.open(response.authorizeUrl, '_blank', 'noopener');
|
|
444
|
+
}
|
|
445
|
+
startPolling(provider);
|
|
446
|
+
}
|
|
447
|
+
catch (error) {
|
|
448
|
+
setProviderError(provider, messageOf(error));
|
|
449
|
+
}
|
|
450
|
+
}, [rpc, t, setProviderError, startPolling]);
|
|
451
|
+
const cancel = useCallback(async (provider) => {
|
|
452
|
+
if (rpc === undefined)
|
|
453
|
+
return;
|
|
454
|
+
stopPolling(provider);
|
|
455
|
+
try {
|
|
456
|
+
await callSubscriptionsAuth(rpc, 'cancel', { provider });
|
|
457
|
+
}
|
|
458
|
+
catch (error) {
|
|
459
|
+
setProviderError(provider, messageOf(error));
|
|
460
|
+
}
|
|
461
|
+
await refresh();
|
|
462
|
+
}, [rpc, stopPolling, setProviderError, refresh]);
|
|
463
|
+
const submitManual = useCallback(async (provider) => {
|
|
464
|
+
if (rpc === undefined)
|
|
465
|
+
return;
|
|
466
|
+
const input = manualDrafts[provider].trim();
|
|
467
|
+
if (input === '')
|
|
468
|
+
return;
|
|
469
|
+
setProviderError(provider, undefined);
|
|
470
|
+
try {
|
|
471
|
+
await callSubscriptionsAuth(rpc, 'manual', { provider, input });
|
|
472
|
+
if (mountedRef.current)
|
|
473
|
+
setManualDrafts(prev => ({ ...prev, [provider]: '' }));
|
|
474
|
+
}
|
|
475
|
+
catch (error) {
|
|
476
|
+
setProviderError(provider, messageOf(error));
|
|
477
|
+
}
|
|
478
|
+
await refresh();
|
|
479
|
+
}, [rpc, manualDrafts, setProviderError, refresh]);
|
|
480
|
+
const logout = useCallback(async (provider, account, display, name) => {
|
|
481
|
+
if (rpc === undefined)
|
|
482
|
+
return;
|
|
483
|
+
if (!window.confirm(t('logoutAccountConfirm', { provider: name, account: display })))
|
|
484
|
+
return;
|
|
485
|
+
setProviderError(provider, undefined);
|
|
486
|
+
try {
|
|
487
|
+
await callSubscriptionsAuth(rpc, 'logout', { provider, account });
|
|
488
|
+
}
|
|
489
|
+
catch (error) {
|
|
490
|
+
setProviderError(provider, messageOf(error));
|
|
491
|
+
}
|
|
492
|
+
await refresh();
|
|
493
|
+
}, [rpc, t, setProviderError, refresh]);
|
|
494
|
+
const setDefault = useCallback(async (provider, account) => {
|
|
495
|
+
if (rpc === undefined)
|
|
496
|
+
return;
|
|
497
|
+
setProviderError(provider, undefined);
|
|
498
|
+
try {
|
|
499
|
+
await callSubscriptionsAuth(rpc, 'setDefault', { provider, account });
|
|
500
|
+
}
|
|
501
|
+
catch (error) {
|
|
502
|
+
setProviderError(provider, messageOf(error));
|
|
503
|
+
}
|
|
504
|
+
await refresh();
|
|
505
|
+
}, [rpc, setProviderError, refresh]);
|
|
506
|
+
const copyDeviceCode = useCallback((provider, userCode) => {
|
|
507
|
+
void navigator.clipboard?.writeText(userCode).then(() => {
|
|
508
|
+
if (!mountedRef.current)
|
|
509
|
+
return;
|
|
510
|
+
setCopiedCode(provider);
|
|
511
|
+
setTimeout(() => {
|
|
512
|
+
if (mountedRef.current) {
|
|
513
|
+
setCopiedCode(current => current === provider ? undefined : current);
|
|
514
|
+
}
|
|
515
|
+
}, 1500);
|
|
516
|
+
}).catch(() => undefined);
|
|
517
|
+
}, []);
|
|
518
|
+
if (rpc === undefined) {
|
|
519
|
+
return _jsx("p", { style: styles.intro, children: t('unavailable') });
|
|
520
|
+
}
|
|
521
|
+
return (_jsxs("div", { style: styles.section, children: [_jsx("p", { style: styles.intro, children: t('intro') }), _jsx(UsageBadgeDisplaySetting, { t: t }), PROVIDERS.map(({ id, name }) => {
|
|
522
|
+
const status = statuses[id];
|
|
523
|
+
const busy = status?.busy === true;
|
|
524
|
+
const deviceCode = deviceCodes[id];
|
|
525
|
+
const accounts = status?.accounts ?? [];
|
|
526
|
+
return (_jsxs("div", { style: styles.card, children: [_jsxs("div", { style: styles.cardHeader, children: [_jsx("span", { style: { ...styles.dot, background: dotColor(status) } }), _jsx("span", { style: styles.name, children: name }), status?.clientVersion !== undefined && (_jsx("span", { style: styles.clientVersion, title: t('clientVersionHint'), children: clientVersionText(t, status.clientVersion) }))] }), _jsx("p", { style: styles.statusLine, children: statusText(t, status) }), status?.detail !== undefined && status.detail !== '' && (_jsx("p", { style: styles.statusLine, children: status.detail })), errors[id] !== undefined && _jsx("p", { style: styles.errorLine, children: errors[id] }), accounts.map((account) => {
|
|
527
|
+
const usageKey = `${id}:${account.key}`;
|
|
528
|
+
const usage = usages[usageKey];
|
|
529
|
+
const usageError = usageErrors[usageKey];
|
|
530
|
+
const display = account.account ?? account.key;
|
|
531
|
+
// Providers without a usage endpoint answer supported:false — no block.
|
|
532
|
+
const showUsage = usage?.supported !== false
|
|
533
|
+
&& (usage !== undefined || usageError !== undefined || usageLoading[usageKey] === true);
|
|
534
|
+
return (_jsxs("div", { style: styles.accountRow, children: [_jsxs("div", { style: styles.accountHeader, children: [_jsx("button", { type: "button", style: styles.starButton, title: account.isDefault ? t('defaultBadge') : t('setDefault'), onClick: () => {
|
|
535
|
+
if (!account.isDefault)
|
|
536
|
+
void setDefault(id, account.key);
|
|
537
|
+
}, children: account.isDefault ? '★' : '☆' }), _jsx("span", { style: styles.accountName, children: display }), account.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: account.plan })), account.expiresAt !== undefined && (_jsx("span", { style: styles.statusLine, children: t('accountExpires', { date: new Date(account.expiresAt).toLocaleString() }) })), _jsx("button", { type: "button", style: { ...styles.button, marginLeft: 'auto', flexShrink: 0 }, onClick: () => { void logout(id, account.key, display, name); }, children: t('logout') })] }), showUsage && (_jsxs("div", { style: styles.usage, children: [_jsxs("div", { style: styles.usageHeader, children: [_jsx("span", { style: styles.usageTitle, children: t('usageTitle') }), usage?.plan !== undefined && (_jsx("span", { style: styles.usagePlan, children: t('usagePlan', { plan: usage.plan }) })), _jsx("button", { type: "button", style: { ...styles.usageRefresh, ...usageLoading[usageKey] === true ? { opacity: 0.5, cursor: 'default' } : {} }, disabled: usageLoading[usageKey] === true, onClick: () => { void loadUsage(id, account.key, true); }, children: t('usageRefresh') })] }), usage === undefined && usageError === undefined && (_jsx("p", { style: styles.statusLine, children: t('usageLoading') })), usageError !== undefined && (_jsx("p", { style: styles.errorLine, children: t('usageError', { message: usageError }) })), usage?.windows !== undefined && usage.windows.length === 0 && (_jsx("p", { style: styles.statusLine, children: t('usageEmpty') })), (usage?.windows ?? []).map((window, index) => {
|
|
538
|
+
const percent = Math.min(100, Math.max(0, window.usedPercent));
|
|
539
|
+
return (_jsxs("div", { style: styles.usageRow, children: [_jsxs("div", { style: styles.usageMeta, children: [_jsx("span", { children: usageWindowLabel(t, window) }), _jsxs("span", { children: [`${String(Math.round(percent))}%`, window.resetsAt !== undefined
|
|
540
|
+
&& ` · ${t('usageResets', { date: new Date(window.resetsAt).toLocaleString() })}`] })] }), _jsx("div", { style: styles.usageTrack, children: _jsx("div", { style: { ...styles.usageFill, width: `${String(percent)}%`, background: usageBarColor(percent) } }) })] }, index));
|
|
541
|
+
})] }))] }, account.key));
|
|
542
|
+
}), _jsxs("div", { style: styles.actions, children: [!busy && accounts.length === 0 && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id); }, children: t('login') })), !busy && accounts.length > 0 && id === 'claude' && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id, 'oauth'); }, children: t('addAccountOAuth') }), _jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id, 'keychain'); }, children: t('addAccountKeychain') })] })), !busy && accounts.length > 0 && id !== 'claude' && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void login(id); }, children: t('addAccount') })), _jsx("button", { type: "button", style: styles.button, "aria-haspopup": "dialog", onClick: () => setManagedProvider({ id, name }), children: t('accountsManage') }), busy && (_jsx("button", { type: "button", style: styles.button, onClick: () => { void cancel(id); }, children: t('cancel') }))] }), !busy && accounts.length > 0 && (_jsx("p", { style: styles.statusLine, children: t('addAccountHint') })), busy && deviceCode !== undefined && (_jsxs("div", { style: styles.deviceCode, children: [_jsx("span", { style: styles.statusLine, children: t('deviceCodePrompt') }), _jsx("span", { style: styles.deviceCodeText, children: deviceCode.userCode }), _jsxs("div", { style: styles.actions, children: [_jsx("button", { type: "button", style: styles.button, onClick: () => { copyDeviceCode(id, deviceCode.userCode); }, children: copiedCode === id ? t('deviceCodeCopied') : t('deviceCodeCopy') }), _jsx("button", { type: "button", style: styles.button, onClick: () => { window.open(deviceCode.verificationUrl, '_blank', 'noopener'); }, children: t('deviceCodeOpenPage') })] })] })), busy && deviceCode === undefined && (_jsxs("details", { style: styles.manual, children: [_jsx("summary", { children: t('manualSummary') }), _jsxs("div", { style: styles.manualRow, children: [_jsx("input", { style: styles.manualInput, value: manualDrafts[id], placeholder: t('manualPlaceholder'), onChange: event => setManualDrafts(prev => ({ ...prev, [id]: event.target.value })) }), _jsx("button", { type: "button", style: styles.button, onClick: () => { void submitManual(id); }, children: t('submit') })] })] }))] }, id));
|
|
543
|
+
}), _jsx(CursorCard, { rpc: rpc, t: t }), _jsx(ExternalUsageCards, { rpc: rpc, t: t }), managedProvider && _jsx(ProviderAccountManager, { provider: managedProvider.id, name: managedProvider.name, rpc: rpc, t: t, onClose: () => setManagedProvider(undefined) })] }));
|
|
544
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SubscriptionsKey } from './locales.js';
|
|
2
|
+
type Translate = (key: SubscriptionsKey, params?: Record<string, unknown>) => string;
|
|
3
|
+
/** Browser-local display preference; never changes provider or account settings. */
|
|
4
|
+
export declare function UsageBadgeDisplaySetting({ t }: {
|
|
5
|
+
t: Translate;
|
|
6
|
+
}): import("react").JSX.Element;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { setUsageBadgeMode, useUsageBadgeMode } from './usage-badge-preferences.js';
|
|
4
|
+
/** Browser-local display preference; never changes provider or account settings. */
|
|
5
|
+
export function UsageBadgeDisplaySetting({ t }) {
|
|
6
|
+
const mode = useUsageBadgeMode();
|
|
7
|
+
const [failed, setFailed] = useState(false);
|
|
8
|
+
return (_jsxs("div", { style: styles.card, children: [_jsxs("label", { style: styles.field, children: [_jsx("span", { children: t('usageBadgeDisplay') }), _jsxs("select", { style: styles.select, "aria-label": t('usageBadgeDisplay'), value: mode, onChange: event => {
|
|
9
|
+
const value = event.currentTarget.value;
|
|
10
|
+
if (value !== 'recent' && value !== 'hidden')
|
|
11
|
+
return;
|
|
12
|
+
try {
|
|
13
|
+
setUsageBadgeMode(value);
|
|
14
|
+
setFailed(false);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
if (!(error instanceof DOMException) || !['SecurityError', 'QuotaExceededError'].includes(error.name))
|
|
18
|
+
throw error;
|
|
19
|
+
setFailed(true);
|
|
20
|
+
}
|
|
21
|
+
}, children: [_jsx("option", { value: "recent", children: t('usageBadgeDisplayRecent') }), _jsx("option", { value: "hidden", children: t('usageBadgeDisplayHidden') })] })] }), _jsx("p", { style: styles.hint, children: t('usageBadgeDisplayHint') }), failed && _jsx("p", { role: "alert", style: { ...styles.hint, color: 'var(--dsw-alias-state-error-primary)' }, children: t('usageBadgeDisplaySaveFailed') })] }));
|
|
22
|
+
}
|
|
23
|
+
const styles = {
|
|
24
|
+
card: {
|
|
25
|
+
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 12,
|
|
26
|
+
padding: '12px 14px', display: 'flex', flexDirection: 'column', gap: 6,
|
|
27
|
+
},
|
|
28
|
+
field: { display: 'flex', flexDirection: 'column', gap: 4, fontSize: 14, lineHeight: '22px' },
|
|
29
|
+
select: {
|
|
30
|
+
height: 32, width: '100%', boxSizing: 'border-box',
|
|
31
|
+
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
32
|
+
padding: '0 10px', font: 'inherit',
|
|
33
|
+
background: 'var(--dsw-alias-bg-layer-1)', color: 'var(--dsw-alias-label-primary)',
|
|
34
|
+
},
|
|
35
|
+
hint: { margin: 0, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)' },
|
|
36
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ConnectionHandle } from '@deepseek-ai/dsh-api-remotes/client';
|
|
2
|
+
import type { ToolCallBlock } from '@deepseek-ai/dsh-client-ui-conversation/client';
|
|
3
|
+
import type { SubscriptionsKey } from './locales.js';
|
|
4
|
+
/** Mirror of ui-tool's ToolCallOwnerProps (see ImageGenerateToolview). */
|
|
5
|
+
interface ToolCallOwnerProps {
|
|
6
|
+
callId: string;
|
|
7
|
+
toolName: string;
|
|
8
|
+
block: ToolCallBlock;
|
|
9
|
+
cwd?: string | undefined;
|
|
10
|
+
openFile: (path: string) => void;
|
|
11
|
+
inspect?: (() => void) | undefined;
|
|
12
|
+
}
|
|
13
|
+
/** Decoded video bytes as the node half's `video` endpoint answers them. */
|
|
14
|
+
export interface VideoBytes {
|
|
15
|
+
mediaType: string;
|
|
16
|
+
dataBase64: string;
|
|
17
|
+
}
|
|
18
|
+
/** Injected dependencies of {@link VideoGenerateToolview} (slot `inject`). */
|
|
19
|
+
export interface VideoGenerateToolviewInjected {
|
|
20
|
+
/** Loads one generated video's bytes by bare file name. */
|
|
21
|
+
loadVideo: (name: string) => Promise<VideoBytes>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Props delivered by the toolview outlet: the owner share plus the inject
|
|
25
|
+
* face and the framework locale seat, spread flat.
|
|
26
|
+
*/
|
|
27
|
+
export type VideoGenerateToolviewProps = Partial<ToolCallOwnerProps> & Partial<VideoGenerateToolviewInjected> & {
|
|
28
|
+
t?: ((key: SubscriptionsKey, params?: Record<string, unknown>) => string) | undefined;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Build the video loader over the `/subscriptions-auth` `video` endpoint.
|
|
32
|
+
* @param rpc - Connection RPC caller.
|
|
33
|
+
* @returns loader resolving a bare file name to the decoded bytes.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createVideoLoader(rpc: ConnectionHandle['rpc']): (name: string) => Promise<VideoBytes>;
|
|
36
|
+
/**
|
|
37
|
+
* The `video_generate` keyed toolview component.
|
|
38
|
+
* @param props - owner share, inject face, and locale seat (spread flat).
|
|
39
|
+
* @returns the call row plus, once settled, the player / text / error body.
|
|
40
|
+
*/
|
|
41
|
+
export declare function VideoGenerateToolview(props: VideoGenerateToolviewProps): import("react").JSX.Element | null;
|
|
42
|
+
export {};
|