dsh-coding-subscription-oauth 0.6.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/CHANGELOG.md +291 -255
  2. package/CONTRIBUTING.md +138 -129
  3. package/INSTALL.md +261 -256
  4. package/LICENSE +19 -19
  5. package/NOTICE +11 -11
  6. package/README.de.md +303 -303
  7. package/README.es.md +304 -304
  8. package/README.fr.md +304 -304
  9. package/README.ja.md +304 -304
  10. package/README.ko.md +304 -304
  11. package/README.md +321 -320
  12. package/README.pt-BR.md +304 -304
  13. package/README.ru.md +304 -304
  14. package/README.zh-CN.md +319 -318
  15. package/compatibility/dsh-bom.json +36 -30
  16. package/cordis.patch.yml +13 -13
  17. package/docs/00-project-rules.md +213 -212
  18. package/docs/02-architecture.md +142 -138
  19. package/docs/02-architecture.zh-CN.md +142 -138
  20. package/lib/auth-routes.d.ts +10 -1
  21. package/lib/auth-routes.d.ts.map +1 -1
  22. package/lib/bin.js +483 -50
  23. package/lib/bin.js.map +4 -4
  24. package/lib/client.js +5 -5
  25. package/lib/client.js.map +4 -4
  26. package/lib/compatibility.d.ts +0 -1
  27. package/lib/compatibility.d.ts.map +1 -1
  28. package/lib/gateway-protocol.d.ts +3 -46
  29. package/lib/gateway-protocol.d.ts.map +1 -1
  30. package/lib/gateway-routes.d.ts.map +1 -1
  31. package/lib/grok-errors.d.ts +2 -12
  32. package/lib/grok-errors.d.ts.map +1 -1
  33. package/lib/grok-imagine/client.d.ts +73 -0
  34. package/lib/grok-imagine/client.d.ts.map +1 -0
  35. package/lib/grok-imagine/index.d.ts +8 -0
  36. package/lib/grok-imagine/index.d.ts.map +1 -0
  37. package/lib/grok-imagine/net.d.ts +10 -0
  38. package/lib/grok-imagine/net.d.ts.map +1 -0
  39. package/lib/grok-imagine/parse.d.ts +20 -0
  40. package/lib/grok-imagine/parse.d.ts.map +1 -0
  41. package/lib/grok-imagine/types.d.ts +191 -0
  42. package/lib/grok-imagine/types.d.ts.map +1 -0
  43. package/lib/grok-imagine.d.ts +3 -263
  44. package/lib/grok-imagine.d.ts.map +1 -1
  45. package/lib/http-json.d.ts +2 -9
  46. package/lib/http-json.d.ts.map +1 -1
  47. package/lib/ids.d.ts +4 -0
  48. package/lib/ids.d.ts.map +1 -1
  49. package/lib/index.d.ts +2 -1
  50. package/lib/index.d.ts.map +1 -1
  51. package/lib/index.js +730 -196
  52. package/lib/index.js.map +4 -4
  53. package/lib/invariant.js.map +1 -1
  54. package/lib/kimi-errors.d.ts +2 -12
  55. package/lib/kimi-errors.d.ts.map +1 -1
  56. package/lib/oauth-session.d.ts +2 -0
  57. package/lib/oauth-session.d.ts.map +1 -1
  58. package/lib/session.d.ts +2 -0
  59. package/lib/session.d.ts.map +1 -1
  60. package/lib/store.d.ts +73 -1
  61. package/lib/store.d.ts.map +1 -1
  62. package/media/en/settings_accounts.png +0 -0
  63. package/media/en/settings_capabilities.png +0 -0
  64. package/media/en/settings_gateway.png +0 -0
  65. package/media/settings_accounts.png +0 -0
  66. package/media/settings_capabilities.png +0 -0
  67. package/media/settings_gateway.png +0 -0
  68. package/media/settings_overview.png +0 -0
  69. package/media/zh-CN/settings_accounts.png +0 -0
  70. package/media/zh-CN/settings_capabilities.png +0 -0
  71. package/media/zh-CN/settings_gateway.png +0 -0
  72. package/package.json +224 -221
  73. package/patches/dsh-agy@0.1.2.patch +25 -25
  74. package/scripts/release.mjs +187 -186
  75. package/scripts/smoke-deployed-routes.mjs +146 -146
  76. package/scripts/verify-deployed-catalog.mjs +87 -87
  77. package/src/adapter.ts +0 -348
  78. package/src/alias-adapter.ts +0 -147
  79. package/src/auth-routes.ts +0 -921
  80. package/src/auth.ts +0 -67
  81. package/src/bin.ts +0 -350
  82. package/src/capability-routes.ts +0 -279
  83. package/src/capability-runtime.ts +0 -314
  84. package/src/capability-settings.ts +0 -671
  85. package/src/capability-tools.ts +0 -685
  86. package/src/catalog.ts +0 -271
  87. package/src/client/GrokBuildSettings.tsx +0 -771
  88. package/src/client/api.ts +0 -88
  89. package/src/client/components/AboutTab.tsx +0 -30
  90. package/src/client/components/AccountsTab.tsx +0 -241
  91. package/src/client/components/Badge.tsx +0 -33
  92. package/src/client/components/CapabilitiesTab.tsx +0 -265
  93. package/src/client/components/CliPullPreview.tsx +0 -116
  94. package/src/client/components/CopyButton.tsx +0 -57
  95. package/src/client/components/GatewayTab.tsx +0 -469
  96. package/src/client/components/NoticeBanner.tsx +0 -46
  97. package/src/client/components/ProgressBar.tsx +0 -53
  98. package/src/client/components/ProviderCard.tsx +0 -606
  99. package/src/client/components/SettingsTabs.tsx +0 -75
  100. package/src/client/components/ToggleSwitch.tsx +0 -71
  101. package/src/client/constants.ts +0 -230
  102. package/src/client/display.ts +0 -61
  103. package/src/client/dshClientAdapter.ts +0 -127
  104. package/src/client/gatewaySnippets.ts +0 -37
  105. package/src/client/index.tsx +0 -156
  106. package/src/client/locales.ts +0 -540
  107. package/src/client/microStyles.ts +0 -52
  108. package/src/client/parsers.ts +0 -398
  109. package/src/client/styles.ts +0 -325
  110. package/src/client/types.ts +0 -199
  111. package/src/codex-http.ts +0 -447
  112. package/src/codex-images.ts +0 -503
  113. package/src/codex-model-capabilities.ts +0 -320
  114. package/src/codex-search.ts +0 -245
  115. package/src/codex-usage.ts +0 -263
  116. package/src/compatibility.ts +0 -55
  117. package/src/dsh-host-adapter.ts +0 -173
  118. package/src/gateway-anthropic-messages.ts +0 -84
  119. package/src/gateway-auth.ts +0 -102
  120. package/src/gateway-backend.ts +0 -274
  121. package/src/gateway-body.ts +0 -49
  122. package/src/gateway-config.ts +0 -76
  123. package/src/gateway-http.ts +0 -104
  124. package/src/gateway-openai-chat.ts +0 -124
  125. package/src/gateway-openai-responses.ts +0 -53
  126. package/src/gateway-parse.ts +0 -224
  127. package/src/gateway-protocol.ts +0 -52
  128. package/src/gateway-routes.ts +0 -158
  129. package/src/gateway.ts +0 -258
  130. package/src/grok-errors.ts +0 -24
  131. package/src/grok-imagine.ts +0 -1627
  132. package/src/grok-import.ts +0 -151
  133. package/src/http-json.ts +0 -82
  134. package/src/ids.ts +0 -59
  135. package/src/imagine-routes.ts +0 -463
  136. package/src/index.ts +0 -735
  137. package/src/invariant.ts +0 -17
  138. package/src/kimi-errors.ts +0 -26
  139. package/src/media-store.ts +0 -927
  140. package/src/oauth-import-routes.ts +0 -324
  141. package/src/oauth-providers.ts +0 -152
  142. package/src/oauth-session.ts +0 -183
  143. package/src/oauth-sources.ts +0 -1104
  144. package/src/oauth.ts +0 -620
  145. package/src/provider.ts +0 -128
  146. package/src/proxy.ts +0 -11
  147. package/src/redact.ts +0 -72
  148. package/src/session.ts +0 -218
  149. package/src/store.ts +0 -217
  150. package/src/web-origin.ts +0 -296
  151. package/src/web-routes.ts +0 -38
