dsh-plugin-subscriptions 0.5.2 → 0.6.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.md +79 -5
- package/README.zh.md +78 -4
- package/lib/auth/rpc.d.ts +64 -13
- package/lib/auth/rpc.js +75 -10
- package/lib/auth/store.d.ts +75 -17
- package/lib/auth/store.js +148 -27
- package/lib/client/ImageGenerateToolview.d.ts +1 -1
- package/lib/client/SpeedSelect.d.ts +25 -2
- package/lib/client/SpeedSelect.js +10 -6
- package/lib/client/SubscriptionsSection.d.ts +83 -3
- package/lib/client/SubscriptionsSection.js +411 -62
- package/lib/client/VideoGenerateToolview.d.ts +1 -1
- package/lib/client/index.d.ts +1 -9
- package/lib/client/index.js +7 -4
- package/lib/client/locales.d.ts +46 -10
- package/lib/client/locales.js +46 -10
- package/lib/client.js +703 -132
- package/lib/client.js.map +1 -1
- package/lib/compat.d.ts +36 -0
- package/lib/compat.js +20 -0
- package/lib/index.d.ts +26 -1
- package/lib/index.js +2377 -309
- package/lib/model-defaults.d.ts +23 -0
- package/lib/model-defaults.js +237 -0
- package/lib/providers/accounts.d.ts +102 -0
- package/lib/providers/accounts.js +123 -0
- package/lib/providers/claude.d.ts +46 -7
- package/lib/providers/claude.js +125 -34
- package/lib/providers/codex.d.ts +45 -3
- package/lib/providers/codex.js +152 -26
- package/lib/providers/common.d.ts +87 -6
- package/lib/providers/common.js +185 -22
- package/lib/providers/copilot.d.ts +32 -3
- package/lib/providers/copilot.js +111 -19
- package/lib/providers/grok.d.ts +45 -4
- package/lib/providers/grok.js +136 -20
- 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 +148 -0
- package/lib/providers/pool-usage.d.ts +78 -0
- package/lib/providers/pool-usage.js +185 -0
- package/lib/providers/pool.d.ts +107 -0
- package/lib/providers/pool.js +371 -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 +3 -3
- package/lib/tools/image-generate.js +2 -1
- package/lib/tools/video-generate.d.ts +2 -2
- package/lib/tools/video-generate.js +2 -1
- package/lib/tools/x-search.d.ts +2 -2
- package/lib/tools/x-search.js +2 -1
- package/lib/translate/anthropic.js +5 -4
- package/lib/translate/chat-completions.js +5 -4
- package/lib/translate/responses.js +5 -4
- package/package.json +21 -21
- package/lib/providers/antigravity.d.ts +0 -90
- package/lib/providers/antigravity.js +0 -392
- package/lib/translate/antigravity.d.ts +0 -110
- package/lib/translate/antigravity.js +0 -303
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Subscriptions settings section: one card per subscription provider with an
|
|
4
4
|
* OAuth login/logout flow driven by the node half's `/subscriptions-auth` RPC
|
|
@@ -19,6 +19,17 @@ import { en } from './locales.js';
|
|
|
19
19
|
const SUBSCRIPTIONS_AUTH_CHANNEL = '/subscriptions-auth';
|
|
20
20
|
/** Poll cadence while a provider login attempt is busy. */
|
|
21
21
|
const POLL_INTERVAL_MS = 2000;
|
|
22
|
+
/**
|
|
23
|
+
* Model count above which the expanded default-effort list also offers a name
|
|
24
|
+
* filter; below it the list is short enough to scan.
|
|
25
|
+
*/
|
|
26
|
+
const MODEL_FILTER_THRESHOLD = 8;
|
|
27
|
+
/**
|
|
28
|
+
* Height cap of the expanded default-effort list, in px. Past it the list
|
|
29
|
+
* scrolls internally so a provider with dozens of models cannot stretch the
|
|
30
|
+
* card (roughly 7 rows, which keeps the next card's header on screen).
|
|
31
|
+
*/
|
|
32
|
+
const MODEL_LIST_MAX_HEIGHT = 260;
|
|
22
33
|
/** Card display metadata, in page order (names are brand names, not translated). */
|
|
23
34
|
const PROVIDERS = [
|
|
24
35
|
{ id: 'codex', name: 'Codex (ChatGPT)' },
|
|
@@ -54,6 +65,16 @@ export async function callSubscriptionsAuth(rpc, endpoint, payload) {
|
|
|
54
65
|
function messageOf(error) {
|
|
55
66
|
return error instanceof Error ? error.message : String(error);
|
|
56
67
|
}
|
|
68
|
+
/** Copy a keyed map without the entries whose key is not in `live`. */
|
|
69
|
+
function dropStale(map, live) {
|
|
70
|
+
const stale = Object.keys(map).filter(key => !live.has(key));
|
|
71
|
+
if (stale.length === 0)
|
|
72
|
+
return map;
|
|
73
|
+
const next = { ...map };
|
|
74
|
+
for (const key of stale)
|
|
75
|
+
delete next[key];
|
|
76
|
+
return next;
|
|
77
|
+
}
|
|
57
78
|
/**
|
|
58
79
|
* English-dictionary fallback for a missing inject `t` (standalone renders);
|
|
59
80
|
* the slot inject always supplies the locale-bound one.
|
|
@@ -114,11 +135,63 @@ const styles = {
|
|
|
114
135
|
display: 'flex', justifyContent: 'space-between', gap: 8,
|
|
115
136
|
fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)',
|
|
116
137
|
},
|
|
138
|
+
accountRow: {
|
|
139
|
+
display: 'flex', flexDirection: 'column', gap: 6,
|
|
140
|
+
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
141
|
+
padding: '8px 10px', marginTop: 4,
|
|
142
|
+
},
|
|
143
|
+
accountHeader: { display: 'flex', alignItems: 'center', gap: 8 },
|
|
144
|
+
accountName: { fontSize: 13, lineHeight: '20px', color: 'var(--dsw-alias-label-primary)', userSelect: 'all' },
|
|
145
|
+
starButton: {
|
|
146
|
+
border: 'none', background: 'transparent', padding: 0,
|
|
147
|
+
font: 'inherit', fontSize: 14, lineHeight: '20px', cursor: 'pointer',
|
|
148
|
+
color: 'var(--dsw-alias-state-warn-label)',
|
|
149
|
+
},
|
|
117
150
|
usageTrack: {
|
|
118
151
|
height: 6, borderRadius: 3, overflow: 'hidden',
|
|
119
152
|
background: 'var(--dsw-alias-bg-layer-1)', border: '1px solid var(--dsw-alias-border-l2)',
|
|
120
153
|
},
|
|
121
154
|
usageFill: { height: '100%', borderRadius: 3 },
|
|
155
|
+
defaultEffort: {
|
|
156
|
+
display: 'flex', flexDirection: 'column', gap: 6, marginTop: 4,
|
|
157
|
+
borderTop: '1px solid var(--dsw-alias-border-l2)', paddingTop: 8,
|
|
158
|
+
},
|
|
159
|
+
/** The always-visible disclosure header: title, summary, chevron. */
|
|
160
|
+
defaultEffortToggle: {
|
|
161
|
+
boxSizing: 'border-box', display: 'flex', alignItems: 'center', gap: 8,
|
|
162
|
+
width: '100%', padding: 0, border: 'none', background: 'transparent',
|
|
163
|
+
font: 'inherit', textAlign: 'left', cursor: 'pointer',
|
|
164
|
+
},
|
|
165
|
+
defaultEffortChevron: {
|
|
166
|
+
marginLeft: 'auto', flexShrink: 0, fontSize: 10, lineHeight: '18px',
|
|
167
|
+
color: 'var(--dsw-alias-label-tertiary)',
|
|
168
|
+
},
|
|
169
|
+
/** Body of the expanded disclosure: bounded height so a long catalog scrolls. */
|
|
170
|
+
defaultEffortList: {
|
|
171
|
+
display: 'flex', flexDirection: 'column', gap: 6,
|
|
172
|
+
maxHeight: MODEL_LIST_MAX_HEIGHT, overflowY: 'auto', paddingRight: 2,
|
|
173
|
+
},
|
|
174
|
+
defaultEffortRow: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 8 },
|
|
175
|
+
defaultEffortName: {
|
|
176
|
+
fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-primary)',
|
|
177
|
+
overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap',
|
|
178
|
+
},
|
|
179
|
+
defaultEffortSaving: {
|
|
180
|
+
marginLeft: 'auto', flexShrink: 0,
|
|
181
|
+
fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-tertiary)',
|
|
182
|
+
},
|
|
183
|
+
defaultEffortSelect: {
|
|
184
|
+
maxWidth: 220, flexShrink: 0, height: 28, boxSizing: 'border-box',
|
|
185
|
+
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
186
|
+
padding: '0 8px', font: 'inherit', fontSize: 12, lineHeight: '18px',
|
|
187
|
+
background: 'var(--dsw-alias-bg-layer-1)', color: 'var(--dsw-alias-label-primary)',
|
|
188
|
+
},
|
|
189
|
+
defaultEffortFilter: {
|
|
190
|
+
height: 28, width: '100%', boxSizing: 'border-box',
|
|
191
|
+
border: '1px solid var(--dsw-alias-border-l2)', borderRadius: 8,
|
|
192
|
+
padding: '0 8px', font: 'inherit', fontSize: 12, lineHeight: '18px',
|
|
193
|
+
background: 'var(--dsw-alias-bg-layer-1)', color: 'var(--dsw-alias-label-primary)',
|
|
194
|
+
},
|
|
122
195
|
manual: { marginTop: 4, fontSize: 12, lineHeight: '18px', color: 'var(--dsw-alias-label-secondary)' },
|
|
123
196
|
manualRow: { display: 'flex', gap: 8, marginTop: 6 },
|
|
124
197
|
manualInput: {
|
|
@@ -171,10 +244,20 @@ const styles = {
|
|
|
171
244
|
function dotColor(status) {
|
|
172
245
|
if (status?.busy === true)
|
|
173
246
|
return 'var(--dsw-alias-state-warn-label)';
|
|
174
|
-
if (status?.
|
|
247
|
+
if ((status?.accounts.length ?? 0) > 0)
|
|
175
248
|
return 'var(--dsw-alias-state-success-primary)';
|
|
176
249
|
return 'var(--dsw-alias-label-dimmed)';
|
|
177
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Whether a provider has at least one connected account. Multi-account made
|
|
253
|
+
* "logged in" a property of the account list rather than a flag, so every
|
|
254
|
+
* logged-in test goes through this one predicate.
|
|
255
|
+
* @param status - the provider's last reported state, possibly absent.
|
|
256
|
+
* @returns true when the provider serves at least one account.
|
|
257
|
+
*/
|
|
258
|
+
function hasAccount(status) {
|
|
259
|
+
return (status?.accounts.length ?? 0) > 0;
|
|
260
|
+
}
|
|
178
261
|
/**
|
|
179
262
|
* One-line status text for one provider state.
|
|
180
263
|
* @param t - section translate.
|
|
@@ -186,20 +269,8 @@ function statusText(t, status) {
|
|
|
186
269
|
return t('checking');
|
|
187
270
|
if (status.busy)
|
|
188
271
|
return t('loginInProgress');
|
|
189
|
-
if (status.
|
|
190
|
-
|
|
191
|
-
if (status.account !== undefined)
|
|
192
|
-
params.account = status.account;
|
|
193
|
-
if (status.expiresAt !== undefined)
|
|
194
|
-
params.date = new Date(status.expiresAt).toLocaleString();
|
|
195
|
-
if (params.account !== undefined && params.date !== undefined)
|
|
196
|
-
return t('loggedInAccountExpires', params);
|
|
197
|
-
if (params.account !== undefined)
|
|
198
|
-
return t('loggedInAccount', params);
|
|
199
|
-
if (params.date !== undefined)
|
|
200
|
-
return t('loggedInExpires', params);
|
|
201
|
-
return t('loggedIn');
|
|
202
|
-
}
|
|
272
|
+
if (status.accounts.length > 0)
|
|
273
|
+
return t('loggedInCount', { count: status.accounts.length });
|
|
203
274
|
return t('notLoggedIn');
|
|
204
275
|
}
|
|
205
276
|
/**
|
|
@@ -240,6 +311,65 @@ function messageColor(tone) {
|
|
|
240
311
|
? 'var(--dsw-alias-state-error-primary)'
|
|
241
312
|
: 'var(--dsw-alias-state-success-primary)';
|
|
242
313
|
}
|
|
314
|
+
/**
|
|
315
|
+
* Derive one provider's default-effort section from its catalog and filter.
|
|
316
|
+
* Pure so the collapsed-header counts and the filter stay testable without a
|
|
317
|
+
* DOM: rows come only from models that advertise levels, the count of the rest
|
|
318
|
+
* rides as one line, and the filter matches display name or model id.
|
|
319
|
+
* @param models - the provider's catalog models, or undefined while loading.
|
|
320
|
+
* @param filter - the raw filter input (trimmed and lowercased here).
|
|
321
|
+
* @returns the section's rows and header counts.
|
|
322
|
+
*/
|
|
323
|
+
export function deriveModelDefaultsView(models, filter) {
|
|
324
|
+
const all = models ?? [];
|
|
325
|
+
const withEfforts = all.filter(model => model.efforts.length > 0);
|
|
326
|
+
const query = filter.trim().toLowerCase();
|
|
327
|
+
const shown = query === ''
|
|
328
|
+
? withEfforts
|
|
329
|
+
: withEfforts.filter(model => model.name.toLowerCase().includes(query)
|
|
330
|
+
|| model.id.toLowerCase().includes(query));
|
|
331
|
+
return {
|
|
332
|
+
shown,
|
|
333
|
+
total: withEfforts.length,
|
|
334
|
+
overridden: withEfforts.filter(model => model.configured !== undefined).length,
|
|
335
|
+
withoutEfforts: all.length - withEfforts.length,
|
|
336
|
+
showFilter: withEfforts.length > MODEL_FILTER_THRESHOLD,
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Whether the default-effort catalog needs (re)fetching.
|
|
341
|
+
*
|
|
342
|
+
* Fetching is gated on an *attempt* signature rather than on the payload
|
|
343
|
+
* being empty: an empty answer is a legitimate result (a narrowed
|
|
344
|
+
* `config.providers`, or a catalog that is momentarily unavailable), and
|
|
345
|
+
* treating it as "not loaded yet" re-ran this effect forever. The signature
|
|
346
|
+
* also covers the accounts, so logging a second provider in refetches
|
|
347
|
+
* instead of leaving that card on the previous answer.
|
|
348
|
+
* @param input - the decision inputs.
|
|
349
|
+
* @returns true when the caller should start a fetch.
|
|
350
|
+
*/
|
|
351
|
+
export function shouldFetchModelDefaults(input) {
|
|
352
|
+
if (input.failed)
|
|
353
|
+
return false;
|
|
354
|
+
if (input.loggedIn.length === 0)
|
|
355
|
+
return false;
|
|
356
|
+
// Only an open disclosure pays for the per-model live resolve.
|
|
357
|
+
if (!input.open.some(provider => input.loggedIn.includes(provider)))
|
|
358
|
+
return false;
|
|
359
|
+
return input.loadedFor !== input.signature;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Stable signature of the accounts a catalog answer depends on. A change
|
|
363
|
+
* means a previous answer is stale (an account arrived or left), so the next
|
|
364
|
+
* open disclosure refetches.
|
|
365
|
+
* @param statuses - the per-provider status snapshot.
|
|
366
|
+
* @returns a signature string, stable across renders with equal accounts.
|
|
367
|
+
*/
|
|
368
|
+
export function modelDefaultsSignature(statuses) {
|
|
369
|
+
return PROVIDERS
|
|
370
|
+
.map(({ id }) => `${id}:${(statuses[id]?.accounts ?? []).map(account => account.key).sort().join(',')}`)
|
|
371
|
+
.join('|');
|
|
372
|
+
}
|
|
243
373
|
/**
|
|
244
374
|
* The Subscriptions settings page component.
|
|
245
375
|
* @param props - the slot inject face ({@link SubscriptionsSectionInjected}).
|
|
@@ -256,12 +386,13 @@ export function SubscriptionsSection(props) {
|
|
|
256
386
|
/** Pending device-flow codes (copilot), shown while the attempt polls. */
|
|
257
387
|
const [deviceCodes, setDeviceCodes] = useState({});
|
|
258
388
|
const [copiedCode, setCopiedCode] = useState(undefined);
|
|
389
|
+
/** Usage snapshots keyed `${provider}:${accountKey}` — every account tracks its own windows. */
|
|
259
390
|
const [usages, setUsages] = useState({});
|
|
260
391
|
const [usageErrors, setUsageErrors] = useState({});
|
|
261
392
|
const [usageLoading, setUsageLoading] = useState({});
|
|
262
393
|
const mountedRef = useRef(true);
|
|
263
394
|
const pollersRef = useRef(new Map());
|
|
264
|
-
/**
|
|
395
|
+
/** Accounts with a `usage` call in flight; guards the auto-fetch effect against re-entry. */
|
|
265
396
|
const usageInflightRef = useRef(new Set());
|
|
266
397
|
/** Proxy config as last answered by `proxyGet`/`proxySet`. */
|
|
267
398
|
const [proxy, setProxy] = useState(undefined);
|
|
@@ -278,6 +409,24 @@ export function SubscriptionsSection(props) {
|
|
|
278
409
|
const [proxyTesting, setProxyTesting] = useState(false);
|
|
279
410
|
const [proxyMessage, setProxyMessage] = useState(undefined);
|
|
280
411
|
const [proxyTestResult, setProxyTestResult] = useState(undefined);
|
|
412
|
+
/** Per-model default-effort picker state as answered by `modelDefaults`. */
|
|
413
|
+
const [modelDefaults, setModelDefaults] = useState({});
|
|
414
|
+
const [modelDefaultsLoading, setModelDefaultsLoading] = useState(false);
|
|
415
|
+
const [modelDefaultsLoadError, setModelDefaultsLoadError] = useState(undefined);
|
|
416
|
+
/** One set in flight: the `${provider}/${model}` key. */
|
|
417
|
+
const [modelDefaultsSaving, setModelDefaultsSaving] = useState(undefined);
|
|
418
|
+
/** Per-model save failures, keyed `${provider}/${model}`. */
|
|
419
|
+
const [modelDefaultsSaveErrors, setModelDefaultsSaveErrors] = useState({});
|
|
420
|
+
/** Providers whose default-effort disclosure is open (collapsed by default). */
|
|
421
|
+
const [modelDefaultsOpen, setModelDefaultsOpen] = useState({});
|
|
422
|
+
/** Per-provider name filter of the expanded list. */
|
|
423
|
+
const [modelDefaultsFilters, setModelDefaultsFilters] = useState({});
|
|
424
|
+
/** Account signature the last completed catalog fetch was answered for. */
|
|
425
|
+
const [modelDefaultsLoadedFor, setModelDefaultsLoadedFor] = useState(undefined);
|
|
426
|
+
/** Optimistic in-flight selections, keyed `${provider}/${model}` ('' = follow provider). */
|
|
427
|
+
const [modelDefaultsPending, setModelDefaultsPending] = useState({});
|
|
428
|
+
/** Guard the catalog effect against concurrent loads. */
|
|
429
|
+
const modelDefaultsInflightRef = useRef(false);
|
|
281
430
|
const setProviderError = useCallback((provider, message) => {
|
|
282
431
|
if (!mountedRef.current)
|
|
283
432
|
return;
|
|
@@ -315,7 +464,7 @@ export function SubscriptionsSection(props) {
|
|
|
315
464
|
setStatuses(response.providers);
|
|
316
465
|
for (const { id } of PROVIDERS) {
|
|
317
466
|
const status = response.providers[id];
|
|
318
|
-
if (status.
|
|
467
|
+
if (status.accounts.length > 0 || !status.busy) {
|
|
319
468
|
stopPolling(id);
|
|
320
469
|
// The attempt settled (success, timeout, or cancel): drop the code card.
|
|
321
470
|
setDeviceCodes((prev) => {
|
|
@@ -355,64 +504,198 @@ export function SubscriptionsSection(props) {
|
|
|
355
504
|
pollersRef.current.clear();
|
|
356
505
|
};
|
|
357
506
|
}, [refresh, startPolling]);
|
|
358
|
-
const loadUsage = useCallback(async (provider) => {
|
|
359
|
-
|
|
507
|
+
const loadUsage = useCallback(async (provider, account, force = false) => {
|
|
508
|
+
const key = `${provider}:${account}`;
|
|
509
|
+
if (rpc === undefined || usageInflightRef.current.has(key))
|
|
360
510
|
return;
|
|
361
|
-
usageInflightRef.current.add(
|
|
362
|
-
setUsageLoading(prev => ({ ...prev, [
|
|
511
|
+
usageInflightRef.current.add(key);
|
|
512
|
+
setUsageLoading(prev => ({ ...prev, [key]: true }));
|
|
363
513
|
try {
|
|
364
|
-
const usage = await callSubscriptionsAuth(rpc, 'usage', { provider });
|
|
514
|
+
const usage = await callSubscriptionsAuth(rpc, 'usage', { provider, account, ...force ? { force: true } : {} });
|
|
365
515
|
if (!mountedRef.current)
|
|
366
516
|
return;
|
|
367
|
-
setUsages(prev => ({ ...prev, [
|
|
517
|
+
setUsages(prev => ({ ...prev, [key]: usage }));
|
|
368
518
|
setUsageErrors((prev) => {
|
|
369
519
|
const next = { ...prev };
|
|
370
|
-
delete next[
|
|
520
|
+
delete next[key];
|
|
371
521
|
return next;
|
|
372
522
|
});
|
|
373
523
|
}
|
|
374
524
|
catch (error) {
|
|
375
525
|
if (mountedRef.current)
|
|
376
|
-
setUsageErrors(prev => ({ ...prev, [
|
|
526
|
+
setUsageErrors(prev => ({ ...prev, [key]: messageOf(error) }));
|
|
377
527
|
}
|
|
378
528
|
finally {
|
|
379
|
-
usageInflightRef.current.delete(
|
|
529
|
+
usageInflightRef.current.delete(key);
|
|
380
530
|
if (mountedRef.current)
|
|
381
|
-
setUsageLoading(prev => ({ ...prev, [
|
|
531
|
+
setUsageLoading(prev => ({ ...prev, [key]: false }));
|
|
382
532
|
}
|
|
383
533
|
}, [rpc]);
|
|
384
|
-
// Fetch usage once
|
|
385
|
-
//
|
|
386
|
-
// per-
|
|
534
|
+
// Fetch usage once an account is logged in; drop the snapshots of accounts
|
|
535
|
+
// that vanished so a re-login refetches. A failed lookup does not auto-retry
|
|
536
|
+
// — the per-account Refresh button is the retry path.
|
|
387
537
|
useEffect(() => {
|
|
538
|
+
const live = new Set();
|
|
388
539
|
for (const { id } of PROVIDERS) {
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
void loadUsage(id);
|
|
540
|
+
for (const account of statuses[id]?.accounts ?? []) {
|
|
541
|
+
const key = `${id}:${account.key}`;
|
|
542
|
+
live.add(key);
|
|
543
|
+
if (usages[key] === undefined && usageErrors[key] === undefined)
|
|
544
|
+
void loadUsage(id, account.key);
|
|
395
545
|
}
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
546
|
+
}
|
|
547
|
+
setUsages(prev => dropStale(prev, live));
|
|
548
|
+
setUsageErrors(prev => dropStale(prev, live));
|
|
549
|
+
}, [statuses, usages, usageErrors, loadUsage]);
|
|
550
|
+
const loadModelDefaultsData = useCallback(async (signature) => {
|
|
551
|
+
if (rpc === undefined || modelDefaultsInflightRef.current)
|
|
552
|
+
return;
|
|
553
|
+
modelDefaultsInflightRef.current = true;
|
|
554
|
+
setModelDefaultsLoading(true);
|
|
555
|
+
try {
|
|
556
|
+
const catalog = await callSubscriptionsAuth(rpc, 'modelDefaults', {});
|
|
557
|
+
if (!mountedRef.current)
|
|
558
|
+
return;
|
|
559
|
+
const next = {};
|
|
560
|
+
for (const entry of catalog)
|
|
561
|
+
next[entry.provider] = entry;
|
|
562
|
+
setModelDefaults(next);
|
|
563
|
+
setModelDefaultsLoadError(undefined);
|
|
564
|
+
// Latch the answered signature, empty answer included: an empty catalog
|
|
565
|
+
// is a result, not a missing load, and re-deriving "loaded" from the
|
|
566
|
+
// payload re-triggered this fetch on every render.
|
|
567
|
+
setModelDefaultsLoadedFor(signature);
|
|
568
|
+
// The shown state is now authoritative; stale per-row failures would
|
|
569
|
+
// otherwise linger next to rows that are correct again.
|
|
570
|
+
setModelDefaultsSaveErrors({});
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
if (mountedRef.current)
|
|
574
|
+
setModelDefaultsLoadError(messageOf(error));
|
|
575
|
+
}
|
|
576
|
+
finally {
|
|
577
|
+
modelDefaultsInflightRef.current = false;
|
|
578
|
+
if (mountedRef.current)
|
|
579
|
+
setModelDefaultsLoading(false);
|
|
580
|
+
}
|
|
581
|
+
}, [rpc]);
|
|
582
|
+
// Fetch the default-effort catalogs only once a card's list is expanded: the
|
|
583
|
+
// node half resolves live model info per model, so a collapsed page must not
|
|
584
|
+
// pay for it. One fetch covers every logged-in provider (the node half
|
|
585
|
+
// answers them together), and the levels follow the picker's catalog union
|
|
586
|
+
// across that provider's accounts. The account signature drives refetching,
|
|
587
|
+
// so connecting another provider or account does not leave an open card on
|
|
588
|
+
// the previous answer. Everything resets once the last account logs out.
|
|
589
|
+
useEffect(() => {
|
|
590
|
+
const loggedIn = PROVIDERS.filter(({ id }) => hasAccount(statuses[id])).map(({ id }) => id);
|
|
591
|
+
const signature = modelDefaultsSignature(statuses);
|
|
592
|
+
if (loggedIn.length > 0) {
|
|
593
|
+
const open = PROVIDERS.filter(({ id }) => modelDefaultsOpen[id] === true).map(({ id }) => id);
|
|
594
|
+
if (shouldFetchModelDefaults({
|
|
595
|
+
loggedIn,
|
|
596
|
+
open,
|
|
597
|
+
loadedFor: modelDefaultsLoadedFor,
|
|
598
|
+
signature,
|
|
599
|
+
failed: modelDefaultsLoadError !== undefined,
|
|
600
|
+
})) {
|
|
601
|
+
void loadModelDefaultsData(signature);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
else if (modelDefaultsLoadedFor !== undefined
|
|
605
|
+
|| Object.keys(modelDefaults).length > 0
|
|
606
|
+
|| Object.keys(modelDefaultsOpen).length > 0) {
|
|
607
|
+
setModelDefaults({});
|
|
608
|
+
setModelDefaultsSaveErrors({});
|
|
609
|
+
setModelDefaultsLoadError(undefined);
|
|
610
|
+
setModelDefaultsLoadedFor(undefined);
|
|
611
|
+
setModelDefaultsLoading(false);
|
|
612
|
+
setModelDefaultsOpen({});
|
|
613
|
+
setModelDefaultsFilters({});
|
|
614
|
+
}
|
|
615
|
+
}, [
|
|
616
|
+
statuses,
|
|
617
|
+
modelDefaults,
|
|
618
|
+
modelDefaultsOpen,
|
|
619
|
+
modelDefaultsLoadError,
|
|
620
|
+
modelDefaultsLoadedFor,
|
|
621
|
+
loadModelDefaultsData,
|
|
622
|
+
]);
|
|
623
|
+
/** Open or close one provider's default-effort disclosure. */
|
|
624
|
+
const toggleModelDefaults = useCallback((provider) => {
|
|
625
|
+
setModelDefaultsOpen(prev => ({ ...prev, [provider]: prev[provider] !== true }));
|
|
626
|
+
}, []);
|
|
627
|
+
const setModelDefault = useCallback(async (provider, model, effort) => {
|
|
628
|
+
if (rpc === undefined)
|
|
629
|
+
return;
|
|
630
|
+
const key = `${provider}/${model}`;
|
|
631
|
+
setModelDefaultsSaving(key);
|
|
632
|
+
// Hold the picked level locally for the duration of the save: the select
|
|
633
|
+
// is controlled by server state, which only updates after the round trip,
|
|
634
|
+
// so without this the row visibly snaps back to "Follow provider" (greyed
|
|
635
|
+
// out) mid-save and reads as a rejected change.
|
|
636
|
+
setModelDefaultsPending(prev => ({ ...prev, [key]: effort ?? '' }));
|
|
637
|
+
setModelDefaultsSaveErrors((prev) => {
|
|
638
|
+
const next = { ...prev };
|
|
639
|
+
delete next[key];
|
|
640
|
+
return next;
|
|
641
|
+
});
|
|
642
|
+
try {
|
|
643
|
+
await callSubscriptionsAuth(rpc, 'setModelDefault', {
|
|
644
|
+
provider,
|
|
645
|
+
model,
|
|
646
|
+
...(effort === undefined ? {} : { effort }),
|
|
647
|
+
});
|
|
648
|
+
if (!mountedRef.current)
|
|
649
|
+
return;
|
|
650
|
+
setModelDefaults((prev) => {
|
|
651
|
+
const section = prev[provider];
|
|
652
|
+
if (section === undefined)
|
|
653
|
+
return prev;
|
|
654
|
+
return {
|
|
655
|
+
...prev,
|
|
656
|
+
[provider]: {
|
|
657
|
+
...section,
|
|
658
|
+
models: section.models.map((entry) => {
|
|
659
|
+
if (entry.id !== model)
|
|
660
|
+
return entry;
|
|
661
|
+
if (effort !== undefined)
|
|
662
|
+
return { ...entry, configured: effort };
|
|
663
|
+
// Cleared: drop the key rather than keep the stale level, or the
|
|
664
|
+
// select would snap back and the header would keep counting it.
|
|
665
|
+
const { configured: _cleared, ...rest } = entry;
|
|
666
|
+
return rest;
|
|
667
|
+
}),
|
|
668
|
+
},
|
|
669
|
+
};
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
catch (error) {
|
|
673
|
+
if (mountedRef.current)
|
|
674
|
+
setModelDefaultsSaveErrors((prev) => ({ ...prev, [key]: messageOf(error) }));
|
|
675
|
+
}
|
|
676
|
+
finally {
|
|
677
|
+
if (mountedRef.current) {
|
|
678
|
+
setModelDefaultsSaving(current => current === key ? undefined : current);
|
|
679
|
+
// Drop the optimistic value: on success the server state now carries
|
|
680
|
+
// it, on failure the row must fall back to the real stored level
|
|
681
|
+
// rather than keep showing a change that did not land.
|
|
682
|
+
setModelDefaultsPending((prev) => {
|
|
403
683
|
const next = { ...prev };
|
|
404
|
-
delete next[
|
|
684
|
+
delete next[key];
|
|
405
685
|
return next;
|
|
406
686
|
});
|
|
407
687
|
}
|
|
408
688
|
}
|
|
409
|
-
}, [
|
|
410
|
-
const login = useCallback(async (provider) => {
|
|
689
|
+
}, [rpc]);
|
|
690
|
+
const login = useCallback(async (provider, method) => {
|
|
411
691
|
if (rpc === undefined)
|
|
412
692
|
return;
|
|
413
693
|
setProviderError(provider, undefined);
|
|
414
694
|
try {
|
|
415
|
-
const response = await callSubscriptionsAuth(rpc, 'login', {
|
|
695
|
+
const response = await callSubscriptionsAuth(rpc, 'login', {
|
|
696
|
+
provider,
|
|
697
|
+
...method === undefined ? {} : { method },
|
|
698
|
+
});
|
|
416
699
|
if (typeof response.authorizeUrl === 'string' && response.authorizeUrl === '') {
|
|
417
700
|
// Instant login (e.g. imported from Claude Code credentials)
|
|
418
701
|
await refresh();
|
|
@@ -424,7 +707,10 @@ export function SubscriptionsSection(props) {
|
|
|
424
707
|
if (!mountedRef.current)
|
|
425
708
|
return;
|
|
426
709
|
// Optimistic busy so Cancel and the manual fallback appear before the first poll tick.
|
|
427
|
-
setStatuses(prev => ({
|
|
710
|
+
setStatuses(prev => ({
|
|
711
|
+
...prev,
|
|
712
|
+
[provider]: { accounts: prev[provider]?.accounts ?? [], ...prev[provider], busy: true },
|
|
713
|
+
}));
|
|
428
714
|
if (typeof response.userCode === 'string' && response.userCode.length > 0) {
|
|
429
715
|
// Device flow: show the code card instead of opening the page blind —
|
|
430
716
|
// the user copies the code first, then opens the verification page.
|
|
@@ -468,20 +754,32 @@ export function SubscriptionsSection(props) {
|
|
|
468
754
|
}
|
|
469
755
|
await refresh();
|
|
470
756
|
}, [rpc, manualDrafts, setProviderError, refresh]);
|
|
471
|
-
const logout = useCallback(async (provider, name) => {
|
|
757
|
+
const logout = useCallback(async (provider, account, display, name) => {
|
|
472
758
|
if (rpc === undefined)
|
|
473
759
|
return;
|
|
474
|
-
if (!window.confirm(t('
|
|
760
|
+
if (!window.confirm(t('logoutAccountConfirm', { provider: name, account: display })))
|
|
475
761
|
return;
|
|
476
762
|
setProviderError(provider, undefined);
|
|
477
763
|
try {
|
|
478
|
-
await callSubscriptionsAuth(rpc, 'logout', { provider });
|
|
764
|
+
await callSubscriptionsAuth(rpc, 'logout', { provider, account });
|
|
479
765
|
}
|
|
480
766
|
catch (error) {
|
|
481
767
|
setProviderError(provider, messageOf(error));
|
|
482
768
|
}
|
|
483
769
|
await refresh();
|
|
484
770
|
}, [rpc, t, setProviderError, refresh]);
|
|
771
|
+
const setDefault = useCallback(async (provider, account) => {
|
|
772
|
+
if (rpc === undefined)
|
|
773
|
+
return;
|
|
774
|
+
setProviderError(provider, undefined);
|
|
775
|
+
try {
|
|
776
|
+
await callSubscriptionsAuth(rpc, 'setDefault', { provider, account });
|
|
777
|
+
}
|
|
778
|
+
catch (error) {
|
|
779
|
+
setProviderError(provider, messageOf(error));
|
|
780
|
+
}
|
|
781
|
+
await refresh();
|
|
782
|
+
}, [rpc, setProviderError, refresh]);
|
|
485
783
|
const copyDeviceCode = useCallback((provider, userCode) => {
|
|
486
784
|
void navigator.clipboard?.writeText(userCode).then(() => {
|
|
487
785
|
if (!mountedRef.current)
|
|
@@ -593,16 +891,67 @@ export function SubscriptionsSection(props) {
|
|
|
593
891
|
const status = statuses[id];
|
|
594
892
|
const busy = status?.busy === true;
|
|
595
893
|
const deviceCode = deviceCodes[id];
|
|
596
|
-
const
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
894
|
+
const accounts = status?.accounts ?? [];
|
|
895
|
+
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 })] }), _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) => {
|
|
896
|
+
const usageKey = `${id}:${account.key}`;
|
|
897
|
+
const usage = usages[usageKey];
|
|
898
|
+
const usageError = usageErrors[usageKey];
|
|
899
|
+
const display = account.account ?? account.key;
|
|
900
|
+
// Providers without a usage endpoint answer supported:false — no block.
|
|
901
|
+
const showUsage = usage?.supported !== false
|
|
902
|
+
&& (usage !== undefined || usageError !== undefined || usageLoading[usageKey] === true);
|
|
903
|
+
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: () => {
|
|
904
|
+
if (!account.isDefault)
|
|
905
|
+
void setDefault(id, account.key);
|
|
906
|
+
}, 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) => {
|
|
907
|
+
const percent = Math.min(100, Math.max(0, window.usedPercent));
|
|
908
|
+
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
|
|
909
|
+
&& ` · ${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));
|
|
910
|
+
})] }))] }, account.key));
|
|
911
|
+
}), _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') })), 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') })), accounts.length > 0 && (() => {
|
|
912
|
+
// Collapsed by default: providers with a large catalog (Copilot
|
|
913
|
+
// lists dozens of models) must not push the page down. The
|
|
914
|
+
// header carries the summary so the collapsed state still says
|
|
915
|
+
// how many models are overridden. The section is per provider,
|
|
916
|
+
// not per account: the override keys off the model id, which the
|
|
917
|
+
// pool shares across a provider's accounts.
|
|
918
|
+
const open = modelDefaultsOpen[id] === true;
|
|
919
|
+
const catalog = modelDefaults[id];
|
|
920
|
+
const filter = modelDefaultsFilters[id] ?? '';
|
|
921
|
+
const view = deriveModelDefaultsView(catalog?.models, filter);
|
|
922
|
+
const saveErrors = Object.entries(modelDefaultsSaveErrors).filter(([key]) => key.startsWith(`${id}/`));
|
|
923
|
+
return (_jsxs("div", { style: styles.defaultEffort, children: [_jsxs("button", { type: "button", style: styles.defaultEffortToggle, "aria-expanded": open, onClick: () => { toggleModelDefaults(id); }, children: [_jsx("span", { style: styles.usageTitle, children: t('modelDefaultsTitle') }), _jsx("span", { style: styles.usagePlan, children: catalog === undefined
|
|
924
|
+
? (modelDefaultsLoading ? t('modelDefaultsLoading') : '')
|
|
925
|
+
: view.total === 0
|
|
926
|
+
? t('modelDefaultsSummaryEmpty')
|
|
927
|
+
: view.overridden === 0
|
|
928
|
+
? t('modelDefaultsSummaryNone', { total: view.total })
|
|
929
|
+
: t('modelDefaultsSummary', { total: view.total, configured: view.overridden }) }), _jsx("span", { style: styles.defaultEffortChevron, "aria-hidden": "true", children: open ? '▲' : '▼' })] }), saveErrors.map(([key, message]) => (
|
|
930
|
+
// Named: the failing row may be scrolled out of the
|
|
931
|
+
// bounded list, and several anonymous "Save failed" lines
|
|
932
|
+
// cannot be told apart.
|
|
933
|
+
_jsx("p", { style: styles.errorLine, role: "alert", children: t('modelDefaultsSaveFailedNamed', {
|
|
934
|
+
model: catalog?.models.find(entry => `${id}/${entry.id}` === key)?.name
|
|
935
|
+
?? key.slice(id.length + 1),
|
|
936
|
+
message,
|
|
937
|
+
}) }, key))), open && (_jsxs(_Fragment, { children: [_jsx("p", { style: styles.statusLine, children: t('modelDefaultsHint') }), modelDefaultsLoadError !== undefined && (_jsxs(_Fragment, { children: [_jsx("p", { style: styles.errorLine, children: t('modelDefaultsLoadFailed', { message: modelDefaultsLoadError }) }), _jsx("div", { style: styles.actions, children: _jsx("button", { type: "button", style: styles.button, onClick: () => {
|
|
938
|
+
// Clearing the latch lets the effect pick the
|
|
939
|
+
// fetch back up on the next render.
|
|
940
|
+
setModelDefaultsLoadError(undefined);
|
|
941
|
+
}, children: t('modelDefaultsRetry') }) })] })), modelDefaultsLoadError === undefined && catalog === undefined && (_jsx("p", { style: styles.statusLine, children: t('modelDefaultsLoading') })), view.showFilter && (_jsx("input", { style: styles.defaultEffortFilter, value: filter, placeholder: t('modelDefaultsFilterPlaceholder'), "aria-label": t('modelDefaultsFilterPlaceholder'), onChange: (event) => {
|
|
942
|
+
setModelDefaultsFilters(prev => ({ ...prev, [id]: event.target.value }));
|
|
943
|
+
} })), view.shown.length > 0 && (_jsx("div", { style: styles.defaultEffortList, children: view.shown.map((model) => {
|
|
944
|
+
const rowKey = `${id}/${model.id}`;
|
|
945
|
+
const saving = modelDefaultsSaving === rowKey;
|
|
946
|
+
// Ids carry the provider: a model id alone repeats
|
|
947
|
+
// across cards, and duplicate ids break the label
|
|
948
|
+
// association the select relies on.
|
|
949
|
+
const labelId = `dsh-model-default-${id}-${model.id}`;
|
|
950
|
+
return (_jsxs("div", { style: styles.defaultEffortRow, children: [_jsx("span", { id: labelId, style: styles.defaultEffortName, title: model.id, children: model.name }), saving && (_jsx("span", { style: styles.defaultEffortSaving, children: t('modelDefaultsSaving') })), _jsxs("select", { style: styles.defaultEffortSelect, "aria-labelledby": labelId, value: modelDefaultsPending[rowKey] ?? model.configured ?? '', disabled: saving, onChange: (event) => {
|
|
951
|
+
void setModelDefault(id, model.id, event.target.value === '' ? undefined : event.target.value);
|
|
952
|
+
}, children: [_jsx("option", { value: "", children: t('modelDefaultsFollowProvider') }), model.efforts.map(effort => (_jsx("option", { value: effort.id, children: effort.name }, effort.id)))] })] }, model.id));
|
|
953
|
+
}) })), catalog !== undefined && view.shown.length === 0 && filter.trim() !== '' && (_jsx("p", { style: styles.statusLine, children: t('modelDefaultsFilterEmpty', { query: filter.trim() }) })), view.withoutEfforts > 0 && (_jsx("p", { style: styles.statusLine, children: t('modelDefaultsNoLevels', { count: view.withoutEfforts }) }))] }))] }));
|
|
954
|
+
})(), 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));
|
|
606
955
|
}), proxyOpen && (_jsx("div", { style: styles.modalOverlay, onClick: () => setProxyOpen(false), children: _jsxs("div", { style: styles.modal, onClick: event => event.stopPropagation(), children: [_jsxs("div", { style: styles.modalHeader, children: [_jsx("span", { style: styles.modalTitle, children: t('proxyDialogTitle') }), _jsx("button", { type: "button", style: { ...styles.button, marginLeft: 'auto' }, onClick: () => setProxyOpen(false), children: t('proxyDialogClose') })] }), _jsxs("label", { style: styles.proxyCheck, children: [_jsx("input", { type: "checkbox", checked: proxyEnabled, onChange: event => setProxyEnabled(event.target.checked) }), _jsx("span", { children: t('proxyEnabled') })] }), _jsxs("label", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyUrl') }), _jsx("input", { style: styles.proxyInput, value: proxyUrl, placeholder: t('proxyUrlPlaceholder'), onChange: event => setProxyUrl(event.target.value) }), _jsx("p", { style: styles.proxyHint, children: t('proxyUrlHint') })] }), _jsxs("label", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyUsername') }), _jsx("input", { style: styles.proxyInput, value: proxyUsername, placeholder: t('proxyUsernamePlaceholder'), onChange: event => setProxyUsername(event.target.value) })] }), _jsxs("div", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyPassword') }), _jsx("input", { type: "password", style: styles.proxyInput, value: proxyPassword, placeholder: t('proxyPasswordPlaceholder'), onChange: event => setProxyPassword(event.target.value) }), _jsxs("label", { style: styles.proxyCheck, children: [_jsx("input", { type: "checkbox", checked: proxyClearPassword, onChange: event => setProxyClearPassword(event.target.checked) }), _jsx("span", { children: t('proxyClearPassword') })] })] }), _jsxs("label", { style: styles.proxyField, children: [_jsx("span", { style: styles.proxyLabel, children: t('proxyBypass') }), _jsx("input", { style: styles.proxyInput, value: proxyBypass, placeholder: t('proxyBypassPlaceholder'), onChange: event => setProxyBypass(event.target.value) }), _jsx("p", { style: styles.proxyHint, children: t('proxyBypassHint') })] }), _jsx("p", { style: styles.proxyHint, children: t('proxyNote') }), proxyMessage !== undefined && (_jsx("p", { style: { ...styles.proxyMessage, color: messageColor(proxyMessage.tone) }, children: proxyMessage.text })), proxyTestResult !== undefined && (_jsx("p", { style: {
|
|
607
956
|
...styles.proxyMessage,
|
|
608
957
|
color: proxyTestResult.ok
|