dsh-coding-subscription-oauth 0.6.2 → 0.6.3

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.
Files changed (121) hide show
  1. package/CHANGELOG.md +222 -206
  2. package/CONTRIBUTING.md +129 -120
  3. package/INSTALL.md +218 -218
  4. package/LICENSE +19 -19
  5. package/NOTICE +11 -11
  6. package/README.de.md +289 -289
  7. package/README.es.md +290 -290
  8. package/README.fr.md +290 -290
  9. package/README.ja.md +290 -290
  10. package/README.ko.md +290 -290
  11. package/README.md +304 -304
  12. package/README.pt-BR.md +290 -290
  13. package/README.ru.md +290 -290
  14. package/README.zh-CN.md +302 -302
  15. package/compatibility/dsh-bom.json +0 -0
  16. package/cordis.patch.yml +13 -13
  17. package/docs/00-project-rules.md +212 -195
  18. package/docs/02-architecture.md +130 -130
  19. package/docs/02-architecture.zh-CN.md +130 -130
  20. package/lib/bin.js.map +1 -1
  21. package/lib/capability-settings.d.ts +8 -2
  22. package/lib/capability-settings.d.ts.map +1 -1
  23. package/lib/capability-tools.d.ts +7 -2
  24. package/lib/capability-tools.d.ts.map +1 -1
  25. package/lib/client.js +3 -3
  26. package/lib/client.js.map +2 -2
  27. package/lib/codex-images.d.ts +8 -0
  28. package/lib/codex-images.d.ts.map +1 -1
  29. package/lib/index.js +32 -7
  30. package/lib/index.js.map +2 -2
  31. package/lib/invariant.js.map +1 -1
  32. package/media/en/settings_accounts.png +0 -0
  33. package/media/en/settings_capabilities.png +0 -0
  34. package/media/en/settings_gateway.png +0 -0
  35. package/media/settings_accounts.png +0 -0
  36. package/media/settings_capabilities.png +0 -0
  37. package/media/settings_gateway.png +0 -0
  38. package/media/settings_overview.png +0 -0
  39. package/media/zh-CN/settings_accounts.png +0 -0
  40. package/media/zh-CN/settings_capabilities.png +0 -0
  41. package/media/zh-CN/settings_gateway.png +0 -0
  42. package/package.json +148 -148
  43. package/patches/dsh-agy@0.1.2.patch +25 -25
  44. package/scripts/release.mjs +186 -186
  45. package/scripts/smoke-deployed-routes.mjs +146 -146
  46. package/scripts/verify-deployed-catalog.mjs +87 -87
  47. package/src/adapter.ts +273 -273
  48. package/src/alias-adapter.ts +130 -130
  49. package/src/auth-routes.ts +921 -921
  50. package/src/auth.ts +67 -67
  51. package/src/bin.ts +350 -350
  52. package/src/capability-routes.ts +279 -279
  53. package/src/capability-runtime.ts +314 -313
  54. package/src/capability-settings.ts +671 -658
  55. package/src/capability-tools.ts +685 -666
  56. package/src/catalog.ts +271 -271
  57. package/src/client/GrokBuildSettings.tsx +771 -770
  58. package/src/client/api.ts +88 -88
  59. package/src/client/components/AboutTab.tsx +30 -30
  60. package/src/client/components/AccountsTab.tsx +241 -241
  61. package/src/client/components/Badge.tsx +33 -33
  62. package/src/client/components/CapabilitiesTab.tsx +265 -265
  63. package/src/client/components/CliPullPreview.tsx +116 -116
  64. package/src/client/components/CopyButton.tsx +57 -57
  65. package/src/client/components/GatewayTab.tsx +469 -469
  66. package/src/client/components/NoticeBanner.tsx +46 -46
  67. package/src/client/components/ProgressBar.tsx +53 -53
  68. package/src/client/components/ProviderCard.tsx +606 -606
  69. package/src/client/components/SettingsTabs.tsx +75 -75
  70. package/src/client/components/ToggleSwitch.tsx +71 -71
  71. package/src/client/constants.ts +230 -224
  72. package/src/client/display.ts +61 -61
  73. package/src/client/dshClientAdapter.ts +127 -127
  74. package/src/client/gatewaySnippets.ts +37 -37
  75. package/src/client/index.tsx +156 -156
  76. package/src/client/locales.ts +540 -535
  77. package/src/client/microStyles.ts +52 -52
  78. package/src/client/parsers.ts +398 -396
  79. package/src/client/styles.ts +325 -325
  80. package/src/client/types.ts +199 -197
  81. package/src/codex-http.ts +447 -447
  82. package/src/codex-images.ts +503 -485
  83. package/src/codex-model-capabilities.ts +320 -320
  84. package/src/codex-search.ts +245 -245
  85. package/src/codex-usage.ts +263 -263
  86. package/src/compatibility.ts +41 -41
  87. package/src/dsh-host-adapter.ts +173 -173
  88. package/src/gateway-anthropic-messages.ts +84 -84
  89. package/src/gateway-auth.ts +102 -102
  90. package/src/gateway-backend.ts +274 -274
  91. package/src/gateway-body.ts +49 -49
  92. package/src/gateway-config.ts +76 -76
  93. package/src/gateway-http.ts +104 -104
  94. package/src/gateway-openai-chat.ts +124 -124
  95. package/src/gateway-openai-responses.ts +53 -53
  96. package/src/gateway-parse.ts +224 -224
  97. package/src/gateway-protocol.ts +52 -52
  98. package/src/gateway-routes.ts +158 -158
  99. package/src/gateway.ts +258 -258
  100. package/src/grok-errors.ts +24 -24
  101. package/src/grok-imagine.ts +1627 -1627
  102. package/src/grok-import.ts +151 -151
  103. package/src/http-json.ts +82 -82
  104. package/src/ids.ts +59 -59
  105. package/src/imagine-routes.ts +463 -463
  106. package/src/index.ts +709 -709
  107. package/src/invariant.ts +17 -17
  108. package/src/kimi-errors.ts +26 -26
  109. package/src/media-store.ts +927 -927
  110. package/src/oauth-import-routes.ts +324 -324
  111. package/src/oauth-providers.ts +152 -152
  112. package/src/oauth-session.ts +183 -183
  113. package/src/oauth-sources.ts +1104 -1104
  114. package/src/oauth.ts +620 -620
  115. package/src/provider.ts +128 -128
  116. package/src/proxy.ts +11 -11
  117. package/src/redact.ts +72 -72
  118. package/src/session.ts +218 -218
  119. package/src/store.ts +217 -217
  120. package/src/web-origin.ts +296 -296
  121. package/src/web-routes.ts +38 -38
