|
1 | 1 | import { useCallback, useState, useEffect, useMemo } from "react" |
2 | 2 | import { useEvent } from "react-use" |
3 | | -import { VSCodeTextField } from "@vscode/webview-ui-toolkit/react" |
| 3 | +import { VSCodeTextField, VSCodeButton } from "@vscode/webview-ui-toolkit/react" |
| 4 | +import pkceChallenge from "pkce-challenge" |
4 | 5 |
|
5 | 6 | import type { ProviderSettings } from "@roo-code/types" |
6 | 7 |
|
7 | 8 | import { ExtensionMessage } from "@roo/ExtensionMessage" |
8 | 9 | import { vscode } from "@src/utils/vscode" |
9 | 10 | import { useAppTranslation } from "@src/i18n/TranslationContext" |
10 | | -import { VSCodeButtonLink } from "@src/components/common/VSCodeButtonLink" |
11 | 11 | import { SearchableSelect, type SearchableSelectOption } from "@src/components/ui" |
12 | 12 | import { cn } from "@src/lib/utils" |
13 | 13 | import { formatPrice } from "@/utils/formatPrice" |
| 14 | +import { getHuggingFaceAuthUrl } from "@src/oauth/urls" |
14 | 15 |
|
15 | 16 | import { inputEventTransform } from "../transforms" |
16 | 17 |
|
@@ -39,9 +40,10 @@ type HuggingFaceProps = { |
39 | 40 | value: ProviderSettings[keyof ProviderSettings], |
40 | 41 | isUserAction?: boolean, |
41 | 42 | ) => void |
| 43 | + uriScheme?: string |
42 | 44 | } |
43 | 45 |
|
44 | | -export const HuggingFace = ({ apiConfiguration, setApiConfigurationField }: HuggingFaceProps) => { |
| 46 | +export const HuggingFace = ({ apiConfiguration, setApiConfigurationField, uriScheme }: HuggingFaceProps) => { |
45 | 47 | const { t } = useAppTranslation() |
46 | 48 | const [models, setModels] = useState<HuggingFaceModel[]>([]) |
47 | 49 | const [loading, setLoading] = useState(false) |
@@ -109,6 +111,28 @@ export const HuggingFace = ({ apiConfiguration, setApiConfigurationField }: Hugg |
109 | 111 | setApiConfigurationField, |
110 | 112 | ]) |
111 | 113 |
|
| 114 | + // Start OAuth with PKCE |
| 115 | + const handleStartOauth = useCallback(async () => { |
| 116 | + try { |
| 117 | + // Generate PKCE challenge using the library |
| 118 | + const pkce = await pkceChallenge() |
| 119 | + const state = crypto.randomUUID() // Use built-in UUID for state |
| 120 | + |
| 121 | + // Store verifier/state in extension (secrets) |
| 122 | + vscode.postMessage({ |
| 123 | + type: "storeHuggingFacePkce", |
| 124 | + values: { verifier: pkce.code_verifier, state }, |
| 125 | + }) |
| 126 | + |
| 127 | + const authUrl = getHuggingFaceAuthUrl(uriScheme, pkce.code_challenge, state) |
| 128 | + |
| 129 | + // Open externally via extension |
| 130 | + vscode.postMessage({ type: "openExternal", url: authUrl }) |
| 131 | + } catch (e) { |
| 132 | + console.error("Failed to start Hugging Face OAuth:", e) |
| 133 | + } |
| 134 | + }, [uriScheme]) |
| 135 | + |
112 | 136 | const handleModelSelect = (modelId: string) => { |
113 | 137 | setApiConfigurationField("huggingFaceModelId", modelId) |
114 | 138 | // Reset provider selection when model changes |
@@ -180,9 +204,9 @@ export const HuggingFace = ({ apiConfiguration, setApiConfigurationField }: Hugg |
180 | 204 | </div> |
181 | 205 |
|
182 | 206 | {!apiConfiguration?.huggingFaceApiKey && ( |
183 | | - <VSCodeButtonLink href="https://huggingface.co/settings/tokens" appearance="secondary"> |
| 207 | + <VSCodeButton appearance="primary" onClick={handleStartOauth} style={{ width: "100%" }}> |
184 | 208 | {t("settings:providers.getHuggingFaceApiKey")} |
185 | | - </VSCodeButtonLink> |
| 209 | + </VSCodeButton> |
186 | 210 | )} |
187 | 211 |
|
188 | 212 | <div className="flex flex-col gap-2"> |
|
0 commit comments