mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-06 08:18:39 +00:00
Switch to new welcome view (#9741)
This commit is contained in:
parent
906c6f0de4
commit
152af1474e
3 changed files with 2 additions and 294 deletions
|
|
@ -1,7 +1,6 @@
|
|||
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,8 +14,7 @@ import { ExtensionStateContextProvider, useExtensionState } from "./context/Exte
|
|||
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 WelcomeView from "./components/welcome/WelcomeViewProvider"
|
||||
import { MarketplaceView } from "./components/marketplace/MarketplaceView"
|
||||
import { HumanRelayDialog } from "./components/human-relay/HumanRelayDialog"
|
||||
import { CheckpointRestoreDialog } from "./components/chat/CheckpointRestoreDialog"
|
||||
|
|
@ -79,21 +77,6 @@ const App = () => {
|
|||
mdmCompliant,
|
||||
} = useExtensionState()
|
||||
|
||||
const [useProviderSignupView, setUseProviderSignupView] = useState(false)
|
||||
|
||||
// Check PostHog feature flag for provider signup view
|
||||
// Wait for telemetry to be initialized before checking feature flags
|
||||
useEffect(() => {
|
||||
if (!didHydrateState || telemetrySetting === "disabled") {
|
||||
return
|
||||
}
|
||||
|
||||
posthog.onFeatureFlags(function () {
|
||||
// Feature flag for new provider-focused welcome view
|
||||
setUseProviderSignupView(posthog?.getFeatureFlag("welcome-provider-signup") === "test")
|
||||
})
|
||||
}, [didHydrateState, telemetrySetting])
|
||||
|
||||
// Create a persistent state manager
|
||||
const marketplaceStateManager = useMemo(() => new MarketplaceViewStateManager(), [])
|
||||
|
||||
|
|
@ -260,11 +243,7 @@ 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 ? (
|
||||
<WelcomeViewProvider />
|
||||
) : (
|
||||
<WelcomeView />
|
||||
)
|
||||
<WelcomeView />
|
||||
) : (
|
||||
<>
|
||||
{tab === "history" && <HistoryView onDone={() => switchTab("chat")} />}
|
||||
|
|
|
|||
|
|
@ -2,18 +2,9 @@
|
|||
|
||||
import React from "react"
|
||||
import { render, screen, act, cleanup } from "@/utils/test-utils"
|
||||
import posthog from "posthog-js"
|
||||
|
||||
import AppWithProviders from "../App"
|
||||
|
||||
// Mock posthog
|
||||
vi.mock("posthog-js", () => ({
|
||||
default: {
|
||||
onFeatureFlags: vi.fn(),
|
||||
getFeatureFlag: vi.fn(),
|
||||
},
|
||||
}))
|
||||
|
||||
vi.mock("@src/utils/vscode", () => ({
|
||||
vscode: {
|
||||
postMessage: vi.fn(),
|
||||
|
|
@ -312,54 +303,4 @@ describe("App", () => {
|
|||
expect(chatView.getAttribute("data-hidden")).toBe("false")
|
||||
expect(screen.queryByTestId("marketplace-view")).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
describe("PostHog feature flag initialization", () => {
|
||||
it("waits for state hydration before checking feature flags", () => {
|
||||
mockUseExtensionState.mockReturnValue({
|
||||
didHydrateState: false,
|
||||
showWelcome: false,
|
||||
shouldShowAnnouncement: false,
|
||||
experiments: {},
|
||||
language: "en",
|
||||
telemetrySetting: "enabled",
|
||||
})
|
||||
|
||||
render(<AppWithProviders />)
|
||||
|
||||
// PostHog feature flag check should not be called before hydration
|
||||
expect(posthog.onFeatureFlags).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("checks feature flags after state hydration when telemetry is enabled", () => {
|
||||
mockUseExtensionState.mockReturnValue({
|
||||
didHydrateState: true,
|
||||
showWelcome: false,
|
||||
shouldShowAnnouncement: false,
|
||||
experiments: {},
|
||||
language: "en",
|
||||
telemetrySetting: "enabled",
|
||||
})
|
||||
|
||||
render(<AppWithProviders />)
|
||||
|
||||
// PostHog feature flag check should be called after hydration
|
||||
expect(posthog.onFeatureFlags).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it("does not check feature flags when telemetry is disabled", () => {
|
||||
mockUseExtensionState.mockReturnValue({
|
||||
didHydrateState: true,
|
||||
showWelcome: false,
|
||||
shouldShowAnnouncement: false,
|
||||
experiments: {},
|
||||
language: "en",
|
||||
telemetrySetting: "disabled",
|
||||
})
|
||||
|
||||
render(<AppWithProviders />)
|
||||
|
||||
// PostHog feature flag check should not be called when telemetry is disabled
|
||||
expect(posthog.onFeatureFlags).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,212 +0,0 @@
|
|||
import { useCallback, useState, useEffect } from "react"
|
||||
import knuthShuffle from "knuth-shuffle-seeded"
|
||||
import { Trans } from "react-i18next"
|
||||
import { VSCodeLink } from "@vscode/webview-ui-toolkit/react"
|
||||
import posthog from "posthog-js"
|
||||
|
||||
import type { ProviderSettings } from "@roo-code/types"
|
||||
import { TelemetryEventName } 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 { getRequestyAuthUrl, getOpenRouterAuthUrl } from "@src/oauth/urls"
|
||||
import { telemetryClient } from "@src/utils/TelemetryClient"
|
||||
import { Button } from "@src/components/ui"
|
||||
|
||||
import ApiOptions from "../settings/ApiOptions"
|
||||
import { Tab, TabContent } from "../common/Tab"
|
||||
|
||||
import RooHero from "./RooHero"
|
||||
|
||||
const WelcomeView = () => {
|
||||
const { apiConfiguration, currentApiConfigName, setApiConfiguration, uriScheme, machineId } = useExtensionState()
|
||||
const { t } = useAppTranslation()
|
||||
const [errorMessage, setErrorMessage] = useState<string | undefined>(undefined)
|
||||
const [showRooProvider, setShowRooProvider] = useState(false)
|
||||
|
||||
// Check PostHog feature flag for Roo provider
|
||||
useEffect(() => {
|
||||
posthog.onFeatureFlags(function () {
|
||||
setShowRooProvider(posthog?.getFeatureFlag("roo-provider-featured") === "test")
|
||||
})
|
||||
}, [])
|
||||
|
||||
// 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 handleSubmit = useCallback(() => {
|
||||
const error = apiConfiguration ? validateApiConfiguration(apiConfiguration) : undefined
|
||||
|
||||
if (error) {
|
||||
setErrorMessage(error)
|
||||
return
|
||||
}
|
||||
|
||||
setErrorMessage(undefined)
|
||||
vscode.postMessage({ type: "upsertApiConfiguration", text: currentApiConfigName, apiConfiguration })
|
||||
}, [apiConfiguration, currentApiConfigName])
|
||||
|
||||
// Using a lazy initializer so it reads once at mount
|
||||
const [imagesBaseUri] = useState(() => {
|
||||
const w = window as any
|
||||
return w.IMAGES_BASE_URI || ""
|
||||
})
|
||||
|
||||
return (
|
||||
<Tab>
|
||||
<TabContent className="flex flex-col gap-4 p-6">
|
||||
<RooHero />
|
||||
<h2 className="mt-0 mb-4 text-xl">{t("welcome:greeting")}</h2>
|
||||
|
||||
<div className="text-base text-vscode-foreground py-2 mb-4">
|
||||
<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">
|
||||
<p className="text-sm font-medium mt-4 mb-3">{t("welcome:startRouter")}</p>
|
||||
|
||||
<div>
|
||||
{/* Define the providers */}
|
||||
{(() => {
|
||||
// Provider card configuration
|
||||
const baseProviders = [
|
||||
{
|
||||
slug: "requesty",
|
||||
name: "Requesty",
|
||||
description: t("welcome:routers.requesty.description"),
|
||||
incentive: t("welcome:routers.requesty.incentive"),
|
||||
authUrl: getRequestyAuthUrl(uriScheme),
|
||||
},
|
||||
{
|
||||
slug: "openrouter",
|
||||
name: "OpenRouter",
|
||||
description: t("welcome:routers.openrouter.description"),
|
||||
authUrl: getOpenRouterAuthUrl(uriScheme),
|
||||
},
|
||||
]
|
||||
|
||||
// Conditionally add Roo provider based on feature flag
|
||||
const providers = showRooProvider
|
||||
? [
|
||||
...baseProviders,
|
||||
{
|
||||
slug: "roo",
|
||||
name: "Roo Code Cloud",
|
||||
description: t("welcome:routers.roo.description"),
|
||||
incentive: t("welcome:routers.roo.incentive"),
|
||||
authUrl: "#", // Placeholder since onClick handler will prevent default
|
||||
},
|
||||
]
|
||||
: baseProviders
|
||||
|
||||
// Shuffle providers based on machine ID (will be consistent for the same machine)
|
||||
const orderedProviders = [...providers]
|
||||
knuthShuffle(orderedProviders, (machineId as any) || Date.now())
|
||||
|
||||
// Render the provider cards
|
||||
return orderedProviders.map((provider, index) => (
|
||||
<a
|
||||
key={index}
|
||||
href={provider.authUrl}
|
||||
className="relative flex-1 border border-vscode-panel-border hover:bg-secondary rounded-md py-3 px-4 mb-2 flex flex-row gap-3 cursor-pointer transition-all no-underline text-inherit"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
onClick={(e) => {
|
||||
// Track telemetry for featured provider click
|
||||
telemetryClient.capture(TelemetryEventName.FEATURED_PROVIDER_CLICKED, {
|
||||
provider: provider.slug,
|
||||
})
|
||||
|
||||
// Special handling for Roo provider
|
||||
if (provider.slug === "roo") {
|
||||
e.preventDefault()
|
||||
|
||||
// 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
|
||||
vscode.postMessage({ type: "rooCloudSignIn" })
|
||||
}
|
||||
// For other providers, let the default link behavior work
|
||||
}}>
|
||||
{provider.incentive && (
|
||||
<div className="absolute top-0 right-0 text-[10px] text-vscode-badge-foreground bg-vscode-badge-background px-2 py-0.5 rounded-bl rounded-tr-md">
|
||||
{provider.incentive}
|
||||
</div>
|
||||
)}
|
||||
<div className="w-8 h-8 flex-shrink-0">
|
||||
<img
|
||||
src={`${imagesBaseUri}/${provider.slug}.png`}
|
||||
alt={provider.name}
|
||||
className="w-full h-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-sm font-medium text-vscode-foreground">
|
||||
{provider.name}
|
||||
</div>
|
||||
<div className="text-xs text-vscode-descriptionForeground">
|
||||
{provider.description}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))
|
||||
})()}
|
||||
</div>
|
||||
|
||||
<p className="text-sm font-medium mt-6 mb-3">{t("welcome:startCustom")}</p>
|
||||
<ApiOptions
|
||||
fromWelcomeView
|
||||
apiConfiguration={apiConfiguration || {}}
|
||||
uriScheme={uriScheme}
|
||||
setApiConfigurationField={setApiConfigurationFieldForApiOptions}
|
||||
errorMessage={errorMessage}
|
||||
setErrorMessage={setErrorMessage}
|
||||
/>
|
||||
</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">
|
||||
<div className="flex justify-end">
|
||||
<VSCodeLink
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
vscode.postMessage({ type: "importSettings" })
|
||||
}}
|
||||
className="text-sm">
|
||||
{t("welcome:importSettings")}
|
||||
</VSCodeLink>
|
||||
</div>
|
||||
<Button onClick={handleSubmit} variant="primary">
|
||||
{t("welcome:start")}
|
||||
</Button>
|
||||
{errorMessage && <div className="text-vscode-errorForeground">{errorMessage}</div>}
|
||||
</div>
|
||||
</div>
|
||||
</Tab>
|
||||
)
|
||||
}
|
||||
|
||||
export default WelcomeView
|
||||
Loading…
Add table
Reference in a new issue