From c373daaf7c039606eccff6a91563a72c58afbe41 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Fri, 21 Nov 2025 16:22:15 -0500 Subject: [PATCH 1/3] Test a provider-oriented welcome screen --- packages/cloud/src/CloudService.ts | 7 +- packages/cloud/src/StaticTokenAuthService.ts | 3 +- packages/cloud/src/WebAuthService.ts | 17 +- .../cloud/src/__tests__/CloudService.test.ts | 14 +- .../src/__tests__/WebAuthService.spec.ts | 49 +++++- packages/types/src/cloud.ts | 9 +- src/activate/handleUri.ts | 2 + src/core/webview/webviewMessageHandler.ts | 3 +- src/extension.ts | 25 +++ src/shared/WebviewMessage.ts | 1 + webview-ui/src/App.tsx | 18 +- .../src/components/welcome/WelcomeView.tsx | 4 +- .../welcome/WelcomeViewProvider.tsx | 157 ++++++++++++++++++ webview-ui/src/i18n/locales/ca/welcome.json | 10 +- webview-ui/src/i18n/locales/de/welcome.json | 10 +- webview-ui/src/i18n/locales/en/welcome.json | 10 +- webview-ui/src/i18n/locales/es/welcome.json | 10 +- webview-ui/src/i18n/locales/fr/welcome.json | 10 +- webview-ui/src/i18n/locales/hi/welcome.json | 10 +- webview-ui/src/i18n/locales/id/welcome.json | 10 +- webview-ui/src/i18n/locales/it/welcome.json | 10 +- webview-ui/src/i18n/locales/ja/welcome.json | 10 +- webview-ui/src/i18n/locales/ko/welcome.json | 10 +- webview-ui/src/i18n/locales/nl/welcome.json | 10 +- webview-ui/src/i18n/locales/pl/welcome.json | 10 +- .../src/i18n/locales/pt-BR/welcome.json | 10 +- webview-ui/src/i18n/locales/ru/welcome.json | 10 +- webview-ui/src/i18n/locales/tr/welcome.json | 10 +- webview-ui/src/i18n/locales/vi/welcome.json | 10 +- .../src/i18n/locales/zh-CN/welcome.json | 10 +- .../src/i18n/locales/zh-TW/welcome.json | 10 +- 31 files changed, 455 insertions(+), 34 deletions(-) create mode 100644 webview-ui/src/components/welcome/WelcomeViewProvider.tsx diff --git a/packages/cloud/src/CloudService.ts b/packages/cloud/src/CloudService.ts index b321545ea2d..41781d09bf4 100644 --- a/packages/cloud/src/CloudService.ts +++ b/packages/cloud/src/CloudService.ts @@ -178,9 +178,9 @@ export class CloudService extends EventEmitter implements Di // AuthService - public async login(landingPageSlug?: string): Promise { + public async login(landingPageSlug?: string, useProviderSignup: boolean = false): Promise { this.ensureInitialized() - return this.authService!.login(landingPageSlug) + return this.authService!.login(landingPageSlug, useProviderSignup) } public async logout(): Promise { @@ -245,9 +245,10 @@ export class CloudService extends EventEmitter implements Di code: string | null, state: string | null, organizationId?: string | null, + providerModel?: string | null, ): Promise { this.ensureInitialized() - return this.authService!.handleCallback(code, state, organizationId) + return this.authService!.handleCallback(code, state, organizationId, providerModel) } public async switchOrganization(organizationId: string | null): Promise { diff --git a/packages/cloud/src/StaticTokenAuthService.ts b/packages/cloud/src/StaticTokenAuthService.ts index 97ce6eac590..2ff7b75f0e0 100644 --- a/packages/cloud/src/StaticTokenAuthService.ts +++ b/packages/cloud/src/StaticTokenAuthService.ts @@ -47,7 +47,7 @@ export class StaticTokenAuthService extends EventEmitter impl this.emit("user-info", { userInfo: this.userInfo }) } - public async login(): Promise { + public async login(_landingPageSlug?: string, _useProviderSignup?: boolean): Promise { throw new Error("Authentication methods are disabled in StaticTokenAuthService") } @@ -59,6 +59,7 @@ export class StaticTokenAuthService extends EventEmitter impl _code: string | null, _state: string | null, _organizationId?: string | null, + _providerModel?: string | null, ): Promise { throw new Error("Authentication methods are disabled in StaticTokenAuthService") } diff --git a/packages/cloud/src/WebAuthService.ts b/packages/cloud/src/WebAuthService.ts index e9961b02b88..0b5c108e89c 100644 --- a/packages/cloud/src/WebAuthService.ts +++ b/packages/cloud/src/WebAuthService.ts @@ -252,8 +252,9 @@ export class WebAuthService extends EventEmitter implements A * and opening the browser to the authorization URL. * * @param landingPageSlug Optional slug of a specific landing page (e.g., "supernova", "special-offer", etc.) + * @param useProviderSignup If true, uses provider signup flow (/extension/provider-sign-up). If false, uses standard sign-in (/extension/sign-in). Defaults to false. */ - public async login(landingPageSlug?: string): Promise { + public async login(landingPageSlug?: string, useProviderSignup: boolean = false): Promise { try { const vscode = await importVscode() @@ -272,10 +273,12 @@ export class WebAuthService extends EventEmitter implements A auth_redirect: `${vscode.env.uriScheme}://${publisher}.${name}`, }) - // Use landing page URL if slug is provided, otherwise use default sign-in URL + // Use landing page URL if slug is provided, otherwise use provider sign-up or sign-in URL based on parameter const url = landingPageSlug ? `${getRooCodeApiUrl()}/l/${landingPageSlug}?${params.toString()}` - : `${getRooCodeApiUrl()}/extension/sign-in?${params.toString()}` + : useProviderSignup + ? `${getRooCodeApiUrl()}/extension/provider-sign-up?${params.toString()}` + : `${getRooCodeApiUrl()}/extension/sign-in?${params.toString()}` await vscode.env.openExternal(vscode.Uri.parse(url)) } catch (error) { @@ -294,11 +297,13 @@ export class WebAuthService extends EventEmitter implements A * @param code The authorization code from the callback * @param state The state parameter from the callback * @param organizationId The organization ID from the callback (null for personal accounts) + * @param providerModel The model ID selected during signup (optional) */ public async handleCallback( code: string | null, state: string | null, organizationId?: string | null, + providerModel?: string | null, ): Promise { if (!code || !state) { const vscode = await importVscode() @@ -326,6 +331,12 @@ export class WebAuthService extends EventEmitter implements A await this.storeCredentials(credentials) + // Store the provider model if provided + if (providerModel) { + await this.context.globalState.update("roo-provider-model", providerModel) + this.log(`[auth] Stored provider model: ${providerModel}`) + } + const vscode = await importVscode() if (vscode) { diff --git a/packages/cloud/src/__tests__/CloudService.test.ts b/packages/cloud/src/__tests__/CloudService.test.ts index 4e0283eac7d..8c557ae7ada 100644 --- a/packages/cloud/src/__tests__/CloudService.test.ts +++ b/packages/cloud/src/__tests__/CloudService.test.ts @@ -296,12 +296,22 @@ describe("CloudService", () => { it("should delegate handleAuthCallback to AuthService", async () => { await cloudService.handleAuthCallback("code", "state") - expect(mockAuthService.handleCallback).toHaveBeenCalledWith("code", "state", undefined) + expect(mockAuthService.handleCallback).toHaveBeenCalledWith("code", "state", undefined, undefined) }) it("should delegate handleAuthCallback with organizationId to AuthService", async () => { await cloudService.handleAuthCallback("code", "state", "org_123") - expect(mockAuthService.handleCallback).toHaveBeenCalledWith("code", "state", "org_123") + expect(mockAuthService.handleCallback).toHaveBeenCalledWith("code", "state", "org_123", undefined) + }) + + it("should delegate handleAuthCallback with providerModel to AuthService", async () => { + await cloudService.handleAuthCallback("code", "state", "org_123", "xai/grok-code-fast-1") + expect(mockAuthService.handleCallback).toHaveBeenCalledWith( + "code", + "state", + "org_123", + "xai/grok-code-fast-1", + ) }) it("should return stored organization ID from AuthService", () => { diff --git a/packages/cloud/src/__tests__/WebAuthService.spec.ts b/packages/cloud/src/__tests__/WebAuthService.spec.ts index fc6bfa90e82..2f9528dec5c 100644 --- a/packages/cloud/src/__tests__/WebAuthService.spec.ts +++ b/packages/cloud/src/__tests__/WebAuthService.spec.ts @@ -261,7 +261,7 @@ describe("WebAuthService", () => { ) }) - it("should use package.json values for redirect URI", async () => { + it("should use package.json values for redirect URI with default sign-in endpoint", async () => { const mockOpenExternal = vi.fn() const vscode = await import("vscode") vi.mocked(vscode.env.openExternal).mockImplementation(mockOpenExternal) @@ -281,6 +281,26 @@ describe("WebAuthService", () => { expect(calledUri.toString()).toBe(expectedUrl) }) + it("should use provider signup URL when useProviderSignup is true", async () => { + const mockOpenExternal = vi.fn() + const vscode = await import("vscode") + vi.mocked(vscode.env.openExternal).mockImplementation(mockOpenExternal) + + await authService.login(undefined, true) + + const expectedUrl = + "https://api.test.com/extension/provider-sign-up?state=746573742d72616e646f6d2d6279746573&auth_redirect=vscode%3A%2F%2FRooVeterinaryInc.roo-cline" + expect(mockOpenExternal).toHaveBeenCalledWith( + expect.objectContaining({ + toString: expect.any(Function), + }), + ) + + // Verify the actual URL + const calledUri = mockOpenExternal.mock.calls[0]?.[0] + expect(calledUri.toString()).toBe(expectedUrl) + }) + it("should handle errors during login", async () => { vi.mocked(crypto.randomBytes).mockImplementation(() => { throw new Error("Crypto error") @@ -351,6 +371,33 @@ describe("WebAuthService", () => { expect(mockShowInfo).toHaveBeenCalledWith("Successfully authenticated with Roo Code Cloud") }) + it("should store provider model when provided in callback", async () => { + const storedState = "valid-state" + mockContext.globalState.get.mockReturnValue(storedState) + + // Mock successful Clerk sign-in response + const mockResponse = { + ok: true, + json: () => + Promise.resolve({ + response: { created_session_id: "session-123" }, + }), + headers: { + get: (header: string) => (header === "authorization" ? "Bearer token-123" : null), + }, + } + mockFetch.mockResolvedValue(mockResponse) + + const vscode = await import("vscode") + const mockShowInfo = vi.fn() + vi.mocked(vscode.window.showInformationMessage).mockImplementation(mockShowInfo) + + await authService.handleCallback("auth-code", storedState, null, "xai/grok-code-fast-1") + + expect(mockContext.globalState.update).toHaveBeenCalledWith("roo-provider-model", "xai/grok-code-fast-1") + expect(mockLog).toHaveBeenCalledWith("[auth] Stored provider model: xai/grok-code-fast-1") + }) + it("should handle Clerk API errors", async () => { const storedState = "valid-state" mockContext.globalState.get.mockReturnValue(storedState) diff --git a/packages/types/src/cloud.ts b/packages/types/src/cloud.ts index b412eb18891..53870f34d4e 100644 --- a/packages/types/src/cloud.ts +++ b/packages/types/src/cloud.ts @@ -239,9 +239,14 @@ export interface AuthService extends EventEmitter { broadcast(): void // Authentication methods - login(landingPageSlug?: string): Promise + login(landingPageSlug?: string, useProviderSignup?: boolean): Promise logout(): Promise - handleCallback(code: string | null, state: string | null, organizationId?: string | null): Promise + handleCallback( + code: string | null, + state: string | null, + organizationId?: string | null, + providerModel?: string | null, + ): Promise switchOrganization(organizationId: string | null): Promise // State methods diff --git a/src/activate/handleUri.ts b/src/activate/handleUri.ts index 468785fd92a..75345e3f314 100644 --- a/src/activate/handleUri.ts +++ b/src/activate/handleUri.ts @@ -40,11 +40,13 @@ export const handleUri = async (uri: vscode.Uri) => { const code = query.get("code") const state = query.get("state") const organizationId = query.get("organizationId") + const providerModel = query.get("provider_model") await CloudService.instance.handleAuthCallback( code, state, organizationId === "null" ? null : organizationId, + providerModel, ) break } diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index 8f89a9ec516..300e4d220f2 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -2146,7 +2146,8 @@ export const webviewMessageHandler = async ( case "rooCloudSignIn": { try { TelemetryService.instance.captureEvent(TelemetryEventName.AUTHENTICATION_INITIATED) - await CloudService.instance.login() + // Use provider signup flow if useProviderSignup is explicitly true + await CloudService.instance.login(undefined, message.useProviderSignup ?? false) } catch (error) { provider.log(`AuthService#login failed: ${error}`) vscode.window.showErrorMessage("Sign in failed.") diff --git a/src/extension.ts b/src/extension.ts index bf0ceec02c2..23ec1a1d97e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -168,6 +168,31 @@ export async function activate(context: vscode.ExtensionContext) { if (data.state === "active-session" || data.state === "logged-out") { await handleRooModelsCache() + + // Apply stored provider model to API configuration if present + if (data.state === "active-session") { + try { + const storedModel = context.globalState.get("roo-provider-model") + if (storedModel) { + cloudLogger(`[authStateChangedHandler] Applying stored provider model: ${storedModel}`) + // Get the current API configuration name + const currentConfigName = + provider.contextProxy.getGlobalState("currentApiConfigName") || "default" + // Update it with the stored model using upsertProviderProfile + await provider.upsertProviderProfile(currentConfigName, { + apiProvider: "roo", + apiModelId: storedModel, + }) + // Clear the stored model after applying + await context.globalState.update("roo-provider-model", undefined) + cloudLogger(`[authStateChangedHandler] Applied and cleared stored provider model`) + } + } catch (error) { + cloudLogger( + `[authStateChangedHandler] Failed to apply stored provider model: ${error instanceof Error ? error.message : String(error)}`, + ) + } + } } } diff --git a/src/shared/WebviewMessage.ts b/src/shared/WebviewMessage.ts index 1d403f16caa..66e4c931273 100644 --- a/src/shared/WebviewMessage.ts +++ b/src/shared/WebviewMessage.ts @@ -214,6 +214,7 @@ export interface WebviewMessage { upsellId?: string // For dismissUpsell list?: string[] // For dismissedUpsells response organizationId?: string | null // For organization switching + useProviderSignup?: boolean // For rooCloudSignIn to use provider signup flow codeIndexSettings?: { // Global state settings codebaseIndexEnabled: boolean diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index 220c8cf3af2..9f98c660198 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -1,6 +1,7 @@ import React, { useCallback, useEffect, useRef, useState, useMemo } from "react" import { useEvent } from "react-use" import { QueryClient, QueryClientProvider } from "@tanstack/react-query" +import posthog from "posthog-js" import { ExtensionMessage } from "@roo/ExtensionMessage" import TranslationProvider from "./i18n/TranslationContext" @@ -15,6 +16,7 @@ import ChatView, { ChatViewRef } from "./components/chat/ChatView" import HistoryView from "./components/history/HistoryView" import SettingsView, { SettingsViewRef } from "./components/settings/SettingsView" import WelcomeView from "./components/welcome/WelcomeView" +import WelcomeViewProvider from "./components/welcome/WelcomeViewProvider" import McpView from "./components/mcp/McpView" import { MarketplaceView } from "./components/marketplace/MarketplaceView" import ModesView from "./components/modes/ModesView" @@ -81,6 +83,16 @@ const App = () => { mdmCompliant, } = useExtensionState() + const [useProviderSignupView, setUseProviderSignupView] = useState(true) // Hardcoded to true for now + + // Check PostHog feature flag for provider signup view + useEffect(() => { + posthog.onFeatureFlags(function () { + // Feature flag for new provider-focused welcome view + setUseProviderSignupView(posthog?.getFeatureFlag("welcome-provider-signup") === "test") + }) + }, []) + // Create a persistent state manager const marketplaceStateManager = useMemo(() => new MarketplaceViewStateManager(), []) @@ -247,7 +259,11 @@ const App = () => { // Do not conditionally load ChatView, it's expensive and there's state we // don't want to lose (user input, disableInput, askResponse promise, etc.) return showWelcome ? ( - + useProviderSignupView ? ( + + ) : ( + + ) ) : ( <> {tab === "modes" && switchTab("chat")} />} diff --git a/webview-ui/src/components/welcome/WelcomeView.tsx b/webview-ui/src/components/welcome/WelcomeView.tsx index 395e97bca8b..76bd64459c1 100644 --- a/webview-ui/src/components/welcome/WelcomeView.tsx +++ b/webview-ui/src/components/welcome/WelcomeView.tsx @@ -63,9 +63,9 @@ const WelcomeView = () => { -

