dsh-coding-subscription-oauth 0.5.2 → 0.5.4
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/CHANGELOG.md +20 -0
- package/INSTALL.md +11 -11
- package/README.de.md +34 -9
- package/README.es.md +35 -9
- package/README.fr.md +35 -9
- package/README.ja.md +35 -9
- package/README.ko.md +35 -9
- package/README.md +34 -13
- package/README.pt-BR.md +35 -9
- package/README.ru.md +35 -9
- package/README.zh-CN.md +34 -13
- package/docs/02-architecture.md +4 -3
- package/docs/02-architecture.zh-CN.md +8 -3
- package/lib/adapter.d.ts.map +1 -1
- package/lib/alias-adapter.d.ts.map +1 -1
- package/lib/client.js +1 -1
- package/lib/client.js.map +4 -4
- package/lib/grok-errors.d.ts +13 -0
- package/lib/grok-errors.d.ts.map +1 -0
- package/lib/index.js +13 -3
- package/lib/index.js.map +3 -3
- package/media/settings_accounts.png +0 -0
- package/media/settings_capabilities.png +0 -0
- package/media/settings_gateway.png +0 -0
- package/media/settings_overview.png +0 -0
- package/package.json +2 -1
- package/src/adapter.ts +7 -2
- package/src/alias-adapter.ts +2 -1
- package/src/client/GrokBuildSettings.tsx +247 -1772
- package/src/client/api.ts +88 -0
- package/src/client/components/AboutTab.tsx +20 -0
- package/src/client/components/AccountsTab.tsx +224 -0
- package/src/client/components/CapabilitiesTab.tsx +214 -0
- package/src/client/components/CliPullPreview.tsx +107 -0
- package/src/client/components/GatewayTab.tsx +287 -0
- package/src/client/components/ProviderCard.tsx +322 -0
- package/src/client/components/SettingsTabs.tsx +65 -0
- package/src/client/constants.ts +206 -0
- package/src/client/display.ts +61 -0
- package/src/client/locales.ts +47 -15
- package/src/client/parsers.ts +386 -0
- package/src/client/styles.ts +180 -0
- package/src/client/types.ts +185 -0
- package/src/grok-errors.ts +24 -0
|
@@ -1,971 +1,81 @@
|
|
|
1
1
|
/** Plugin-owned coding subscription account section inside the dsh Settings shell. */
|
|
2
2
|
|
|
3
|
-
import type { CSSProperties } from "react";
|
|
4
3
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
5
|
-
import
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
loginMethods: readonly ("browser" | "device")[];
|
|
70
|
-
recommendedLoginMethod: "browser" | "device";
|
|
71
|
-
models: string[];
|
|
72
|
-
available: string[];
|
|
73
|
-
selected: string[];
|
|
74
|
-
} & (
|
|
75
|
-
| { status: "signed-out" }
|
|
76
|
-
| { status: "signing-in"; method: "browser" | "device"; url?: string; userCode?: string }
|
|
77
|
-
| { status: "signed-in"; expiresAt?: number }
|
|
78
|
-
| { status: "error"; message: string }
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
type ProviderStatus = GrokStatus | SubscriptionStatus;
|
|
82
|
-
|
|
83
|
-
interface CodingOAuthStatus {
|
|
84
|
-
providers: {
|
|
85
|
-
grok: GrokStatus;
|
|
86
|
-
codex: SubscriptionStatus;
|
|
87
|
-
kimi: SubscriptionStatus;
|
|
88
|
-
claude: SubscriptionStatus;
|
|
89
|
-
};
|
|
90
|
-
antigravity: { installed: boolean; route: "agy"; management: "cli" };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
interface LoginChallenge {
|
|
94
|
-
method: LoginMethod;
|
|
95
|
-
url: string;
|
|
96
|
-
userCode?: string;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
interface ProviderCardDefinition {
|
|
100
|
-
slug: ProviderSlug;
|
|
101
|
-
route: string;
|
|
102
|
-
titleKey: GrokBuildSettingsKey;
|
|
103
|
-
descriptionKey: GrokBuildSettingsKey;
|
|
104
|
-
methods: readonly LoginMethod[];
|
|
105
|
-
recommended: LoginMethod;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
interface SourceStatus {
|
|
109
|
-
kind: SourceKind;
|
|
110
|
-
displayPath: string;
|
|
111
|
-
available: boolean;
|
|
112
|
-
expiresAt?: number;
|
|
113
|
-
reason?: SourceReason;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
interface SourcePreview {
|
|
117
|
-
previewId: string;
|
|
118
|
-
kind: SourceKind;
|
|
119
|
-
displayPath: string;
|
|
120
|
-
expiresAt?: number;
|
|
121
|
-
ticketExpiresAt?: number;
|
|
122
|
-
conflict?: SourceConflict;
|
|
123
|
-
action?: SourcePreviewAction;
|
|
124
|
-
warnings: string[];
|
|
125
|
-
confirmOverwriteRequired: boolean;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
interface CapabilityFlags {
|
|
129
|
-
codexSearch: boolean;
|
|
130
|
-
codexImages: boolean;
|
|
131
|
-
codexImageEdits: boolean;
|
|
132
|
-
codexUsage: boolean;
|
|
133
|
-
codexFast: boolean;
|
|
134
|
-
grokImagineImage: boolean;
|
|
135
|
-
grokImagineVideo: boolean;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
interface CapabilitySettingsView extends CapabilityFlags {
|
|
139
|
-
searchResults: number;
|
|
140
|
-
imageCount: number;
|
|
141
|
-
videoArtifactTtlMs: number;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
interface CapabilitySnapshot {
|
|
145
|
-
value: CapabilitySettingsView;
|
|
146
|
-
revision: number;
|
|
147
|
-
writable: boolean;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
interface UsageWindowView {
|
|
151
|
-
usedPercent?: number;
|
|
152
|
-
remainingPercent?: number;
|
|
153
|
-
windowSeconds?: number;
|
|
154
|
-
resetsAt?: number;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
interface UsageLimitView {
|
|
158
|
-
id: string;
|
|
159
|
-
name?: string;
|
|
160
|
-
windows: UsageWindowView[];
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
interface UsageView {
|
|
164
|
-
rateLimits: UsageLimitView[];
|
|
165
|
-
creditsUnlimited?: boolean;
|
|
166
|
-
creditsBalance?: string;
|
|
167
|
-
individualLimit?: string;
|
|
168
|
-
individualUsed?: string;
|
|
169
|
-
individualRemaining?: string;
|
|
170
|
-
individualRemainingPercent?: number;
|
|
171
|
-
individualResetsAt?: number;
|
|
172
|
-
spendControlReached?: boolean;
|
|
173
|
-
resetCredits?: number;
|
|
174
|
-
fetchedAt?: number;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
interface ImagineCredentialView {
|
|
178
|
-
configured: boolean;
|
|
179
|
-
source?: string;
|
|
180
|
-
writable?: boolean;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
interface PluginRequestError extends Error {
|
|
184
|
-
status: number;
|
|
185
|
-
code?: string;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const SOURCE_KINDS: readonly SourceKind[] = ["grok", "codex", "kimi", "claude"];
|
|
189
|
-
const SOURCE_REASONS: readonly SourceReason[] = ["missing", "unsafe", "invalid", "too_large"];
|
|
190
|
-
const SOURCE_CONFLICTS: readonly SourceConflict[] = [
|
|
191
|
-
"none",
|
|
192
|
-
"same_credential",
|
|
193
|
-
"same_account",
|
|
194
|
-
"different_account",
|
|
195
|
-
"unknown_account",
|
|
196
|
-
"unreadable_destination",
|
|
197
|
-
"unsafe_destination",
|
|
198
|
-
];
|
|
199
|
-
const SOURCE_PREVIEW_ACTIONS: readonly SourcePreviewAction[] = ["import", "reuse", "overwrite", "blocked"];
|
|
200
|
-
const SOURCE_COMMIT_ACTIONS: readonly SourceCommitAction[] = ["imported", "unchanged", "overwritten"];
|
|
201
|
-
const SOURCE_DEFAULT_PATH: { readonly [K in SourceKind]: string } = {
|
|
202
|
-
grok: "~/.grok/auth.json",
|
|
203
|
-
codex: "~/.codex/auth.json",
|
|
204
|
-
kimi: "~/.kimi/credentials/kimi-code.json",
|
|
205
|
-
claude: "~/.claude/.credentials.json",
|
|
206
|
-
};
|
|
207
|
-
const SOURCE_KIND_KEY: { readonly [K in SourceKind]: GrokBuildSettingsKey } = {
|
|
208
|
-
grok: "sourceKindGrok",
|
|
209
|
-
codex: "sourceKindCodex",
|
|
210
|
-
kimi: "sourceKindKimi",
|
|
211
|
-
claude: "sourceKindClaude",
|
|
212
|
-
};
|
|
213
|
-
const SOURCE_REASON_KEY: { readonly [K in SourceReason]: GrokBuildSettingsKey } = {
|
|
214
|
-
missing: "sourceReasonMissing",
|
|
215
|
-
unsafe: "sourceReasonUnsafe",
|
|
216
|
-
invalid: "sourceReasonInvalid",
|
|
217
|
-
too_large: "sourceReasonTooLarge",
|
|
218
|
-
};
|
|
219
|
-
const SOURCE_CONFLICT_KEY: { readonly [K in SourceConflict]: GrokBuildSettingsKey } = {
|
|
220
|
-
none: "sourceConflictNone",
|
|
221
|
-
same_credential: "sourceConflictSameCredential",
|
|
222
|
-
same_account: "sourceConflictSameAccount",
|
|
223
|
-
different_account: "sourceConflictDifferentAccount",
|
|
224
|
-
unknown_account: "sourceConflictUnknownAccount",
|
|
225
|
-
unreadable_destination: "sourceConflictUnreadableDestination",
|
|
226
|
-
unsafe_destination: "sourceConflictUnsafeDestination",
|
|
227
|
-
};
|
|
228
|
-
const SOURCE_PREVIEW_ACTION_KEY: { readonly [K in SourcePreviewAction]: GrokBuildSettingsKey } = {
|
|
229
|
-
import: "sourceActionImport",
|
|
230
|
-
reuse: "sourceActionReuse",
|
|
231
|
-
overwrite: "sourceActionOverwrite",
|
|
232
|
-
blocked: "sourceActionBlocked",
|
|
233
|
-
};
|
|
234
|
-
const SOURCE_COMMIT_ACTION_KEY: { readonly [K in SourceCommitAction]: GrokBuildSettingsKey } = {
|
|
235
|
-
imported: "sourceCommitImported",
|
|
236
|
-
unchanged: "sourceCommitUnchanged",
|
|
237
|
-
overwritten: "sourceCommitOverwritten",
|
|
238
|
-
};
|
|
239
|
-
const CAPABILITY_TOGGLES: readonly {
|
|
240
|
-
key: CapabilityFlagKey;
|
|
241
|
-
label: GrokBuildSettingsKey;
|
|
242
|
-
hint: GrokBuildSettingsKey;
|
|
243
|
-
requiresImages?: true;
|
|
244
|
-
}[] = [
|
|
245
|
-
{ key: "codexSearch", label: "capCodexSearch", hint: "capCodexSearchHint" },
|
|
246
|
-
{ key: "codexImages", label: "capCodexImages", hint: "capCodexImagesHint" },
|
|
247
|
-
{ key: "codexImageEdits", label: "capCodexImageEdits", hint: "capCodexImageEditsHint", requiresImages: true },
|
|
248
|
-
{ key: "codexUsage", label: "capCodexUsage", hint: "capCodexUsageHint" },
|
|
249
|
-
{ key: "codexFast", label: "capCodexFast", hint: "capCodexFastHint" },
|
|
250
|
-
{ key: "grokImagineImage", label: "capGrokImagineImage", hint: "capGrokImagineImageHint" },
|
|
251
|
-
{ key: "grokImagineVideo", label: "capGrokImagineVideo", hint: "capGrokImagineVideoHint" },
|
|
252
|
-
];
|
|
253
|
-
const HOUR_MS = 60 * 60 * 1000;
|
|
254
|
-
const CAPABILITY_LIMITS: readonly {
|
|
255
|
-
key: CapabilityLimitKey;
|
|
256
|
-
label: GrokBuildSettingsKey;
|
|
257
|
-
hint: GrokBuildSettingsKey;
|
|
258
|
-
min: number;
|
|
259
|
-
max: number;
|
|
260
|
-
scale: number;
|
|
261
|
-
}[] = [
|
|
262
|
-
{ key: "searchResults", label: "capSearchResults", hint: "capSearchResultsHint", min: 1, max: 20, scale: 1 },
|
|
263
|
-
{ key: "imageCount", label: "capImageCount", hint: "capImageCountHint", min: 1, max: 4, scale: 1 },
|
|
264
|
-
{
|
|
265
|
-
key: "videoArtifactTtlMs",
|
|
266
|
-
label: "capVideoTtlHours",
|
|
267
|
-
hint: "capVideoTtlHoursHint",
|
|
268
|
-
min: 1,
|
|
269
|
-
max: 168,
|
|
270
|
-
scale: HOUR_MS,
|
|
271
|
-
},
|
|
272
|
-
];
|
|
273
|
-
const IMAGINE_SOURCE_KEY: { readonly [source: string]: GrokBuildSettingsKey } = {
|
|
274
|
-
none: "imagineSourceNone",
|
|
275
|
-
env: "imagineSourceEnv",
|
|
276
|
-
environment: "imagineSourceEnv",
|
|
277
|
-
"xai-api-key": "imagineSourceEnv",
|
|
278
|
-
xai_api_key: "imagineSourceEnv",
|
|
279
|
-
"api-key": "imagineSourceApiKey",
|
|
280
|
-
api_key: "imagineSourceApiKey",
|
|
281
|
-
apikey: "imagineSourceApiKey",
|
|
282
|
-
key: "imagineSourceApiKey",
|
|
283
|
-
settings: "imagineSourceApiKey",
|
|
284
|
-
oauth: "imagineSourceOAuth",
|
|
285
|
-
"oauth-access": "imagineSourceOAuth",
|
|
286
|
-
"grok-cli-key": "imagineSourceCliKey",
|
|
287
|
-
"cli-key": "imagineSourceCliKey",
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
const PROVIDERS: readonly ProviderCardDefinition[] = [
|
|
291
|
-
{
|
|
292
|
-
slug: "grok",
|
|
293
|
-
route: "grok-build",
|
|
294
|
-
titleKey: "grokTitle",
|
|
295
|
-
descriptionKey: "grokDescription",
|
|
296
|
-
methods: ["pkce", "device"],
|
|
297
|
-
recommended: "pkce",
|
|
298
|
-
},
|
|
299
|
-
{
|
|
300
|
-
slug: "codex",
|
|
301
|
-
route: "codex-oauth",
|
|
302
|
-
titleKey: "codexTitle",
|
|
303
|
-
descriptionKey: "codexDescription",
|
|
304
|
-
methods: ["device", "browser"],
|
|
305
|
-
recommended: "device",
|
|
306
|
-
},
|
|
307
|
-
{
|
|
308
|
-
slug: "kimi",
|
|
309
|
-
route: "kimi-code-oauth",
|
|
310
|
-
titleKey: "kimiTitle",
|
|
311
|
-
descriptionKey: "kimiDescription",
|
|
312
|
-
methods: ["device"],
|
|
313
|
-
recommended: "device",
|
|
314
|
-
},
|
|
315
|
-
{
|
|
316
|
-
slug: "claude",
|
|
317
|
-
route: "claude-code-oauth",
|
|
318
|
-
titleKey: "claudeTitle",
|
|
319
|
-
descriptionKey: "claudeDescription",
|
|
320
|
-
methods: ["browser"],
|
|
321
|
-
recommended: "browser",
|
|
322
|
-
},
|
|
323
|
-
];
|
|
324
|
-
|
|
325
|
-
type SettingsTabId = "accounts" | "capabilities" | "gateway" | "about";
|
|
326
|
-
type CopyField = "openai" | "anthropic" | "key";
|
|
327
|
-
|
|
328
|
-
const SETTINGS_TABS: readonly { id: SettingsTabId; label: GrokBuildSettingsKey }[] = [
|
|
329
|
-
{ id: "accounts", label: "tabAccounts" },
|
|
330
|
-
{ id: "gateway", label: "tabGateway" },
|
|
331
|
-
{ id: "capabilities", label: "tabCapabilities" },
|
|
332
|
-
{ id: "about", label: "tabAbout" },
|
|
333
|
-
];
|
|
334
|
-
|
|
335
|
-
export interface GrokBuildSettingsInjected {
|
|
336
|
-
t: (key: GrokBuildSettingsKey, params?: Record<string, unknown>) => string;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
export type GrokBuildSettingsProps = Partial<GrokBuildSettingsInjected>;
|
|
340
|
-
|
|
341
|
-
const pageStyle: CSSProperties = { display: "flex", flexDirection: "column", gap: 16, maxWidth: 780 };
|
|
342
|
-
const titleStyle: CSSProperties = {
|
|
343
|
-
margin: 0,
|
|
344
|
-
fontSize: 20,
|
|
345
|
-
lineHeight: "28px",
|
|
346
|
-
fontWeight: 600,
|
|
347
|
-
color: "var(--dsw-alias-label-primary)",
|
|
348
|
-
};
|
|
349
|
-
const bodyStyle: CSSProperties = {
|
|
350
|
-
margin: 0,
|
|
351
|
-
fontSize: 14,
|
|
352
|
-
lineHeight: "22px",
|
|
353
|
-
color: "var(--dsw-alias-label-secondary)",
|
|
354
|
-
};
|
|
355
|
-
const cardStyle: CSSProperties = {
|
|
356
|
-
display: "flex",
|
|
357
|
-
flexDirection: "column",
|
|
358
|
-
gap: 14,
|
|
359
|
-
padding: "18px 20px",
|
|
360
|
-
border: "1px solid var(--dsw-alias-border-l2)",
|
|
361
|
-
borderRadius: 12,
|
|
362
|
-
background: "var(--dsw-alias-bg-module-platform)",
|
|
363
|
-
};
|
|
364
|
-
const rowStyle: CSSProperties = {
|
|
365
|
-
display: "flex",
|
|
366
|
-
alignItems: "center",
|
|
367
|
-
justifyContent: "space-between",
|
|
368
|
-
flexWrap: "wrap",
|
|
369
|
-
gap: 12,
|
|
370
|
-
};
|
|
371
|
-
const statusStyle: CSSProperties = {
|
|
372
|
-
display: "flex",
|
|
373
|
-
alignItems: "center",
|
|
374
|
-
gap: 9,
|
|
375
|
-
fontSize: 14,
|
|
376
|
-
fontWeight: 500,
|
|
377
|
-
color: "var(--dsw-alias-label-primary)",
|
|
378
|
-
};
|
|
379
|
-
const buttonStyle: CSSProperties = {
|
|
380
|
-
boxSizing: "border-box",
|
|
381
|
-
minHeight: 34,
|
|
382
|
-
padding: "6px 14px",
|
|
383
|
-
border: "1px solid var(--dsw-alias-border-l4, rgba(127, 127, 127, 0.4))",
|
|
384
|
-
borderRadius: 18,
|
|
385
|
-
background: "var(--dsw-alias-button-elevated-fill, var(--dsw-alias-bg-layer-1))",
|
|
386
|
-
color: "var(--dsw-alias-label-primary)",
|
|
387
|
-
boxShadow: "0 1px 2px rgba(0, 0, 0, 0.18)",
|
|
388
|
-
font: "inherit",
|
|
389
|
-
fontSize: 14,
|
|
390
|
-
fontWeight: 500,
|
|
391
|
-
cursor: "pointer",
|
|
392
|
-
};
|
|
393
|
-
const primaryButtonStyle: CSSProperties = {
|
|
394
|
-
...buttonStyle,
|
|
395
|
-
borderColor: "#315fc7",
|
|
396
|
-
background: "#315fc7",
|
|
397
|
-
color: "#ffffff",
|
|
398
|
-
boxShadow: "0 1px 3px rgba(0, 0, 0, 0.28)",
|
|
399
|
-
fontWeight: 600,
|
|
400
|
-
};
|
|
401
|
-
const errorStyle: CSSProperties = { ...bodyStyle, color: "var(--dsw-alias-state-error-primary)" };
|
|
402
|
-
const warningStyle: CSSProperties = {
|
|
403
|
-
...bodyStyle,
|
|
404
|
-
padding: "10px 12px",
|
|
405
|
-
borderRadius: 8,
|
|
406
|
-
background: "var(--dsw-alias-bg-layer-1)",
|
|
407
|
-
};
|
|
408
|
-
const codeStyle: CSSProperties = {
|
|
409
|
-
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",
|
|
410
|
-
fontSize: 20,
|
|
411
|
-
letterSpacing: "0.08em",
|
|
412
|
-
fontWeight: 600,
|
|
413
|
-
color: "var(--dsw-alias-label-primary)",
|
|
414
|
-
};
|
|
415
|
-
const monoStyle: CSSProperties = { fontFamily: "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace" };
|
|
416
|
-
const linkStyle: CSSProperties = { color: "var(--dsw-alias-brand-primary)", wordBreak: "break-all" };
|
|
417
|
-
const listStyle: CSSProperties = {
|
|
418
|
-
display: "flex",
|
|
419
|
-
flexDirection: "column",
|
|
420
|
-
gap: 8,
|
|
421
|
-
margin: 0,
|
|
422
|
-
padding: 0,
|
|
423
|
-
listStyle: "none",
|
|
424
|
-
};
|
|
425
|
-
const checkRowStyle: CSSProperties = {
|
|
426
|
-
display: "flex",
|
|
427
|
-
alignItems: "flex-start",
|
|
428
|
-
gap: 8,
|
|
429
|
-
fontSize: 14,
|
|
430
|
-
color: "var(--dsw-alias-label-primary)",
|
|
431
|
-
};
|
|
432
|
-
const inputStyle: CSSProperties = {
|
|
433
|
-
boxSizing: "border-box",
|
|
434
|
-
width: "100%",
|
|
435
|
-
minHeight: 34,
|
|
436
|
-
padding: "6px 12px",
|
|
437
|
-
border: "1px solid var(--dsw-alias-border-l2)",
|
|
438
|
-
borderRadius: 8,
|
|
439
|
-
background: "var(--dsw-alias-bg-layer-1)",
|
|
440
|
-
color: "var(--dsw-alias-label-primary)",
|
|
441
|
-
font: "inherit",
|
|
442
|
-
fontSize: 13,
|
|
443
|
-
};
|
|
444
|
-
const nestedStyle: CSSProperties = {
|
|
445
|
-
display: "flex",
|
|
446
|
-
flexDirection: "column",
|
|
447
|
-
gap: 8,
|
|
448
|
-
padding: "12px 14px",
|
|
449
|
-
border: "1px solid var(--dsw-alias-border-l2)",
|
|
450
|
-
borderRadius: 8,
|
|
451
|
-
background: "var(--dsw-alias-bg-layer-1)",
|
|
452
|
-
};
|
|
453
|
-
const hintStyle: CSSProperties = { ...bodyStyle, fontSize: 13 };
|
|
454
|
-
const tabNavStyle: CSSProperties = {
|
|
455
|
-
display: "flex",
|
|
456
|
-
flexWrap: "wrap",
|
|
457
|
-
gap: 8,
|
|
458
|
-
};
|
|
459
|
-
const tabButtonStyle: CSSProperties = {
|
|
460
|
-
...buttonStyle,
|
|
461
|
-
borderRadius: 10,
|
|
462
|
-
};
|
|
463
|
-
const tabButtonActiveStyle: CSSProperties = {
|
|
464
|
-
...primaryButtonStyle,
|
|
465
|
-
borderRadius: 10,
|
|
466
|
-
};
|
|
467
|
-
const panelStyle: CSSProperties = {
|
|
468
|
-
display: "flex",
|
|
469
|
-
flexDirection: "column",
|
|
470
|
-
gap: 14,
|
|
471
|
-
minWidth: 0,
|
|
472
|
-
};
|
|
473
|
-
const accountGridStyle: CSSProperties = {
|
|
474
|
-
display: "grid",
|
|
475
|
-
gridTemplateColumns: "repeat(auto-fill, minmax(320px, 1fr))",
|
|
476
|
-
gap: 14,
|
|
477
|
-
};
|
|
478
|
-
const copyRowStyle: CSSProperties = {
|
|
479
|
-
display: "flex",
|
|
480
|
-
alignItems: "center",
|
|
481
|
-
justifyContent: "space-between",
|
|
482
|
-
flexWrap: "wrap",
|
|
483
|
-
gap: 8,
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
function dotStyle(
|
|
487
|
-
status: ProviderStatus["status"] | "loading" | "available" | "unavailable",
|
|
488
|
-
installed = true,
|
|
489
|
-
): CSSProperties {
|
|
490
|
-
const color = !installed
|
|
491
|
-
? "var(--dsw-alias-label-dimmed, #9aa0a6)"
|
|
492
|
-
: status === "signed-in" || status === "available"
|
|
493
|
-
? "var(--dsw-alias-state-success-primary, #22a06b)"
|
|
494
|
-
: status === "error"
|
|
495
|
-
? "var(--dsw-alias-state-error-primary, #d92d20)"
|
|
496
|
-
: status === "signing-in" || status === "loading"
|
|
497
|
-
? "var(--dsw-alias-brand-primary, #1677ff)"
|
|
498
|
-
: "var(--dsw-alias-label-dimmed, #9aa0a6)";
|
|
499
|
-
return { width: 9, height: 9, borderRadius: "50%", flex: "0 0 auto", background: color };
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
503
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
function optionalString(value: unknown): string | undefined {
|
|
507
|
-
return typeof value === "string" && value.length > 0 && value.length < 500 ? value : undefined;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
function optionalFiniteNumber(value: unknown): number | undefined {
|
|
511
|
-
return typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
function optionalBoolean(value: unknown): boolean | undefined {
|
|
515
|
-
return typeof value === "boolean" ? value : undefined;
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
function optionalPercent(value: unknown): number | undefined {
|
|
519
|
-
const numeric = optionalFiniteNumber(value);
|
|
520
|
-
return numeric !== undefined && numeric >= 0 && numeric <= 100 ? numeric : undefined;
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
function isSourceKind(value: string): value is SourceKind {
|
|
524
|
-
return (SOURCE_KINDS as readonly string[]).includes(value);
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
function isSourceReason(value: string): value is SourceReason {
|
|
528
|
-
return (SOURCE_REASONS as readonly string[]).includes(value);
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
function isSourceConflict(value: string): value is SourceConflict {
|
|
532
|
-
return (SOURCE_CONFLICTS as readonly string[]).includes(value);
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
function isSourcePreviewAction(value: string): value is SourcePreviewAction {
|
|
536
|
-
return (SOURCE_PREVIEW_ACTIONS as readonly string[]).includes(value);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
function isSourceCommitAction(value: string): value is SourceCommitAction {
|
|
540
|
-
return (SOURCE_COMMIT_ACTIONS as readonly string[]).includes(value);
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
function looksSecret(value: string): boolean {
|
|
544
|
-
return /eyJ[A-Za-z0-9_-]+\.|sk-[A-Za-z0-9_-]{8,}|Bearer\s+\S+/u.test(value);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
function safeDisplayPath(value: unknown, kind: SourceKind): string {
|
|
548
|
-
const text = optionalString(value);
|
|
549
|
-
if (text === undefined || looksSecret(text) || text.length > 180) return SOURCE_DEFAULT_PATH[kind];
|
|
550
|
-
return text;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
function safeWarning(value: unknown): string | undefined {
|
|
554
|
-
const text = optionalString(value);
|
|
555
|
-
if (text === undefined || looksSecret(text)) return undefined;
|
|
556
|
-
return text;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
function formatEpoch(value: number | undefined): string | undefined {
|
|
560
|
-
if (value === undefined || !Number.isFinite(value) || value <= 0) return undefined;
|
|
561
|
-
const ms = value > 1e12 ? value : value > 1e9 ? value * 1000 : undefined;
|
|
562
|
-
if (ms === undefined) return undefined;
|
|
563
|
-
const formatted = new Date(ms).toLocaleString();
|
|
564
|
-
return formatted.length > 0 ? formatted : undefined;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
function isPluginRequestError(error: unknown): error is PluginRequestError {
|
|
568
|
-
return error instanceof Error && error.name === "PluginRequestError" && "status" in error;
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
function isConflictError(error: unknown): boolean {
|
|
572
|
-
if (!isPluginRequestError(error)) {
|
|
573
|
-
return (
|
|
574
|
-
error instanceof Error && /SETTINGS_CONFLICT|settings-conflict|changed since it was read/iu.test(error.message)
|
|
575
|
-
);
|
|
576
|
-
}
|
|
577
|
-
return error.status === 409 || error.code === "SETTINGS_CONFLICT" || /conflict/iu.test(error.message);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
const CONSUMED_PREVIEW_CODES = new Set([
|
|
581
|
-
"preview_invalid",
|
|
582
|
-
"preview_expired",
|
|
583
|
-
"source_changed",
|
|
584
|
-
"destination_changed",
|
|
585
|
-
"confirm_required",
|
|
586
|
-
"unsafe_destination",
|
|
587
|
-
]);
|
|
588
|
-
|
|
589
|
-
function isConsumedPreviewError(error: unknown): boolean {
|
|
590
|
-
if (!isPluginRequestError(error)) return false;
|
|
591
|
-
if (error.code !== undefined && CONSUMED_PREVIEW_CODES.has(error.code)) return true;
|
|
592
|
-
return error.status === 404 || error.status === 410;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
function cancelPreviewTicket(previewId: string, keepalive = false): void {
|
|
596
|
-
void fetch(SOURCES_CANCEL_PATH, {
|
|
597
|
-
method: "POST",
|
|
598
|
-
headers: { accept: "application/json", "content-type": "application/json" },
|
|
599
|
-
credentials: "same-origin",
|
|
600
|
-
body: JSON.stringify({ previewId }),
|
|
601
|
-
...(keepalive ? { keepalive: true } : {}),
|
|
602
|
-
}).catch(() => undefined);
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
async function jsonRequest<T>(path: string, method = "GET", body?: unknown): Promise<T> {
|
|
606
|
-
const response = await fetch(path, {
|
|
607
|
-
method,
|
|
608
|
-
headers: { accept: "application/json", ...(body === undefined ? {} : { "content-type": "application/json" }) },
|
|
609
|
-
credentials: "same-origin",
|
|
610
|
-
...(body === undefined ? {} : { body: JSON.stringify(body) }),
|
|
611
|
-
});
|
|
612
|
-
const value: unknown = await response.json().catch(() => undefined);
|
|
613
|
-
if (!response.ok) {
|
|
614
|
-
const record = isRecord(value) ? value : undefined;
|
|
615
|
-
const message =
|
|
616
|
-
record !== undefined && typeof record["error"] === "string"
|
|
617
|
-
? record["error"]
|
|
618
|
-
: record !== undefined && typeof record["message"] === "string"
|
|
619
|
-
? record["message"]
|
|
620
|
-
: `HTTP ${response.status}`;
|
|
621
|
-
const code = record !== undefined && typeof record["code"] === "string" ? record["code"] : undefined;
|
|
622
|
-
const error = new Error(message) as PluginRequestError;
|
|
623
|
-
error.name = "PluginRequestError";
|
|
624
|
-
error.status = response.status;
|
|
625
|
-
if (code !== undefined) error.code = code;
|
|
626
|
-
throw error;
|
|
627
|
-
}
|
|
628
|
-
return value as T;
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
function parseSource(value: unknown): SourceStatus | undefined {
|
|
632
|
-
if (!isRecord(value) || typeof value["kind"] !== "string" || !isSourceKind(value["kind"])) return undefined;
|
|
633
|
-
const kind = value["kind"];
|
|
634
|
-
const reasonRaw = optionalString(value["reason"]);
|
|
635
|
-
const expiresAt = optionalFiniteNumber(value["expiresAt"]);
|
|
636
|
-
return {
|
|
637
|
-
kind,
|
|
638
|
-
displayPath: safeDisplayPath(value["displayPath"], kind),
|
|
639
|
-
available: value["available"] === true,
|
|
640
|
-
...(expiresAt === undefined ? {} : { expiresAt }),
|
|
641
|
-
...(reasonRaw !== undefined && isSourceReason(reasonRaw) ? { reason: reasonRaw } : {}),
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
function mergeSources(discovered: readonly SourceStatus[]): SourceStatus[] {
|
|
646
|
-
return SOURCE_KINDS.map((kind) => {
|
|
647
|
-
const found = discovered.find((entry) => entry.kind === kind);
|
|
648
|
-
return found ?? { kind, displayPath: SOURCE_DEFAULT_PATH[kind], available: false, reason: "missing" };
|
|
649
|
-
});
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
function parseSources(value: unknown): SourceStatus[] {
|
|
653
|
-
const rows = Array.isArray(value)
|
|
654
|
-
? value
|
|
655
|
-
: isRecord(value) && Array.isArray(value["sources"])
|
|
656
|
-
? value["sources"]
|
|
657
|
-
: [];
|
|
658
|
-
return mergeSources(rows.map(parseSource).filter((entry): entry is SourceStatus => entry !== undefined));
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
function parsePreview(value: unknown): SourcePreview | undefined {
|
|
662
|
-
if (!isRecord(value)) return undefined;
|
|
663
|
-
const previewId = optionalString(value["previewId"]);
|
|
664
|
-
const kindRaw = optionalString(value["kind"]);
|
|
665
|
-
if (previewId === undefined || kindRaw === undefined || !isSourceKind(kindRaw)) return undefined;
|
|
666
|
-
const conflictRaw = optionalString(value["conflict"]);
|
|
667
|
-
const actionRaw = optionalString(value["action"]);
|
|
668
|
-
const expiresAt = optionalFiniteNumber(value["expiresAt"]);
|
|
669
|
-
const ticketExpiresAt = optionalFiniteNumber(value["ticketExpiresAt"]);
|
|
670
|
-
const warnings = Array.isArray(value["warnings"])
|
|
671
|
-
? value["warnings"].map(safeWarning).filter((entry): entry is string => entry !== undefined)
|
|
672
|
-
: [];
|
|
673
|
-
return {
|
|
674
|
-
previewId,
|
|
675
|
-
kind: kindRaw,
|
|
676
|
-
displayPath: safeDisplayPath(value["displayPath"], kindRaw),
|
|
677
|
-
confirmOverwriteRequired: value["confirmOverwriteRequired"] === true,
|
|
678
|
-
warnings,
|
|
679
|
-
...(expiresAt === undefined ? {} : { expiresAt }),
|
|
680
|
-
...(ticketExpiresAt === undefined ? {} : { ticketExpiresAt }),
|
|
681
|
-
...(conflictRaw !== undefined && isSourceConflict(conflictRaw) ? { conflict: conflictRaw } : {}),
|
|
682
|
-
...(actionRaw !== undefined && isSourcePreviewAction(actionRaw) ? { action: actionRaw } : {}),
|
|
683
|
-
};
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
function parseCommitAction(value: unknown): SourceCommitAction | undefined {
|
|
687
|
-
if (!isRecord(value)) return undefined;
|
|
688
|
-
const action = optionalString(value["action"]);
|
|
689
|
-
return action !== undefined && isSourceCommitAction(action) ? action : undefined;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
function boundedInteger(value: unknown, min: number, max: number, fallback: number): number {
|
|
693
|
-
const numeric = optionalFiniteNumber(value);
|
|
694
|
-
return numeric !== undefined && Number.isInteger(numeric) && numeric >= min && numeric <= max ? numeric : fallback;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
function emptyCapabilitySettings(): CapabilitySettingsView {
|
|
698
|
-
return {
|
|
699
|
-
codexSearch: false,
|
|
700
|
-
codexImages: false,
|
|
701
|
-
codexImageEdits: false,
|
|
702
|
-
codexUsage: false,
|
|
703
|
-
codexFast: false,
|
|
704
|
-
grokImagineImage: false,
|
|
705
|
-
grokImagineVideo: false,
|
|
706
|
-
searchResults: 5,
|
|
707
|
-
imageCount: 1,
|
|
708
|
-
videoArtifactTtlMs: 7 * 24 * HOUR_MS,
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
function parseCapabilitySettings(value: unknown): CapabilitySettingsView {
|
|
713
|
-
const source = isRecord(value) ? value : {};
|
|
714
|
-
return {
|
|
715
|
-
codexSearch: source["codexSearch"] === true,
|
|
716
|
-
codexImages: source["codexImages"] === true,
|
|
717
|
-
codexImageEdits: source["codexImageEdits"] === true,
|
|
718
|
-
codexUsage: source["codexUsage"] === true,
|
|
719
|
-
codexFast: source["codexFast"] === true,
|
|
720
|
-
grokImagineImage: source["grokImagineImage"] === true,
|
|
721
|
-
grokImagineVideo: source["grokImagineVideo"] === true,
|
|
722
|
-
searchResults: boundedInteger(source["searchResults"], 1, 20, 5),
|
|
723
|
-
imageCount: boundedInteger(source["imageCount"], 1, 4, 1),
|
|
724
|
-
videoArtifactTtlMs: boundedInteger(source["videoArtifactTtlMs"], HOUR_MS, 7 * 24 * HOUR_MS, 7 * 24 * HOUR_MS),
|
|
725
|
-
};
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
function parseCapabilities(value: unknown): CapabilitySnapshot | undefined {
|
|
729
|
-
if (!isRecord(value)) return undefined;
|
|
730
|
-
const nested = isRecord(value["value"]) ? value["value"] : value;
|
|
731
|
-
const revision = optionalFiniteNumber(value["revision"]);
|
|
732
|
-
if (revision === undefined && !isRecord(value["value"]) && value["writable"] === undefined) return undefined;
|
|
733
|
-
return {
|
|
734
|
-
value: parseCapabilitySettings(nested),
|
|
735
|
-
revision: revision ?? 0,
|
|
736
|
-
writable: value["writable"] === true,
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
function parseUsageWindow(value: unknown): UsageWindowView | undefined {
|
|
741
|
-
if (!isRecord(value)) return undefined;
|
|
742
|
-
const usedPercent = optionalPercent(value["usedPercent"] ?? value["used_percent"]);
|
|
743
|
-
const remainingPercent = optionalPercent(value["remainingPercent"] ?? value["remaining_percent"]);
|
|
744
|
-
const windowSeconds = optionalFiniteNumber(value["windowSeconds"] ?? value["limit_window_seconds"]);
|
|
745
|
-
const resetsAt = optionalFiniteNumber(value["resetsAt"] ?? value["reset_at"]);
|
|
746
|
-
if (
|
|
747
|
-
usedPercent === undefined &&
|
|
748
|
-
remainingPercent === undefined &&
|
|
749
|
-
windowSeconds === undefined &&
|
|
750
|
-
resetsAt === undefined
|
|
751
|
-
) {
|
|
752
|
-
return undefined;
|
|
753
|
-
}
|
|
754
|
-
return {
|
|
755
|
-
...(usedPercent === undefined ? {} : { usedPercent }),
|
|
756
|
-
...(remainingPercent === undefined ? {} : { remainingPercent }),
|
|
757
|
-
...(windowSeconds !== undefined && windowSeconds > 0 ? { windowSeconds } : {}),
|
|
758
|
-
...(resetsAt === undefined ? {} : { resetsAt }),
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
function parseUsageLimit(value: unknown, fallbackId: string): UsageLimitView | undefined {
|
|
763
|
-
if (!isRecord(value)) return undefined;
|
|
764
|
-
const id = optionalString(value["id"]) ?? optionalString(value["metered_feature"]) ?? fallbackId;
|
|
765
|
-
const name = optionalString(value["name"]) ?? optionalString(value["limit_name"]);
|
|
766
|
-
const nested = isRecord(value["rate_limit"]) ? value["rate_limit"] : value;
|
|
767
|
-
const windows = Array.isArray(value["windows"])
|
|
768
|
-
? value["windows"].map(parseUsageWindow).filter((entry): entry is UsageWindowView => entry !== undefined)
|
|
769
|
-
: [
|
|
770
|
-
parseUsageWindow(nested["primary_window"]),
|
|
771
|
-
parseUsageWindow(nested["secondary_window"]),
|
|
772
|
-
parseUsageWindow(nested),
|
|
773
|
-
].filter((entry): entry is UsageWindowView => entry !== undefined);
|
|
774
|
-
if (windows.length === 0 && name === undefined && optionalString(value["id"]) === undefined) return undefined;
|
|
775
|
-
return { id, windows, ...(name === undefined ? {} : { name }) };
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
function parseUsage(value: unknown): UsageView | undefined {
|
|
779
|
-
if (!isRecord(value)) return undefined;
|
|
780
|
-
const payload = isRecord(value["usage"]) ? value["usage"] : value;
|
|
781
|
-
const rateLimits: UsageLimitView[] = [];
|
|
782
|
-
const seen = new Set<string>();
|
|
783
|
-
const add = (limit: UsageLimitView | undefined): void => {
|
|
784
|
-
if (limit === undefined || seen.has(limit.id)) return;
|
|
785
|
-
seen.add(limit.id);
|
|
786
|
-
rateLimits.push(limit);
|
|
787
|
-
};
|
|
788
|
-
if (Array.isArray(payload["rateLimits"])) {
|
|
789
|
-
payload["rateLimits"].forEach((entry, index) => add(parseUsageLimit(entry, `limit-${String(index)}`)));
|
|
790
|
-
} else {
|
|
791
|
-
add(parseUsageLimit(payload["rate_limit"], "codex"));
|
|
792
|
-
if (Array.isArray(payload["additional_rate_limits"])) {
|
|
793
|
-
payload["additional_rate_limits"].forEach((entry, index) =>
|
|
794
|
-
add(parseUsageLimit(entry, `extra-${String(index)}`)),
|
|
795
|
-
);
|
|
796
|
-
}
|
|
797
|
-
add(parseUsageLimit(payload["code_review_rate_limit"], "code_review"));
|
|
798
|
-
}
|
|
799
|
-
const credits = isRecord(payload["credits"]) ? payload["credits"] : undefined;
|
|
800
|
-
const spend = isRecord(payload["individualLimit"])
|
|
801
|
-
? payload["individualLimit"]
|
|
802
|
-
: isRecord(payload["spend_control"])
|
|
803
|
-
? isRecord(payload["spend_control"]["individual_limit"])
|
|
804
|
-
? payload["spend_control"]["individual_limit"]
|
|
805
|
-
: payload["spend_control"]
|
|
806
|
-
: undefined;
|
|
807
|
-
const resetRaw = isRecord(payload["resetCredits"])
|
|
808
|
-
? payload["resetCredits"]["availableCount"]
|
|
809
|
-
: isRecord(payload["rate_limit_reset_credits"])
|
|
810
|
-
? payload["rate_limit_reset_credits"]["available_count"]
|
|
811
|
-
: undefined;
|
|
812
|
-
const resetCredits = optionalFiniteNumber(resetRaw);
|
|
813
|
-
const fetchedAt = optionalFiniteNumber(payload["fetchedAt"]);
|
|
814
|
-
const spendControlReached =
|
|
815
|
-
optionalBoolean(payload["spendControlReached"]) ??
|
|
816
|
-
(isRecord(payload["spend_control"]) ? optionalBoolean(payload["spend_control"]["reached"]) : undefined);
|
|
817
|
-
const creditsBalance = credits === undefined ? undefined : optionalString(credits["balance"]);
|
|
818
|
-
const individualLimit = spend === undefined ? undefined : optionalString(spend["limit"]);
|
|
819
|
-
const individualUsed = spend === undefined ? undefined : optionalString(spend["used"]);
|
|
820
|
-
const individualRemaining = spend === undefined ? undefined : optionalString(spend["remaining"]);
|
|
821
|
-
const individualRemainingPercent =
|
|
822
|
-
spend === undefined ? undefined : optionalPercent(spend["remainingPercent"] ?? spend["remaining_percent"]);
|
|
823
|
-
const individualResetsAt =
|
|
824
|
-
spend === undefined ? undefined : optionalFiniteNumber(spend["resetsAt"] ?? spend["reset_at"]);
|
|
825
|
-
return {
|
|
826
|
-
rateLimits,
|
|
827
|
-
...(credits !== undefined && typeof credits["unlimited"] === "boolean"
|
|
828
|
-
? { creditsUnlimited: credits["unlimited"] }
|
|
829
|
-
: {}),
|
|
830
|
-
...(creditsBalance === undefined ? {} : { creditsBalance }),
|
|
831
|
-
...(individualLimit === undefined ? {} : { individualLimit }),
|
|
832
|
-
...(individualUsed === undefined ? {} : { individualUsed }),
|
|
833
|
-
...(individualRemaining === undefined ? {} : { individualRemaining }),
|
|
834
|
-
...(individualRemainingPercent === undefined ? {} : { individualRemainingPercent }),
|
|
835
|
-
...(individualResetsAt === undefined ? {} : { individualResetsAt }),
|
|
836
|
-
...(spendControlReached === undefined ? {} : { spendControlReached }),
|
|
837
|
-
...(resetCredits !== undefined && resetCredits >= 0 && Number.isSafeInteger(resetCredits) ? { resetCredits } : {}),
|
|
838
|
-
...(fetchedAt === undefined ? {} : { fetchedAt }),
|
|
839
|
-
};
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
function usageHasVisibleFields(usage: UsageView): boolean {
|
|
843
|
-
return (
|
|
844
|
-
usage.rateLimits.some((limit) => limit.windows.length > 0 || limit.name !== undefined) ||
|
|
845
|
-
usage.creditsUnlimited !== undefined ||
|
|
846
|
-
usage.creditsBalance !== undefined ||
|
|
847
|
-
usage.individualLimit !== undefined ||
|
|
848
|
-
usage.individualUsed !== undefined ||
|
|
849
|
-
usage.individualRemaining !== undefined ||
|
|
850
|
-
usage.individualRemainingPercent !== undefined ||
|
|
851
|
-
usage.spendControlReached === true ||
|
|
852
|
-
usage.resetCredits !== undefined
|
|
853
|
-
);
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
interface GatewayView {
|
|
857
|
-
enabled: boolean;
|
|
858
|
-
running: boolean;
|
|
859
|
-
bind: string;
|
|
860
|
-
port: number;
|
|
861
|
-
keyHint: string;
|
|
862
|
-
warning: string;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
function parseGateway(value: unknown): GatewayView | undefined {
|
|
866
|
-
if (!isRecord(value)) return undefined;
|
|
867
|
-
const bind = optionalString(value["bind"]);
|
|
868
|
-
const port = optionalFiniteNumber(value["port"]);
|
|
869
|
-
if (bind === undefined || port === undefined) return undefined;
|
|
870
|
-
return {
|
|
871
|
-
enabled: value["enabled"] === true,
|
|
872
|
-
running: value["running"] === true,
|
|
873
|
-
bind,
|
|
874
|
-
port,
|
|
875
|
-
keyHint: optionalString(value["keyHint"]) ?? "",
|
|
876
|
-
warning: optionalString(value["warning"]) ?? "",
|
|
877
|
-
};
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
function formatGatewayBaseUrl(bind: string, port: number): string {
|
|
881
|
-
const host = bind.includes(":") && !bind.startsWith("[") ? `[${bind}]` : bind;
|
|
882
|
-
return `http://${host}:${String(port)}`;
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
const GATEWAY_PORT_MIN = 1024;
|
|
886
|
-
const GATEWAY_PORT_MAX = 65_535;
|
|
887
|
-
const GATEWAY_RANDOM_PORT_MIN = 18_100;
|
|
888
|
-
const GATEWAY_RANDOM_PORT_MAX = 18_999;
|
|
889
|
-
const GATEWAY_RANDOM_RESERVED = new Set([22, 53, 3080, 7890, 9090, 18_080]);
|
|
890
|
-
|
|
891
|
-
function randomGatewayPort(exclude?: number): number {
|
|
892
|
-
for (let attempt = 0; attempt < 32; attempt += 1) {
|
|
893
|
-
const span = GATEWAY_RANDOM_PORT_MAX - GATEWAY_RANDOM_PORT_MIN + 1;
|
|
894
|
-
const candidate = GATEWAY_RANDOM_PORT_MIN + Math.floor(Math.random() * span);
|
|
895
|
-
if (candidate !== exclude && !GATEWAY_RANDOM_RESERVED.has(candidate)) return candidate;
|
|
896
|
-
}
|
|
897
|
-
return exclude === GATEWAY_RANDOM_PORT_MIN ? GATEWAY_RANDOM_PORT_MIN + 1 : GATEWAY_RANDOM_PORT_MIN;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
function parseGatewayPort(value: string): number | undefined {
|
|
901
|
-
const port = Number(value);
|
|
902
|
-
if (!Number.isInteger(port) || port < GATEWAY_PORT_MIN || port > GATEWAY_PORT_MAX) return undefined;
|
|
903
|
-
return port;
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
async function copyText(text: string): Promise<boolean> {
|
|
4
|
+
import { cancelPreviewTicket, copyText, isConflictError, isConsumedPreviewError, jsonRequest } from "./api.ts";
|
|
5
|
+
import { AboutTab } from "./components/AboutTab.tsx";
|
|
6
|
+
import { AccountsTab } from "./components/AccountsTab.tsx";
|
|
7
|
+
import { CapabilitiesTab } from "./components/CapabilitiesTab.tsx";
|
|
8
|
+
import { GatewayTab } from "./components/GatewayTab.tsx";
|
|
9
|
+
import { SettingsTabs } from "./components/SettingsTabs.tsx";
|
|
10
|
+
import {
|
|
11
|
+
CAPABILITIES_PATH,
|
|
12
|
+
CODEX_USAGE_PATH,
|
|
13
|
+
GATEWAY_PATH,
|
|
14
|
+
GATEWAY_REVEAL_PATH,
|
|
15
|
+
GATEWAY_ROTATE_PATH,
|
|
16
|
+
IMAGINE_CREDENTIAL_PATH,
|
|
17
|
+
LOGIN_CANCEL_PATH,
|
|
18
|
+
LOGIN_CODE_PATH,
|
|
19
|
+
LOGIN_PATH,
|
|
20
|
+
LOGOUT_PATH,
|
|
21
|
+
MODELS_PATH,
|
|
22
|
+
POLL_INTERVAL_MS,
|
|
23
|
+
SOURCE_COMMIT_ACTION_KEY,
|
|
24
|
+
SOURCES_CANCEL_PATH,
|
|
25
|
+
SOURCES_COMMIT_PATH,
|
|
26
|
+
SOURCES_PATH,
|
|
27
|
+
SOURCES_PREVIEW_PATH,
|
|
28
|
+
STATUS_PATH,
|
|
29
|
+
} from "./constants.ts";
|
|
30
|
+
import { isLikelyRemoteHost } from "./display.ts";
|
|
31
|
+
import {
|
|
32
|
+
emptyCapabilitySettings,
|
|
33
|
+
mergeSources,
|
|
34
|
+
parseCapabilities,
|
|
35
|
+
parseCommitAction,
|
|
36
|
+
parseGateway,
|
|
37
|
+
parseGatewayPort,
|
|
38
|
+
parseImagineCredential,
|
|
39
|
+
parsePreview,
|
|
40
|
+
parseSources,
|
|
41
|
+
parseUsage,
|
|
42
|
+
} from "./parsers.ts";
|
|
43
|
+
import { bodyStyle, errorStyle, pageStyle, panelStyle, titleStyle } from "./styles.ts";
|
|
44
|
+
import type {
|
|
45
|
+
CapabilitySettingKey,
|
|
46
|
+
CapabilitySettingsView,
|
|
47
|
+
CapabilitySnapshot,
|
|
48
|
+
CodingOAuthStatus,
|
|
49
|
+
CopyField,
|
|
50
|
+
GatewayView,
|
|
51
|
+
GrokBuildSettingsProps,
|
|
52
|
+
ImagineCredentialView,
|
|
53
|
+
LoginChallenge,
|
|
54
|
+
LoginMethod,
|
|
55
|
+
ProviderSlug,
|
|
56
|
+
SettingsTabId,
|
|
57
|
+
SourceKind,
|
|
58
|
+
SourcePreview,
|
|
59
|
+
SourceStatus,
|
|
60
|
+
UsageView,
|
|
61
|
+
} from "./types.ts";
|
|
62
|
+
|
|
63
|
+
export type { GrokBuildSettingsInjected, GrokBuildSettingsProps } from "./types.ts";
|
|
64
|
+
|
|
65
|
+
const REMOTE_TIP_STORAGE_KEY = "dsh-coding-oauth-remote-tip-dismissed";
|
|
66
|
+
|
|
67
|
+
function readRemoteTipDismissed(): boolean {
|
|
907
68
|
try {
|
|
908
|
-
|
|
909
|
-
await navigator.clipboard.writeText(text);
|
|
910
|
-
return true;
|
|
911
|
-
}
|
|
912
|
-
} catch {
|
|
913
|
-
// fall through to execCommand
|
|
914
|
-
}
|
|
915
|
-
try {
|
|
916
|
-
const area = document.createElement("textarea");
|
|
917
|
-
area.value = text;
|
|
918
|
-
area.setAttribute("readonly", "");
|
|
919
|
-
area.style.position = "fixed";
|
|
920
|
-
area.style.left = "-9999px";
|
|
921
|
-
document.body.appendChild(area);
|
|
922
|
-
area.select();
|
|
923
|
-
const ok = document.execCommand("copy");
|
|
924
|
-
document.body.removeChild(area);
|
|
925
|
-
return ok;
|
|
69
|
+
return globalThis.sessionStorage?.getItem(REMOTE_TIP_STORAGE_KEY) === "1";
|
|
926
70
|
} catch {
|
|
927
71
|
return false;
|
|
928
72
|
}
|
|
929
73
|
}
|
|
930
74
|
|
|
931
|
-
function parseImagineCredential(value: unknown): ImagineCredentialView | undefined {
|
|
932
|
-
if (!isRecord(value)) return undefined;
|
|
933
|
-
const configured = optionalBoolean(value["configured"]);
|
|
934
|
-
if (configured === undefined && value["source"] === undefined && value["writable"] === undefined) return undefined;
|
|
935
|
-
const source = optionalString(value["source"]);
|
|
936
|
-
const writable = optionalBoolean(value["writable"]);
|
|
937
|
-
return {
|
|
938
|
-
configured: configured === true,
|
|
939
|
-
...(source === undefined || looksSecret(source) ? {} : { source }),
|
|
940
|
-
...(writable === undefined ? {} : { writable }),
|
|
941
|
-
};
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
function imagineSourceLabel(source: string | undefined, t: GrokBuildSettingsInjected["t"]): string {
|
|
945
|
-
if (source === undefined) return t("imagineSourceUnknown");
|
|
946
|
-
const mapped = IMAGINE_SOURCE_KEY[source] ?? IMAGINE_SOURCE_KEY[source.toLowerCase()];
|
|
947
|
-
if (mapped !== undefined) return t(mapped);
|
|
948
|
-
if (source.length <= 40 && /^[a-z0-9._-]+$/iu.test(source) && !looksSecret(source)) return source;
|
|
949
|
-
return t("imagineSourceUnknown");
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
function methodLabel(method: LoginMethod, t: GrokBuildSettingsInjected["t"]): string {
|
|
953
|
-
if (method === "device") return t("deviceLogin");
|
|
954
|
-
if (method === "browser") return t("browserLogin");
|
|
955
|
-
return t("pkceLogin");
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
function modelFields(status: ProviderStatus): { available: string[]; selected: string[] } {
|
|
959
|
-
if (status.status !== "signed-in") return { available: [], selected: [] };
|
|
960
|
-
return {
|
|
961
|
-
available: "available" in status ? status.available : [],
|
|
962
|
-
selected: "selected" in status ? status.selected : [],
|
|
963
|
-
};
|
|
964
|
-
}
|
|
965
|
-
|
|
966
75
|
/** Multi-provider coding subscription status and OAuth actions. */
|
|
967
76
|
export function GrokBuildSettings({ t }: GrokBuildSettingsProps) {
|
|
968
77
|
if (t === undefined) throw new Error("Coding OAuth settings requires its translation function");
|
|
78
|
+
|
|
969
79
|
const [status, setStatus] = useState<CodingOAuthStatus | undefined>(undefined);
|
|
970
80
|
const [requestError, setRequestError] = useState<string | undefined>(undefined);
|
|
971
81
|
const [busyProvider, setBusyProvider] = useState<ProviderSlug | undefined>(undefined);
|
|
@@ -983,6 +93,7 @@ export function GrokBuildSettings({ t }: GrokBuildSettingsProps) {
|
|
|
983
93
|
const [capabilities, setCapabilities] = useState<CapabilitySnapshot | undefined>(undefined);
|
|
984
94
|
const [capabilitiesError, setCapabilitiesError] = useState<string | undefined>(undefined);
|
|
985
95
|
const [capabilitiesBusy, setCapabilitiesBusy] = useState(false);
|
|
96
|
+
const [capabilitiesLoaded, setCapabilitiesLoaded] = useState(false);
|
|
986
97
|
const [usage, setUsage] = useState<UsageView | undefined>(undefined);
|
|
987
98
|
const [usageError, setUsageError] = useState<string | undefined>(undefined);
|
|
988
99
|
const [usageLoading, setUsageLoading] = useState(false);
|
|
@@ -991,6 +102,7 @@ export function GrokBuildSettings({ t }: GrokBuildSettingsProps) {
|
|
|
991
102
|
const [gateway, setGateway] = useState<GatewayView | undefined>(undefined);
|
|
992
103
|
const [gatewayError, setGatewayError] = useState<string | undefined>(undefined);
|
|
993
104
|
const [gatewayBusy, setGatewayBusy] = useState(false);
|
|
105
|
+
const [gatewayLoaded, setGatewayLoaded] = useState(false);
|
|
994
106
|
const [gatewayOnceKey, setGatewayOnceKey] = useState<string | undefined>(undefined);
|
|
995
107
|
const [gatewayKeyVisible, setGatewayKeyVisible] = useState(false);
|
|
996
108
|
const [gatewayRotateConfirm, setGatewayRotateConfirm] = useState(false);
|
|
@@ -1000,7 +112,9 @@ export function GrokBuildSettings({ t }: GrokBuildSettingsProps) {
|
|
|
1000
112
|
const [copiedField, setCopiedField] = useState<CopyField | undefined>(undefined);
|
|
1001
113
|
const [copyFailedField, setCopyFailedField] = useState<CopyField | undefined>(undefined);
|
|
1002
114
|
const [expandedProviders, setExpandedProviders] = useState<Partial<Record<ProviderSlug, boolean>>>({});
|
|
115
|
+
const [remoteTipDismissed, setRemoteTipDismissed] = useState(readRemoteTipDismissed);
|
|
1003
116
|
const copiedTimerRef = useRef<number | undefined>(undefined);
|
|
117
|
+
const remote = typeof window !== "undefined" ? isLikelyRemoteHost(window.location.hostname) : false;
|
|
1004
118
|
|
|
1005
119
|
const refresh = useCallback(async () => {
|
|
1006
120
|
try {
|
|
@@ -1031,6 +145,8 @@ export function GrokBuildSettings({ t }: GrokBuildSettingsProps) {
|
|
|
1031
145
|
setCapabilities({ value: emptyCapabilitySettings(), revision: 0, writable: false });
|
|
1032
146
|
setCapabilitiesError(error instanceof Error ? error.message : t("capabilitiesLoadFailed"));
|
|
1033
147
|
return undefined;
|
|
148
|
+
} finally {
|
|
149
|
+
setCapabilitiesLoaded(true);
|
|
1034
150
|
}
|
|
1035
151
|
}, [t]);
|
|
1036
152
|
|
|
@@ -1040,6 +156,8 @@ export function GrokBuildSettings({ t }: GrokBuildSettingsProps) {
|
|
|
1040
156
|
setGatewayError(undefined);
|
|
1041
157
|
} catch (error: unknown) {
|
|
1042
158
|
setGatewayError(error instanceof Error ? error.message : t("gatewayLoadFailed"));
|
|
159
|
+
} finally {
|
|
160
|
+
setGatewayLoaded(true);
|
|
1043
161
|
}
|
|
1044
162
|
}, [t]);
|
|
1045
163
|
|
|
@@ -1065,16 +183,41 @@ export function GrokBuildSettings({ t }: GrokBuildSettingsProps) {
|
|
|
1065
183
|
}
|
|
1066
184
|
}, [t]);
|
|
1067
185
|
|
|
186
|
+
// Accounts: status immediately; sources shortly after (non-blocking for first paint).
|
|
1068
187
|
useEffect(() => {
|
|
1069
188
|
void refresh();
|
|
1070
|
-
|
|
1071
|
-
|
|
189
|
+
const timer = window.setTimeout(() => {
|
|
190
|
+
void refreshSources();
|
|
191
|
+
}, 0);
|
|
192
|
+
return () => {
|
|
193
|
+
window.clearTimeout(timer);
|
|
194
|
+
};
|
|
195
|
+
}, [refresh, refreshSources]);
|
|
196
|
+
|
|
197
|
+
// Capabilities tab: load settings + Imagine status on first visit.
|
|
198
|
+
useEffect(() => {
|
|
199
|
+
if (activeTab !== "capabilities") return;
|
|
200
|
+
if (!capabilitiesLoaded) void refreshCapabilities();
|
|
1072
201
|
void refreshImagine();
|
|
202
|
+
}, [activeTab, capabilitiesLoaded, refreshCapabilities, refreshImagine]);
|
|
203
|
+
|
|
204
|
+
// Soft-fetch capabilities when Codex is signed in so Accounts can show quota without opening the tab.
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
if (capabilitiesLoaded) return;
|
|
207
|
+
if (status?.providers.codex.status !== "signed-in") return;
|
|
208
|
+
void refreshCapabilities();
|
|
209
|
+
}, [capabilitiesLoaded, refreshCapabilities, status?.providers.codex.status]);
|
|
210
|
+
|
|
211
|
+
// Gateway: load only when that tab is opened.
|
|
212
|
+
useEffect(() => {
|
|
213
|
+
if (activeTab !== "gateway" || gatewayLoaded) return;
|
|
1073
214
|
void refreshGateway();
|
|
1074
|
-
}, [
|
|
215
|
+
}, [activeTab, gatewayLoaded, refreshGateway]);
|
|
216
|
+
|
|
1075
217
|
useEffect(() => {
|
|
1076
218
|
previewRef.current = preview;
|
|
1077
219
|
}, [preview]);
|
|
220
|
+
|
|
1078
221
|
useEffect(() => {
|
|
1079
222
|
mountedRef.current = true;
|
|
1080
223
|
return () => {
|
|
@@ -1085,6 +228,7 @@ export function GrokBuildSettings({ t }: GrokBuildSettingsProps) {
|
|
|
1085
228
|
if (active !== undefined) cancelPreviewTicket(active.previewId, true);
|
|
1086
229
|
};
|
|
1087
230
|
}, []);
|
|
231
|
+
|
|
1088
232
|
useEffect(() => {
|
|
1089
233
|
const signingIn =
|
|
1090
234
|
status !== undefined && Object.values(status.providers).some((provider) => provider.status === "signing-in");
|
|
@@ -1096,6 +240,7 @@ export function GrokBuildSettings({ t }: GrokBuildSettingsProps) {
|
|
|
1096
240
|
window.clearInterval(timer);
|
|
1097
241
|
};
|
|
1098
242
|
}, [refresh, status]);
|
|
243
|
+
|
|
1099
244
|
useEffect(() => {
|
|
1100
245
|
const signedIn = status?.providers.codex.status === "signed-in";
|
|
1101
246
|
if (capabilities?.value.codexUsage === true && signedIn) {
|
|
@@ -1107,6 +252,10 @@ export function GrokBuildSettings({ t }: GrokBuildSettingsProps) {
|
|
|
1107
252
|
setUsageLoading(false);
|
|
1108
253
|
}, [capabilities?.value.codexUsage, refreshUsage, status?.providers.codex.status]);
|
|
1109
254
|
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
if (gateway !== undefined) setPortDraft(String(gateway.port));
|
|
257
|
+
}, [gateway]);
|
|
258
|
+
|
|
1110
259
|
const signIn = async (provider: ProviderSlug, method: LoginMethod): Promise<void> => {
|
|
1111
260
|
const popup = window.open("about:blank", "_blank");
|
|
1112
261
|
if (popup !== null) popup.opener = null;
|
|
@@ -1315,12 +464,6 @@ export function GrokBuildSettings({ t }: GrokBuildSettingsProps) {
|
|
|
1315
464
|
setCopyFailedField(field);
|
|
1316
465
|
};
|
|
1317
466
|
|
|
1318
|
-
const copyLabel = (field: CopyField, idle?: string): string => {
|
|
1319
|
-
if (copiedField === field) return t("copied");
|
|
1320
|
-
if (copyFailedField === field) return t("copyFailed");
|
|
1321
|
-
return idle ?? t("copy");
|
|
1322
|
-
};
|
|
1323
|
-
|
|
1324
467
|
const ensureGatewayKey = async (): Promise<string> => {
|
|
1325
468
|
if (gatewayOnceKey !== undefined) return gatewayOnceKey;
|
|
1326
469
|
const value = await jsonRequest<{ apiKey?: string }>(GATEWAY_REVEAL_PATH, "POST");
|
|
@@ -1355,10 +498,6 @@ export function GrokBuildSettings({ t }: GrokBuildSettingsProps) {
|
|
|
1355
498
|
}
|
|
1356
499
|
};
|
|
1357
500
|
|
|
1358
|
-
useEffect(() => {
|
|
1359
|
-
if (gateway !== undefined) setPortDraft(String(gateway.port));
|
|
1360
|
-
}, [gateway]);
|
|
1361
|
-
|
|
1362
501
|
const applyGatewayPort = async (): Promise<void> => {
|
|
1363
502
|
const port = parseGatewayPort(portDraft);
|
|
1364
503
|
if (port === undefined) {
|
|
@@ -1394,827 +533,163 @@ export function GrokBuildSettings({ t }: GrokBuildSettingsProps) {
|
|
|
1394
533
|
}
|
|
1395
534
|
};
|
|
1396
535
|
|
|
536
|
+
const setGatewayEnabled = (enabled: boolean): void => {
|
|
537
|
+
setGatewayBusy(true);
|
|
538
|
+
void jsonRequest<unknown>(GATEWAY_PATH, "PATCH", { enabled })
|
|
539
|
+
.then((value) => {
|
|
540
|
+
setGateway(parseGateway(value) ?? gateway);
|
|
541
|
+
setGatewayError(undefined);
|
|
542
|
+
})
|
|
543
|
+
.catch((error: unknown) => {
|
|
544
|
+
setGatewayError(error instanceof Error ? error.message : t("gatewaySaveFailed"));
|
|
545
|
+
})
|
|
546
|
+
.finally(() => {
|
|
547
|
+
setGatewayBusy(false);
|
|
548
|
+
});
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
const dismissRemoteTip = (): void => {
|
|
552
|
+
setRemoteTipDismissed(true);
|
|
553
|
+
try {
|
|
554
|
+
globalThis.sessionStorage?.setItem(REMOTE_TIP_STORAGE_KEY, "1");
|
|
555
|
+
} catch {
|
|
556
|
+
// ignore storage failures
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
|
|
1397
560
|
return (
|
|
1398
561
|
<section style={pageStyle} aria-labelledby="coding-oauth-settings-title">
|
|
1399
562
|
<div>
|
|
1400
563
|
<h2 id="coding-oauth-settings-title" style={titleStyle}>
|
|
1401
564
|
{t("title")}
|
|
1402
565
|
</h2>
|
|
1403
|
-
<p style={{ ...bodyStyle, marginTop: 6 }}>{t("intro")}</p>
|
|
566
|
+
{activeTab === "accounts" ? <p style={{ ...bodyStyle, marginTop: 6 }}>{t("intro")}</p> : null}
|
|
1404
567
|
</div>
|
|
1405
568
|
{requestError === undefined ? null : (
|
|
1406
569
|
<p style={errorStyle} role="alert">
|
|
1407
570
|
{requestError}
|
|
1408
571
|
</p>
|
|
1409
572
|
)}
|
|
1410
|
-
<
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
onClick={() => {
|
|
1418
|
-
setActiveTab(tab.id);
|
|
1419
|
-
}}
|
|
1420
|
-
>
|
|
1421
|
-
{t(tab.label)}
|
|
1422
|
-
</button>
|
|
1423
|
-
))}
|
|
1424
|
-
</nav>
|
|
1425
|
-
<div style={panelStyle}>
|
|
573
|
+
<SettingsTabs t={t} activeTab={activeTab} onChange={setActiveTab} />
|
|
574
|
+
<div
|
|
575
|
+
id={`coding-oauth-panel-${activeTab}`}
|
|
576
|
+
role="tabpanel"
|
|
577
|
+
aria-labelledby={`coding-oauth-tab-${activeTab}`}
|
|
578
|
+
style={panelStyle}
|
|
579
|
+
>
|
|
1426
580
|
{activeTab === "accounts" ? (
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
<>
|
|
1486
|
-
<p style={{ ...bodyStyle, marginTop: 4 }}>{t(definition.descriptionKey)}</p>
|
|
1487
|
-
<p style={{ ...bodyStyle, marginTop: 4 }}>
|
|
1488
|
-
<span style={monoStyle}>{definition.route}</span>
|
|
1489
|
-
</p>
|
|
1490
|
-
</>
|
|
1491
|
-
)}
|
|
1492
|
-
</div>
|
|
1493
|
-
<div style={statusStyle} role="status">
|
|
1494
|
-
<span aria-hidden="true" style={dotStyle(providerStatus.status)} />
|
|
1495
|
-
<span>{statusLabel}</span>
|
|
1496
|
-
</div>
|
|
1497
|
-
</div>
|
|
1498
|
-
<div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
|
|
1499
|
-
{providerStatus.status === "signed-in" ? (
|
|
1500
|
-
<>
|
|
1501
|
-
<button
|
|
1502
|
-
type="button"
|
|
1503
|
-
style={buttonStyle}
|
|
1504
|
-
disabled={busy}
|
|
1505
|
-
onClick={() => {
|
|
1506
|
-
void signOut(definition.slug);
|
|
1507
|
-
}}
|
|
1508
|
-
>
|
|
1509
|
-
{busy ? t("working") : t("logout")}
|
|
1510
|
-
</button>
|
|
1511
|
-
<button
|
|
1512
|
-
type="button"
|
|
1513
|
-
style={buttonStyle}
|
|
1514
|
-
onClick={() => {
|
|
1515
|
-
setExpandedProviders((current) => ({
|
|
1516
|
-
...current,
|
|
1517
|
-
[definition.slug]: !expanded,
|
|
1518
|
-
}));
|
|
1519
|
-
}}
|
|
1520
|
-
>
|
|
1521
|
-
{expanded ? t("collapseModels") : t("expandModels")}
|
|
1522
|
-
</button>
|
|
1523
|
-
{source?.available === true ? (
|
|
1524
|
-
<button
|
|
1525
|
-
type="button"
|
|
1526
|
-
style={buttonStyle}
|
|
1527
|
-
disabled={sourcesBusy}
|
|
1528
|
-
onClick={() => {
|
|
1529
|
-
void previewSource(definition.slug);
|
|
1530
|
-
}}
|
|
1531
|
-
>
|
|
1532
|
-
{t("sourcesPullCopy")}
|
|
1533
|
-
</button>
|
|
1534
|
-
) : null}
|
|
1535
|
-
</>
|
|
1536
|
-
) : providerStatus.status === "signing-in" ? (
|
|
1537
|
-
<>
|
|
1538
|
-
{definition.methods
|
|
1539
|
-
.filter((method) => method !== activeMethod)
|
|
1540
|
-
.map((method) => (
|
|
1541
|
-
<button
|
|
1542
|
-
key={method}
|
|
1543
|
-
type="button"
|
|
1544
|
-
style={buttonStyle}
|
|
1545
|
-
disabled={busy}
|
|
1546
|
-
onClick={() => {
|
|
1547
|
-
void signIn(definition.slug, method);
|
|
1548
|
-
}}
|
|
1549
|
-
>
|
|
1550
|
-
{methodLabel(method, t)}
|
|
1551
|
-
</button>
|
|
1552
|
-
))}
|
|
1553
|
-
<button
|
|
1554
|
-
type="button"
|
|
1555
|
-
style={buttonStyle}
|
|
1556
|
-
disabled={busy}
|
|
1557
|
-
onClick={() => {
|
|
1558
|
-
void cancelLogin(definition.slug);
|
|
1559
|
-
}}
|
|
1560
|
-
>
|
|
1561
|
-
{t("cancelLogin")}
|
|
1562
|
-
</button>
|
|
1563
|
-
</>
|
|
1564
|
-
) : (
|
|
1565
|
-
<>
|
|
1566
|
-
{definition.methods.map((method, index) => (
|
|
1567
|
-
<button
|
|
1568
|
-
key={method}
|
|
1569
|
-
type="button"
|
|
1570
|
-
style={index === 0 ? primaryButtonStyle : buttonStyle}
|
|
1571
|
-
disabled={busy}
|
|
1572
|
-
onClick={() => {
|
|
1573
|
-
void signIn(definition.slug, method);
|
|
1574
|
-
}}
|
|
1575
|
-
>
|
|
1576
|
-
{busy ? t("working") : methodLabel(method, t)}
|
|
1577
|
-
</button>
|
|
1578
|
-
))}
|
|
1579
|
-
{source?.available === true ? (
|
|
1580
|
-
<button
|
|
1581
|
-
type="button"
|
|
1582
|
-
style={buttonStyle}
|
|
1583
|
-
disabled={sourcesBusy}
|
|
1584
|
-
onClick={() => {
|
|
1585
|
-
void previewSource(definition.slug);
|
|
1586
|
-
}}
|
|
1587
|
-
>
|
|
1588
|
-
{t("sourcesPullCopy")}
|
|
1589
|
-
</button>
|
|
1590
|
-
) : source !== undefined && source.reason !== undefined ? (
|
|
1591
|
-
<span style={hintStyle}>{t(SOURCE_REASON_KEY[source.reason])}</span>
|
|
1592
|
-
) : null}
|
|
1593
|
-
</>
|
|
1594
|
-
)}
|
|
1595
|
-
</div>
|
|
1596
|
-
{providerStatus.status === "error" ? <p style={errorStyle}>{providerStatus.message}</p> : null}
|
|
1597
|
-
{providerStatus.status === "signing-in" && providerStatus.userCode !== undefined ? (
|
|
1598
|
-
<p style={bodyStyle}>
|
|
1599
|
-
{t("userCode")} <span style={codeStyle}>{providerStatus.userCode}</span>
|
|
1600
|
-
</p>
|
|
1601
|
-
) : null}
|
|
1602
|
-
{providerStatus.status === "signing-in" && providerStatus.url !== undefined ? (
|
|
1603
|
-
<p style={bodyStyle}>
|
|
1604
|
-
{popupBlocked[definition.slug] === true ? t("popupBlocked") : t("openUrl")}{" "}
|
|
1605
|
-
<a href={providerStatus.url} target="_blank" rel="noreferrer" style={linkStyle}>
|
|
1606
|
-
{providerStatus.url}
|
|
1607
|
-
</a>
|
|
1608
|
-
</p>
|
|
1609
|
-
) : null}
|
|
1610
|
-
{providerStatus.status === "signing-in" && activeMethod !== "device" ? (
|
|
1611
|
-
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
1612
|
-
<p style={bodyStyle}>
|
|
1613
|
-
{t(activeMethod === "browser" ? "pasteBrowserCodeHint" : "pasteCodeHint")}
|
|
1614
|
-
</p>
|
|
1615
|
-
<div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
|
|
1616
|
-
<input
|
|
1617
|
-
style={{ ...inputStyle, flex: "1 1 360px" }}
|
|
1618
|
-
value={localCode}
|
|
1619
|
-
placeholder={t("pasteCodePlaceholder")}
|
|
1620
|
-
disabled={busy}
|
|
1621
|
-
onChange={(event) =>
|
|
1622
|
-
setCodeInputs((current) => ({ ...current, [definition.slug]: event.target.value }))
|
|
1623
|
-
}
|
|
1624
|
-
onKeyDown={(event) => {
|
|
1625
|
-
if (event.key === "Enter") {
|
|
1626
|
-
event.preventDefault();
|
|
1627
|
-
void submitCode(definition.slug);
|
|
1628
|
-
}
|
|
1629
|
-
}}
|
|
1630
|
-
/>
|
|
1631
|
-
<button
|
|
1632
|
-
type="button"
|
|
1633
|
-
style={primaryButtonStyle}
|
|
1634
|
-
disabled={busy || localCode.trim().length === 0}
|
|
1635
|
-
onClick={() => {
|
|
1636
|
-
void submitCode(definition.slug);
|
|
1637
|
-
}}
|
|
1638
|
-
>
|
|
1639
|
-
{t("submitCode")}
|
|
1640
|
-
</button>
|
|
1641
|
-
</div>
|
|
1642
|
-
</div>
|
|
1643
|
-
) : null}
|
|
1644
|
-
{providerStatus.status === "signed-in" && expanded ? (
|
|
1645
|
-
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
|
1646
|
-
<div style={rowStyle}>
|
|
1647
|
-
<h4 style={{ ...titleStyle, fontSize: 14 }}>{t("models")}</h4>
|
|
1648
|
-
<button
|
|
1649
|
-
type="button"
|
|
1650
|
-
style={buttonStyle}
|
|
1651
|
-
disabled={busy}
|
|
1652
|
-
onClick={() => {
|
|
1653
|
-
void saveModels(definition.slug, []);
|
|
1654
|
-
}}
|
|
1655
|
-
>
|
|
1656
|
-
{t("selectAll")}
|
|
1657
|
-
</button>
|
|
1658
|
-
</div>
|
|
1659
|
-
{grokProviderStatus?.status === "signed-in" ? (
|
|
1660
|
-
<p style={bodyStyle}>
|
|
1661
|
-
{grokProviderStatus.catalogSource === "live"
|
|
1662
|
-
? t("catalogLive")
|
|
1663
|
-
: grokProviderStatus.catalogSource === "cache"
|
|
1664
|
-
? t("catalogCache")
|
|
1665
|
-
: t("catalogFallback")}
|
|
1666
|
-
</p>
|
|
1667
|
-
) : null}
|
|
1668
|
-
<p style={bodyStyle}>
|
|
1669
|
-
{t("modelHint")} <span style={monoStyle}>{definition.route}/<id></span>
|
|
1670
|
-
</p>
|
|
1671
|
-
<ul style={listStyle}>
|
|
1672
|
-
{available.map((id) => {
|
|
1673
|
-
const checked = selected.includes(id);
|
|
1674
|
-
return (
|
|
1675
|
-
<li key={id}>
|
|
1676
|
-
<label style={checkRowStyle}>
|
|
1677
|
-
<input
|
|
1678
|
-
type="checkbox"
|
|
1679
|
-
checked={checked}
|
|
1680
|
-
disabled={busy}
|
|
1681
|
-
onChange={() => {
|
|
1682
|
-
const current = new Set(selected);
|
|
1683
|
-
if (checked) current.delete(id);
|
|
1684
|
-
else current.add(id);
|
|
1685
|
-
void saveModels(definition.slug, [...current]);
|
|
1686
|
-
}}
|
|
1687
|
-
/>
|
|
1688
|
-
<span style={monoStyle}>{id}</span>
|
|
1689
|
-
</label>
|
|
1690
|
-
</li>
|
|
1691
|
-
);
|
|
1692
|
-
})}
|
|
1693
|
-
</ul>
|
|
1694
|
-
{grokProviderStatus?.status === "signed-in" &&
|
|
1695
|
-
grokProviderStatus.catalogError !== undefined ? (
|
|
1696
|
-
<p style={errorStyle}>{t("catalogError")}</p>
|
|
1697
|
-
) : null}
|
|
1698
|
-
{definition.slug === "codex" && showUsage ? (
|
|
1699
|
-
<div style={nestedStyle}>
|
|
1700
|
-
<p style={{ ...bodyStyle, color: "var(--dsw-alias-label-primary)" }}>{t("usageTitle")}</p>
|
|
1701
|
-
{usageError === undefined ? null : (
|
|
1702
|
-
<p style={errorStyle} role="alert">
|
|
1703
|
-
{usageError}
|
|
1704
|
-
</p>
|
|
1705
|
-
)}
|
|
1706
|
-
{usageLoading && usage === undefined ? (
|
|
1707
|
-
<p style={hintStyle}>{t("usageLoading")}</p>
|
|
1708
|
-
) : usage === undefined || !usageHasVisibleFields(usage) ? (
|
|
1709
|
-
<p style={hintStyle}>{t("usageEmpty")}</p>
|
|
1710
|
-
) : (
|
|
1711
|
-
<>
|
|
1712
|
-
{formatEpoch(usage.fetchedAt) === undefined ? null : (
|
|
1713
|
-
<p style={hintStyle}>
|
|
1714
|
-
{t("usageFetchedAt", { time: formatEpoch(usage.fetchedAt) })}
|
|
1715
|
-
</p>
|
|
1716
|
-
)}
|
|
1717
|
-
{usage.rateLimits.map((limit) => (
|
|
1718
|
-
<p key={limit.id} style={hintStyle}>
|
|
1719
|
-
{limit.name ?? t("usageRateLimit")}
|
|
1720
|
-
{limit.windows[0]?.usedPercent === undefined
|
|
1721
|
-
? ""
|
|
1722
|
-
: ` · ${t("usageUsed", { value: `${String(limit.windows[0].usedPercent)}%` })}`}
|
|
1723
|
-
{formatEpoch(limit.windows[0]?.resetsAt) === undefined
|
|
1724
|
-
? ""
|
|
1725
|
-
: ` · ${t("usageResets", { time: formatEpoch(limit.windows[0]?.resetsAt) })}`}
|
|
1726
|
-
</p>
|
|
1727
|
-
))}
|
|
1728
|
-
</>
|
|
1729
|
-
)}
|
|
1730
|
-
</div>
|
|
1731
|
-
) : null}
|
|
1732
|
-
</div>
|
|
1733
|
-
) : null}
|
|
1734
|
-
</div>
|
|
1735
|
-
);
|
|
1736
|
-
})}
|
|
1737
|
-
</div>
|
|
1738
|
-
{preview === undefined ? null : (
|
|
1739
|
-
<div style={cardStyle} aria-live="polite">
|
|
1740
|
-
<p style={bodyStyle}>
|
|
1741
|
-
{t("sourcesPreviewTitle")} · {t(SOURCE_KIND_KEY[preview.kind])}
|
|
1742
|
-
</p>
|
|
1743
|
-
<p style={hintStyle}>
|
|
1744
|
-
<span style={monoStyle}>{preview.displayPath}</span>
|
|
1745
|
-
</p>
|
|
1746
|
-
<p style={bodyStyle}>
|
|
1747
|
-
{t("sourcesConflict", {
|
|
1748
|
-
detail: t(
|
|
1749
|
-
preview.conflict === undefined
|
|
1750
|
-
? "sourceConflictUnrecognized"
|
|
1751
|
-
: SOURCE_CONFLICT_KEY[preview.conflict],
|
|
1752
|
-
),
|
|
1753
|
-
})}
|
|
1754
|
-
</p>
|
|
1755
|
-
<p style={bodyStyle}>
|
|
1756
|
-
{t("sourcesAction", {
|
|
1757
|
-
detail: t(
|
|
1758
|
-
preview.action === undefined
|
|
1759
|
-
? "sourceActionUnrecognized"
|
|
1760
|
-
: SOURCE_PREVIEW_ACTION_KEY[preview.action],
|
|
1761
|
-
),
|
|
1762
|
-
})}
|
|
1763
|
-
</p>
|
|
1764
|
-
{formatEpoch(preview.expiresAt) === undefined ? null : (
|
|
1765
|
-
<p style={hintStyle}>{t("sourcesPreviewExpires", { time: formatEpoch(preview.expiresAt) })}</p>
|
|
1766
|
-
)}
|
|
1767
|
-
{formatEpoch(preview.ticketExpiresAt) === undefined ? null : (
|
|
1768
|
-
<p style={hintStyle}>{t("sourcesTicketExpires", { time: formatEpoch(preview.ticketExpiresAt) })}</p>
|
|
1769
|
-
)}
|
|
1770
|
-
{preview.warnings.length === 0 ? null : (
|
|
1771
|
-
<ul style={{ ...listStyle, gap: 4 }} aria-label={t("sourcesWarnings")}>
|
|
1772
|
-
{preview.warnings.map((warning) => (
|
|
1773
|
-
<li key={warning} style={hintStyle}>
|
|
1774
|
-
{warning}
|
|
1775
|
-
</li>
|
|
1776
|
-
))}
|
|
1777
|
-
</ul>
|
|
1778
|
-
)}
|
|
1779
|
-
{preview.confirmOverwriteRequired ? (
|
|
1780
|
-
<label style={checkRowStyle}>
|
|
1781
|
-
<input
|
|
1782
|
-
type="checkbox"
|
|
1783
|
-
checked={confirmOverwrite}
|
|
1784
|
-
disabled={sourcesBusy || preview.action === "blocked"}
|
|
1785
|
-
onChange={(event) => setConfirmOverwrite(event.target.checked)}
|
|
1786
|
-
/>
|
|
1787
|
-
<span>
|
|
1788
|
-
{t("sourcesConfirmOverwrite")}
|
|
1789
|
-
<span style={{ display: "block", ...hintStyle }}>{t("sourcesConfirmOverwriteHint")}</span>
|
|
1790
|
-
</span>
|
|
1791
|
-
</label>
|
|
1792
|
-
) : null}
|
|
1793
|
-
<div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
|
|
1794
|
-
<button
|
|
1795
|
-
type="button"
|
|
1796
|
-
style={primaryButtonStyle}
|
|
1797
|
-
disabled={
|
|
1798
|
-
sourcesBusy ||
|
|
1799
|
-
preview.action === "blocked" ||
|
|
1800
|
-
(preview.confirmOverwriteRequired && !confirmOverwrite)
|
|
1801
|
-
}
|
|
1802
|
-
onClick={() => {
|
|
1803
|
-
void commitSource();
|
|
1804
|
-
}}
|
|
1805
|
-
>
|
|
1806
|
-
{t("sourcesCommit")}
|
|
1807
|
-
</button>
|
|
1808
|
-
<button
|
|
1809
|
-
type="button"
|
|
1810
|
-
style={buttonStyle}
|
|
1811
|
-
disabled={sourcesBusy}
|
|
1812
|
-
onClick={() => {
|
|
1813
|
-
void cancelSourcePreview();
|
|
1814
|
-
}}
|
|
1815
|
-
>
|
|
1816
|
-
{t("sourcesCancelPreview")}
|
|
1817
|
-
</button>
|
|
1818
|
-
</div>
|
|
1819
|
-
</div>
|
|
1820
|
-
)}
|
|
1821
|
-
</>
|
|
1822
|
-
)
|
|
581
|
+
<AccountsTab
|
|
582
|
+
t={t}
|
|
583
|
+
status={status}
|
|
584
|
+
remote={remote}
|
|
585
|
+
remoteTipDismissed={remoteTipDismissed}
|
|
586
|
+
onDismissRemoteTip={dismissRemoteTip}
|
|
587
|
+
sources={sources}
|
|
588
|
+
sourcesError={sourcesError}
|
|
589
|
+
sourcesNotice={sourcesNotice}
|
|
590
|
+
sourcesBusy={sourcesBusy}
|
|
591
|
+
preview={preview}
|
|
592
|
+
confirmOverwrite={confirmOverwrite}
|
|
593
|
+
busyProvider={busyProvider}
|
|
594
|
+
codeInputs={codeInputs}
|
|
595
|
+
popupBlocked={popupBlocked}
|
|
596
|
+
expandedProviders={expandedProviders}
|
|
597
|
+
showUsage={showUsage === true}
|
|
598
|
+
usage={usage}
|
|
599
|
+
usageError={usageError}
|
|
600
|
+
usageLoading={usageLoading}
|
|
601
|
+
onSignIn={(slug, method) => {
|
|
602
|
+
void signIn(slug, method);
|
|
603
|
+
}}
|
|
604
|
+
onSignOut={(slug) => {
|
|
605
|
+
void signOut(slug);
|
|
606
|
+
}}
|
|
607
|
+
onCancelLogin={(slug) => {
|
|
608
|
+
void cancelLogin(slug);
|
|
609
|
+
}}
|
|
610
|
+
onSubmitCode={(slug) => {
|
|
611
|
+
void submitCode(slug);
|
|
612
|
+
}}
|
|
613
|
+
onCodeChange={(slug, value) => {
|
|
614
|
+
setCodeInputs((current) => ({ ...current, [slug]: value }));
|
|
615
|
+
}}
|
|
616
|
+
onToggleExpanded={(slug) => {
|
|
617
|
+
setExpandedProviders((current) => ({
|
|
618
|
+
...current,
|
|
619
|
+
[slug]: !(current[slug] === true),
|
|
620
|
+
}));
|
|
621
|
+
}}
|
|
622
|
+
onPreviewSource={(slug) => {
|
|
623
|
+
void previewSource(slug);
|
|
624
|
+
}}
|
|
625
|
+
onSaveModels={(slug, selected) => {
|
|
626
|
+
void saveModels(slug, selected);
|
|
627
|
+
}}
|
|
628
|
+
onConfirmOverwriteChange={setConfirmOverwrite}
|
|
629
|
+
onCommitSource={() => {
|
|
630
|
+
void commitSource();
|
|
631
|
+
}}
|
|
632
|
+
onCancelSourcePreview={() => {
|
|
633
|
+
void cancelSourcePreview();
|
|
634
|
+
}}
|
|
635
|
+
onRefreshSources={() => {
|
|
636
|
+
void refreshSources();
|
|
637
|
+
}}
|
|
638
|
+
/>
|
|
1823
639
|
) : null}
|
|
1824
640
|
{activeTab === "capabilities" ? (
|
|
1825
|
-
<
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
<p style={errorStyle} role="alert">
|
|
1835
|
-
{imagineError}
|
|
1836
|
-
</p>
|
|
1837
|
-
)}
|
|
1838
|
-
{imagine === undefined && imagineError === undefined ? (
|
|
1839
|
-
<div style={statusStyle} role="status">
|
|
1840
|
-
<span aria-hidden="true" style={dotStyle("loading")} />
|
|
1841
|
-
{t("imagineLoading")}
|
|
1842
|
-
</div>
|
|
1843
|
-
) : imagine === undefined ? null : (
|
|
1844
|
-
<div style={nestedStyle}>
|
|
1845
|
-
<p style={statusStyle} role="status">
|
|
1846
|
-
<span aria-hidden="true" style={dotStyle(imagine.configured ? "available" : "unavailable")} />
|
|
1847
|
-
<span>{imagine.configured ? t("imagineConfigured") : t("imagineNotConfigured")}</span>
|
|
1848
|
-
</p>
|
|
1849
|
-
<p style={hintStyle}>{t("imagineSource", { source: imagineSourceLabel(imagine.source, t) })}</p>
|
|
1850
|
-
</div>
|
|
1851
|
-
)}
|
|
1852
|
-
{capabilitiesError === undefined ? null : (
|
|
1853
|
-
<p style={errorStyle} role="alert">
|
|
1854
|
-
{capabilitiesError}
|
|
1855
|
-
</p>
|
|
1856
|
-
)}
|
|
1857
|
-
{capabilities === undefined ? (
|
|
1858
|
-
<div style={statusStyle} role="status">
|
|
1859
|
-
<span aria-hidden="true" style={dotStyle("loading")} />
|
|
1860
|
-
{t("capabilitiesLoading")}
|
|
1861
|
-
</div>
|
|
1862
|
-
) : (
|
|
1863
|
-
<fieldset style={{ border: 0, margin: 0, padding: 0, minWidth: 0 }}>
|
|
1864
|
-
<legend style={{ ...bodyStyle, position: "absolute", width: 1, height: 1, overflow: "hidden" }}>
|
|
1865
|
-
{t("capabilitiesTitle")}
|
|
1866
|
-
</legend>
|
|
1867
|
-
{capabilities.writable ? null : <p style={hintStyle}>{t("capabilitiesReadOnly")}</p>}
|
|
1868
|
-
<ul style={listStyle}>
|
|
1869
|
-
{CAPABILITY_TOGGLES.filter((item) => !item.key.startsWith("grokImagine")).map((item) => {
|
|
1870
|
-
const checked = capabilities.value[item.key];
|
|
1871
|
-
const imagesOff = item.requiresImages === true && !capabilities.value.codexImages;
|
|
1872
|
-
const disabled = capabilitiesBusy || !capabilities.writable || imagesOff;
|
|
1873
|
-
return (
|
|
1874
|
-
<li key={item.key}>
|
|
1875
|
-
<label style={checkRowStyle}>
|
|
1876
|
-
<input
|
|
1877
|
-
type="checkbox"
|
|
1878
|
-
checked={checked}
|
|
1879
|
-
disabled={disabled}
|
|
1880
|
-
aria-describedby={`cap-hint-${item.key}`}
|
|
1881
|
-
onChange={(event) => {
|
|
1882
|
-
void patchCapability(item.key, event.target.checked);
|
|
1883
|
-
}}
|
|
1884
|
-
/>
|
|
1885
|
-
<span>
|
|
1886
|
-
<span style={{ display: "block" }}>{t(item.label)}</span>
|
|
1887
|
-
<span id={`cap-hint-${item.key}`} style={{ display: "block", ...hintStyle }}>
|
|
1888
|
-
{t(item.hint)}
|
|
1889
|
-
</span>
|
|
1890
|
-
</span>
|
|
1891
|
-
</label>
|
|
1892
|
-
</li>
|
|
1893
|
-
);
|
|
1894
|
-
})}
|
|
1895
|
-
</ul>
|
|
1896
|
-
<h4 style={{ ...titleStyle, fontSize: 14 }}>{t("imagineTitle")}</h4>
|
|
1897
|
-
<ul style={listStyle}>
|
|
1898
|
-
{CAPABILITY_TOGGLES.filter((item) => item.key.startsWith("grokImagine")).map((item) => {
|
|
1899
|
-
const checked = capabilities.value[item.key];
|
|
1900
|
-
const disabled = capabilitiesBusy || !capabilities.writable;
|
|
1901
|
-
return (
|
|
1902
|
-
<li key={item.key}>
|
|
1903
|
-
<label style={checkRowStyle}>
|
|
1904
|
-
<input
|
|
1905
|
-
type="checkbox"
|
|
1906
|
-
checked={checked}
|
|
1907
|
-
disabled={disabled}
|
|
1908
|
-
aria-describedby={`cap-hint-${item.key}`}
|
|
1909
|
-
onChange={(event) => {
|
|
1910
|
-
void patchCapability(item.key, event.target.checked);
|
|
1911
|
-
}}
|
|
1912
|
-
/>
|
|
1913
|
-
<span>
|
|
1914
|
-
<span style={{ display: "block" }}>{t(item.label)}</span>
|
|
1915
|
-
<span id={`cap-hint-${item.key}`} style={{ display: "block", ...hintStyle }}>
|
|
1916
|
-
{t(item.hint)}
|
|
1917
|
-
</span>
|
|
1918
|
-
</span>
|
|
1919
|
-
</label>
|
|
1920
|
-
</li>
|
|
1921
|
-
);
|
|
1922
|
-
})}
|
|
1923
|
-
</ul>
|
|
1924
|
-
<div style={nestedStyle}>
|
|
1925
|
-
<h4 style={{ ...titleStyle, fontSize: 14 }}>{t("capabilityLimitsTitle")}</h4>
|
|
1926
|
-
<p style={hintStyle}>{t("capabilityLimitsHint")}</p>
|
|
1927
|
-
<ul style={listStyle}>
|
|
1928
|
-
{CAPABILITY_LIMITS.map((item) => {
|
|
1929
|
-
const displayValue = capabilities.value[item.key] / item.scale;
|
|
1930
|
-
const inputId = `cap-limit-${item.key}`;
|
|
1931
|
-
return (
|
|
1932
|
-
<li key={item.key} style={rowStyle}>
|
|
1933
|
-
<label htmlFor={inputId} style={{ ...bodyStyle, flex: "1 1 360px" }}>
|
|
1934
|
-
<span style={{ display: "block", color: "var(--dsw-alias-label-primary)" }}>
|
|
1935
|
-
{t(item.label)}
|
|
1936
|
-
</span>
|
|
1937
|
-
<span id={`${inputId}-hint`} style={{ display: "block", ...hintStyle }}>
|
|
1938
|
-
{t(item.hint)}
|
|
1939
|
-
</span>
|
|
1940
|
-
</label>
|
|
1941
|
-
<input
|
|
1942
|
-
key={`${item.key}-${String(capabilities.revision)}-${String(displayValue)}`}
|
|
1943
|
-
id={inputId}
|
|
1944
|
-
type="number"
|
|
1945
|
-
inputMode="numeric"
|
|
1946
|
-
min={item.min}
|
|
1947
|
-
max={item.max}
|
|
1948
|
-
step={1}
|
|
1949
|
-
defaultValue={displayValue}
|
|
1950
|
-
disabled={capabilitiesBusy || !capabilities.writable}
|
|
1951
|
-
aria-describedby={`${inputId}-hint`}
|
|
1952
|
-
style={{ ...inputStyle, width: 112, flex: "0 0 112px" }}
|
|
1953
|
-
onInput={(event) => event.currentTarget.setCustomValidity("")}
|
|
1954
|
-
onKeyDown={(event) => {
|
|
1955
|
-
if (event.key === "Enter") event.currentTarget.blur();
|
|
1956
|
-
if (event.key === "Escape") {
|
|
1957
|
-
event.currentTarget.value = String(displayValue);
|
|
1958
|
-
event.currentTarget.setCustomValidity("");
|
|
1959
|
-
}
|
|
1960
|
-
}}
|
|
1961
|
-
onBlur={(event) => {
|
|
1962
|
-
const target = event.currentTarget;
|
|
1963
|
-
const next = Number(target.value);
|
|
1964
|
-
if (!Number.isInteger(next) || next < item.min || next > item.max) {
|
|
1965
|
-
target.setCustomValidity(t("capabilityLimitInvalid", { min: item.min, max: item.max }));
|
|
1966
|
-
target.reportValidity();
|
|
1967
|
-
return;
|
|
1968
|
-
}
|
|
1969
|
-
target.setCustomValidity("");
|
|
1970
|
-
const apiValue = next * item.scale;
|
|
1971
|
-
if (apiValue === capabilities.value[item.key]) return;
|
|
1972
|
-
void patchCapability(item.key, apiValue).then((saved) => {
|
|
1973
|
-
if (!saved && target.isConnected) target.value = String(displayValue);
|
|
1974
|
-
});
|
|
1975
|
-
}}
|
|
1976
|
-
/>
|
|
1977
|
-
</li>
|
|
1978
|
-
);
|
|
1979
|
-
})}
|
|
1980
|
-
</ul>
|
|
1981
|
-
</div>
|
|
1982
|
-
</fieldset>
|
|
1983
|
-
)}
|
|
1984
|
-
</section>
|
|
641
|
+
<CapabilitiesTab
|
|
642
|
+
t={t}
|
|
643
|
+
capabilities={capabilities}
|
|
644
|
+
capabilitiesError={capabilitiesError}
|
|
645
|
+
capabilitiesBusy={capabilitiesBusy}
|
|
646
|
+
imagine={imagine}
|
|
647
|
+
imagineError={imagineError}
|
|
648
|
+
onPatchCapability={(key, value) => patchCapability(key, value)}
|
|
649
|
+
/>
|
|
1985
650
|
) : null}
|
|
1986
|
-
{activeTab === "about" ? <p style={warningStyle}>{t("termsWarning")}</p> : null}
|
|
1987
651
|
{activeTab === "gateway" ? (
|
|
1988
|
-
<
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
{
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
{
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
)
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
)
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
setGateway(parseGateway(value) ?? gateway);
|
|
2028
|
-
setGatewayError(undefined);
|
|
2029
|
-
})
|
|
2030
|
-
.catch((error: unknown) => {
|
|
2031
|
-
setGatewayError(error instanceof Error ? error.message : t("gatewaySaveFailed"));
|
|
2032
|
-
})
|
|
2033
|
-
.finally(() => setGatewayBusy(false));
|
|
2034
|
-
}}
|
|
2035
|
-
/>
|
|
2036
|
-
<span>{t("gatewayEnabled")}</span>
|
|
2037
|
-
</label>
|
|
2038
|
-
<div>
|
|
2039
|
-
<label
|
|
2040
|
-
htmlFor="coding-oauth-gateway-port"
|
|
2041
|
-
style={{ ...bodyStyle, color: "var(--dsw-alias-label-primary)" }}
|
|
2042
|
-
>
|
|
2043
|
-
{t("gatewayPort")}
|
|
2044
|
-
</label>
|
|
2045
|
-
<p id="coding-oauth-gateway-port-hint" style={hintStyle}>
|
|
2046
|
-
{t("gatewayPortHint")}
|
|
2047
|
-
</p>
|
|
2048
|
-
<div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginTop: 8 }}>
|
|
2049
|
-
<input
|
|
2050
|
-
id="coding-oauth-gateway-port"
|
|
2051
|
-
type="number"
|
|
2052
|
-
inputMode="numeric"
|
|
2053
|
-
min={GATEWAY_PORT_MIN}
|
|
2054
|
-
max={GATEWAY_PORT_MAX}
|
|
2055
|
-
step={1}
|
|
2056
|
-
value={portDraft}
|
|
2057
|
-
disabled={gatewayBusy}
|
|
2058
|
-
aria-describedby="coding-oauth-gateway-port-hint"
|
|
2059
|
-
style={{ ...inputStyle, width: 112, flex: "0 0 112px" }}
|
|
2060
|
-
onChange={(event) => setPortDraft(event.target.value)}
|
|
2061
|
-
onKeyDown={(event) => {
|
|
2062
|
-
if (event.key === "Enter") {
|
|
2063
|
-
event.preventDefault();
|
|
2064
|
-
void applyGatewayPort();
|
|
2065
|
-
}
|
|
2066
|
-
if (event.key === "Escape" && gateway !== undefined) {
|
|
2067
|
-
setPortDraft(String(gateway.port));
|
|
2068
|
-
}
|
|
2069
|
-
}}
|
|
2070
|
-
/>
|
|
2071
|
-
<button
|
|
2072
|
-
type="button"
|
|
2073
|
-
style={primaryButtonStyle}
|
|
2074
|
-
disabled={
|
|
2075
|
-
gatewayBusy || portDraft === String(gateway.port) || parseGatewayPort(portDraft) === undefined
|
|
2076
|
-
}
|
|
2077
|
-
onClick={() => {
|
|
2078
|
-
void applyGatewayPort();
|
|
2079
|
-
}}
|
|
2080
|
-
>
|
|
2081
|
-
{t("gatewayPortApply")}
|
|
2082
|
-
</button>
|
|
2083
|
-
<button
|
|
2084
|
-
type="button"
|
|
2085
|
-
style={buttonStyle}
|
|
2086
|
-
disabled={gatewayBusy}
|
|
2087
|
-
onClick={() => {
|
|
2088
|
-
setPortDraft(String(randomGatewayPort(gateway.port)));
|
|
2089
|
-
}}
|
|
2090
|
-
>
|
|
2091
|
-
{t("gatewayPortRandom")}
|
|
2092
|
-
</button>
|
|
2093
|
-
</div>
|
|
2094
|
-
</div>
|
|
2095
|
-
<p style={copyRowStyle}>
|
|
2096
|
-
<span style={hintStyle}>
|
|
2097
|
-
{t("gatewayOpenAiUrl")}
|
|
2098
|
-
<span style={{ display: "block", ...monoStyle }}>
|
|
2099
|
-
{`${formatGatewayBaseUrl(gateway.bind, gateway.port)}/v1`}
|
|
2100
|
-
</span>
|
|
2101
|
-
</span>
|
|
2102
|
-
<button
|
|
2103
|
-
type="button"
|
|
2104
|
-
style={primaryButtonStyle}
|
|
2105
|
-
onClick={() => {
|
|
2106
|
-
void handleCopy("openai", `${formatGatewayBaseUrl(gateway.bind, gateway.port)}/v1`);
|
|
2107
|
-
}}
|
|
2108
|
-
>
|
|
2109
|
-
{copyLabel("openai")}
|
|
2110
|
-
</button>
|
|
2111
|
-
</p>
|
|
2112
|
-
<p style={copyRowStyle}>
|
|
2113
|
-
<span style={hintStyle}>
|
|
2114
|
-
{t("gatewayAnthropicUrl")}
|
|
2115
|
-
<span style={{ display: "block", ...monoStyle }}>
|
|
2116
|
-
{formatGatewayBaseUrl(gateway.bind, gateway.port)}
|
|
2117
|
-
</span>
|
|
2118
|
-
</span>
|
|
2119
|
-
<button
|
|
2120
|
-
type="button"
|
|
2121
|
-
style={buttonStyle}
|
|
2122
|
-
onClick={() => {
|
|
2123
|
-
void handleCopy("anthropic", formatGatewayBaseUrl(gateway.bind, gateway.port));
|
|
2124
|
-
}}
|
|
2125
|
-
>
|
|
2126
|
-
{copyLabel("anthropic")}
|
|
2127
|
-
</button>
|
|
2128
|
-
</p>
|
|
2129
|
-
<p style={copyRowStyle}>
|
|
2130
|
-
<span style={hintStyle}>
|
|
2131
|
-
{t("gatewayKeyHint")}
|
|
2132
|
-
<span style={{ display: "block", ...monoStyle, overflowWrap: "anywhere" }}>
|
|
2133
|
-
{gatewayKeyVisible && gatewayOnceKey !== undefined ? gatewayOnceKey : gateway.keyHint || "—"}
|
|
2134
|
-
</span>
|
|
2135
|
-
</span>
|
|
2136
|
-
<span style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
|
|
2137
|
-
<button
|
|
2138
|
-
type="button"
|
|
2139
|
-
style={primaryButtonStyle}
|
|
2140
|
-
disabled={gatewayBusy}
|
|
2141
|
-
onClick={() => {
|
|
2142
|
-
void copyGatewayKey();
|
|
2143
|
-
}}
|
|
2144
|
-
>
|
|
2145
|
-
{copyLabel("key", t("gatewayCopyKey"))}
|
|
2146
|
-
</button>
|
|
2147
|
-
<button
|
|
2148
|
-
type="button"
|
|
2149
|
-
style={buttonStyle}
|
|
2150
|
-
disabled={gatewayBusy}
|
|
2151
|
-
onClick={() => {
|
|
2152
|
-
void toggleGatewayKeyVisible();
|
|
2153
|
-
}}
|
|
2154
|
-
>
|
|
2155
|
-
{gatewayKeyVisible ? t("gatewayHideKey") : t("gatewayShowKey")}
|
|
2156
|
-
</button>
|
|
2157
|
-
</span>
|
|
2158
|
-
</p>
|
|
2159
|
-
<p style={hintStyle}>{t("gatewayKeyCopyHint")}</p>
|
|
2160
|
-
{gatewayRotateConfirm ? (
|
|
2161
|
-
<div style={nestedStyle}>
|
|
2162
|
-
<p style={bodyStyle}>{t("gatewayRotateConfirm")}</p>
|
|
2163
|
-
<p style={hintStyle}>{t("gatewayRotateConfirmHint")}</p>
|
|
2164
|
-
<div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
|
|
2165
|
-
<button
|
|
2166
|
-
type="button"
|
|
2167
|
-
style={buttonStyle}
|
|
2168
|
-
disabled={gatewayBusy}
|
|
2169
|
-
onClick={() => {
|
|
2170
|
-
void rotateGatewayKey();
|
|
2171
|
-
}}
|
|
2172
|
-
>
|
|
2173
|
-
{t("gatewayRotateConfirmAction")}
|
|
2174
|
-
</button>
|
|
2175
|
-
<button
|
|
2176
|
-
type="button"
|
|
2177
|
-
style={buttonStyle}
|
|
2178
|
-
disabled={gatewayBusy}
|
|
2179
|
-
onClick={() => setGatewayRotateConfirm(false)}
|
|
2180
|
-
>
|
|
2181
|
-
{t("gatewayRotateCancel")}
|
|
2182
|
-
</button>
|
|
2183
|
-
</div>
|
|
2184
|
-
</div>
|
|
2185
|
-
) : (
|
|
2186
|
-
<button
|
|
2187
|
-
type="button"
|
|
2188
|
-
style={buttonStyle}
|
|
2189
|
-
disabled={gatewayBusy}
|
|
2190
|
-
onClick={() => setGatewayRotateConfirm(true)}
|
|
2191
|
-
>
|
|
2192
|
-
{t("gatewayRotate")}
|
|
2193
|
-
</button>
|
|
2194
|
-
)}
|
|
2195
|
-
</div>
|
|
2196
|
-
)}
|
|
2197
|
-
</section>
|
|
2198
|
-
) : null}
|
|
2199
|
-
{activeTab === "about" && status !== undefined ? (
|
|
2200
|
-
<div style={cardStyle}>
|
|
2201
|
-
<div style={rowStyle}>
|
|
2202
|
-
<div>
|
|
2203
|
-
<h3 style={{ ...titleStyle, fontSize: 16 }}>{t("antigravityTitle")}</h3>
|
|
2204
|
-
<p style={{ ...bodyStyle, marginTop: 4 }}>{t("antigravityDescription")}</p>
|
|
2205
|
-
<p style={{ ...bodyStyle, marginTop: 4 }}>
|
|
2206
|
-
<span style={monoStyle}>{status.antigravity.route}</span>
|
|
2207
|
-
</p>
|
|
2208
|
-
</div>
|
|
2209
|
-
<div style={statusStyle} role="status">
|
|
2210
|
-
<span aria-hidden="true" style={dotStyle("signed-out", status.antigravity.installed)} />
|
|
2211
|
-
<span>{status.antigravity.installed ? t("antigravityInstalled") : t("antigravityMissing")}</span>
|
|
2212
|
-
</div>
|
|
2213
|
-
</div>
|
|
2214
|
-
<p style={bodyStyle}>{t("antigravityCliHint")}</p>
|
|
2215
|
-
<code style={{ ...monoStyle, fontSize: 12, overflowWrap: "anywhere" }}>{t("antigravityCliCommand")}</code>
|
|
2216
|
-
</div>
|
|
652
|
+
<GatewayTab
|
|
653
|
+
t={t}
|
|
654
|
+
gateway={gateway}
|
|
655
|
+
gatewayError={gatewayError}
|
|
656
|
+
gatewayBusy={gatewayBusy}
|
|
657
|
+
gatewayOnceKey={gatewayOnceKey}
|
|
658
|
+
gatewayKeyVisible={gatewayKeyVisible}
|
|
659
|
+
gatewayRotateConfirm={gatewayRotateConfirm}
|
|
660
|
+
gatewayRevealError={gatewayRevealError}
|
|
661
|
+
portDraft={portDraft}
|
|
662
|
+
copiedField={copiedField}
|
|
663
|
+
copyFailedField={copyFailedField}
|
|
664
|
+
onEnabledChange={setGatewayEnabled}
|
|
665
|
+
onPortDraftChange={setPortDraft}
|
|
666
|
+
onApplyPort={() => {
|
|
667
|
+
void applyGatewayPort();
|
|
668
|
+
}}
|
|
669
|
+
onRandomPort={(port) => {
|
|
670
|
+
setPortDraft(String(port));
|
|
671
|
+
}}
|
|
672
|
+
onCopy={(field, text) => {
|
|
673
|
+
void handleCopy(field, text);
|
|
674
|
+
}}
|
|
675
|
+
onCopyKey={() => {
|
|
676
|
+
void copyGatewayKey();
|
|
677
|
+
}}
|
|
678
|
+
onToggleKeyVisible={() => {
|
|
679
|
+
void toggleGatewayKeyVisible();
|
|
680
|
+
}}
|
|
681
|
+
onRotateConfirm={() => {
|
|
682
|
+
setGatewayRotateConfirm(true);
|
|
683
|
+
}}
|
|
684
|
+
onRotateCancel={() => {
|
|
685
|
+
setGatewayRotateConfirm(false);
|
|
686
|
+
}}
|
|
687
|
+
onRotate={() => {
|
|
688
|
+
void rotateGatewayKey();
|
|
689
|
+
}}
|
|
690
|
+
/>
|
|
2217
691
|
) : null}
|
|
692
|
+
{activeTab === "about" ? <AboutTab t={t} /> : null}
|
|
2218
693
|
</div>
|
|
2219
694
|
</section>
|
|
2220
695
|
);
|