From 7150082f5a18363a7994ab5a9d1179711e9ecec9 Mon Sep 17 00:00:00 2001 From: Will Chen Date: Wed, 10 Sep 2025 13:00:31 -0700 Subject: [PATCH] Add OpenRouter to setup banner (#1242) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary by cubic Added OpenRouter as a first-class option in the setup banner and introduced a reusable provider card component. This streamlines provider selection and adds E2E coverage for the setup flow. - **New Features** - Added SetupProviderCard and used it for Google and OpenRouter in SetupBanner. - Clicking Google or OpenRouter routes to the correct provider settings and logs PostHog events. - Kept “Other providers” link to Settings. - Added setup.spec.ts E2E test to verify Google, OpenRouter, and Other navigation; introduced test config flag showSetupScreen to control the OPENAI_API_KEY shortcut. --- e2e-tests/helpers/test_helper.ts | 7 ++- e2e-tests/setup.spec.ts | 31 ++++++++++ src/components/SetupBanner.tsx | 65 +++++++++++++-------- src/components/SetupProviderCard.tsx | 87 ++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 26 deletions(-) create mode 100644 e2e-tests/setup.spec.ts create mode 100644 src/components/SetupProviderCard.tsx diff --git a/e2e-tests/helpers/test_helper.ts b/e2e-tests/helpers/test_helper.ts index 45d5921..194aab5 100644 --- a/e2e-tests/helpers/test_helper.ts +++ b/e2e-tests/helpers/test_helper.ts @@ -1002,6 +1002,7 @@ export class PageObject { interface ElectronConfig { preLaunchHook?: ({ userDataDir }: { userDataDir: string }) => Promise; + showSetupScreen?: boolean; } // From https://github.com/microsoft/playwright/issues/8208#issuecomment-1435475930 @@ -1064,8 +1065,10 @@ export const test = base.extend<{ process.env.DYAD_ENGINE_URL = "http://localhost:3500/engine/v1"; process.env.DYAD_GATEWAY_URL = "http://localhost:3500/gateway/v1"; process.env.E2E_TEST_BUILD = "true"; - // This is just a hack to avoid the AI setup screen. - process.env.OPENAI_API_KEY = "sk-test"; + if (!electronConfig.showSetupScreen) { + // This is just a hack to avoid the AI setup screen. + process.env.OPENAI_API_KEY = "sk-test"; + } const baseTmpDir = os.tmpdir(); const userDataDir = path.join(baseTmpDir, `dyad-e2e-tests-${Date.now()}`); if (electronConfig.preLaunchHook) { diff --git a/e2e-tests/setup.spec.ts b/e2e-tests/setup.spec.ts new file mode 100644 index 0000000..54d4e2b --- /dev/null +++ b/e2e-tests/setup.spec.ts @@ -0,0 +1,31 @@ +import { testWithConfig } from "./helpers/test_helper"; +import { expect } from "@playwright/test"; + +const testSetup = testWithConfig({ + showSetupScreen: true, +}); + +testSetup("setup ai provider", async ({ po }) => { + await po.page + .getByRole("button", { name: "Setup Google Gemini API Key" }) + .click(); + await expect( + po.page.getByRole("heading", { name: "Configure Google" }), + ).toBeVisible(); + expect(po.page.url()).toEqual("file:///providers/google"); + + await po.page.getByRole("button", { name: "Go Back" }).click(); + await po.page + .getByRole("button", { name: "Setup OpenRouter API Key Free" }) + .click(); + await expect( + po.page.getByRole("heading", { name: "Configure OpenRouter" }), + ).toBeVisible(); + expect(po.page.url()).toEqual("file:///providers/openrouter"); + + await po.page.getByRole("button", { name: "Go Back" }).click(); + await po.page + .getByRole("button", { name: "Setup other AI providers" }) + .click(); + expect(po.page.url()).toEqual("file:///settings"); +}); diff --git a/src/components/SetupBanner.tsx b/src/components/SetupBanner.tsx index 0d3d34e..f9dd64e 100644 --- a/src/components/SetupBanner.tsx +++ b/src/components/SetupBanner.tsx @@ -11,6 +11,7 @@ import { } from "lucide-react"; import { providerSettingsRoute } from "@/routes/settings/providers/$provider"; import { settingsRoute } from "@/routes/settings"; +import SetupProviderCard from "@/components/SetupProviderCard"; import { useState, useEffect, useCallback } from "react"; import { IpcClient } from "@/ipc/ipc_client"; @@ -58,7 +59,7 @@ export function SetupBanner() { checkNode(); }, [checkNode]); - const handleAiSetupClick = () => { + const handleGoogleSetupClick = () => { posthog.capture("setup-flow:ai-provider-setup:google:click"); navigate({ to: providerSettingsRoute.id, @@ -66,6 +67,14 @@ export function SetupBanner() { }); }; + const handleOpenRouterSetupClick = () => { + posthog.capture("setup-flow:ai-provider-setup:openrouter:click"); + navigate({ + to: providerSettingsRoute.id, + params: { provider: "openrouter" }, + }); + }; + const handleOtherProvidersClick = () => { posthog.capture("setup-flow:ai-provider-setup:other:click"); navigate({ @@ -226,30 +235,38 @@ export function SetupBanner() {

Connect your preferred AI provider to start generating code.

-
-
-
-
- -
-
-

- Setup Google Gemini API Key -

-

- - Use Google Gemini for free -

-
-
- -
-
+ leadingIcon={ + + } + title="Setup Google Gemini API Key" + subtitle={ + <> + + Use Google Gemini for free + + } + /> + + + } + title="Setup OpenRouter API Key" + subtitle={ + <> + + Free models available + + } + />
void; + tabIndex?: number; + className?: string; +}) { + const styles = getVariantStyles(variant); + + return ( +
+
+
+
+ {leadingIcon} +
+
+

+ {title} +

+ {subtitle ? ( +
+ {subtitle} +
+ ) : null} +
+
+ +
+
+ ); +} + +function getVariantStyles(variant: SetupProviderVariant) { + switch (variant) { + case "google": + return { + container: + "bg-blue-50 dark:bg-blue-900/50 border-blue-200 dark:border-blue-700 hover:bg-blue-100 dark:hover:bg-blue-900/70", + iconWrapper: "bg-blue-100 dark:bg-blue-800", + titleColor: "text-blue-800 dark:text-blue-300", + subtitleColor: "text-blue-600 dark:text-blue-400", + chevronColor: "text-blue-600 dark:text-blue-400", + } as const; + case "openrouter": + return { + container: + "bg-purple-50 dark:bg-purple-900/50 border-purple-200 dark:border-purple-700 hover:bg-purple-100 dark:hover:bg-purple-900/70", + iconWrapper: "bg-purple-100 dark:bg-purple-800", + titleColor: "text-purple-800 dark:text-purple-300", + subtitleColor: "text-purple-600 dark:text-purple-400", + chevronColor: "text-purple-600 dark:text-purple-400", + } as const; + } +} + +export default SetupProviderCard;