Test a provider-oriented welcome screen (#9484)

Co-authored-by: roomote[bot] <219738659+roomote[bot]@users.noreply.github.com>
This commit is contained in:
Matt Rubens 2025-11-21 17:37:30 -05:00 committed by GitHub
parent 1dd223d240
commit 7edc86bb52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 455 additions and 34 deletions

View file

@ -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> {

View file

@ -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")
}

View file

@ -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) {

View file

@ -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", () => {

View file

@ -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)

View file

@ -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

View file

@ -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
}

View file

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

View file

@ -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)}`,
)
}
}
}
}

View file

@ -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

View file

@ -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")} />}

View file

@ -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>

View file

@ -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

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {

View file

@ -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": {