@@ -1,116 +0,0 @@
1
- /** CLI credential pull preview panel. */
2
-
3
- import { SOURCE_CONFLICT_KEY, SOURCE_KIND_KEY, SOURCE_PREVIEW_ACTION_KEY } from "../constants.ts";
4
- import { formatEpoch } from "../parsers.ts";
5
- import {
6
- bodyStyle,
7
- buttonStyle,
8
- cardStyle,
9
- checkRowStyle,
10
- hintStyle,
11
- listStyle,
12
- monoStyle,
13
- primaryButtonStyle,
14
- } from "../styles.ts";
15
- import type { GrokBuildSettingsInjected, SourcePreview } from "../types.ts";
16
-
17
- export interface CliPullPreviewProps {
18
- t: GrokBuildSettingsInjected["t"];
19
- preview: SourcePreview;
20
- confirmOverwrite: boolean;
21
- sourcesBusy: boolean;
22
- onConfirmOverwriteChange: (checked: boolean) => void;
23
- onCommit: () => void;
24
- onCancel: () => void;
25
- }
26
-
27
- export function CliPullPreview({
28
- t,
29
- preview,
30
- confirmOverwrite,
31
- sourcesBusy,
32
- onConfirmOverwriteChange,
33
- onCommit,
34
- onCancel,
35
- }: CliPullPreviewProps) {
36
- const expiresAt = formatEpoch(preview.expiresAt);
37
- const ticketExpiresAt = formatEpoch(preview.ticketExpiresAt);
38
-
39
- return (
40
- <section
41
- id={`coding-oauth-source-preview-${preview.kind}`}
42
- style={cardStyle}
43
- aria-labelledby={`coding-oauth-source-preview-title-${preview.kind}`}
44
- aria-live="polite"
45
- tabIndex={-1}
46
- >
47
- <p id={`coding-oauth-source-preview-title-${preview.kind}`} style={bodyStyle}>
48
- {t("sourcesPreviewTitle")} · {t(SOURCE_KIND_KEY[preview.kind])}
49
- </p>
50
- <p style={hintStyle}>
51
- <span style={monoStyle}>{preview.displayPath}</span>
52
- </p>
53
- <p style={bodyStyle}>
54
- {t("sourcesConflict", {
55
- detail: t(
56
- preview.conflict === undefined ? "sourceConflictUnrecognized" : SOURCE_CONFLICT_KEY[preview.conflict],
57
- ),
58
- })}
59
- </p>
60
- <p style={bodyStyle}>
61
- {t("sourcesAction", {
62
- detail: t(
63
- preview.action === undefined ? "sourceActionUnrecognized" : SOURCE_PREVIEW_ACTION_KEY[preview.action],
64
- ),
65
- })}
66
- </p>
67
- {expiresAt === undefined ? null : <p style={hintStyle}>{t("sourcesPreviewExpires", { time: expiresAt })}</p>}
68
- {ticketExpiresAt === undefined ? null : (
69
- <p style={hintStyle}>{t("sourcesTicketExpires", { time: ticketExpiresAt })}</p>
70
- )}
71
- {preview.warnings.length === 0 ? null : (
72
- <ul style={{ ...listStyle, gap: 4 }} aria-label={t("sourcesWarnings")}>
73
- {preview.warnings.map((warning) => (
74
- <li key={warning} style={hintStyle}>
75
- {warning}
76
- </li>
77
- ))}
78
- </ul>
79
- )}
80
- {preview.confirmOverwriteRequired ? (
81
- <label style={checkRowStyle}>
82
- <input
83
- aria-describedby={`coding-oauth-source-overwrite-hint-${preview.kind}`}
84
- type="checkbox"
85
- checked={confirmOverwrite}
86
- disabled={sourcesBusy || preview.action === "blocked"}
87
- onChange={(event) => {
88
- onConfirmOverwriteChange(event.target.checked);
89
- }}
90
- />
91
- <span>
92
- {t("sourcesConfirmOverwrite")}
93
- <span id={`coding-oauth-source-overwrite-hint-${preview.kind}`} style={{ display: "block", ...hintStyle }}>
94
- {t("sourcesConfirmOverwriteHint")}
95
- </span>
96
- </span>
97
- </label>
98
- ) : null}
99
- <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
100
- <button
101
- type="button"
102
- style={primaryButtonStyle}
103
- disabled={
104
- sourcesBusy || preview.action === "blocked" || (preview.confirmOverwriteRequired && !confirmOverwrite)
105
- }
106
- onClick={onCommit}
107
- >
108
- {t("sourcesCommit")}
109
- </button>
110
- <button type="button" style={buttonStyle} disabled={sourcesBusy} onClick={onCancel}>
111
- {t("sourcesCancelPreview")}
112
- </button>
113
- </div>
114
- </section>
115
- );
116
- }
@@ -1,57 +0,0 @@
1
- /** Compact copy button with copied / failed feedback. */
2
-
3
- import { useCallback, useEffect, useRef, useState } from "react";
4
- import { copyText } from "../api.ts";
5
- import { compactButtonStyle, primaryButtonStyle } from "../styles.ts";
6
-
7
- export interface CopyButtonProps {
8
- text: string;
9
- idleLabel: string;
10
- copiedLabel: string;
11
- failedLabel: string;
12
- primary?: boolean;
13
- disabled?: boolean;
14
- }
15
-
16
- export function CopyButton({
17
- text,
18
- idleLabel,
19
- copiedLabel,
20
- failedLabel,
21
- primary = false,
22
- disabled = false,
23
- }: CopyButtonProps) {
24
- const [state, setState] = useState<"idle" | "copied" | "failed">("idle");
25
- const timerRef = useRef<number | undefined>(undefined);
26
-
27
- useEffect(() => {
28
- return () => {
29
- if (timerRef.current !== undefined) window.clearTimeout(timerRef.current);
30
- };
31
- }, []);
32
-
33
- const handleClick = useCallback(async () => {
34
- const ok = await copyText(text);
35
- setState(ok ? "copied" : "failed");
36
- if (timerRef.current !== undefined) window.clearTimeout(timerRef.current);
37
- timerRef.current = window.setTimeout(() => {
38
- setState("idle");
39
- timerRef.current = undefined;
40
- }, 2000);
41
- }, [text]);
42
-
43
- const label = state === "copied" ? copiedLabel : state === "failed" ? failedLabel : idleLabel;
44
-
45
- return (
46
- <button
47
- type="button"
48
- style={primary ? primaryButtonStyle : compactButtonStyle}
49
- disabled={disabled || text.length === 0}
50
- onClick={() => {
51
- void handleClick();
52
- }}
53
- >
54
- {label}
55
- </button>
56
- );
57
- }
@@ -1,469 +0,0 @@
1
- /** Local API gateway settings tab. */
2
-
3
- import { type KeyboardEvent, useEffect, useMemo, useRef, useState } from "react";
4
- import { GATEWAY_PORT_MAX, GATEWAY_PORT_MIN } from "../constants.ts";
5
- import { buildGatewaySnippets, type GatewaySnippetId } from "../gatewaySnippets.ts";
6
- import { formatGatewayBaseUrl, parseGatewayPort, randomGatewayPort } from "../parsers.ts";
7
- import {
8
- bodyStyle,
9
- buttonStyle,
10
- cardStyle,
11
- copyRowStyle,
12
- dotStyle,
13
- errorStyle,
14
- hintStyle,
15
- inputStyle,
16
- monoStyle,
17
- nestedStyle,
18
- primaryButtonStyle,
19
- segmentedNavStyle,
20
- segmentedTabActiveStyle,
21
- segmentedTabStyle,
22
- skeletonStyle,
23
- snippetStyle,
24
- statusStyle,
25
- titleStyle,
26
- warningStyle,
27
- } from "../styles.ts";
28
- import type { CopyField, GatewayView, GrokBuildSettingsInjected } from "../types.ts";
29
- import { Badge } from "./Badge.tsx";
30
- import { CopyButton } from "./CopyButton.tsx";
31
- import { ToggleSwitch } from "./ToggleSwitch.tsx";
32
-
33
- export interface GatewayTabProps {
34
- t: GrokBuildSettingsInjected["t"];
35
- gateway: GatewayView | undefined;
36
- gatewayError: string | undefined;
37
- gatewayBusy: boolean;
38
- gatewayOnceKey: string | undefined;
39
- gatewayKeyVisible: boolean;
40
- gatewayRotateConfirm: boolean;
41
- gatewayRevealError: string | undefined;
42
- portDraft: string;
43
- copiedField: CopyField | undefined;
44
- copyFailedField: CopyField | undefined;
45
- onEnabledChange: (enabled: boolean) => void;
46
- onRetry: () => void;
47
- onPortDraftChange: (value: string) => void;
48
- onApplyPort: () => void;
49
- onRandomPort: (port: number) => void;
50
- onCopy: (field: CopyField, text: string) => void;
51
- onCopyKey: () => void;
52
- onToggleKeyVisible: () => void;
53
- onRotateConfirm: () => void;
54
- onRotateCancel: () => void;
55
- onRotate: () => void;
56
- }
57
-
58
- const SNIPPET_TABS: readonly {
59
- id: GatewaySnippetId;
60
- labelKey: "gatewaySnippetCurl" | "gatewaySnippetPython" | "gatewaySnippetIde";
61
- }[] = [
62
- { id: "curl", labelKey: "gatewaySnippetCurl" },
63
- { id: "python", labelKey: "gatewaySnippetPython" },
64
- { id: "ide", labelKey: "gatewaySnippetIde" },
65
- ];
66
-
67
- export function GatewayTab({
68
- t,
69
- gateway,
70
- gatewayError,
71
- gatewayBusy,
72
- gatewayOnceKey,
73
- gatewayKeyVisible,
74
- gatewayRotateConfirm,
75
- gatewayRevealError,
76
- portDraft,
77
- copiedField,
78
- copyFailedField,
79
- onEnabledChange,
80
- onRetry,
81
- onPortDraftChange,
82
- onApplyPort,
83
- onRandomPort,
84
- onCopyKey,
85
- onToggleKeyVisible,
86
- onRotateConfirm,
87
- onRotateCancel,
88
- onRotate,
89
- }: GatewayTabProps) {
90
- const [enableConfirm, setEnableConfirm] = useState(false);
91
- const [activeSnippet, setActiveSnippet] = useState<GatewaySnippetId>("curl");
92
- const enableConfirmAction = useRef<HTMLButtonElement>(null);
93
- const rotateTrigger = useRef<HTMLButtonElement>(null);
94
- const rotateConfirmAction = useRef<HTMLButtonElement>(null);
95
- const restoreEnableFocus = useRef(false);
96
- const restoreRotateFocus = useRef(false);
97
-
98
- useEffect(() => {
99
- if (enableConfirm) {
100
- enableConfirmAction.current?.focus();
101
- return;
102
- }
103
- if (!restoreEnableFocus.current) return;
104
- restoreEnableFocus.current = false;
105
- document.getElementById("coding-oauth-gateway-enabled")?.focus();
106
- }, [enableConfirm]);
107
- useEffect(() => {
108
- if (gatewayRotateConfirm) {
109
- rotateConfirmAction.current?.focus();
110
- return;
111
- }
112
- if (!restoreRotateFocus.current) return;
113
- restoreRotateFocus.current = false;
114
- rotateTrigger.current?.focus();
115
- }, [gatewayRotateConfirm]);
116
-
117
- const portValid = parseGatewayPort(portDraft) !== undefined;
118
- const portChanged = gateway !== undefined && portDraft !== String(gateway.port);
119
-
120
- const snippets = useMemo(() => {
121
- if (gateway === undefined || !gatewayKeyVisible || gatewayOnceKey === undefined || gateway.models.length === 0) {
122
- return undefined;
123
- }
124
- const openAi = `${formatGatewayBaseUrl(gateway.bind, gateway.port)}/v1`;
125
- const anthropic = formatGatewayBaseUrl(gateway.bind, gateway.port);
126
- // A masked keyHint is display-only and can never produce an executable command.
127
- return buildGatewaySnippets(openAi, anthropic, gatewayOnceKey, gateway.models[0]);
128
- }, [gateway, gatewayKeyVisible, gatewayOnceKey]);
129
-
130
- const copyLabel = (field: CopyField, idle?: string): string => {
131
- if (copyFailedField === field) return t("copyFailed");
132
- if (copiedField === field) return t("copied");
133
- return idle ?? t("copy");
134
- };
135
-
136
- const focusSnippetTab = (index: number): void => {
137
- const tab = SNIPPET_TABS[index];
138
- if (tab === undefined) return;
139
- setActiveSnippet(tab.id);
140
- document.getElementById(`coding-oauth-snippet-tab-${tab.id}`)?.focus();
141
- };
142
-
143
- const onSnippetKeyDown = (event: KeyboardEvent<HTMLDivElement>): void => {
144
- const current = SNIPPET_TABS.findIndex((tab) => tab.id === activeSnippet);
145
- if (current < 0) return;
146
- if (event.key === "ArrowRight" || event.key === "ArrowDown") {
147
- event.preventDefault();
148
- focusSnippetTab((current + 1) % SNIPPET_TABS.length);
149
- } else if (event.key === "ArrowLeft" || event.key === "ArrowUp") {
150
- event.preventDefault();
151
- focusSnippetTab((current - 1 + SNIPPET_TABS.length) % SNIPPET_TABS.length);
152
- } else if (event.key === "Home") {
153
- event.preventDefault();
154
- focusSnippetTab(0);
155
- } else if (event.key === "End") {
156
- event.preventDefault();
157
- focusSnippetTab(SNIPPET_TABS.length - 1);
158
- }
159
- };
160
-
161
- const openAiUrl = gateway === undefined ? "" : `${formatGatewayBaseUrl(gateway.bind, gateway.port)}/v1`;
162
- const anthropicUrl = gateway === undefined ? "" : formatGatewayBaseUrl(gateway.bind, gateway.port);
163
-
164
- return (
165
- <section style={cardStyle} aria-labelledby="coding-oauth-gateway-title">
166
- <div>
167
- <h3 id="coding-oauth-gateway-title" style={{ ...titleStyle, fontSize: 16 }}>
168
- {t("gatewayTitle")}
169
- </h3>
170
- <p style={{ ...bodyStyle, marginTop: 4 }}>{t("gatewayIntro")}</p>
171
- <p style={{ ...warningStyle, marginTop: 8 }}>{t("gatewayWarning")}</p>
172
- <p style={{ ...hintStyle, marginTop: 8 }}>{t("gatewayLoopbackHint")}</p>
173
- </div>
174
- {gatewayError === undefined ? null : (
175
- <p style={errorStyle} role="alert">
176
- {gatewayError}
177
- </p>
178
- )}
179
- {gatewayRevealError === undefined ? null : (
180
- <p style={errorStyle} role="alert">
181
- {gatewayRevealError}
182
- </p>
183
- )}
184
- {gateway === undefined && gatewayError === undefined ? (
185
- <div style={skeletonStyle} role="status" aria-busy="true">
186
- <div style={statusStyle}>
187
- <span aria-hidden="true" style={dotStyle("loading")} />
188
- {t("gatewayLoading")}
189
- </div>
190
- </div>
191
- ) : gateway === undefined ? (
192
- <div style={nestedStyle} role="status">
193
- <p style={hintStyle}>{t("gatewayLoadFailed")}</p>
194
- <button type="button" style={buttonStyle} onClick={onRetry}>
195
- {t("retry")}
196
- </button>
197
- </div>
198
- ) : (
199
- <div style={nestedStyle}>
200
- <Badge
201
- label={gateway.running ? t("gatewayRunning") : t("gatewayStopped")}
202
- tone={gateway.running ? "success" : "neutral"}
203
- />
204
- <p style={hintStyle}>
205
- {gateway.models.length > 0
206
- ? t("gatewayModelsReady", { count: gateway.models.length, model: gateway.models[0] })
207
- : t("gatewayModelsUnavailable")}
208
- </p>
209
- <p style={hintStyle}>{gateway.keyConfigured ? t("gatewayKeyConfigured") : t("gatewayKeyNotConfigured")}</p>
210
- {enableConfirm ? (
211
- <div style={nestedStyle}>
212
- <p style={bodyStyle}>{t("gatewayEnableConfirm")}</p>
213
- <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
214
- <button
215
- ref={enableConfirmAction}
216
- type="button"
217
- style={primaryButtonStyle}
218
- disabled={gatewayBusy}
219
- onClick={() => {
220
- restoreEnableFocus.current = true;
221
- setEnableConfirm(false);
222
- onEnabledChange(true);
223
- }}
224
- >
225
- {t("gatewayEnableConfirmAction")}
226
- </button>
227
- <button
228
- type="button"
229
- style={buttonStyle}
230
- disabled={gatewayBusy}
231
- onClick={() => {
232
- restoreEnableFocus.current = true;
233
- setEnableConfirm(false);
234
- }}
235
- >
236
- {t("gatewayEnableCancel")}
237
- </button>
238
- </div>
239
- </div>
240
- ) : (
241
- <label
242
- htmlFor="coding-oauth-gateway-enabled"
243
- style={{
244
- display: "flex",
245
- alignItems: "center",
246
- justifyContent: "space-between",
247
- gap: 12,
248
- fontSize: 14,
249
- color: "var(--dsw-alias-label-primary)",
250
- }}
251
- >
252
- <span>{t("gatewayEnabled")}</span>
253
- <ToggleSwitch
254
- id="coding-oauth-gateway-enabled"
255
- checked={gateway.enabled}
256
- disabled={gatewayBusy}
257
- onChange={(enabled) => {
258
- if (enabled) {
259
- setEnableConfirm(true);
260
- return;
261
- }
262
- onEnabledChange(false);
263
- }}
264
- />
265
- </label>
266
- )}
267
- <div>
268
- <label
269
- htmlFor="coding-oauth-gateway-port"
270
- style={{ ...bodyStyle, color: "var(--dsw-alias-label-primary)" }}
271
- >
272
- {t("gatewayPort")}
273
- </label>
274
- <p id="coding-oauth-gateway-port-hint" style={hintStyle}>
275
- {t("gatewayPortHint")}
276
- </p>
277
- {!portValid && portDraft.length > 0 ? (
278
- <p style={{ ...hintStyle, color: "var(--dsw-alias-state-error-primary)" }} role="alert">
279
- {t("gatewayPortInvalid")}
280
- </p>
281
- ) : null}
282
- <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginTop: 8 }}>
283
- <input
284
- id="coding-oauth-gateway-port"
285
- type="number"
286
- inputMode="numeric"
287
- min={GATEWAY_PORT_MIN}
288
- max={GATEWAY_PORT_MAX}
289
- step={1}
290
- value={portDraft}
291
- disabled={gatewayBusy}
292
- aria-describedby="coding-oauth-gateway-port-hint"
293
- aria-invalid={!portValid && portDraft.length > 0}
294
- style={{
295
- ...inputStyle,
296
- width: 112,
297
- flex: "0 0 112px",
298
- borderColor:
299
- !portValid && portDraft.length > 0
300
- ? "var(--dsw-alias-state-error-primary)"
301
- : "var(--dsw-alias-border-l2)",
302
- }}
303
- onChange={(event) => {
304
- onPortDraftChange(event.target.value);
305
- }}
306
- onKeyDown={(event) => {
307
- if (event.key === "Enter") {
308
- event.preventDefault();
309
- onApplyPort();
310
- }
311
- if (event.key === "Escape") {
312
- onPortDraftChange(String(gateway.port));
313
- }
314
- }}
315
- />
316
- <button
317
- type="button"
318
- style={primaryButtonStyle}
319
- disabled={gatewayBusy || !portChanged || !portValid}
320
- onClick={onApplyPort}
321
- >
322
- {t("gatewayPortApply")}
323
- </button>
324
- <button
325
- type="button"
326
- style={buttonStyle}
327
- disabled={gatewayBusy}
328
- onClick={() => {
329
- onRandomPort(randomGatewayPort(gateway.port));
330
- }}
331
- >
332
- {t("gatewayPortRandom")}
333
- </button>
334
- </div>
335
- </div>
336
- <p style={copyRowStyle}>
337
- <span style={hintStyle}>
338
- {t("gatewayOpenAiUrl")}
339
- <span style={{ display: "block", ...monoStyle }}>{openAiUrl}</span>
340
- </span>
341
- <CopyButton
342
- text={openAiUrl}
343
- idleLabel={t("copy")}
344
- copiedLabel={t("copied")}
345
- failedLabel={t("copyFailed")}
346
- />
347
- </p>
348
- <p style={copyRowStyle}>
349
- <span style={hintStyle}>
350
- {t("gatewayAnthropicUrl")}
351
- <span style={{ display: "block", ...monoStyle }}>{anthropicUrl}</span>
352
- </span>
353
- <CopyButton
354
- text={anthropicUrl}
355
- idleLabel={t("copy")}
356
- copiedLabel={t("copied")}
357
- failedLabel={t("copyFailed")}
358
- />
359
- </p>
360
- <p style={copyRowStyle}>
361
- <span style={hintStyle}>
362
- {t("gatewayKeyHint")}
363
- <span style={{ display: "block", ...monoStyle, overflowWrap: "anywhere" }}>
364
- {gatewayKeyVisible && gatewayOnceKey !== undefined ? gatewayOnceKey : gateway.keyHint || "—"}
365
- </span>
366
- </span>
367
- <span style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
368
- <button type="button" style={primaryButtonStyle} disabled={gatewayBusy} onClick={onCopyKey}>
369
- {copyLabel("key", t("gatewayCopyKey"))}
370
- </button>
371
- <button type="button" style={buttonStyle} disabled={gatewayBusy} onClick={onToggleKeyVisible}>
372
- {gatewayKeyVisible ? t("gatewayHideKey") : t("gatewayShowKey")}
373
- </button>
374
- </span>
375
- </p>
376
- <p style={hintStyle}>{t("gatewayKeyCopyHint")}</p>
377
- {gateway.enabled && snippets !== undefined ? (
378
- <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
379
- <h4 style={{ ...titleStyle, fontSize: 14 }}>{t("gatewaySnippetsTitle")}</h4>
380
- <div
381
- role="tablist"
382
- aria-label={t("gatewaySnippetsTitle")}
383
- style={segmentedNavStyle}
384
- onKeyDown={onSnippetKeyDown}
385
- >
386
- {SNIPPET_TABS.map((tab) => {
387
- const selected = activeSnippet === tab.id;
388
- return (
389
- <button
390
- key={tab.id}
391
- id={`coding-oauth-snippet-tab-${tab.id}`}
392
- type="button"
393
- role="tab"
394
- aria-selected={selected}
395
- aria-controls="coding-oauth-snippet-panel"
396
- tabIndex={selected ? 0 : -1}
397
- style={selected ? segmentedTabActiveStyle : segmentedTabStyle}
398
- onClick={() => {
399
- setActiveSnippet(tab.id);
400
- }}
401
- >
402
- {t(tab.labelKey)}
403
- </button>
404
- );
405
- })}
406
- </div>
407
- <div
408
- id="coding-oauth-snippet-panel"
409
- role="tabpanel"
410
- aria-labelledby={`coding-oauth-snippet-tab-${activeSnippet}`}
411
- >
412
- <code style={snippetStyle}>{snippets[activeSnippet]}</code>
413
- </div>
414
- <CopyButton
415
- text={snippets[activeSnippet]}
416
- idleLabel={t("copy")}
417
- copiedLabel={t("copied")}
418
- failedLabel={t("copyFailed")}
419
- />
420
- </div>
421
- ) : gateway.enabled ? (
422
- <p style={hintStyle}>{t("gatewaySnippetsUnavailable")}</p>
423
- ) : null}
424
- {gatewayRotateConfirm ? (
425
- <div style={nestedStyle}>
426
- <p style={bodyStyle}>{t("gatewayRotateConfirm")}</p>
427
- <p style={hintStyle}>{t("gatewayRotateConfirmHint")}</p>
428
- <div style={{ display: "flex", flexWrap: "wrap", gap: 8 }}>
429
- <button
430
- ref={rotateConfirmAction}
431
- type="button"
432
- style={primaryButtonStyle}
433
- disabled={gatewayBusy}
434
- onClick={() => {
435
- restoreRotateFocus.current = true;
436
- onRotate();
437
- }}
438
- >
439
- {t("gatewayRotateConfirmAction")}
440
- </button>
441
- <button
442
- type="button"
443
- style={buttonStyle}
444
- disabled={gatewayBusy}
445
- onClick={() => {
446
- restoreRotateFocus.current = true;
447
- onRotateCancel();
448
- }}
449
- >
450
- {t("gatewayRotateCancel")}
451
- </button>
452
- </div>
453
- </div>
454
- ) : (
455
- <button
456
- ref={rotateTrigger}
457
- type="button"
458
- style={buttonStyle}
459
- disabled={gatewayBusy}
460
- onClick={onRotateConfirm}
461
- >
462
- {t("gatewayRotate")}
463
- </button>
464
- )}
465
- </div>
466
- )}
467
- </section>
468
- );
469
- }
@@ -1,46 +0,0 @@
1
- /** Dismissible notice banner with optional auto-hide. */
2
-
3
- import { useEffect } from "react";
4
- import { bodyStyle, buttonStyle, noticeStyle } from "../styles.ts";
5
-
6
- export interface NoticeBannerProps {
7
- message: string;
8
- dismissLabel?: string;
9
- onDismiss?: () => void;
10
- autoHideMs?: number;
11
- tone?: "info" | "success";
12
- }
13
-
14
- export function NoticeBanner({ message, dismissLabel, onDismiss, autoHideMs, tone = "info" }: NoticeBannerProps) {
15
- useEffect(() => {
16
- if (autoHideMs === undefined || onDismiss === undefined) return;
17
- const timer = window.setTimeout(onDismiss, autoHideMs);
18
- return () => {
19
- window.clearTimeout(timer);
20
- };
21
- }, [autoHideMs, onDismiss]);
22
-
23
- const borderColor =
24
- tone === "success" ? "var(--dsw-alias-state-success-primary, #22a06b)" : "var(--dsw-alias-brand-primary, #1677ff)";
25
-
26
- return (
27
- <div
28
- style={{
29
- ...noticeStyle,
30
- display: "flex",
31
- alignItems: "flex-start",
32
- justifyContent: "space-between",
33
- gap: 12,
34
- borderLeft: `3px solid ${borderColor}`,
35
- }}
36
- role="status"
37
- >
38
- <p style={{ ...bodyStyle, margin: 0, color: "var(--dsw-alias-label-primary)" }}>{message}</p>
39
- {onDismiss === undefined || dismissLabel === undefined ? null : (
40
- <button type="button" style={buttonStyle} onClick={onDismiss}>
41
- {dismissLabel}
42
- </button>
43
- )}
44
- </div>
45
- );
46
- }