{t("welcome:greeting")}

+

{t("welcome:greeting")}

-
+

diff --git a/webview-ui/src/components/welcome/WelcomeViewProvider.tsx b/webview-ui/src/components/welcome/WelcomeViewProvider.tsx new file mode 100644 index 00000000000..2750f2db07f --- /dev/null +++ b/webview-ui/src/components/welcome/WelcomeViewProvider.tsx @@ -0,0 +1,157 @@ +import { useCallback, useState } from "react" +import { VSCodeLink } from "@vscode/webview-ui-toolkit/react" + +import type { ProviderSettings } from "@roo-code/types" + +import { useExtensionState } from "@src/context/ExtensionStateContext" +import { validateApiConfiguration } from "@src/utils/validate" +import { vscode } from "@src/utils/vscode" +import { useAppTranslation } from "@src/i18n/TranslationContext" +import { Button } from "@src/components/ui" + +import ApiOptions from "../settings/ApiOptions" +import { Tab, TabContent } from "../common/Tab" + +import RooHero from "./RooHero" +import { Trans } from "react-i18next" + +type ProviderOption = "roo" | "custom" + +const WelcomeViewProvider = () => { + const { apiConfiguration, currentApiConfigName, setApiConfiguration, uriScheme } = useExtensionState() + const { t } = useAppTranslation() + const [errorMessage, setErrorMessage] = useState(undefined) + const [selectedProvider, setSelectedProvider] = useState("roo") + + // Memoize the setApiConfigurationField function to pass to ApiOptions + const setApiConfigurationFieldForApiOptions = useCallback( + (field: K, value: ProviderSettings[K]) => { + setApiConfiguration({ [field]: value }) + }, + [setApiConfiguration], // setApiConfiguration from context is stable + ) + + const handleGetStarted = useCallback(() => { + if (selectedProvider === "roo") { + // Set the Roo provider configuration + const rooConfig: ProviderSettings = { + apiProvider: "roo", + } + + // Save the Roo provider configuration + vscode.postMessage({ + type: "upsertApiConfiguration", + text: currentApiConfigName, + apiConfiguration: rooConfig, + }) + + // Then trigger cloud sign-in with provider signup flow + vscode.postMessage({ type: "rooCloudSignIn", useProviderSignup: true }) + } else { + // Use custom provider - validate first + const error = apiConfiguration ? validateApiConfiguration(apiConfiguration) : undefined + + if (error) { + setErrorMessage(error) + return + } + + setErrorMessage(undefined) + vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration }) + } + }, [selectedProvider, apiConfiguration, currentApiConfigName]) + + return ( + + + +

{t("welcome:greeting")}

+ +
+

+ +

+

+ +

+
+ +
+ {/* Roo Code Cloud Provider Option */} +
setSelectedProvider("roo")}> + setSelectedProvider("roo")} + className="mt-1" + /> +
+ + {t("welcome:providerSignup.rooCloudProvider")} + +

