import { useState } from "react"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { useToast } from "@/components/ui/use-toast"; import { getClient, setApiKeyHeader } from "@/api/AxiosClient"; import { AuthStatusValue, useAuthDiagnostics, } from "@/hooks/useAuthDiagnostics"; type BannerStatus = Exclude | "error"; function getCopy(status: BannerStatus): { title: string; description: string } { switch (status) { case "missing_env": return { title: "Skyvern API key missing", description: "All requests from the UI to the local backend will fail until a valid key is configured.", }; case "invalid_format": return { title: "Skyvern API key is invalid", description: "The configured key cannot be decoded. Regenerate a new key to continue using the UI.", }; case "invalid": return { title: "Skyvern API key not recognized", description: "The backend rejected the configured key. Regenerate it to refresh local auth.", }; case "expired": return { title: "Skyvern API key expired", description: "The current key is no longer valid. Generate a fresh key to restore connectivity.", }; case "not_found": return { title: "Local organization missing", description: "The backend could not find the Skyvern-local organization. Regenerate the key to recreate it.", }; case "error": default: return { title: "Unable to verify Skyvern API key", description: "The UI could not reach the diagnostics endpoint. Ensure the backend is running locally.", }; } } function SelfHealApiKeyBanner() { const diagnosticsQuery = useAuthDiagnostics(); const { toast } = useToast(); const [isRepairing, setIsRepairing] = useState(false); const [errorMessage, setErrorMessage] = useState(null); const isProductionBuild = !import.meta.env.DEV; const { data, error, isLoading, refetch } = diagnosticsQuery; const rawStatus = data?.status; const bannerStatus: BannerStatus | null = error ? "error" : rawStatus && rawStatus !== "ok" ? rawStatus : null; if (!bannerStatus && !errorMessage) { if (isLoading) { return null; } return null; } const copy = getCopy(bannerStatus ?? "missing_env"); const queryErrorMessage = error?.message ?? null; const handleRepair = async () => { setIsRepairing(true); setErrorMessage(null); try { const client = await getClient(null); const response = await client.post<{ fingerprint?: string; api_key?: string; backend_env_path?: string; frontend_env_path?: string; }>("/internal/auth/repair"); const { fingerprint, api_key: apiKey, backend_env_path: backendEnvPath, frontend_env_path: frontendEnvPath, } = response.data; if (!apiKey) { throw new Error("Repair succeeded but no API key was returned."); } setApiKeyHeader(apiKey); const fingerprintSuffix = fingerprint ? ` (fingerprint ${fingerprint})` : ""; const pathsElements = []; if (backendEnvPath) { pathsElements.push(
Backend: {backendEnvPath}
); } if (frontendEnvPath) { pathsElements.push(
Frontend: {frontendEnvPath}
, ); } toast({ title: "API key regenerated", description: (
Requests now use the updated key automatically{fingerprintSuffix}{" "} persisted to sessionStorage and written to the following .env paths:
{pathsElements.length > 0 && (
{pathsElements}
)} {isProductionBuild && (
Restart the UI server for more robust API key persistence.
)}
), }); await refetch({ throwOnError: false }); } catch (fetchError) { const message = fetchError instanceof Error ? fetchError.message : "Unable to repair API key"; setErrorMessage(message); } finally { setIsRepairing(false); } }; return (
{copy.title} {bannerStatus !== "error" ? ( <>

{copy.description} Update VITE_SKYVERN_API_KEY in{" "} skyvern-frontend/.env by running skyvern init or click the button below to regenerate it automatically.

{isProductionBuild && (

When running a production build, the regenerated API key is stored in sessionStorage. Closing this tab or browser window will lose the key. Restart the UI server for more robust persistence.

)}
) : (

{copy.description}

)} {errorMessage ? (

{errorMessage}

) : null} {queryErrorMessage && !errorMessage ? (

{queryErrorMessage}

) : null}
); } export { SelfHealApiKeyBanner };