@@ -1,606 +1,606 @@
1
- /** Single provider account card for the Accounts tab. */
2
-
3
- import { useEffect, useMemo, useRef, useState } from "react";
4
- import { SOURCE_REASON_KEY } from "../constants.ts";
5
- import { methodLabel, orderedLoginMethods, shouldShowPerCardSourceReason } from "../display.ts";
6
- import { formatEpoch, modelFields, usageHasVisibleFields } from "../parsers.ts";
7
- import {
8
- bodyStyle,
9
- buttonStyle,
10
- cardStyle,
11
- checkRowStyle,
12
- codeStyle,
13
- compactButtonStyle,
14
- hintStyle,
15
- inputStyle,
16
- listStyle,
17
- monoStyle,
18
- nestedStyle,
19
- primaryButtonStyle,
20
- rowStyle,
21
- stepActiveStyle,
22
- stepNumberActiveStyle,
23
- stepNumberStyle,
24
- stepRowStyle,
25
- titleStyle,
26
- visuallyHiddenStyle,
27
- } from "../styles.ts";
28
- import type {
29
- GrokBuildSettingsInjected,
30
- GrokStatus,
31
- LoginMethod,
32
- ProviderCardDefinition,
33
- ProviderStatus,
34
- SourceStatus,
35
- UsageView,
36
- } from "../types.ts";
37
- import { Badge } from "./Badge.tsx";
38
- import { CopyButton } from "./CopyButton.tsx";
39
- import { ProgressBar } from "./ProgressBar.tsx";
40
-
41
- export interface ProviderCardProps {
42
- t: GrokBuildSettingsInjected["t"];
43
- definition: ProviderCardDefinition;
44
- providerStatus: ProviderStatus;
45
- busy: boolean;
46
- sourcesBusy: boolean;
47
- remote: boolean;
48
- codeInput: string;
49
- popupBlocked: boolean;
50
- expanded: boolean;
51
- source: SourceStatus | undefined;
52
- showUsage: boolean;
53
- usage: UsageView | undefined;
54
- usageError: string | undefined;
55
- usageLoading: boolean;
56
- onSignIn: (method: LoginMethod) => void;
57
- onSignOut: () => void;
58
- onCancelLogin: () => void;
59
- onSubmitCode: () => void;
60
- onCodeChange: (value: string) => void;
61
- onToggleExpanded: () => void;
62
- onPreviewSource: () => void;
63
- onSaveModels: (selected: string[]) => void;
64
- }
65
-
66
- function SignInSteps({
67
- t,
68
- activeMethod,
69
- userCode,
70
- url,
71
- popupBlocked,
72
- }: {
73
- t: GrokBuildSettingsInjected["t"];
74
- activeMethod: LoginMethod;
75
- userCode: string | undefined;
76
- url: string | undefined;
77
- popupBlocked: boolean;
78
- }) {
79
- const hasCode = userCode !== undefined && userCode.length > 0;
80
- const hasUrl = url !== undefined && url.length > 0;
81
- const needsPaste = activeMethod !== "device";
82
-
83
- return (
84
- <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
85
- <div style={hasUrl ? stepActiveStyle : stepRowStyle}>
86
- <span style={hasUrl ? stepNumberActiveStyle : stepNumberStyle} aria-hidden="true">
87
- 1
88
- </span>
89
- <span>{t("signInStepOpen")}</span>
90
- </div>
91
- {hasUrl ? (
92
- <div style={{ display: "flex", flexWrap: "wrap", gap: 8, paddingLeft: 32 }}>
93
- <a href={url} target="_blank" rel="noreferrer" style={primaryButtonStyle}>
94
- {t("openAuthUrl")}
95
- </a>
96
- <CopyButton text={url} idleLabel={t("copy")} copiedLabel={t("copied")} failedLabel={t("copyFailed")} />
97
- </div>
98
- ) : null}
99
- {popupBlocked && hasUrl ? <p style={hintStyle}>{t("popupBlocked")}</p> : null}
100
- {hasCode ? (
101
- <>
102
- <div style={stepActiveStyle}>
103
- <span style={stepNumberActiveStyle} aria-hidden="true">
104
- 2
105
- </span>
106
- <span>{t("signInStepCode")}</span>
107
- </div>
108
- <div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: 8, paddingLeft: 32 }}>
109
- <span style={codeStyle}>{userCode}</span>
110
- <CopyButton
111
- text={userCode}
112
- idleLabel={t("copyUserCode")}
113
- copiedLabel={t("copied")}
114
- failedLabel={t("copyFailed")}
115
- primary
116
- />
117
- </div>
118
- </>
119
- ) : null}
120
- <div style={stepActiveStyle}>
121
- <span style={stepNumberActiveStyle} aria-hidden="true">
122
- {hasCode ? 3 : 2}
123
- </span>
124
- <span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
125
- <span
126
- aria-hidden="true"
127
- style={{
128
- width: 14,
129
- height: 14,
130
- border: "2px solid var(--dsw-alias-brand-primary, #1677ff)",
131
- borderTopColor: "transparent",
132
- borderRadius: "50%",
133
- animation: "dsh-coding-oauth-spin 0.8s linear infinite",
134
- }}
135
- />
136
- {t("signInStepWait")}
137
- </span>
138
- </div>
139
- {needsPaste ? (
140
- <div style={{ display: "flex", flexDirection: "column", gap: 8, paddingLeft: 32 }}>
141
- <p style={bodyStyle}>{t(activeMethod === "browser" ? "pasteBrowserCodeHint" : "pasteCodeHint")}</p>
142
- </div>
143
- ) : null}
144
- </div>
145
- );
146
- }
147
-
148
- export function ProviderCard({
149
- t,
150
- definition,
151
- providerStatus,
152
- busy,
153
- sourcesBusy,
154
- remote,
155
- codeInput,
156
- popupBlocked,
157
- expanded,
158
- source,
159
- showUsage,
160
- usage,
161
- usageError,
162
- usageLoading,
163
- onSignIn,
164
- onSignOut,
165
- onCancelLogin,
166
- onSubmitCode,
167
- onCodeChange,
168
- onToggleExpanded,
169
- onPreviewSource,
170
- onSaveModels,
171
- }: ProviderCardProps) {
172
- const [showAltMethods, setShowAltMethods] = useState(false);
173
- const [modelFilter, setModelFilter] = useState("");
174
- const [logoutConfirm, setLogoutConfirm] = useState(false);
175
- const logoutTrigger = useRef<HTMLButtonElement>(null);
176
- const logoutConfirmAction = useRef<HTMLButtonElement>(null);
177
- const restoreLogoutFocus = useRef(false);
178
-
179
- const ordered = orderedLoginMethods(definition, remote);
180
- const primaryMethod: LoginMethod = ordered[0] ?? definition.recommended;
181
- const altMethods = ordered.filter((method) => method !== primaryMethod);
182
-
183
- const statusLabel =
184
- providerStatus.status === "signed-in"
185
- ? t("signedIn")
186
- : providerStatus.status === "signing-in"
187
- ? t("signingIn")
188
- : providerStatus.status === "error"
189
- ? t("requestFailed")
190
- : t("signedOut");
191
- const activeMethod = providerStatus.status === "signing-in" ? providerStatus.method : primaryMethod;
192
- const { available, selected } = useMemo(() => modelFields(providerStatus), [providerStatus]);
193
- const [modelDraft, setModelDraft] = useState<string[]>(selected);
194
- useEffect(() => {
195
- setModelDraft(selected);
196
- }, [selected]);
197
- useEffect(() => {
198
- if (providerStatus.status !== "signed-in") setLogoutConfirm(false);
199
- }, [providerStatus.status]);
200
- useEffect(() => {
201
- if (logoutConfirm) {
202
- logoutConfirmAction.current?.focus();
203
- return;
204
- }
205
- if (!restoreLogoutFocus.current) return;
206
- restoreLogoutFocus.current = false;
207
- if (providerStatus.status === "signed-in") {
208
- logoutTrigger.current?.focus();
209
- return;
210
- }
211
- document.getElementById(`coding-oauth-login-${definition.slug}`)?.focus();
212
- }, [definition.slug, logoutConfirm, providerStatus.status]);
213
- const modelDraftDirty =
214
- modelDraft.length !== selected.length || modelDraft.some((model, index) => model !== selected[index]);
215
- const grokProviderStatus = definition.slug === "grok" ? (providerStatus as GrokStatus) : undefined;
216
- const showSourceReason = shouldShowPerCardSourceReason(source);
217
-
218
- const filteredModels = useMemo(() => {
219
- const query = modelFilter.trim().toLowerCase();
220
- if (query.length === 0) return available;
221
- return available.filter((id) => id.toLowerCase().includes(query));
222
- }, [available, modelFilter]);
223
-
224
- const usagePercent =
225
- definition.slug === "codex" && showUsage
226
- ? usage?.individualRemainingPercent === undefined
227
- ? usage?.rateLimits[0]?.windows[0]?.usedPercent
228
- : 100 - usage.individualRemainingPercent
229
- : undefined;
230
- const fetchedAt = formatEpoch(usage?.fetchedAt);
231
-
232
- return (
233
- <div style={cardStyle}>
234
- <div style={rowStyle}>
235
- <div>
236
- <h3 style={{ ...titleStyle, fontSize: 16 }}>{t(definition.titleKey)}</h3>
237
- {providerStatus.status === "signed-in" && !expanded ? (
238
- <p style={{ ...hintStyle, marginTop: 4 }}>
239
- {t("modelsSummary", { selected: selected.length, total: available.length })}
240
- {usagePercent === undefined ? "" : ` · ${t("usageUsedShort", { value: `${String(usagePercent)}%` })}`}
241
- </p>
242
- ) : (
243
- <>
244
- <p style={{ ...bodyStyle, marginTop: 4 }}>{t(definition.descriptionKey)}</p>
245
- <p style={{ ...bodyStyle, marginTop: 4 }}>
246
- <span style={monoStyle}>{definition.route}</span>
247
- </p>
248
- </>
249
- )}
250
- </div>
251
- <Badge label={statusLabel} providerStatus={providerStatus.status} />
252
- </div>
253
- <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
254
- {providerStatus.status === "signed-in" ? (
255
- <>
256
- {logoutConfirm ? (
257
- <fieldset style={{ ...nestedStyle, margin: 0, minWidth: 0 }}>
258
- <legend style={visuallyHiddenStyle}>{t("logoutConfirmTitle")}</legend>
259
- <p style={bodyStyle}>{t("logoutConfirmHint")}</p>
260
- <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
261
- <button
262
- ref={logoutConfirmAction}
263
- type="button"
264
- style={primaryButtonStyle}
265
- disabled={busy}
266
- onClick={() => {
267
- restoreLogoutFocus.current = true;
268
- onSignOut();
269
- }}
270
- >
271
- {busy ? t("working") : t("logoutConfirmAction")}
272
- </button>
273
- <button
274
- type="button"
275
- style={buttonStyle}
276
- disabled={busy}
277
- onClick={() => {
278
- restoreLogoutFocus.current = true;
279
- setLogoutConfirm(false);
280
- }}
281
- >
282
- {t("cancel")}
283
- </button>
284
- </div>
285
- </fieldset>
286
- ) : (
287
- <button
288
- ref={logoutTrigger}
289
- type="button"
290
- style={buttonStyle}
291
- disabled={busy}
292
- onClick={() => setLogoutConfirm(true)}
293
- >
294
- {t("logout")}
295
- </button>
296
- )}
297
- <button
298
- id={`coding-oauth-models-toggle-${definition.slug}`}
299
- type="button"
300
- style={buttonStyle}
301
- aria-expanded={expanded}
302
- aria-controls={expanded ? `coding-oauth-models-${definition.slug}` : undefined}
303
- onClick={onToggleExpanded}
304
- >
305
- {expanded ? t("collapseModels") : t("expandModels")}
306
- </button>
307
- {source?.available === true ? (
308
- <button
309
- id={`coding-oauth-source-pull-${definition.slug}`}
310
- type="button"
311
- style={buttonStyle}
312
- disabled={sourcesBusy}
313
- onClick={onPreviewSource}
314
- >
315
- {t("sourcesPullCopy")}
316
- </button>
317
- ) : null}
318
- </>
319
- ) : providerStatus.status === "signing-in" ? (
320
- <>
321
- <button type="button" style={buttonStyle} disabled={busy} onClick={onCancelLogin}>
322
- {t("cancelLogin")}
323
- </button>
324
- {altMethods.length > 0 ? (
325
- <button
326
- type="button"
327
- style={compactButtonStyle}
328
- disabled={busy}
329
- onClick={() => {
330
- setShowAltMethods((current) => !current);
331
- }}
332
- >
333
- {showAltMethods ? t("hideOtherLoginMethods") : t("otherLoginMethods")}
334
- </button>
335
- ) : null}
336
- {showAltMethods
337
- ? altMethods.map((method) => (
338
- <button
339
- key={method}
340
- type="button"
341
- style={compactButtonStyle}
342
- disabled={busy}
343
- onClick={() => {
344
- onSignIn(method);
345
- }}
346
- >
347
- {methodLabel(method, t, { remote, primary: false })}
348
- </button>
349
- ))
350
- : null}
351
- </>
352
- ) : (
353
- <>
354
- <button
355
- id={`coding-oauth-login-${definition.slug}`}
356
- type="button"
357
- style={primaryButtonStyle}
358
- disabled={busy}
359
- onClick={() => {
360
- onSignIn(primaryMethod);
361
- }}
362
- >
363
- {busy ? t("working") : methodLabel(primaryMethod, t, { remote, primary: true })}
364
- </button>
365
- {altMethods.length > 0 ? (
366
- <button
367
- type="button"
368
- style={compactButtonStyle}
369
- disabled={busy}
370
- onClick={() => {
371
- setShowAltMethods((current) => !current);
372
- }}
373
- >
374
- {showAltMethods ? t("hideOtherLoginMethods") : t("otherLoginMethods")}
375
- </button>
376
- ) : null}
377
- {showAltMethods
378
- ? altMethods.map((method) => (
379
- <button
380
- key={method}
381
- type="button"
382
- style={compactButtonStyle}
383
- disabled={busy}
384
- onClick={() => {
385
- onSignIn(method);
386
- }}
387
- >
388
- {methodLabel(method, t, { remote, primary: false })}
389
- </button>
390
- ))
391
- : null}
392
- {source?.available === true ? (
393
- <button
394
- id={`coding-oauth-source-pull-${definition.slug}`}
395
- type="button"
396
- style={buttonStyle}
397
- disabled={sourcesBusy}
398
- onClick={onPreviewSource}
399
- >
400
- {t("sourcesPullCopy")}
401
- </button>
402
- ) : showSourceReason && source?.reason !== undefined ? (
403
- <span style={hintStyle}>{t(SOURCE_REASON_KEY[source.reason])}</span>
404
- ) : null}
405
- </>
406
- )}
407
- </div>
408
- {providerStatus.status === "error" ? (
409
- <p style={{ ...bodyStyle, color: "var(--dsw-alias-state-error-primary)" }}>{providerStatus.message}</p>
410
- ) : null}
411
- {providerStatus.status === "signing-in" ? (
412
- <SignInSteps
413
- t={t}
414
- activeMethod={activeMethod}
415
- userCode={providerStatus.userCode}
416
- url={providerStatus.url}
417
- popupBlocked={popupBlocked}
418
- />
419
- ) : null}
420
- {providerStatus.status === "signing-in" && activeMethod !== "device" ? (
421
- <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
422
- <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
423
- <label htmlFor={`coding-oauth-code-${definition.slug}`} style={visuallyHiddenStyle}>
424
- {t("pasteCodeLabel")}
425
- </label>
426
- <input
427
- id={`coding-oauth-code-${definition.slug}`}
428
- style={{ ...inputStyle, flex: "1 1 360px" }}
429
- value={codeInput}
430
- placeholder={t("pasteCodePlaceholder")}
431
- disabled={busy}
432
- onChange={(event) => {
433
- onCodeChange(event.target.value);
434
- }}
435
- onKeyDown={(event) => {
436
- if (event.key === "Enter") {
437
- event.preventDefault();
438
- onSubmitCode();
439
- }
440
- }}
441
- />
442
- <button
443
- type="button"
444
- style={primaryButtonStyle}
445
- disabled={busy || codeInput.trim().length === 0}
446
- onClick={onSubmitCode}
447
- >
448
- {t("submitCode")}
449
- </button>
450
- </div>
451
- </div>
452
- ) : null}
453
- {providerStatus.status === "signed-in" && expanded ? (
454
- <div id={`coding-oauth-models-${definition.slug}`} style={{ display: "flex", flexDirection: "column", gap: 8 }}>
455
- <div style={rowStyle}>
456
- <h4 style={{ ...titleStyle, fontSize: 14 }}>{t("models")}</h4>
457
- <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
458
- <button
459
- type="button"
460
- style={compactButtonStyle}
461
- disabled={busy}
462
- onClick={() => {
463
- setModelDraft([]);
464
- }}
465
- >
466
- {t("deselectAll")}
467
- </button>
468
- <button
469
- type="button"
470
- style={compactButtonStyle}
471
- disabled={busy}
472
- onClick={() => {
473
- setModelDraft([...available]);
474
- }}
475
- >
476
- {t("selectAll")}
477
- </button>
478
- <button
479
- type="button"
480
- style={compactButtonStyle}
481
- disabled={busy}
482
- onClick={() => {
483
- setModelDraft(available.length > 0 ? [available[0]!] : []);
484
- }}
485
- >
486
- {t("resetModelsDefault")}
487
- </button>
488
- </div>
489
- </div>
490
- <label htmlFor={`coding-oauth-model-filter-${definition.slug}`} style={visuallyHiddenStyle}>
491
- {t("modelFilterLabel", { provider: t(definition.titleKey) })}
492
- </label>
493
- <input
494
- id={`coding-oauth-model-filter-${definition.slug}`}
495
- type="search"
496
- style={inputStyle}
497
- value={modelFilter}
498
- placeholder={t("modelFilterPlaceholder")}
499
- disabled={busy}
500
- onChange={(event) => {
501
- setModelFilter(event.target.value);
502
- }}
503
- />
504
- {grokProviderStatus?.status === "signed-in" ? (
505
- <p style={bodyStyle}>
506
- {grokProviderStatus.catalogSource === "live"
507
- ? t("catalogLive")
508
- : grokProviderStatus.catalogSource === "cache"
509
- ? t("catalogCache")
510
- : t("catalogFallback")}
511
- </p>
512
- ) : null}
513
- <p style={bodyStyle}>
514
- {t("modelHint")} <span style={monoStyle}>{definition.route}/&lt;id&gt;</span>
515
- </p>
516
- <ul style={listStyle}>
517
- {filteredModels.map((id) => {
518
- const checked = modelDraft.includes(id);
519
- return (
520
- <li key={id}>
521
- <label style={checkRowStyle}>
522
- <input
523
- type="checkbox"
524
- checked={checked}
525
- disabled={busy}
526
- onChange={() => {
527
- const current = new Set(modelDraft);
528
- if (checked) current.delete(id);
529
- else current.add(id);
530
- setModelDraft([...available].filter((model) => current.has(model)));
531
- }}
532
- />
533
- <span style={monoStyle}>{id}</span>
534
- </label>
535
- </li>
536
- );
537
- })}
538
- </ul>
539
- <div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: 8 }}>
540
- <button
541
- type="button"
542
- style={primaryButtonStyle}
543
- disabled={busy || !modelDraftDirty}
544
- onClick={() => onSaveModels(modelDraft)}
545
- >
546
- {busy ? t("working") : t("applyModelDraft")}
547
- </button>
548
- {modelDraftDirty ? <span style={hintStyle}>{t("modelDraftPending")}</span> : null}
549
- </div>
550
- {filteredModels.length === 0 ? <p style={hintStyle}>{t("modelFilterPlaceholder")}</p> : null}
551
- {grokProviderStatus?.status === "signed-in" && grokProviderStatus.catalogError !== undefined ? (
552
- <p style={{ ...bodyStyle, color: "var(--dsw-alias-state-error-primary)" }}>{t("catalogError")}</p>
553
- ) : null}
554
- {definition.slug === "codex" && showUsage ? (
555
- <div style={nestedStyle}>
556
- <p style={{ ...bodyStyle, color: "var(--dsw-alias-label-primary)" }}>{t("usageTitle")}</p>
557
- {usageError === undefined ? null : (
558
- <p style={{ ...bodyStyle, color: "var(--dsw-alias-state-error-primary)" }} role="alert">
559
- {usageError}
560
- </p>
561
- )}
562
- {usageLoading && usage === undefined ? (
563
- <p style={hintStyle}>{t("usageLoading")}</p>
564
- ) : usage === undefined || !usageHasVisibleFields(usage) ? (
565
- <p style={hintStyle}>{t("usageEmpty")}</p>
566
- ) : (
567
- <>
568
- {fetchedAt === undefined ? null : <p style={hintStyle}>{t("usageFetchedAt", { time: fetchedAt })}</p>}
569
- {usagePercent !== undefined ? (
570
- <ProgressBar
571
- value={usagePercent}
572
- label={t("usageRateLimit")}
573
- meta={t("usageUsed", { value: `${String(usagePercent)}%` })}
574
- />
575
- ) : null}
576
- {usage.rateLimits.map((limit) => {
577
- const window = limit.windows[0];
578
- const used = window?.usedPercent;
579
- const resetsAt = formatEpoch(window?.resetsAt);
580
- return (
581
- <div key={limit.id} style={{ display: "flex", flexDirection: "column", gap: 4 }}>
582
- {used === undefined ? (
583
- <p style={hintStyle}>{limit.name ?? t("usageRateLimit")}</p>
584
- ) : (
585
- <ProgressBar
586
- value={used}
587
- label={limit.name ?? t("usageRateLimit")}
588
- meta={
589
- resetsAt === undefined
590
- ? t("usageUsed", { value: `${String(used)}%` })
591
- : `${t("usageUsed", { value: `${String(used)}%` })} · ${t("usageResets", { time: resetsAt })}`
592
- }
593
- />
594
- )}
595
- </div>
596
- );
597
- })}
598
- </>
599
- )}
600
- </div>
601
- ) : null}
602
- </div>
603
- ) : null}
604
- </div>
605
- );
606
- }
1
+ /** Single provider account card for the Accounts tab. */
2
+
3
+ import { useEffect, useMemo, useRef, useState } from "react";
4
+ import { SOURCE_REASON_KEY } from "../constants.ts";
5
+ import { methodLabel, orderedLoginMethods, shouldShowPerCardSourceReason } from "../display.ts";
6
+ import { formatEpoch, modelFields, usageHasVisibleFields } from "../parsers.ts";
7
+ import {
8
+ bodyStyle,
9
+ buttonStyle,
10
+ cardStyle,
11
+ checkRowStyle,
12
+ codeStyle,
13
+ compactButtonStyle,
14
+ hintStyle,
15
+ inputStyle,
16
+ listStyle,
17
+ monoStyle,
18
+ nestedStyle,
19
+ primaryButtonStyle,
20
+ rowStyle,
21
+ stepActiveStyle,
22
+ stepNumberActiveStyle,
23
+ stepNumberStyle,
24
+ stepRowStyle,
25
+ titleStyle,
26
+ visuallyHiddenStyle,
27
+ } from "../styles.ts";
28
+ import type {
29
+ GrokBuildSettingsInjected,
30
+ GrokStatus,
31
+ LoginMethod,
32
+ ProviderCardDefinition,
33
+ ProviderStatus,
34
+ SourceStatus,
35
+ UsageView,
36
+ } from "../types.ts";
37
+ import { Badge } from "./Badge.tsx";
38
+ import { CopyButton } from "./CopyButton.tsx";
39
+ import { ProgressBar } from "./ProgressBar.tsx";
40
+
41
+ export interface ProviderCardProps {
42
+ t: GrokBuildSettingsInjected["t"];
43
+ definition: ProviderCardDefinition;
44
+ providerStatus: ProviderStatus;
45
+ busy: boolean;
46
+ sourcesBusy: boolean;
47
+ remote: boolean;
48
+ codeInput: string;
49
+ popupBlocked: boolean;
50
+ expanded: boolean;
51
+ source: SourceStatus | undefined;
52
+ showUsage: boolean;
53
+ usage: UsageView | undefined;
54
+ usageError: string | undefined;
55
+ usageLoading: boolean;
56
+ onSignIn: (method: LoginMethod) => void;
57
+ onSignOut: () => void;
58
+ onCancelLogin: () => void;
59
+ onSubmitCode: () => void;
60
+ onCodeChange: (value: string) => void;
61
+ onToggleExpanded: () => void;
62
+ onPreviewSource: () => void;
63
+ onSaveModels: (selected: string[]) => void;
64
+ }
65
+
66
+ function SignInSteps({
67
+ t,
68
+ activeMethod,
69
+ userCode,
70
+ url,
71
+ popupBlocked,
72
+ }: {
73
+ t: GrokBuildSettingsInjected["t"];
74
+ activeMethod: LoginMethod;
75
+ userCode: string | undefined;
76
+ url: string | undefined;
77
+ popupBlocked: boolean;
78
+ }) {
79
+ const hasCode = userCode !== undefined && userCode.length > 0;
80
+ const hasUrl = url !== undefined && url.length > 0;
81
+ const needsPaste = activeMethod !== "device";
82
+
83
+ return (
84
+ <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
85
+ <div style={hasUrl ? stepActiveStyle : stepRowStyle}>
86
+ <span style={hasUrl ? stepNumberActiveStyle : stepNumberStyle} aria-hidden="true">
87
+ 1
88
+ </span>
89
+ <span>{t("signInStepOpen")}</span>
90
+ </div>
91
+ {hasUrl ? (
92
+ <div style={{ display: "flex", flexWrap: "wrap", gap: 8, paddingLeft: 32 }}>
93
+ <a href={url} target="_blank" rel="noreferrer" style={primaryButtonStyle}>
94
+ {t("openAuthUrl")}
95
+ </a>
96
+ <CopyButton text={url} idleLabel={t("copy")} copiedLabel={t("copied")} failedLabel={t("copyFailed")} />
97
+ </div>
98
+ ) : null}
99
+ {popupBlocked && hasUrl ? <p style={hintStyle}>{t("popupBlocked")}</p> : null}
100
+ {hasCode ? (
101
+ <>
102
+ <div style={stepActiveStyle}>
103
+ <span style={stepNumberActiveStyle} aria-hidden="true">
104
+ 2
105
+ </span>
106
+ <span>{t("signInStepCode")}</span>
107
+ </div>
108
+ <div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: 8, paddingLeft: 32 }}>
109
+ <span style={codeStyle}>{userCode}</span>
110
+ <CopyButton
111
+ text={userCode}
112
+ idleLabel={t("copyUserCode")}
113
+ copiedLabel={t("copied")}
114
+ failedLabel={t("copyFailed")}
115
+ primary
116
+ />
117
+ </div>
118
+ </>
119
+ ) : null}
120
+ <div style={stepActiveStyle}>
121
+ <span style={stepNumberActiveStyle} aria-hidden="true">
122
+ {hasCode ? 3 : 2}
123
+ </span>
124
+ <span style={{ display: "inline-flex", alignItems: "center", gap: 8 }}>
125
+ <span
126
+ aria-hidden="true"
127
+ style={{
128
+ width: 14,
129
+ height: 14,
130
+ border: "2px solid var(--dsw-alias-brand-primary, #1677ff)",
131
+ borderTopColor: "transparent",
132
+ borderRadius: "50%",
133
+ animation: "dsh-coding-oauth-spin 0.8s linear infinite",
134
+ }}
135
+ />
136
+ {t("signInStepWait")}
137
+ </span>
138
+ </div>
139
+ {needsPaste ? (
140
+ <div style={{ display: "flex", flexDirection: "column", gap: 8, paddingLeft: 32 }}>
141
+ <p style={bodyStyle}>{t(activeMethod === "browser" ? "pasteBrowserCodeHint" : "pasteCodeHint")}</p>
142
+ </div>
143
+ ) : null}
144
+ </div>
145
+ );
146
+ }
147
+
148
+ export function ProviderCard({
149
+ t,
150
+ definition,
151
+ providerStatus,
152
+ busy,
153
+ sourcesBusy,
154
+ remote,
155
+ codeInput,
156
+ popupBlocked,
157
+ expanded,
158
+ source,
159
+ showUsage,
160
+ usage,
161
+ usageError,
162
+ usageLoading,
163
+ onSignIn,
164
+ onSignOut,
165
+ onCancelLogin,
166
+ onSubmitCode,
167
+ onCodeChange,
168
+ onToggleExpanded,
169
+ onPreviewSource,
170
+ onSaveModels,
171
+ }: ProviderCardProps) {
172
+ const [showAltMethods, setShowAltMethods] = useState(false);
173
+ const [modelFilter, setModelFilter] = useState("");
174
+ const [logoutConfirm, setLogoutConfirm] = useState(false);
175
+ const logoutTrigger = useRef<HTMLButtonElement>(null);
176
+ const logoutConfirmAction = useRef<HTMLButtonElement>(null);
177
+ const restoreLogoutFocus = useRef(false);
178
+
179
+ const ordered = orderedLoginMethods(definition, remote);
180
+ const primaryMethod: LoginMethod = ordered[0] ?? definition.recommended;
181
+ const altMethods = ordered.filter((method) => method !== primaryMethod);
182
+
183
+ const statusLabel =
184
+ providerStatus.status === "signed-in"
185
+ ? t("signedIn")
186
+ : providerStatus.status === "signing-in"
187
+ ? t("signingIn")
188
+ : providerStatus.status === "error"
189
+ ? t("requestFailed")
190
+ : t("signedOut");
191
+ const activeMethod = providerStatus.status === "signing-in" ? providerStatus.method : primaryMethod;
192
+ const { available, selected } = useMemo(() => modelFields(providerStatus), [providerStatus]);
193
+ const [modelDraft, setModelDraft] = useState<string[]>(selected);
194
+ useEffect(() => {
195
+ setModelDraft(selected);
196
+ }, [selected]);
197
+ useEffect(() => {
198
+ if (providerStatus.status !== "signed-in") setLogoutConfirm(false);
199
+ }, [providerStatus.status]);
200
+ useEffect(() => {
201
+ if (logoutConfirm) {
202
+ logoutConfirmAction.current?.focus();
203
+ return;
204
+ }
205
+ if (!restoreLogoutFocus.current) return;
206
+ restoreLogoutFocus.current = false;
207
+ if (providerStatus.status === "signed-in") {
208
+ logoutTrigger.current?.focus();
209
+ return;
210
+ }
211
+ document.getElementById(`coding-oauth-login-${definition.slug}`)?.focus();
212
+ }, [definition.slug, logoutConfirm, providerStatus.status]);
213
+ const modelDraftDirty =
214
+ modelDraft.length !== selected.length || modelDraft.some((model, index) => model !== selected[index]);
215
+ const grokProviderStatus = definition.slug === "grok" ? (providerStatus as GrokStatus) : undefined;
216
+ const showSourceReason = shouldShowPerCardSourceReason(source);
217
+
218
+ const filteredModels = useMemo(() => {
219
+ const query = modelFilter.trim().toLowerCase();
220
+ if (query.length === 0) return available;
221
+ return available.filter((id) => id.toLowerCase().includes(query));
222
+ }, [available, modelFilter]);
223
+
224
+ const usagePercent =
225
+ definition.slug === "codex" && showUsage
226
+ ? usage?.individualRemainingPercent === undefined
227
+ ? usage?.rateLimits[0]?.windows[0]?.usedPercent
228
+ : 100 - usage.individualRemainingPercent
229
+ : undefined;
230
+ const fetchedAt = formatEpoch(usage?.fetchedAt);
231
+
232
+ return (
233
+ <div style={cardStyle}>
234
+ <div style={rowStyle}>
235
+ <div>
236
+ <h3 style={{ ...titleStyle, fontSize: 16 }}>{t(definition.titleKey)}</h3>
237
+ {providerStatus.status === "signed-in" && !expanded ? (
238
+ <p style={{ ...hintStyle, marginTop: 4 }}>
239
+ {t("modelsSummary", { selected: selected.length, total: available.length })}
240
+ {usagePercent === undefined ? "" : ` · ${t("usageUsedShort", { value: `${String(usagePercent)}%` })}`}
241
+ </p>
242
+ ) : (
243
+ <>
244
+ <p style={{ ...bodyStyle, marginTop: 4 }}>{t(definition.descriptionKey)}</p>
245
+ <p style={{ ...bodyStyle, marginTop: 4 }}>
246
+ <span style={monoStyle}>{definition.route}</span>
247
+ </p>
248
+ </>
249
+ )}
250
+ </div>
251
+ <Badge label={statusLabel} providerStatus={providerStatus.status} />
252
+ </div>
253
+ <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
254
+ {providerStatus.status === "signed-in" ? (
255
+ <>
256
+ {logoutConfirm ? (
257
+ <fieldset style={{ ...nestedStyle, margin: 0, minWidth: 0 }}>
258
+ <legend style={visuallyHiddenStyle}>{t("logoutConfirmTitle")}</legend>
259
+ <p style={bodyStyle}>{t("logoutConfirmHint")}</p>
260
+ <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
261
+ <button
262
+ ref={logoutConfirmAction}
263
+ type="button"
264
+ style={primaryButtonStyle}
265
+ disabled={busy}
266
+ onClick={() => {
267
+ restoreLogoutFocus.current = true;
268
+ onSignOut();
269
+ }}
270
+ >
271
+ {busy ? t("working") : t("logoutConfirmAction")}
272
+ </button>
273
+ <button
274
+ type="button"
275
+ style={buttonStyle}
276
+ disabled={busy}
277
+ onClick={() => {
278
+ restoreLogoutFocus.current = true;
279
+ setLogoutConfirm(false);
280
+ }}
281
+ >
282
+ {t("cancel")}
283
+ </button>
284
+ </div>
285
+ </fieldset>
286
+ ) : (
287
+ <button
288
+ ref={logoutTrigger}
289
+ type="button"
290
+ style={buttonStyle}
291
+ disabled={busy}
292
+ onClick={() => setLogoutConfirm(true)}
293
+ >
294
+ {t("logout")}
295
+ </button>
296
+ )}
297
+ <button
298
+ id={`coding-oauth-models-toggle-${definition.slug}`}
299
+ type="button"
300
+ style={buttonStyle}
301
+ aria-expanded={expanded}
302
+ aria-controls={expanded ? `coding-oauth-models-${definition.slug}` : undefined}
303
+ onClick={onToggleExpanded}
304
+ >
305
+ {expanded ? t("collapseModels") : t("expandModels")}
306
+ </button>
307
+ {source?.available === true ? (
308
+ <button
309
+ id={`coding-oauth-source-pull-${definition.slug}`}
310
+ type="button"
311
+ style={buttonStyle}
312
+ disabled={sourcesBusy}
313
+ onClick={onPreviewSource}
314
+ >
315
+ {t("sourcesPullCopy")}
316
+ </button>
317
+ ) : null}
318
+ </>
319
+ ) : providerStatus.status === "signing-in" ? (
320
+ <>
321
+ <button type="button" style={buttonStyle} disabled={busy} onClick={onCancelLogin}>
322
+ {t("cancelLogin")}
323
+ </button>
324
+ {altMethods.length > 0 ? (
325
+ <button
326
+ type="button"
327
+ style={compactButtonStyle}
328
+ disabled={busy}
329
+ onClick={() => {
330
+ setShowAltMethods((current) => !current);
331
+ }}
332
+ >
333
+ {showAltMethods ? t("hideOtherLoginMethods") : t("otherLoginMethods")}
334
+ </button>
335
+ ) : null}
336
+ {showAltMethods
337
+ ? altMethods.map((method) => (
338
+ <button
339
+ key={method}
340
+ type="button"
341
+ style={compactButtonStyle}
342
+ disabled={busy}
343
+ onClick={() => {
344
+ onSignIn(method);
345
+ }}
346
+ >
347
+ {methodLabel(method, t, { remote, primary: false })}
348
+ </button>
349
+ ))
350
+ : null}
351
+ </>
352
+ ) : (
353
+ <>
354
+ <button
355
+ id={`coding-oauth-login-${definition.slug}`}
356
+ type="button"
357
+ style={primaryButtonStyle}
358
+ disabled={busy}
359
+ onClick={() => {
360
+ onSignIn(primaryMethod);
361
+ }}
362
+ >
363
+ {busy ? t("working") : methodLabel(primaryMethod, t, { remote, primary: true })}
364
+ </button>
365
+ {altMethods.length > 0 ? (
366
+ <button
367
+ type="button"
368
+ style={compactButtonStyle}
369
+ disabled={busy}
370
+ onClick={() => {
371
+ setShowAltMethods((current) => !current);
372
+ }}
373
+ >
374
+ {showAltMethods ? t("hideOtherLoginMethods") : t("otherLoginMethods")}
375
+ </button>
376
+ ) : null}
377
+ {showAltMethods
378
+ ? altMethods.map((method) => (
379
+ <button
380
+ key={method}
381
+ type="button"
382
+ style={compactButtonStyle}
383
+ disabled={busy}
384
+ onClick={() => {
385
+ onSignIn(method);
386
+ }}
387
+ >
388
+ {methodLabel(method, t, { remote, primary: false })}
389
+ </button>
390
+ ))
391
+ : null}
392
+ {source?.available === true ? (
393
+ <button
394
+ id={`coding-oauth-source-pull-${definition.slug}`}
395
+ type="button"
396
+ style={buttonStyle}
397
+ disabled={sourcesBusy}
398
+ onClick={onPreviewSource}
399
+ >
400
+ {t("sourcesPullCopy")}
401
+ </button>
402
+ ) : showSourceReason && source?.reason !== undefined ? (
403
+ <span style={hintStyle}>{t(SOURCE_REASON_KEY[source.reason])}</span>
404
+ ) : null}
405
+ </>
406
+ )}
407
+ </div>
408
+ {providerStatus.status === "error" ? (
409
+ <p style={{ ...bodyStyle, color: "var(--dsw-alias-state-error-primary)" }}>{providerStatus.message}</p>
410
+ ) : null}
411
+ {providerStatus.status === "signing-in" ? (
412
+ <SignInSteps
413
+ t={t}
414
+ activeMethod={activeMethod}
415
+ userCode={providerStatus.userCode}
416
+ url={providerStatus.url}
417
+ popupBlocked={popupBlocked}
418
+ />
419
+ ) : null}
420
+ {providerStatus.status === "signing-in" && activeMethod !== "device" ? (
421
+ <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
422
+ <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
423
+ <label htmlFor={`coding-oauth-code-${definition.slug}`} style={visuallyHiddenStyle}>
424
+ {t("pasteCodeLabel")}
425
+ </label>
426
+ <input
427
+ id={`coding-oauth-code-${definition.slug}`}
428
+ style={{ ...inputStyle, flex: "1 1 360px" }}
429
+ value={codeInput}
430
+ placeholder={t("pasteCodePlaceholder")}
431
+ disabled={busy}
432
+ onChange={(event) => {
433
+ onCodeChange(event.target.value);
434
+ }}
435
+ onKeyDown={(event) => {
436
+ if (event.key === "Enter") {
437
+ event.preventDefault();
438
+ onSubmitCode();
439
+ }
440
+ }}
441
+ />
442
+ <button
443
+ type="button"
444
+ style={primaryButtonStyle}
445
+ disabled={busy || codeInput.trim().length === 0}
446
+ onClick={onSubmitCode}
447
+ >
448
+ {t("submitCode")}
449
+ </button>
450
+ </div>
451
+ </div>
452
+ ) : null}
453
+ {providerStatus.status === "signed-in" && expanded ? (
454
+ <div id={`coding-oauth-models-${definition.slug}`} style={{ display: "flex", flexDirection: "column", gap: 8 }}>
455
+ <div style={rowStyle}>
456
+ <h4 style={{ ...titleStyle, fontSize: 14 }}>{t("models")}</h4>
457
+ <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
458
+ <button
459
+ type="button"
460
+ style={compactButtonStyle}
461
+ disabled={busy}
462
+ onClick={() => {
463
+ setModelDraft([]);
464
+ }}
465
+ >
466
+ {t("deselectAll")}
467
+ </button>
468
+ <button
469
+ type="button"
470
+ style={compactButtonStyle}
471
+ disabled={busy}
472
+ onClick={() => {
473
+ setModelDraft([...available]);
474
+ }}
475
+ >
476
+ {t("selectAll")}
477
+ </button>
478
+ <button
479
+ type="button"
480
+ style={compactButtonStyle}
481
+ disabled={busy}
482
+ onClick={() => {
483
+ setModelDraft(available.length > 0 ? [available[0]!] : []);
484
+ }}
485
+ >
486
+ {t("resetModelsDefault")}
487
+ </button>
488
+ </div>
489
+ </div>
490
+ <label htmlFor={`coding-oauth-model-filter-${definition.slug}`} style={visuallyHiddenStyle}>
491
+ {t("modelFilterLabel", { provider: t(definition.titleKey) })}
492
+ </label>
493
+ <input
494
+ id={`coding-oauth-model-filter-${definition.slug}`}
495
+ type="search"
496
+ style={inputStyle}
497
+ value={modelFilter}
498
+ placeholder={t("modelFilterPlaceholder")}
499
+ disabled={busy}
500
+ onChange={(event) => {
501
+ setModelFilter(event.target.value);
502
+ }}
503
+ />
504
+ {grokProviderStatus?.status === "signed-in" ? (
505
+ <p style={bodyStyle}>
506
+ {grokProviderStatus.catalogSource === "live"
507
+ ? t("catalogLive")
508
+ : grokProviderStatus.catalogSource === "cache"
509
+ ? t("catalogCache")
510
+ : t("catalogFallback")}
511
+ </p>
512
+ ) : null}
513
+ <p style={bodyStyle}>
514
+ {t("modelHint")} <span style={monoStyle}>{definition.route}/&lt;id&gt;</span>
515
+ </p>
516
+ <ul style={listStyle}>
517
+ {filteredModels.map((id) => {
518
+ const checked = modelDraft.includes(id);
519
+ return (
520
+ <li key={id}>
521
+ <label style={checkRowStyle}>
522
+ <input
523
+ type="checkbox"
524
+ checked={checked}
525
+ disabled={busy}
526
+ onChange={() => {
527
+ const current = new Set(modelDraft);
528
+ if (checked) current.delete(id);
529
+ else current.add(id);
530
+ setModelDraft([...available].filter((model) => current.has(model)));
531
+ }}
532
+ />
533
+ <span style={monoStyle}>{id}</span>
534
+ </label>
535
+ </li>
536
+ );
537
+ })}
538
+ </ul>
539
+ <div style={{ display: "flex", flexWrap: "wrap", alignItems: "center", gap: 8 }}>
540
+ <button
541
+ type="button"
542
+ style={primaryButtonStyle}
543
+ disabled={busy || !modelDraftDirty}
544
+ onClick={() => onSaveModels(modelDraft)}
545
+ >
546
+ {busy ? t("working") : t("applyModelDraft")}
547
+ </button>
548
+ {modelDraftDirty ? <span style={hintStyle}>{t("modelDraftPending")}</span> : null}
549
+ </div>
550
+ {filteredModels.length === 0 ? <p style={hintStyle}>{t("modelFilterPlaceholder")}</p> : null}
551
+ {grokProviderStatus?.status === "signed-in" && grokProviderStatus.catalogError !== undefined ? (
552
+ <p style={{ ...bodyStyle, color: "var(--dsw-alias-state-error-primary)" }}>{t("catalogError")}</p>
553
+ ) : null}
554
+ {definition.slug === "codex" && showUsage ? (
555
+ <div style={nestedStyle}>
556
+ <p style={{ ...bodyStyle, color: "var(--dsw-alias-label-primary)" }}>{t("usageTitle")}</p>
557
+ {usageError === undefined ? null : (
558
+ <p style={{ ...bodyStyle, color: "var(--dsw-alias-state-error-primary)" }} role="alert">
559
+ {usageError}
560
+ </p>
561
+ )}
562
+ {usageLoading && usage === undefined ? (
563
+ <p style={hintStyle}>{t("usageLoading")}</p>
564
+ ) : usage === undefined || !usageHasVisibleFields(usage) ? (
565
+ <p style={hintStyle}>{t("usageEmpty")}</p>
566
+ ) : (
567
+ <>
568
+ {fetchedAt === undefined ? null : <p style={hintStyle}>{t("usageFetchedAt", { time: fetchedAt })}</p>}
569
+ {usagePercent !== undefined ? (
570
+ <ProgressBar
571
+ value={usagePercent}
572
+ label={t("usageRateLimit")}
573
+ meta={t("usageUsed", { value: `${String(usagePercent)}%` })}
574
+ />
575
+ ) : null}
576
+ {usage.rateLimits.map((limit) => {
577
+ const window = limit.windows[0];
578
+ const used = window?.usedPercent;
579
+ const resetsAt = formatEpoch(window?.resetsAt);
580
+ return (
581
+ <div key={limit.id} style={{ display: "flex", flexDirection: "column", gap: 4 }}>
582
+ {used === undefined ? (
583
+ <p style={hintStyle}>{limit.name ?? t("usageRateLimit")}</p>
584
+ ) : (
585
+ <ProgressBar
586
+ value={used}
587
+ label={limit.name ?? t("usageRateLimit")}
588
+ meta={
589
+ resetsAt === undefined
590
+ ? t("usageUsed", { value: `${String(used)}%` })
591
+ : `${t("usageUsed", { value: `${String(used)}%` })} · ${t("usageResets", { time: resetsAt })}`
592
+ }
593
+ />
594
+ )}
595
+ </div>
596
+ );
597
+ })}
598
+ </>
599
+ )}
600
+ </div>
601
+ ) : null}
602
+ </div>
603
+ ) : null}
604
+ </div>
605
+ );
606
+ }