diff --git a/apps/open-swe/src/routes/github/issue-webhook.ts b/apps/open-swe/src/routes/github/issue-webhook.ts index c1dfc9e2..9fa5f8a0 100644 --- a/apps/open-swe/src/routes/github/issue-webhook.ts +++ b/apps/open-swe/src/routes/github/issue-webhook.ts @@ -20,7 +20,7 @@ import { } from "../../utils/github/label.js"; import { ManagerGraphUpdate } from "@open-swe/shared/open-swe/manager/types"; import { RequestSource } from "../../constants.js"; -import { isAllowedUser } from "../../utils/github/allowed-users.js"; +import { isAllowedUser } from "@open-swe/shared/github/allowed-users"; const logger = createLogger(LogLevel.INFO, "GitHubIssueWebhook"); diff --git a/apps/open-swe/src/security/auth.ts b/apps/open-swe/src/security/auth.ts index 096ec02b..6192baeb 100644 --- a/apps/open-swe/src/security/auth.ts +++ b/apps/open-swe/src/security/auth.ts @@ -17,7 +17,7 @@ import { verifyGitHubWebhookOrThrow } from "./github.js"; import { createWithOwnerMetadata, createOwnerFilter } from "./utils.js"; import { LANGGRAPH_USER_PERMISSIONS } from "../constants.js"; import { getGitHubPatFromRequest } from "../utils/github-pat.js"; -import { isAllowedUser } from "../utils/github/allowed-users.js"; +import { isAllowedUser } from "@open-swe/shared/github/allowed-users"; import { validate } from "uuid"; // TODO: Export from LangGraph SDK diff --git a/apps/open-swe/src/utils/model-manager.ts b/apps/open-swe/src/utils/model-manager.ts index 4e8d9af9..04625d47 100644 --- a/apps/open-swe/src/utils/model-manager.ts +++ b/apps/open-swe/src/utils/model-manager.ts @@ -5,7 +5,7 @@ import { import { GraphConfig } from "@open-swe/shared/open-swe/types"; import { createLogger, LogLevel } from "./logger.js"; import { Task } from "./load-model.js"; -import { isAllowedUser } from "./github/allowed-users.js"; +import { isAllowedUser } from "@open-swe/shared/github/allowed-users"; import { decryptSecret } from "@open-swe/shared/crypto"; const logger = createLogger(LogLevel.INFO, "ModelManager"); diff --git a/apps/web/src/components/api-key-banner.tsx b/apps/web/src/components/api-key-banner.tsx new file mode 100644 index 00000000..56b432bc --- /dev/null +++ b/apps/web/src/components/api-key-banner.tsx @@ -0,0 +1,105 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { X, Key } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/utils"; +import { useUser } from "@/hooks/useUser"; +import { useConfigStore, DEFAULT_CONFIG_KEY } from "@/hooks/useConfigStore"; +import { isAllowedUser } from "@open-swe/shared/github/allowed-users"; +import Link from "next/link"; + +const API_KEY_BANNER_DISMISSED_KEY = "api_key_banner_dismissed"; + +export function ApiKeyBanner() { + const { user, isLoading } = useUser(); + const { getConfig } = useConfigStore(); + const config = getConfig(DEFAULT_CONFIG_KEY); + const [dismissed, setDismissed] = useState(false); + + useEffect(() => { + if (typeof window === "undefined") { + return; + } + // Check if user has previously dismissed the banner + const hasDismissed = localStorage.getItem(API_KEY_BANNER_DISMISSED_KEY); + if (hasDismissed === "true") { + setDismissed(true); + } + }, []); + + // Don't show banner if: + // - Still loading user data + // - User is not authenticated + // - User has dismissed the banner + if (isLoading || !user || dismissed) { + return null; + } + + // Check if user is in the allowed list + const userIsAllowed = isAllowedUser(user.login); + + // If user is allowed, they don't need API keys + if (userIsAllowed) { + return null; + } + + // Check if user has any API keys configured + const apiKeys = config.apiKeys || {}; + const hasApiKeys = + apiKeys.anthropicApiKey || apiKeys.openaiApiKey || apiKeys.googleApiKey; + + // If user has API keys, don't show banner + if (hasApiKeys) { + return null; + } + + const handleDismiss = () => { + if (typeof window === "undefined") { + return; + } + setDismissed(true); + localStorage.setItem(API_KEY_BANNER_DISMISSED_KEY, "true"); + }; + + return ( +
+ You need to add an API key to use Open SWE. Add your Anthropic, + OpenAI, or Google API key to get started. +
+