+ {t("welcome:providerSignup.rooCloudDescription")} ( + + {t("welcome:providerSignup.learnMore")} + + ). +

+
+
+ + {/* Use Another Provider Option */} +
setSelectedProvider("custom")}> + setSelectedProvider("custom")} + className="mt-1" + /> +
+ + {t("welcome:providerSignup.useAnotherProvider")} + +

+ {t("welcome:providerSignup.useAnotherProviderDescription")} +

+
+
+ + {/* Show API options only when custom provider is selected */} + {selectedProvider === "custom" && ( +
+ +
+ )} +
+
+
+
+ + {errorMessage && selectedProvider === "custom" && ( +
{errorMessage}
+ )} +
+
+
+ ) +} + +export default WelcomeViewProvider diff --git a/webview-ui/src/i18n/locales/ca/welcome.json b/webview-ui/src/i18n/locales/ca/welcome.json index c819a48a19d..60246050c95 100644 --- a/webview-ui/src/i18n/locales/ca/welcome.json +++ b/webview-ui/src/i18n/locales/ca/welcome.json @@ -16,7 +16,15 @@ "incentive": "Prova Roo gratis" } }, - "chooseProvider": "Per fer la seva màgia, Roo necessita una clau API.", + "chooseProvider": "Per començar, necessites un proveïdor LLM:", + "providerSignup": { + "rooCloudProvider": "Proveïdor Roo Code Cloud", + "rooCloudDescription": "La manera més senzilla d'utilitzar Roo. Una combinació seleccionada de models gratuïts i de pagament a un cost baix", + "learnMore": "més informació", + "useAnotherProvider": "Utilitza un altre proveïdor", + "useAnotherProviderDescription": "Introdueix una clau API i comença.", + "getStarted": "Començar" + }, "startRouter": "Recomanem utilitzar un router LLM:", "startCustom": "O pots utilitzar la teva pròpia clau API:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/de/welcome.json b/webview-ui/src/i18n/locales/de/welcome.json index a95ff103739..9bc91118d21 100644 --- a/webview-ui/src/i18n/locales/de/welcome.json +++ b/webview-ui/src/i18n/locales/de/welcome.json @@ -16,7 +16,15 @@ "incentive": "Probier Roo kostenlos aus" } }, - "chooseProvider": "Um seine Magie zu entfalten, benötigt Roo einen API-Schlüssel.", + "chooseProvider": "Um loszulegen, brauchst du einen LLM-Anbieter:", + "providerSignup": { + "rooCloudProvider": "Roo Code Cloud Provider", + "rooCloudDescription": "Der einfachste Weg, Roo zu nutzen. Eine kuratierte Mischung aus kostenlosen und bezahlten Modellen zu niedrigen Kosten", + "learnMore": "mehr erfahren", + "useAnotherProvider": "Anderen Anbieter verwenden", + "useAnotherProviderDescription": "Gib einen API-Schlüssel ein und leg los.", + "getStarted": "Loslegen" + }, "startRouter": "Wir empfehlen die Verwendung eines LLM-Routers:", "startCustom": "Oder du kannst deinen eigenen API-Schlüssel verwenden:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/en/welcome.json b/webview-ui/src/i18n/locales/en/welcome.json index 5cb9b488dc3..705bc92cb7e 100644 --- a/webview-ui/src/i18n/locales/en/welcome.json +++ b/webview-ui/src/i18n/locales/en/welcome.json @@ -16,7 +16,15 @@ "incentive": "Try Roo out for free" } }, - "chooseProvider": "To do its magic, Roo needs an API key.", + "chooseProvider": "To get started, you need an LLM provider:", + "providerSignup": { + "rooCloudProvider": "Roo Code Cloud Provider", + "rooCloudDescription": "The simplest way to use Roo. A curated mix of free and paid models at a low cost", + "learnMore": "learn more", + "useAnotherProvider": "Use another provider", + "useAnotherProviderDescription": "Enter an API key and get going.", + "getStarted": "Get started" + }, "startRouter": "We recommend using an LLM Router:", "startCustom": "Or you can bring your provider API key:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/es/welcome.json b/webview-ui/src/i18n/locales/es/welcome.json index 2f7217d4f3e..9f790919073 100644 --- a/webview-ui/src/i18n/locales/es/welcome.json +++ b/webview-ui/src/i18n/locales/es/welcome.json @@ -16,7 +16,15 @@ "incentive": "Prueba Roo gratis" } }, - "chooseProvider": "Para hacer su magia, Roo necesita una clave API.", + "chooseProvider": "Para comenzar, necesitas un proveedor LLM:", + "providerSignup": { + "rooCloudProvider": "Proveedor Roo Code Cloud", + "rooCloudDescription": "La forma más sencilla de usar Roo. Una mezcla seleccionada de modelos gratuitos y de pago a bajo costo", + "learnMore": "más información", + "useAnotherProvider": "Usar otro proveedor", + "useAnotherProviderDescription": "Introduce una clave API y comienza.", + "getStarted": "Comenzar" + }, "startRouter": "Recomendamos usar un router LLM:", "startCustom": "O puedes traer tu propia clave API:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/fr/welcome.json b/webview-ui/src/i18n/locales/fr/welcome.json index a1e692bb844..a61e577494c 100644 --- a/webview-ui/src/i18n/locales/fr/welcome.json +++ b/webview-ui/src/i18n/locales/fr/welcome.json @@ -16,7 +16,15 @@ "incentive": "Essayez Roo gratuitement" } }, - "chooseProvider": "Pour faire sa magie, Roo a besoin d'une clé API.", + "chooseProvider": "Pour commencer, tu as besoin d'un fournisseur LLM :", + "providerSignup": { + "rooCloudProvider": "Fournisseur Roo Code Cloud", + "rooCloudDescription": "La façon la plus simple d'utiliser Roo. Un mélange sélectionné de modèles gratuits et payants à faible coût", + "learnMore": "en savoir plus", + "useAnotherProvider": "Utiliser un autre fournisseur", + "useAnotherProviderDescription": "Entre une clé API et commence.", + "getStarted": "Commencer" + }, "startRouter": "Nous recommandons d'utiliser un routeur LLM :", "startCustom": "Ou tu peux apporter ta propre clé API :", "telemetry": { diff --git a/webview-ui/src/i18n/locales/hi/welcome.json b/webview-ui/src/i18n/locales/hi/welcome.json index 279f588d706..196b98bad1f 100644 --- a/webview-ui/src/i18n/locales/hi/welcome.json +++ b/webview-ui/src/i18n/locales/hi/welcome.json @@ -16,7 +16,15 @@ "incentive": "Roo को निःशुल्क आज़माएं" } }, - "chooseProvider": "अपना जादू दिखाने के लिए, Roo को एक API कुंजी की आवश्यकता है।", + "chooseProvider": "शुरू करने के लिए, आपको एक LLM प्रदाता की आवश्यकता है:", + "providerSignup": { + "rooCloudProvider": "Roo Code Cloud प्रदाता", + "rooCloudDescription": "Roo का उपयोग करने का सबसे आसान तरीका। कम लागत पर मुफ्त और भुगतान मॉडल का एक चयनित मिश्रण", + "learnMore": "और जानें", + "useAnotherProvider": "दूसरे प्रदाता का उपयोग करें", + "useAnotherProviderDescription": "एक API कुंजी दर्ज करें और शुरू करें।", + "getStarted": "शुरू करें" + }, "startRouter": "हम एक LLM राउटर का उपयोग करने की सलाह देते हैं:", "startCustom": "या आप अपनी खुद की API कुंजी ला सकते हैं:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/id/welcome.json b/webview-ui/src/i18n/locales/id/welcome.json index 73eacc3b1e0..5cf609f7089 100644 --- a/webview-ui/src/i18n/locales/id/welcome.json +++ b/webview-ui/src/i18n/locales/id/welcome.json @@ -16,7 +16,15 @@ "incentive": "Coba Roo gratis" } }, - "chooseProvider": "Untuk melakukan keajaibannya, Roo membutuhkan API key.", + "chooseProvider": "Untuk memulai, kamu memerlukan penyedia LLM:", + "providerSignup": { + "rooCloudProvider": "Penyedia Roo Code Cloud", + "rooCloudDescription": "Cara termudah untuk menggunakan Roo. Campuran model gratis dan berbayar yang dikurasi dengan biaya rendah", + "learnMore": "pelajari lebih lanjut", + "useAnotherProvider": "Gunakan penyedia lain", + "useAnotherProviderDescription": "Masukkan kunci API dan mulai.", + "getStarted": "Mulai" + }, "startRouter": "Kami merekomendasikan menggunakan Router LLM:", "startCustom": "Atau Anda dapat menggunakan API key Anda sendiri:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/it/welcome.json b/webview-ui/src/i18n/locales/it/welcome.json index 46efe5ef625..af185e63f1e 100644 --- a/webview-ui/src/i18n/locales/it/welcome.json +++ b/webview-ui/src/i18n/locales/it/welcome.json @@ -16,7 +16,15 @@ "incentive": "Prova Roo gratuitamente" } }, - "chooseProvider": "Per fare la sua magia, Roo ha bisogno di una chiave API.", + "chooseProvider": "Per iniziare, hai bisogno di un provider LLM:", + "providerSignup": { + "rooCloudProvider": "Provider Roo Code Cloud", + "rooCloudDescription": "Il modo più semplice per usare Roo. Un mix curato di modelli gratuiti e a pagamento a basso costo", + "learnMore": "scopri di più", + "useAnotherProvider": "Usa un altro provider", + "useAnotherProviderDescription": "Inserisci una chiave API e inizia.", + "getStarted": "Inizia" + }, "startRouter": "Consigliamo di utilizzare un router LLM:", "startCustom": "Oppure puoi utilizzare la tua chiave API:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/ja/welcome.json b/webview-ui/src/i18n/locales/ja/welcome.json index 946f6f23226..5b10eb6cdc5 100644 --- a/webview-ui/src/i18n/locales/ja/welcome.json +++ b/webview-ui/src/i18n/locales/ja/welcome.json @@ -16,7 +16,15 @@ "incentive": "Rooを無料で試す" } }, - "chooseProvider": "Rooが機能するには、APIキーが必要です。", + "chooseProvider": "開始するには、LLMプロバイダーが必要です:", + "providerSignup": { + "rooCloudProvider": "Roo Code Cloudプロバイダー", + "rooCloudDescription": "Rooを使用する最も簡単な方法。低コストで厳選された無料および有料モデルの組み合わせ", + "learnMore": "詳細を見る", + "useAnotherProvider": "別のプロバイダーを使用", + "useAnotherProviderDescription": "APIキーを入力して始める。", + "getStarted": "始める" + }, "startRouter": "LLMルーターの使用をお勧めします:", "startCustom": "または、あなた自身のAPIキーを使用できます:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/ko/welcome.json b/webview-ui/src/i18n/locales/ko/welcome.json index 2f65841df47..f8a061430bc 100644 --- a/webview-ui/src/i18n/locales/ko/welcome.json +++ b/webview-ui/src/i18n/locales/ko/welcome.json @@ -16,7 +16,15 @@ "incentive": "Roo를 무료로 체험해보세요" } }, - "chooseProvider": "Roo가 작동하려면 API 키가 필요합니다.", + "chooseProvider": "시작하려면 LLM 제공업체가 필요합니다:", + "providerSignup": { + "rooCloudProvider": "Roo Code Cloud 제공업체", + "rooCloudDescription": "Roo를 사용하는 가장 간단한 방법. 저렴한 비용으로 무료 및 유료 모델을 선별하여 제공", + "learnMore": "자세히 알아보기", + "useAnotherProvider": "다른 제공업체 사용", + "useAnotherProviderDescription": "API 키를 입력하고 시작하세요.", + "getStarted": "시작하기" + }, "startRouter": "LLM 라우터 사용을 권장합니다:", "startCustom": "또는 직접 API 키를 가져올 수 있습니다:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/nl/welcome.json b/webview-ui/src/i18n/locales/nl/welcome.json index 912b38e3eea..8412896a74a 100644 --- a/webview-ui/src/i18n/locales/nl/welcome.json +++ b/webview-ui/src/i18n/locales/nl/welcome.json @@ -16,7 +16,15 @@ "incentive": "Probeer Roo gratis uit" } }, - "chooseProvider": "Om zijn magie te doen, heeft Roo een API-sleutel nodig.", + "chooseProvider": "Om te beginnen heb je een LLM-provider nodig:", + "providerSignup": { + "rooCloudProvider": "Roo Code Cloud Provider", + "rooCloudDescription": "De eenvoudigste manier om Roo te gebruiken. Een samengestelde mix van gratis en betaalde modellen tegen lage kosten", + "learnMore": "meer informatie", + "useAnotherProvider": "Gebruik een andere provider", + "useAnotherProviderDescription": "Voer een API-sleutel in en ga aan de slag.", + "getStarted": "Beginnen" + }, "startRouter": "We raden aan om een LLM-router te gebruiken:", "startCustom": "Of je kunt je eigen API-sleutel gebruiken:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/pl/welcome.json b/webview-ui/src/i18n/locales/pl/welcome.json index 8e63cf9d8f5..a16b2b59dc0 100644 --- a/webview-ui/src/i18n/locales/pl/welcome.json +++ b/webview-ui/src/i18n/locales/pl/welcome.json @@ -16,7 +16,15 @@ "incentive": "Wypróbuj Roo za darmo" } }, - "chooseProvider": "Aby działać, Roo potrzebuje klucza API.", + "chooseProvider": "Aby rozpocząć, potrzebujesz dostawcy LLM:", + "providerSignup": { + "rooCloudProvider": "Dostawca Roo Code Cloud", + "rooCloudDescription": "Najprostszy sposób korzystania z Roo. Wybrana mieszanka darmowych i płatnych modeli po niskich kosztach", + "learnMore": "dowiedz się więcej", + "useAnotherProvider": "Użyj innego dostawcy", + "useAnotherProviderDescription": "Wprowadź klucz API i zacznij.", + "getStarted": "Rozpocznij" + }, "startRouter": "Zalecamy korzystanie z routera LLM:", "startCustom": "Lub możesz użyć własnego klucza API:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/pt-BR/welcome.json b/webview-ui/src/i18n/locales/pt-BR/welcome.json index 489841a78f5..6ac5c2cc2c2 100644 --- a/webview-ui/src/i18n/locales/pt-BR/welcome.json +++ b/webview-ui/src/i18n/locales/pt-BR/welcome.json @@ -16,7 +16,15 @@ "incentive": "Experimente o Roo gratuitamente" } }, - "chooseProvider": "Para fazer sua mágica, o Roo precisa de uma chave API.", + "chooseProvider": "Para começar, você precisa de um provedor LLM:", + "providerSignup": { + "rooCloudProvider": "Provedor Roo Code Cloud", + "rooCloudDescription": "A maneira mais simples de usar o Roo. Uma combinação selecionada de modelos gratuitos e pagos a baixo custo", + "learnMore": "saiba mais", + "useAnotherProvider": "Usar outro provedor", + "useAnotherProviderDescription": "Digite uma chave API e comece.", + "getStarted": "Começar" + }, "startRouter": "Recomendamos usar um roteador LLM:", "startCustom": "Ou você pode trazer sua própria chave API:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/ru/welcome.json b/webview-ui/src/i18n/locales/ru/welcome.json index 978cfc066ba..d83ab8399a9 100644 --- a/webview-ui/src/i18n/locales/ru/welcome.json +++ b/webview-ui/src/i18n/locales/ru/welcome.json @@ -16,7 +16,15 @@ "incentive": "Попробуй Roo бесплатно" } }, - "chooseProvider": "Для своей магии Roo нуждается в API-ключе.", + "chooseProvider": "Для начала работы тебе нужен LLM-провайдер:", + "providerSignup": { + "rooCloudProvider": "Провайдер Roo Code Cloud", + "rooCloudDescription": "Самый простой способ использования Roo. Подобранная комбинация бесплатных и платных моделей по низкой цене", + "learnMore": "подробнее", + "useAnotherProvider": "Использовать другого провайдера", + "useAnotherProviderDescription": "Введи API-ключ и начни.", + "getStarted": "Начать" + }, "startRouter": "Мы рекомендуем использовать маршрутизатор LLM:", "startCustom": "Или вы можете использовать свой собственный API-ключ:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/tr/welcome.json b/webview-ui/src/i18n/locales/tr/welcome.json index ff79c4f98a1..291aafed806 100644 --- a/webview-ui/src/i18n/locales/tr/welcome.json +++ b/webview-ui/src/i18n/locales/tr/welcome.json @@ -16,7 +16,15 @@ "incentive": "Roo'yu ücretsiz dene" } }, - "chooseProvider": "Sihirini yapabilmesi için Roo'nun bir API anahtarına ihtiyacı var.", + "chooseProvider": "Başlamak için bir LLM sağlayıcısına ihtiyacın var:", + "providerSignup": { + "rooCloudProvider": "Roo Code Cloud Sağlayıcısı", + "rooCloudDescription": "Roo'yu kullanmanın en basit yolu. Düşük maliyetle seçilmiş ücretsiz ve ücretli modellerin karışımı", + "learnMore": "daha fazla bilgi", + "useAnotherProvider": "Başka bir sağlayıcı kullan", + "useAnotherProviderDescription": "Bir API anahtarı gir ve başla.", + "getStarted": "Başla" + }, "startRouter": "Bir LLM yönlendiricisi kullanmanı öneririz:", "startCustom": "Veya kendi API anahtarını kullanabilirsin:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/vi/welcome.json b/webview-ui/src/i18n/locales/vi/welcome.json index 6a6545c3abf..d406911d4c7 100644 --- a/webview-ui/src/i18n/locales/vi/welcome.json +++ b/webview-ui/src/i18n/locales/vi/welcome.json @@ -16,7 +16,15 @@ "incentive": "Dùng thử Roo miễn phí" } }, - "chooseProvider": "Để thực hiện phép màu của mình, Roo cần một khóa API.", + "chooseProvider": "Để bắt đầu, bạn cần một nhà cung cấp LLM:", + "providerSignup": { + "rooCloudProvider": "Nhà cung cấp Roo Code Cloud", + "rooCloudDescription": "Cách đơn giản nhất để sử dụng Roo. Sự kết hợp được tuyển chọn của các mô hình miễn phí và trả phí với chi phí thấp", + "learnMore": "tìm hiểu thêm", + "useAnotherProvider": "Sử dụng nhà cung cấp khác", + "useAnotherProviderDescription": "Nhập khóa API và bắt đầu.", + "getStarted": "Bắt đầu" + }, "startRouter": "Chúng tôi khuyên bạn nên sử dụng bộ định tuyến LLM:", "startCustom": "Hoặc bạn có thể sử dụng khóa API của riêng mình:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/zh-CN/welcome.json b/webview-ui/src/i18n/locales/zh-CN/welcome.json index d4fe595e143..783cc342d8d 100644 --- a/webview-ui/src/i18n/locales/zh-CN/welcome.json +++ b/webview-ui/src/i18n/locales/zh-CN/welcome.json @@ -16,7 +16,15 @@ "incentive": "免费试用 Roo" } }, - "chooseProvider": "Roo 需要一个 API 密钥才能发挥魔力。", + "chooseProvider": "开始使用需要 LLM 提供商:", + "providerSignup": { + "rooCloudProvider": "Roo Code Cloud 提供商", + "rooCloudDescription": "使用 Roo 最简单的方式。低成本精选的免费和付费模型组合", + "learnMore": "了解更多", + "useAnotherProvider": "使用其他提供商", + "useAnotherProviderDescription": "输入 API 密钥即可开始。", + "getStarted": "开始使用" + }, "startRouter": "我们推荐使用 LLM 路由器:", "startCustom": "或者你可以使用自己的 API 密钥:", "telemetry": { diff --git a/webview-ui/src/i18n/locales/zh-TW/welcome.json b/webview-ui/src/i18n/locales/zh-TW/welcome.json index ed4526844bb..b03ab1b1441 100644 --- a/webview-ui/src/i18n/locales/zh-TW/welcome.json +++ b/webview-ui/src/i18n/locales/zh-TW/welcome.json @@ -16,7 +16,15 @@ "incentive": "免費試用 Roo" } }, - "chooseProvider": "Roo 需要 API 金鑰才能發揮魔力。", + "chooseProvider": "開始使用需要 LLM 提供者:", + "providerSignup": { + "rooCloudProvider": "Roo Code Cloud 提供者", + "rooCloudDescription": "使用 Roo 最簡單的方式。低成本精選的免費和付費模型組合", + "learnMore": "了解更多", + "useAnotherProvider": "使用其他提供者", + "useAnotherProviderDescription": "輸入 API 金鑰即可開始。", + "getStarted": "開始使用" + }, "startRouter": "我們建議使用 LLM 路由器:", "startCustom": "或者您可以使用自己的 API 金鑰:", "telemetry": { From fb7534e00813e7cea4af1df4154ec1b9cb07d5be Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Fri, 21 Nov 2025 17:15:11 -0500 Subject: [PATCH 2/3] Update webview-ui/src/App.tsx Co-authored-by: roomote[bot] <219738659+roomote[bot]@users.noreply.github.com> --- webview-ui/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index 9f98c660198..c9e490fe4d4 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -83,7 +83,7 @@ const App = () => { mdmCompliant, } = useExtensionState() - const [useProviderSignupView, setUseProviderSignupView] = useState(true) // Hardcoded to true for now +const [useProviderSignupView, setUseProviderSignupView] = useState(false) // Check PostHog feature flag for provider signup view useEffect(() => { From b146f1780a6f710f64024aec386794ccd7b329e8 Mon Sep 17 00:00:00 2001 From: Matt Rubens Date: Fri, 21 Nov 2025 17:36:07 -0500 Subject: [PATCH 3/3] Formatting --- webview-ui/src/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webview-ui/src/App.tsx b/webview-ui/src/App.tsx index c9e490fe4d4..783ddb51385 100644 --- a/webview-ui/src/App.tsx +++ b/webview-ui/src/App.tsx @@ -83,7 +83,7 @@ const App = () => { mdmCompliant, } = useExtensionState() -const [useProviderSignupView, setUseProviderSignupView] = useState(false) + const [useProviderSignupView, setUseProviderSignupView] = useState(false) // Check PostHog feature flag for provider signup view useEffect(() => {