Add OpenRouter to setup banner (#1242)

<!-- This is an auto-generated description by cubic. -->

## 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.

<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Will Chen
2025-09-10 13:00:31 -07:00
committed by GitHub
parent b9672004ed
commit 7150082f5a
4 changed files with 164 additions and 26 deletions

View File

@@ -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() {
<p className="text-sm mb-3">
Connect your preferred AI provider to start generating code.
</p>
<div
className="p-3 bg-blue-50 dark:bg-blue-900/50 border border-blue-200 dark:border-blue-700 rounded-lg cursor-pointer hover:bg-blue-100 dark:hover:bg-blue-900/70 transition-colors"
onClick={handleAiSetupClick}
role="button"
<SetupProviderCard
variant="google"
onClick={handleGoogleSetupClick}
tabIndex={isNodeSetupComplete ? 0 : -1}
>
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2">
<div className="bg-blue-100 dark:bg-blue-800 p-1.5 rounded-full">
<Sparkles className="w-4 h-4 text-blue-600 dark:text-blue-400" />
</div>
<div>
<h4 className="font-medium text-sm text-blue-800 dark:text-blue-300">
Setup Google Gemini API Key
</h4>
<p className="text-xs text-blue-600 dark:text-blue-400 flex items-center gap-1">
<GiftIcon className="w-3 h-3" />
Use Google Gemini for free
</p>
</div>
</div>
<ChevronRight className="w-4 h-4 text-blue-600 dark:text-blue-400" />
</div>
</div>
leadingIcon={
<Sparkles className="w-4 h-4 text-blue-600 dark:text-blue-400" />
}
title="Setup Google Gemini API Key"
subtitle={
<>
<GiftIcon className="w-3 h-3" />
Use Google Gemini for free
</>
}
/>
<SetupProviderCard
className="mt-2"
variant="openrouter"
onClick={handleOpenRouterSetupClick}
tabIndex={isNodeSetupComplete ? 0 : -1}
leadingIcon={
<Sparkles className="w-4 h-4 text-purple-600 dark:text-purple-400" />
}
title="Setup OpenRouter API Key"
subtitle={
<>
<GiftIcon className="w-3 h-3" />
Free models available
</>
}
/>
<div
className="mt-2 p-3 bg-gray-50 dark:bg-gray-800/50 border border-gray-200 dark:border-gray-700 rounded-lg cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-800/70 transition-colors"

View File

@@ -0,0 +1,87 @@
import { ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
import { ReactNode } from "react";
type SetupProviderVariant = "google" | "openrouter";
export function SetupProviderCard({
variant,
title,
subtitle,
leadingIcon,
onClick,
tabIndex = 0,
className,
}: {
variant: SetupProviderVariant;
title: string;
subtitle?: ReactNode;
leadingIcon: ReactNode;
onClick: () => void;
tabIndex?: number;
className?: string;
}) {
const styles = getVariantStyles(variant);
return (
<div
className={cn(
"p-3 border rounded-lg cursor-pointer transition-colors",
styles.container,
className,
)}
onClick={onClick}
role="button"
tabIndex={tabIndex}
>
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-2">
<div className={cn("p-1.5 rounded-full", styles.iconWrapper)}>
{leadingIcon}
</div>
<div>
<h4 className={cn("font-medium text-sm", styles.titleColor)}>
{title}
</h4>
{subtitle ? (
<div
className={cn(
"text-xs flex items-center gap-1",
styles.subtitleColor,
)}
>
{subtitle}
</div>
) : null}
</div>
</div>
<ChevronRight className={cn("w-4 h-4", styles.chevronColor)} />
</div>
</div>
);
}
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;