Просмотр исходного кода

Test a provider-oriented welcome screen (#9484)

Co-authored-by: roomote[bot] <219738659+roomote[bot]@users.noreply.github.com>
Matt Rubens 1 месяц назад
Родитель
Сommit
7edc86bb52
31 измененных файлов с 455 добавлено и 34 удалено
  1. 4 3
      packages/cloud/src/CloudService.ts
  2. 2 1
      packages/cloud/src/StaticTokenAuthService.ts
  3. 14 3
      packages/cloud/src/WebAuthService.ts
  4. 12 2
      packages/cloud/src/__tests__/CloudService.test.ts
  5. 48 1
      packages/cloud/src/__tests__/WebAuthService.spec.ts
  6. 7 2
      packages/types/src/cloud.ts
  7. 2 0
      src/activate/handleUri.ts
  8. 2 1
      src/core/webview/webviewMessageHandler.ts
  9. 25 0
      src/extension.ts
  10. 1 0
      src/shared/WebviewMessage.ts
  11. 17 1
      webview-ui/src/App.tsx
  12. 2 2
      webview-ui/src/components/welcome/WelcomeView.tsx
  13. 157 0
      webview-ui/src/components/welcome/WelcomeViewProvider.tsx
  14. 9 1
      webview-ui/src/i18n/locales/ca/welcome.json
  15. 9 1
      webview-ui/src/i18n/locales/de/welcome.json
  16. 9 1
      webview-ui/src/i18n/locales/en/welcome.json
  17. 9 1
      webview-ui/src/i18n/locales/es/welcome.json
  18. 9 1
      webview-ui/src/i18n/locales/fr/welcome.json
  19. 9 1
      webview-ui/src/i18n/locales/hi/welcome.json
  20. 9 1
      webview-ui/src/i18n/locales/id/welcome.json
  21. 9 1
      webview-ui/src/i18n/locales/it/welcome.json
  22. 9 1
      webview-ui/src/i18n/locales/ja/welcome.json
  23. 9 1
      webview-ui/src/i18n/locales/ko/welcome.json
  24. 9 1
      webview-ui/src/i18n/locales/nl/welcome.json
  25. 9 1
      webview-ui/src/i18n/locales/pl/welcome.json
  26. 9 1
      webview-ui/src/i18n/locales/pt-BR/welcome.json
  27. 9 1
      webview-ui/src/i18n/locales/ru/welcome.json
  28. 9 1
      webview-ui/src/i18n/locales/tr/welcome.json
  29. 9 1
      webview-ui/src/i18n/locales/vi/welcome.json
  30. 9 1
      webview-ui/src/i18n/locales/zh-CN/welcome.json
  31. 9 1
      webview-ui/src/i18n/locales/zh-TW/welcome.json

+ 4 - 3
packages/cloud/src/CloudService.ts

@@ -178,9 +178,9 @@ export class CloudService extends EventEmitter<CloudServiceEvents> implements Di
 
 	// AuthService
 
-	public async login(landingPageSlug?: string): Promise<void> {
+	public async login(landingPageSlug?: string, useProviderSignup: boolean = false): Promise<void> {
 		this.ensureInitialized()
-		return this.authService!.login(landingPageSlug)
+		return this.authService!.login(landingPageSlug, useProviderSignup)
 	}
 
 	public async logout(): Promise<void> {
@@ -245,9 +245,10 @@ export class CloudService extends EventEmitter<CloudServiceEvents> implements Di
 		code: string | null,
 		state: string | null,
 		organizationId?: string | null,
+		providerModel?: string | null,
 	): Promise<void> {
 		this.ensureInitialized()
-		return this.authService!.handleCallback(code, state, organizationId)
+		return this.authService!.handleCallback(code, state, organizationId, providerModel)
 	}
 
 	public async switchOrganization(organizationId: string | null): Promise<void> {

+ 2 - 1
packages/cloud/src/StaticTokenAuthService.ts

@@ -47,7 +47,7 @@ export class StaticTokenAuthService extends EventEmitter<AuthServiceEvents> impl
 		this.emit("user-info", { userInfo: this.userInfo })
 	}
 
-	public async login(): Promise<void> {
+	public async login(_landingPageSlug?: string, _useProviderSignup?: boolean): Promise<void> {
 		throw new Error("Authentication methods are disabled in StaticTokenAuthService")
 	}
 
@@ -59,6 +59,7 @@ export class StaticTokenAuthService extends EventEmitter<AuthServiceEvents> impl
 		_code: string | null,
 		_state: string | null,
 		_organizationId?: string | null,
+		_providerModel?: string | null,
 	): Promise<void> {
 		throw new Error("Authentication methods are disabled in StaticTokenAuthService")
 	}

+ 14 - 3
packages/cloud/src/WebAuthService.ts

@@ -252,8 +252,9 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> 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<void> {
+	public async login(landingPageSlug?: string, useProviderSignup: boolean = false): Promise<void> {
 		try {
 			const vscode = await importVscode()
 
@@ -272,10 +273,12 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> 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<AuthServiceEvents> 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<void> {
 		if (!code || !state) {
 			const vscode = await importVscode()
@@ -326,6 +331,12 @@ export class WebAuthService extends EventEmitter<AuthServiceEvents> 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) {

+ 12 - 2
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", () => {

+ 48 - 1
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)

+ 7 - 2
packages/types/src/cloud.ts

@@ -239,9 +239,14 @@ export interface AuthService extends EventEmitter<AuthServiceEvents> {
 	broadcast(): void
 
 	// Authentication methods
-	login(landingPageSlug?: string): Promise<void>
+	login(landingPageSlug?: string, useProviderSignup?: boolean): Promise<void>
 	logout(): Promise<void>
-	handleCallback(code: string | null, state: string | null, organizationId?: string | null): Promise<void>
+	handleCallback(
+		code: string | null,
+		state: string | null,
+		organizationId?: string | null,
+		providerModel?: string | null,
+	): Promise<void>
 	switchOrganization(organizationId: string | null): Promise<void>
 
 	// State methods

+ 2 - 0
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
 		}

+ 2 - 1
src/core/webview/webviewMessageHandler.ts

@@ -2242,7 +2242,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.")

+ 25 - 0
src/extension.ts

@@ -162,6 +162,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<string>("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)}`,
+					)
+				}
+			}
 		}
 	}
 

+ 1 - 0
src/shared/WebviewMessage.ts

@@ -224,6 +224,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

+ 17 - 1
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(false)
+
+	// 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 ? (
-		<WelcomeView />
+		useProviderSignupView ? (
+			<WelcomeViewProvider />
+		) : (
+			<WelcomeView />
+		)
 	) : (
 		<>
 			{tab === "modes" && <ModesView onDone={() => switchTab("chat")} />}

+ 2 - 2
webview-ui/src/components/welcome/WelcomeView.tsx

@@ -63,9 +63,9 @@ const WelcomeView = () => {
 		<Tab>
 			<TabContent className="flex flex-col gap-4 p-6">
 				<RooHero />
-				<h2 className="mt-0 mb-4 text-xl text-center">{t("welcome:greeting")}</h2>
+				<h2 className="mt-0 mb-4 text-xl">{t("welcome:greeting")}</h2>
 
-				<div className="text-base text-vscode-foreground py-2 px-2 mb-4">
+				<div className="text-base text-vscode-foreground py-2 mb-4">
 					<p className="mb-3 leading-relaxed">
 						<Trans i18nKey="welcome:introduction" />
 					</p>

+ 157 - 0
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<string | undefined>(undefined)
+	const [selectedProvider, setSelectedProvider] = useState<ProviderOption>("roo")
+
+	// Memoize the setApiConfigurationField function to pass to ApiOptions
+	const setApiConfigurationFieldForApiOptions = useCallback(
+		<K extends keyof ProviderSettings>(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 (
+		<Tab>
+			<TabContent className="flex flex-col gap-4 p-6">
+				<RooHero />
+				<h2 className="mt-0 mb-0 text-xl">{t("welcome:greeting")}</h2>
+
+				<div className="text-base text-vscode-foreground">
+					<p className="mb-3 leading-relaxed">
+						<Trans i18nKey="welcome:introduction" />
+					</p>
+					<p className="mb-0 leading-relaxed">
+						<Trans i18nKey="welcome:chooseProvider" />
+					</p>
+				</div>
+
+				<div className="mb-4">
+					{/* Roo Code Cloud Provider Option */}
+					<div
+						className="flex items-start gap-3 p-4 mb-3 border border-vscode-panel-border rounded-md cursor-pointer hover:bg-vscode-list-hoverBackground"
+						onClick={() => setSelectedProvider("roo")}>
+						<input
+							type="radio"
+							name="provider"
+							checked={selectedProvider === "roo"}
+							onChange={() => setSelectedProvider("roo")}
+							className="mt-1"
+						/>
+						<div className="flex-1">
+							<span className="text-sm font-semibold mb-2 block">
+								{t("welcome:providerSignup.rooCloudProvider")}
+							</span>
+							<p className="text-xs text-vscode-descriptionForeground mb-1">
+								{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="text-xs">
+									{t("welcome:providerSignup.learnMore")}
+								</VSCodeLink>
+								).
+							</p>
+						</div>
+					</div>
+
+					{/* Use Another Provider Option */}
+					<div
+						className="flex items-start gap-3 p-4 mb-4 border border-vscode-panel-border rounded-md cursor-pointer hover:bg-vscode-list-hoverBackground"
+						onClick={() => setSelectedProvider("custom")}>
+						<input
+							type="radio"
+							name="provider"
+							checked={selectedProvider === "custom"}
+							onChange={() => setSelectedProvider("custom")}
+							className="mt-1"
+						/>
+						<div className="flex-1">
+							<span className="text-sm font-semibold mb-2 block">
+								{t("welcome:providerSignup.useAnotherProvider")}
+							</span>
+							<p className="text-xs text-vscode-descriptionForeground mb-1">
+								{t("welcome:providerSignup.useAnotherProviderDescription")}
+							</p>
+						</div>
+					</div>
+
+					{/* Show API options only when custom provider is selected */}
+					{selectedProvider === "custom" && (
+						<div className="mt-4">
+							<ApiOptions
+								fromWelcomeView
+								apiConfiguration={apiConfiguration || {}}
+								uriScheme={uriScheme}
+								setApiConfigurationField={setApiConfigurationFieldForApiOptions}
+								errorMessage={errorMessage}
+								setErrorMessage={setErrorMessage}
+							/>
+						</div>
+					)}
+				</div>
+			</TabContent>
+			<div className="sticky bottom-0 bg-vscode-sideBar-background p-4 border-t border-vscode-panel-border">
+				<div className="flex flex-col gap-2">
+					<Button onClick={handleGetStarted} variant="primary">
+						{t("welcome:providerSignup.getStarted")} →
+					</Button>
+					{errorMessage && selectedProvider === "custom" && (
+						<div className="text-vscode-errorForeground text-sm">{errorMessage}</div>
+					)}
+				</div>
+			</div>
+		</Tab>
+	)
+}
+
+export default WelcomeViewProvider

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {

+ 9 - 1
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": {