Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion webview-ui/src/components/settings/ApiOptions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,11 @@ const ApiOptions = ({
options.unshift(rooOption)
}
} else {
// Filter out roo from the welcome view
const filteredOptions = options.filter((opt) => opt.value !== "roo")
options.length = 0
options.push(...filteredOptions)

const openRouterIndex = options.findIndex((opt) => opt.value === "openrouter")
if (openRouterIndex > 0) {
const [openRouterOption] = options.splice(openRouterIndex, 1)
Expand All @@ -472,7 +477,7 @@ const ApiOptions = ({
) : (
docs && (
<VSCodeLink href={docs.url} target="_blank" className="flex gap-2">
{docs.name}
{t("settings:providers.apiProviderDocs")}
<BookOpenText className="size-4 inline ml-2" />
</VSCodeLink>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ describe("ApiOptions", () => {
useExtensionStateMock.mockRestore()
})

it("does not pin roo provider to the top on welcome screen", () => {
it("filters out roo provider on welcome screen", () => {
// Mock useExtensionState to ensure no filtering
const useExtensionStateMock = vi.spyOn(ExtensionStateContext, "useExtensionState")
useExtensionStateMock.mockReturnValue({
Expand All @@ -663,13 +663,9 @@ describe("ApiOptions", () => {
// Filter out the placeholder option (empty value)
const providerOptions = options.filter((opt) => opt.value !== "")

// Check that roo is in the list
// Check that roo is NOT in the list when on welcome screen
const rooOption = providerOptions.find((opt) => opt.value === "roo")

if (rooOption) {
// If roo exists, verify it's NOT at the top (should be in alphabetical order)
expect(providerOptions[0].value).not.toBe("roo")
}
expect(rooOption).toBeUndefined()

useExtensionStateMock.mockRestore()
})
Expand Down
12 changes: 10 additions & 2 deletions webview-ui/src/components/welcome/RooHero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const RooHero = () => {
})

return (
<div className="pb-4 forced-color-adjust-none group">
<div className="mb-4 relative forced-color-adjust-none group flex flex-col items-center w-30 pt-4 overflow-clip">
<div
style={{
backgroundColor: "var(--vscode-foreground)",
Expand All @@ -18,9 +18,17 @@ const RooHero = () => {
maskRepeat: "no-repeat",
maskSize: "contain",
}}
className="mx-auto group-hover:animate-bounce translate-y-0 transition-transform duration-500">
className="z-5 mr-auto group-hover:animate-bounce translate-y-0 transition-transform duration-500">
<img src={imagesBaseUri + "/roo-logo.svg"} alt="Roo logo" className="h-8 opacity-0" />
</div>
<div
className="w-[200%] -mt-0.25 h-0.5 overflow-hidden opacity-0 group-hover:opacity-70 transition-opacity duration-300"
data-testid="roo-hero-ground">
<div className="w-full border-b-1 group-hover:border-b-1 border-dashed border-vscode-foreground animate-ground-slide" />
</div>
<div className="z-4 bg-gradient-to-r from-transparent to-vscode-sideBar-background absolute top-0 right-0 bottom-0 w-10 opacity-100" />
<div className="z-3 bg-gradient-to-l from-transparent to-vscode-sideBar-background absolute top-0 left-0 bottom-0 w-10 opacity-100" />
<div className="bg-vscode-foreground/10 rounded-full size-10 z-1 absolute -bottom-4 animate-sun opacity-0 group-hover:opacity-100 transition-opacity duration-300 blur-[2px]" />
</div>
)
}
Expand Down
182 changes: 130 additions & 52 deletions webview-ui/src/components/welcome/WelcomeViewProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,20 @@ import { Tab, TabContent } from "../common/Tab"

import RooHero from "./RooHero"
import { Trans } from "react-i18next"
import { ArrowLeft, ArrowRight, BadgeInfo } from "lucide-react"
import { ArrowLeft, ArrowRight, BadgeInfo, Brain, TriangleAlert } from "lucide-react"
import { buildDocLink } from "@/utils/docLinks"

type ProviderOption = "roo" | "custom"
type AuthOrigin = "landing" | "providerSelection"

const WelcomeViewProvider = () => {
const { apiConfiguration, currentApiConfigName, setApiConfiguration, uriScheme, cloudIsAuthenticated } =
useExtensionState()
const { t } = useAppTranslation()
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined)
const [selectedProvider, setSelectedProvider] = useState<ProviderOption>("roo")
const [selectedProvider, setSelectedProvider] = useState<ProviderOption | null>(null)
const [authInProgress, setAuthInProgress] = useState(false)
const [authOrigin, setAuthOrigin] = useState<AuthOrigin | null>(null)
const [showManualEntry, setShowManualEntry] = useState(false)
const [manualUrl, setManualUrl] = useState("")
const [manualErrorMessage, setManualErrorMessage] = useState<boolean | undefined>(undefined)
Expand Down Expand Up @@ -72,17 +75,32 @@ const WelcomeViewProvider = () => {
)

const handleGetStarted = useCallback(() => {
if (selectedProvider === "roo") {
// Trigger cloud sign-in with provider signup flow
// NOTE: We intentionally do NOT save the API configuration yet.
// The configuration will be saved by the extension after auth completes.
// This keeps showWelcome true so we can show the waiting state.
// Landing screen - always trigger auth with Roo
if (selectedProvider === null) {
setAuthOrigin("landing")
vscode.postMessage({ type: "rooCloudSignIn", useProviderSignup: true })

// Show the waiting state
setAuthInProgress(true)
}
// Provider Selection screen
else if (selectedProvider === "roo") {
if (cloudIsAuthenticated) {
// Already authenticated - save config and finish
const rooConfig: ProviderSettings = {
apiProvider: "roo",
}
vscode.postMessage({
type: "upsertApiConfiguration",
text: currentApiConfigName,
apiConfiguration: rooConfig,
})
} else {
// Need to authenticate
setAuthOrigin("providerSelection")
vscode.postMessage({ type: "rooCloudSignIn", useProviderSignup: true })
setAuthInProgress(true)
}
} else {
// Use custom provider - validate first
// Custom provider - validate first
const error = apiConfiguration ? validateApiConfiguration(apiConfiguration) : undefined

if (error) {
Expand All @@ -93,14 +111,34 @@ const WelcomeViewProvider = () => {
setErrorMessage(undefined)
vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration })
}
}, [selectedProvider, apiConfiguration, currentApiConfigName])
}, [selectedProvider, cloudIsAuthenticated, apiConfiguration, currentApiConfigName])

const handleNoAccount = useCallback(() => {
// Navigate to Provider Selection, defaulting to Roo option
setSelectedProvider("roo")
}, [])

const handleBackToLanding = useCallback(() => {
// Return to the landing screen
setSelectedProvider(null)
setErrorMessage(undefined)
}, [])

const handleGoBack = useCallback(() => {
setAuthInProgress(false)
setShowManualEntry(false)
setManualUrl("")
setManualErrorMessage(false)
}, [])

// Return to the appropriate screen based on origin
if (authOrigin === "providerSelection") {
// Keep selectedProvider as-is, user returns to Provider Selection
} else {
// Return to Landing
setSelectedProvider(null)
}
setAuthOrigin(null)
}, [authOrigin])

const handleManualUrlChange = (e: any) => {
const url = e.target.value
Expand All @@ -126,17 +164,17 @@ const WelcomeViewProvider = () => {
}, [manualUrl])

const handleOpenSignupUrl = () => {
vscode.postMessage({ type: "rooCloudSignIn", useProviderSignup: true })
vscode.postMessage({ type: "rooCloudSignIn", useProviderSignup: false })
}

// Render the waiting for cloud state
if (authInProgress) {
return (
<Tab>
<TabContent className="flex flex-col gap-4 p-6">
<TabContent className="flex flex-col gap-4 p-6 justify-center">
<div className="flex flex-col items-start gap-4 pt-8">
<VSCodeProgressRing className="size-6" />
<h2 className="mt-0 mb-0 text-lg font-semibold">{t("welcome:waitingForCloud.heading")}</h2>
<h2 className="my-0 text-xl font-semibold">{t("welcome:waitingForCloud.heading")}</h2>
<p className="text-vscode-descriptionForeground mt-0">
{t("welcome:waitingForCloud.description")}
</p>
Expand All @@ -159,25 +197,25 @@ const WelcomeViewProvider = () => {
</div>

<div className="flex gap-2 items-start pr-4 text-vscode-descriptionForeground">
<ArrowRight className="size-4 inline shrink-0" />
<TriangleAlert className="size-4 inline shrink-0" />
<div>
<p className="m-0">
<Trans
i18nKey="welcome:waitingForCloud.havingTrouble"
components={{
clickHere: (
<button
onClick={() => setShowManualEntry(true)}
className="text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground underline cursor-pointer bg-transparent border-none p-0 "
/>
),
}}
/>
</p>

{showManualEntry && (
{!showManualEntry ? (
<p className="m-0">
<Trans
i18nKey="welcome:waitingForCloud.havingTrouble"
components={{
clickHere: (
<button
onClick={() => setShowManualEntry(true)}
className="text-vscode-textLink-foreground hover:text-vscode-textLink-activeForeground underline cursor-pointer bg-transparent border-none p-0 "
/>
),
}}
/>
</p>
) : (
<div className="w-full max-w-sm">
<p className="text-vscode-descriptionForeground">
<p className="text-vscode-descriptionForeground mt-0">
{t("welcome:waitingForCloud.pasteUrl")}
</p>
<div className="flex gap-2 items-center">
Expand All @@ -195,6 +233,22 @@ const WelcomeViewProvider = () => {
<ArrowRight className="size-4" />
</Button>
</div>
<p className="mt-2">
<Trans
i18nKey="welcome:waitingForCloud.docsLink"
components={{
DocsLink: (
<a
href={buildDocLink("roo-code-cloud/login", "setup")}
target="_blank"
rel="noopener noreferrer"
className="text-vscode-textLink-foreground hover:underline">
{t("common:docsLink.label")}
</a>
),
}}
/>
</p>
{manualUrl && manualErrorMessage && (
<p className="text-vscode-errorForeground mt-2">
{t("welcome:waitingForCloud.invalidURL")}
Expand All @@ -217,24 +271,48 @@ const WelcomeViewProvider = () => {
)
}

// Landing screen - shown when selectedProvider === null
if (selectedProvider === null) {
return (
<Tab>
<TabContent className="flex flex-col gap-4 p-6 justify-center">
<RooHero />
<h2 className="mt-0 mb-0 text-xl">{t("welcome:landing.greeting")}</h2>

<div className="space-y-4 leading-normal">
<p className="text-base text-vscode-foreground">
<Trans i18nKey="welcome:landing.introduction" />
</p>
<p className="mb-0 font-semibold">
<Trans i18nKey="welcome:landing.accountMention" />
</p>
</div>

<div className="mt-2 flex gap-2 items-center">
<Button onClick={handleGetStarted} variant="primary">
{t("welcome:landing.getStarted")}
</Button>
<VSCodeLink onClick={handleNoAccount} className="cursor-pointer">
{t("welcome:landing.noAccount")}
</VSCodeLink>
</div>
</TabContent>
</Tab>
)
}

// Provider Selection screen - shown when selectedProvider is "roo" or "custom"
return (
<Tab>
<TabContent className="flex flex-col gap-4 p-6 justify-center">
<RooHero />
<h2 className="mt-0 mb-0 text-xl">{t("welcome:greeting")}</h2>
<Brain className="size-8" strokeWidth={1.5} />
<h2 className="mt-0 mb-0 text-xl">{t("welcome:providerSignup.heading")}</h2>

<div className="text-base text-vscode-foreground space-y-3">
{selectedProvider === "roo" && (
<p>
<Trans i18nKey="welcome:introduction" />
</p>
)}
<p>
<Trans i18nKey="welcome:chooseProvider" />
</p>
</div>
<p className="text-base text-vscode-foreground">
<Trans i18nKey="welcome:providerSignup.chooseProvider" />
</p>

<div className="mb-4">
<div>
<VSCodeRadioGroup
value={selectedProvider}
onChange={(e: Event | React.FormEvent<HTMLElement>) => {
Expand All @@ -249,13 +327,12 @@ const WelcomeViewProvider = () => {
{t("welcome:providerSignup.rooCloudProvider")}
</p>
<p className="text-base text-vscode-descriptionForeground mt-0">
{t("welcome:providerSignup.rooCloudDescription")} (
{t("welcome:providerSignup.rooCloudDescription")}{" "}
<VSCodeLink
href="https://roocode.com/provider/pricing?utm_source=extension&utm_medium=welcome-screen&utm_campaign=provider-signup&utm_content=learn-more"
className="cursor-pointer">
{t("welcome:providerSignup.learnMore")}
</VSCodeLink>
).
</p>
</div>
</VSCodeRadio>
Expand All @@ -277,9 +354,6 @@ const WelcomeViewProvider = () => {
<div className="mb-8 border-l-2 border-vscode-panel-border pl-6 ml-[7px]">
<div
className={`overflow-clip transition-[max-height] ease-in-out duration-300 ${selectedProvider === "custom" ? "max-h-[600px]" : "max-h-0"}`}>
<p className="text-base text-vscode-descriptionForeground mt-0">
{t("welcome:providerSignup.noApiKeys")}
</p>
<ApiOptions
fromWelcomeView
apiConfiguration={apiConfiguration || {}}
Expand All @@ -292,9 +366,13 @@ const WelcomeViewProvider = () => {
</div>
</div>

<div className="-mt-8">
<div className="-mt-4 flex gap-2">
<Button onClick={handleBackToLanding} variant="secondary">
<ArrowLeft className="size-4" />
{t("welcome:providerSignup.goBack")}
</Button>
<Button onClick={handleGetStarted} variant="primary">
{t("welcome:providerSignup.getStarted")} →
{t("welcome:providerSignup.finish")} →
</Button>
</div>
</TabContent>
Expand Down
Loading
Loading