diff --git a/.gitignore b/.gitignore
index dfce8ba810..8660637ae5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -179,3 +179,4 @@ autogpt_platform/backend/settings.py
.test-contents
.claude/settings.local.json
/autogpt_platform/backend/logs
+.next
\ No newline at end of file
diff --git a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/page.tsx b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/page.tsx
index 70d9783ccd..246fe52826 100644
--- a/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/page.tsx
+++ b/autogpt_platform/frontend/src/app/(no-navbar)/onboarding/page.tsx
@@ -1,10 +1,9 @@
"use client";
+import { getV1OnboardingState } from "@/app/api/__generated__/endpoints/onboarding/onboarding";
+import { getOnboardingStatus, resolveResponse } from "@/app/api/helpers";
import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
-import { resolveResponse, getOnboardingStatus } from "@/app/api/helpers";
-import { getV1OnboardingState } from "@/app/api/__generated__/endpoints/onboarding/onboarding";
-import { getHomepageRoute } from "@/lib/constants";
export default function OnboardingPage() {
const router = useRouter();
@@ -13,12 +12,10 @@ export default function OnboardingPage() {
async function redirectToStep() {
try {
// Check if onboarding is enabled (also gets chat flag for redirect)
- const { shouldShowOnboarding, isChatEnabled } =
- await getOnboardingStatus();
- const homepageRoute = getHomepageRoute(isChatEnabled);
+ const { shouldShowOnboarding } = await getOnboardingStatus();
if (!shouldShowOnboarding) {
- router.replace(homepageRoute);
+ router.replace("/");
return;
}
@@ -26,7 +23,7 @@ export default function OnboardingPage() {
// Handle completed onboarding
if (onboarding.completedSteps.includes("GET_RESULTS")) {
- router.replace(homepageRoute);
+ router.replace("/");
return;
}
diff --git a/autogpt_platform/frontend/src/app/(platform)/auth/callback/route.ts b/autogpt_platform/frontend/src/app/(platform)/auth/callback/route.ts
index 15be137f63..e7e2997d0d 100644
--- a/autogpt_platform/frontend/src/app/(platform)/auth/callback/route.ts
+++ b/autogpt_platform/frontend/src/app/(platform)/auth/callback/route.ts
@@ -1,9 +1,8 @@
-import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
-import { getHomepageRoute } from "@/lib/constants";
-import BackendAPI from "@/lib/autogpt-server-api";
-import { NextResponse } from "next/server";
-import { revalidatePath } from "next/cache";
import { getOnboardingStatus } from "@/app/api/helpers";
+import BackendAPI from "@/lib/autogpt-server-api";
+import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
+import { revalidatePath } from "next/cache";
+import { NextResponse } from "next/server";
// Handle the callback to complete the user session login
export async function GET(request: Request) {
@@ -27,13 +26,12 @@ export async function GET(request: Request) {
await api.createUser();
// Get onboarding status from backend (includes chat flag evaluated for this user)
- const { shouldShowOnboarding, isChatEnabled } =
- await getOnboardingStatus();
+ const { shouldShowOnboarding } = await getOnboardingStatus();
if (shouldShowOnboarding) {
next = "/onboarding";
revalidatePath("/onboarding", "layout");
} else {
- next = getHomepageRoute(isChatEnabled);
+ next = "/";
revalidatePath(next, "layout");
}
} catch (createUserError) {
diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/components/CopilotShell/components/SessionsList/useSessionsPagination.ts b/autogpt_platform/frontend/src/app/(platform)/copilot/components/CopilotShell/components/SessionsList/useSessionsPagination.ts
index 11ddd937af..61e3e6f37f 100644
--- a/autogpt_platform/frontend/src/app/(platform)/copilot/components/CopilotShell/components/SessionsList/useSessionsPagination.ts
+++ b/autogpt_platform/frontend/src/app/(platform)/copilot/components/CopilotShell/components/SessionsList/useSessionsPagination.ts
@@ -73,9 +73,9 @@ export function useSessionsPagination({ enabled }: UseSessionsPaginationArgs) {
};
const reset = () => {
+ // Only reset the offset - keep existing sessions visible during refetch
+ // The effect will replace sessions when new data arrives at offset 0
setOffset(0);
- setAccumulatedSessions([]);
- setTotalCount(null);
};
return {
diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/layout.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/layout.tsx
index 89cf72e2ba..876e5accfb 100644
--- a/autogpt_platform/frontend/src/app/(platform)/copilot/layout.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/copilot/layout.tsx
@@ -1,6 +1,13 @@
-import type { ReactNode } from "react";
+"use client";
+import { FeatureFlagPage } from "@/services/feature-flags/FeatureFlagPage";
+import { Flag } from "@/services/feature-flags/use-get-flag";
+import { type ReactNode } from "react";
import { CopilotShell } from "./components/CopilotShell/CopilotShell";
export default function CopilotLayout({ children }: { children: ReactNode }) {
- return {children};
+ return (
+
+ {children}
+
+ );
}
diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/page.tsx b/autogpt_platform/frontend/src/app/(platform)/copilot/page.tsx
index 104b238895..e9bc018c1b 100644
--- a/autogpt_platform/frontend/src/app/(platform)/copilot/page.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/copilot/page.tsx
@@ -14,14 +14,8 @@ export default function CopilotPage() {
const isInterruptModalOpen = useCopilotStore((s) => s.isInterruptModalOpen);
const confirmInterrupt = useCopilotStore((s) => s.confirmInterrupt);
const cancelInterrupt = useCopilotStore((s) => s.cancelInterrupt);
- const {
- greetingName,
- quickActions,
- isLoading,
- hasSession,
- initialPrompt,
- isReady,
- } = state;
+ const { greetingName, quickActions, isLoading, hasSession, initialPrompt } =
+ state;
const {
handleQuickAction,
startChatWithPrompt,
@@ -29,8 +23,6 @@ export default function CopilotPage() {
handleStreamingChange,
} = handlers;
- if (!isReady) return null;
-
if (hasSession) {
return (
diff --git a/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPage.ts b/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPage.ts
index e4713cd24a..9d99f8e7bd 100644
--- a/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPage.ts
+++ b/autogpt_platform/frontend/src/app/(platform)/copilot/useCopilotPage.ts
@@ -3,18 +3,11 @@ import {
postV2CreateSession,
} from "@/app/api/__generated__/endpoints/chat/chat";
import { useToast } from "@/components/molecules/Toast/use-toast";
-import { getHomepageRoute } from "@/lib/constants";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { useOnboarding } from "@/providers/onboarding/onboarding-provider";
-import {
- Flag,
- type FlagValues,
- useGetFlag,
-} from "@/services/feature-flags/use-get-flag";
import { SessionKey, sessionStorage } from "@/services/storage/session-storage";
import * as Sentry from "@sentry/nextjs";
import { useQueryClient } from "@tanstack/react-query";
-import { useFlags } from "launchdarkly-react-client-sdk";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { useCopilotStore } from "./copilot-page-store";
@@ -33,22 +26,6 @@ export function useCopilotPage() {
const isCreating = useCopilotStore((s) => s.isCreatingSession);
const setIsCreating = useCopilotStore((s) => s.setIsCreatingSession);
- // Complete VISIT_COPILOT onboarding step to grant $5 welcome bonus
- useEffect(() => {
- if (isLoggedIn) {
- completeStep("VISIT_COPILOT");
- }
- }, [completeStep, isLoggedIn]);
-
- const isChatEnabled = useGetFlag(Flag.CHAT);
- const flags = useFlags();
- const homepageRoute = getHomepageRoute(isChatEnabled);
- const envEnabled = process.env.NEXT_PUBLIC_LAUNCHDARKLY_ENABLED === "true";
- const clientId = process.env.NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID;
- const isLaunchDarklyConfigured = envEnabled && Boolean(clientId);
- const isFlagReady =
- !isLaunchDarklyConfigured || flags[Flag.CHAT] !== undefined;
-
const greetingName = getGreetingName(user);
const quickActions = getQuickActions();
@@ -58,11 +35,8 @@ export function useCopilotPage() {
: undefined;
useEffect(() => {
- if (!isFlagReady) return;
- if (isChatEnabled === false) {
- router.replace(homepageRoute);
- }
- }, [homepageRoute, isChatEnabled, isFlagReady, router]);
+ if (isLoggedIn) completeStep("VISIT_COPILOT");
+ }, [completeStep, isLoggedIn]);
async function startChatWithPrompt(prompt: string) {
if (!prompt?.trim()) return;
@@ -116,7 +90,6 @@ export function useCopilotPage() {
isLoading: isUserLoading,
hasSession,
initialPrompt,
- isReady: isFlagReady && isChatEnabled !== false && isLoggedIn,
},
handlers: {
handleQuickAction,
diff --git a/autogpt_platform/frontend/src/app/(platform)/error/page.tsx b/autogpt_platform/frontend/src/app/(platform)/error/page.tsx
index b26ca4559b..3cf68178ad 100644
--- a/autogpt_platform/frontend/src/app/(platform)/error/page.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/error/page.tsx
@@ -1,8 +1,6 @@
"use client";
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
-import { getHomepageRoute } from "@/lib/constants";
-import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import { useSearchParams } from "next/navigation";
import { Suspense } from "react";
import { getErrorDetails } from "./helpers";
@@ -11,8 +9,6 @@ function ErrorPageContent() {
const searchParams = useSearchParams();
const errorMessage = searchParams.get("message");
const errorDetails = getErrorDetails(errorMessage);
- const isChatEnabled = useGetFlag(Flag.CHAT);
- const homepageRoute = getHomepageRoute(isChatEnabled);
function handleRetry() {
// Auth-related errors should redirect to login
@@ -30,7 +26,7 @@ function ErrorPageContent() {
}, 2000);
} else {
// For server/network errors, go to home
- window.location.href = homepageRoute;
+ window.location.href = "/";
}
}
diff --git a/autogpt_platform/frontend/src/app/(platform)/login/actions.ts b/autogpt_platform/frontend/src/app/(platform)/login/actions.ts
index 447a25a41d..c4867dd123 100644
--- a/autogpt_platform/frontend/src/app/(platform)/login/actions.ts
+++ b/autogpt_platform/frontend/src/app/(platform)/login/actions.ts
@@ -1,6 +1,5 @@
"use server";
-import { getHomepageRoute } from "@/lib/constants";
import BackendAPI from "@/lib/autogpt-server-api";
import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
import { loginFormSchema } from "@/types/auth";
@@ -38,10 +37,8 @@ export async function login(email: string, password: string) {
await api.createUser();
// Get onboarding status from backend (includes chat flag evaluated for this user)
- const { shouldShowOnboarding, isChatEnabled } = await getOnboardingStatus();
- const next = shouldShowOnboarding
- ? "/onboarding"
- : getHomepageRoute(isChatEnabled);
+ const { shouldShowOnboarding } = await getOnboardingStatus();
+ const next = shouldShowOnboarding ? "/onboarding" : "/";
return {
success: true,
diff --git a/autogpt_platform/frontend/src/app/(platform)/login/useLoginPage.ts b/autogpt_platform/frontend/src/app/(platform)/login/useLoginPage.ts
index e64cc1858d..9b81965c31 100644
--- a/autogpt_platform/frontend/src/app/(platform)/login/useLoginPage.ts
+++ b/autogpt_platform/frontend/src/app/(platform)/login/useLoginPage.ts
@@ -1,8 +1,6 @@
import { useToast } from "@/components/molecules/Toast/use-toast";
-import { getHomepageRoute } from "@/lib/constants";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { environment } from "@/services/environment";
-import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import { loginFormSchema, LoginProvider } from "@/types/auth";
import { zodResolver } from "@hookform/resolvers/zod";
import { useRouter, useSearchParams } from "next/navigation";
@@ -22,17 +20,15 @@ export function useLoginPage() {
const [isGoogleLoading, setIsGoogleLoading] = useState(false);
const [showNotAllowedModal, setShowNotAllowedModal] = useState(false);
const isCloudEnv = environment.isCloud();
- const isChatEnabled = useGetFlag(Flag.CHAT);
- const homepageRoute = getHomepageRoute(isChatEnabled);
// Get redirect destination from 'next' query parameter
const nextUrl = searchParams.get("next");
useEffect(() => {
if (isLoggedIn && !isLoggingIn) {
- router.push(nextUrl || homepageRoute);
+ router.push(nextUrl || "/");
}
- }, [homepageRoute, isLoggedIn, isLoggingIn, nextUrl, router]);
+ }, [isLoggedIn, isLoggingIn, nextUrl, router]);
const form = useForm>({
resolver: zodResolver(loginFormSchema),
@@ -98,7 +94,7 @@ export function useLoginPage() {
}
// Prefer URL's next parameter, then use backend-determined route
- router.replace(nextUrl || result.next || homepageRoute);
+ router.replace(nextUrl || result.next || "/");
} catch (error) {
toast({
title:
diff --git a/autogpt_platform/frontend/src/app/(platform)/signup/actions.ts b/autogpt_platform/frontend/src/app/(platform)/signup/actions.ts
index 0fbba54b8e..204482dbe9 100644
--- a/autogpt_platform/frontend/src/app/(platform)/signup/actions.ts
+++ b/autogpt_platform/frontend/src/app/(platform)/signup/actions.ts
@@ -1,6 +1,5 @@
"use server";
-import { getHomepageRoute } from "@/lib/constants";
import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
import { signupFormSchema } from "@/types/auth";
import * as Sentry from "@sentry/nextjs";
@@ -59,10 +58,8 @@ export async function signup(
}
// Get onboarding status from backend (includes chat flag evaluated for this user)
- const { shouldShowOnboarding, isChatEnabled } = await getOnboardingStatus();
- const next = shouldShowOnboarding
- ? "/onboarding"
- : getHomepageRoute(isChatEnabled);
+ const { shouldShowOnboarding } = await getOnboardingStatus();
+ const next = shouldShowOnboarding ? "/onboarding" : "/";
return { success: true, next };
} catch (err) {
diff --git a/autogpt_platform/frontend/src/app/(platform)/signup/useSignupPage.ts b/autogpt_platform/frontend/src/app/(platform)/signup/useSignupPage.ts
index 5fa8c2c159..fd78b48735 100644
--- a/autogpt_platform/frontend/src/app/(platform)/signup/useSignupPage.ts
+++ b/autogpt_platform/frontend/src/app/(platform)/signup/useSignupPage.ts
@@ -1,8 +1,6 @@
import { useToast } from "@/components/molecules/Toast/use-toast";
-import { getHomepageRoute } from "@/lib/constants";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
import { environment } from "@/services/environment";
-import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import { LoginProvider, signupFormSchema } from "@/types/auth";
import { zodResolver } from "@hookform/resolvers/zod";
import { useRouter, useSearchParams } from "next/navigation";
@@ -22,17 +20,15 @@ export function useSignupPage() {
const [isGoogleLoading, setIsGoogleLoading] = useState(false);
const [showNotAllowedModal, setShowNotAllowedModal] = useState(false);
const isCloudEnv = environment.isCloud();
- const isChatEnabled = useGetFlag(Flag.CHAT);
- const homepageRoute = getHomepageRoute(isChatEnabled);
// Get redirect destination from 'next' query parameter
const nextUrl = searchParams.get("next");
useEffect(() => {
if (isLoggedIn && !isSigningUp) {
- router.push(nextUrl || homepageRoute);
+ router.push(nextUrl || "/");
}
- }, [homepageRoute, isLoggedIn, isSigningUp, nextUrl, router]);
+ }, [isLoggedIn, isSigningUp, nextUrl, router]);
const form = useForm>({
resolver: zodResolver(signupFormSchema),
@@ -133,7 +129,7 @@ export function useSignupPage() {
}
// Prefer the URL's next parameter, then result.next (for onboarding), then default
- const redirectTo = nextUrl || result.next || homepageRoute;
+ const redirectTo = nextUrl || result.next || "/";
router.replace(redirectTo);
} catch (error) {
setIsLoading(false);
diff --git a/autogpt_platform/frontend/src/app/api/helpers.ts b/autogpt_platform/frontend/src/app/api/helpers.ts
index c2104d231a..226f5fa786 100644
--- a/autogpt_platform/frontend/src/app/api/helpers.ts
+++ b/autogpt_platform/frontend/src/app/api/helpers.ts
@@ -181,6 +181,5 @@ export async function getOnboardingStatus() {
const isCompleted = onboarding.completedSteps.includes("CONGRATS");
return {
shouldShowOnboarding: status.is_onboarding_enabled && !isCompleted,
- isChatEnabled: status.is_chat_enabled,
};
}
diff --git a/autogpt_platform/frontend/src/app/page.tsx b/autogpt_platform/frontend/src/app/page.tsx
index dbfab49469..31d1e96e48 100644
--- a/autogpt_platform/frontend/src/app/page.tsx
+++ b/autogpt_platform/frontend/src/app/page.tsx
@@ -1,27 +1,14 @@
"use client";
-import { getHomepageRoute } from "@/lib/constants";
-import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
-import { useRouter } from "next/navigation";
-import { useEffect } from "react";
+import { FeatureFlagRedirect } from "@/services/feature-flags/FeatureFlagRedirect";
+import { Flag } from "@/services/feature-flags/use-get-flag";
export default function Page() {
- const isChatEnabled = useGetFlag(Flag.CHAT);
- const router = useRouter();
- const homepageRoute = getHomepageRoute(isChatEnabled);
- const envEnabled = process.env.NEXT_PUBLIC_LAUNCHDARKLY_ENABLED === "true";
- const clientId = process.env.NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID;
- const isLaunchDarklyConfigured = envEnabled && Boolean(clientId);
- const isFlagReady =
- !isLaunchDarklyConfigured || typeof isChatEnabled === "boolean";
-
- useEffect(
- function redirectToHomepage() {
- if (!isFlagReady) return;
- router.replace(homepageRoute);
- },
- [homepageRoute, isFlagReady, router],
+ return (
+
);
-
- return null;
}
diff --git a/autogpt_platform/frontend/src/components/layout/Navbar/components/NavbarLink.tsx b/autogpt_platform/frontend/src/components/layout/Navbar/components/NavbarLink.tsx
index eab5a7352f..dff1277384 100644
--- a/autogpt_platform/frontend/src/components/layout/Navbar/components/NavbarLink.tsx
+++ b/autogpt_platform/frontend/src/components/layout/Navbar/components/NavbarLink.tsx
@@ -1,7 +1,6 @@
"use client";
import { IconLaptop } from "@/components/__legacy__/ui/icons";
-import { getHomepageRoute } from "@/lib/constants";
import { cn } from "@/lib/utils";
import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import { ListChecksIcon } from "@phosphor-icons/react/dist/ssr";
@@ -24,11 +23,11 @@ interface Props {
export function NavbarLink({ name, href }: Props) {
const pathname = usePathname();
const isChatEnabled = useGetFlag(Flag.CHAT);
- const homepageRoute = getHomepageRoute(isChatEnabled);
+ const expectedHomeRoute = isChatEnabled ? "/copilot" : "/library";
const isActive =
- href === homepageRoute
- ? pathname === "/" || pathname.startsWith(homepageRoute)
+ href === expectedHomeRoute
+ ? pathname === "/" || pathname.startsWith(expectedHomeRoute)
: pathname.includes(href);
return (
diff --git a/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx b/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx
index 6c097c395e..d422e389dd 100644
--- a/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx
+++ b/autogpt_platform/frontend/src/hooks/useAgentGraph.tsx
@@ -66,7 +66,7 @@ export default function useAgentGraph(
>(null);
const [xyNodes, setXYNodes] = useState([]);
const [xyEdges, setXYEdges] = useState([]);
- const betaBlocks = useGetFlag(Flag.BETA_BLOCKS);
+ const betaBlocks = useGetFlag(Flag.BETA_BLOCKS) as string[];
// Filter blocks based on beta flags
const availableBlocks = useMemo(() => {
diff --git a/autogpt_platform/frontend/src/lib/constants.ts b/autogpt_platform/frontend/src/lib/constants.ts
index de5aac1670..19365a56ac 100644
--- a/autogpt_platform/frontend/src/lib/constants.ts
+++ b/autogpt_platform/frontend/src/lib/constants.ts
@@ -11,10 +11,3 @@ export const API_KEY_HEADER_NAME = "X-API-Key";
// Layout
export const NAVBAR_HEIGHT_PX = 60;
-
-// Routes
-export function getHomepageRoute(isChatEnabled?: boolean | null): string {
- if (isChatEnabled === true) return "/copilot";
- if (isChatEnabled === false) return "/library";
- return "/";
-}
diff --git a/autogpt_platform/frontend/src/lib/supabase/helpers.ts b/autogpt_platform/frontend/src/lib/supabase/helpers.ts
index 3fd0eacb5f..26f7711bde 100644
--- a/autogpt_platform/frontend/src/lib/supabase/helpers.ts
+++ b/autogpt_platform/frontend/src/lib/supabase/helpers.ts
@@ -1,4 +1,3 @@
-import { getHomepageRoute } from "@/lib/constants";
import { environment } from "@/services/environment";
import { Key, storage } from "@/services/storage/local-storage";
import { type CookieOptions } from "@supabase/ssr";
@@ -71,7 +70,7 @@ export function getRedirectPath(
}
if (isAdminPage(path) && userRole !== "admin") {
- return getHomepageRoute();
+ return "/";
}
return null;
diff --git a/autogpt_platform/frontend/src/lib/supabase/middleware.ts b/autogpt_platform/frontend/src/lib/supabase/middleware.ts
index de8b867ef0..cd1f4a240e 100644
--- a/autogpt_platform/frontend/src/lib/supabase/middleware.ts
+++ b/autogpt_platform/frontend/src/lib/supabase/middleware.ts
@@ -1,4 +1,3 @@
-import { getHomepageRoute } from "@/lib/constants";
import { environment } from "@/services/environment";
import { createServerClient } from "@supabase/ssr";
import { NextResponse, type NextRequest } from "next/server";
@@ -67,7 +66,7 @@ export async function updateSession(request: NextRequest) {
// 2. Check if user is authenticated but lacks admin role when accessing admin pages
if (user && userRole !== "admin" && isAdminPage(pathname)) {
- url.pathname = getHomepageRoute();
+ url.pathname = "/";
return NextResponse.redirect(url);
}
diff --git a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx
index 1ee4b2b6db..42cb99f187 100644
--- a/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx
+++ b/autogpt_platform/frontend/src/providers/onboarding/onboarding-provider.tsx
@@ -23,9 +23,7 @@ import {
WebSocketNotification,
} from "@/lib/autogpt-server-api";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
-import { getHomepageRoute } from "@/lib/constants";
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
-import { Flag, useGetFlag } from "@/services/feature-flags/use-get-flag";
import Link from "next/link";
import { usePathname, useRouter } from "next/navigation";
import {
@@ -104,8 +102,6 @@ export default function OnboardingProvider({
const pathname = usePathname();
const router = useRouter();
const { isLoggedIn } = useSupabase();
- const isChatEnabled = useGetFlag(Flag.CHAT);
- const homepageRoute = getHomepageRoute(isChatEnabled);
useOnboardingTimezoneDetection();
@@ -150,7 +146,7 @@ export default function OnboardingProvider({
if (isOnOnboardingRoute) {
const enabled = await resolveResponse(getV1IsOnboardingEnabled());
if (!enabled) {
- router.push(homepageRoute);
+ router.push("/");
return;
}
}
@@ -162,7 +158,7 @@ export default function OnboardingProvider({
isOnOnboardingRoute &&
shouldRedirectFromOnboarding(onboarding.completedSteps, pathname)
) {
- router.push(homepageRoute);
+ router.push("/");
}
} catch (error) {
console.error("Failed to initialize onboarding:", error);
@@ -177,7 +173,7 @@ export default function OnboardingProvider({
}
initializeOnboarding();
- }, [api, homepageRoute, isOnOnboardingRoute, router, isLoggedIn, pathname]);
+ }, [api, isOnOnboardingRoute, router, isLoggedIn, pathname]);
const handleOnboardingNotification = useCallback(
(notification: WebSocketNotification) => {
diff --git a/autogpt_platform/frontend/src/services/environment/index.ts b/autogpt_platform/frontend/src/services/environment/index.ts
index f19bc417e3..0214dcb3c8 100644
--- a/autogpt_platform/frontend/src/services/environment/index.ts
+++ b/autogpt_platform/frontend/src/services/environment/index.ts
@@ -83,6 +83,10 @@ function getPostHogCredentials() {
};
}
+function getLaunchDarklyClientId() {
+ return process.env.NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID;
+}
+
function isProductionBuild() {
return process.env.NODE_ENV === "production";
}
@@ -120,7 +124,10 @@ function isVercelPreview() {
}
function areFeatureFlagsEnabled() {
- return process.env.NEXT_PUBLIC_LAUNCHDARKLY_ENABLED === "enabled";
+ return (
+ process.env.NEXT_PUBLIC_LAUNCHDARKLY_ENABLED === "true" &&
+ Boolean(process.env.NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID)
+ );
}
function isPostHogEnabled() {
@@ -143,6 +150,7 @@ export const environment = {
getSupabaseAnonKey,
getPreviewStealingDev,
getPostHogCredentials,
+ getLaunchDarklyClientId,
// Assertions
isServerSide,
isClientSide,
diff --git a/autogpt_platform/frontend/src/services/feature-flags/FeatureFlagPage.tsx b/autogpt_platform/frontend/src/services/feature-flags/FeatureFlagPage.tsx
new file mode 100644
index 0000000000..eef0691de2
--- /dev/null
+++ b/autogpt_platform/frontend/src/services/feature-flags/FeatureFlagPage.tsx
@@ -0,0 +1,59 @@
+"use client";
+
+import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
+import { useLDClient } from "launchdarkly-react-client-sdk";
+import { useRouter } from "next/navigation";
+import { ReactNode, useEffect, useState } from "react";
+import { environment } from "../environment";
+import { Flag, useGetFlag } from "./use-get-flag";
+
+interface FeatureFlagRedirectProps {
+ flag: Flag;
+ whenDisabled: string;
+ children: ReactNode;
+}
+
+export function FeatureFlagPage({
+ flag,
+ whenDisabled,
+ children,
+}: FeatureFlagRedirectProps) {
+ const [isLoading, setIsLoading] = useState(true);
+ const router = useRouter();
+ const flagValue = useGetFlag(flag);
+ const ldClient = useLDClient();
+ const ldEnabled = environment.areFeatureFlagsEnabled();
+ const ldReady = Boolean(ldClient);
+ const flagEnabled = Boolean(flagValue);
+
+ useEffect(() => {
+ const initialize = async () => {
+ if (!ldEnabled) {
+ router.replace(whenDisabled);
+ setIsLoading(false);
+ return;
+ }
+
+ // Wait for LaunchDarkly to initialize when enabled to prevent race conditions
+ if (ldEnabled && !ldReady) return;
+
+ try {
+ await ldClient?.waitForInitialization();
+ if (!flagEnabled) router.replace(whenDisabled);
+ } catch (error) {
+ console.error(error);
+ router.replace(whenDisabled);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ initialize();
+ }, [ldReady, flagEnabled]);
+
+ return isLoading || !flagEnabled ? (
+
+ ) : (
+ <>{children}>
+ );
+}
diff --git a/autogpt_platform/frontend/src/services/feature-flags/FeatureFlagRedirect.tsx b/autogpt_platform/frontend/src/services/feature-flags/FeatureFlagRedirect.tsx
new file mode 100644
index 0000000000..b843b5567c
--- /dev/null
+++ b/autogpt_platform/frontend/src/services/feature-flags/FeatureFlagRedirect.tsx
@@ -0,0 +1,51 @@
+"use client";
+
+import { LoadingSpinner } from "@/components/atoms/LoadingSpinner/LoadingSpinner";
+import { useLDClient } from "launchdarkly-react-client-sdk";
+import { useRouter } from "next/navigation";
+import { useEffect } from "react";
+import { environment } from "../environment";
+import { Flag, useGetFlag } from "./use-get-flag";
+
+interface FeatureFlagRedirectProps {
+ flag: Flag;
+ whenEnabled: string;
+ whenDisabled: string;
+}
+
+export function FeatureFlagRedirect({
+ flag,
+ whenEnabled,
+ whenDisabled,
+}: FeatureFlagRedirectProps) {
+ const router = useRouter();
+ const flagValue = useGetFlag(flag);
+ const ldEnabled = environment.areFeatureFlagsEnabled();
+ const ldClient = useLDClient();
+ const ldReady = Boolean(ldClient);
+ const flagEnabled = Boolean(flagValue);
+
+ useEffect(() => {
+ const initialize = async () => {
+ if (!ldEnabled) {
+ router.replace(whenDisabled);
+ return;
+ }
+
+ // Wait for LaunchDarkly to initialize when enabled to prevent race conditions
+ if (ldEnabled && !ldReady) return;
+
+ try {
+ await ldClient?.waitForInitialization();
+ router.replace(flagEnabled ? whenEnabled : whenDisabled);
+ } catch (error) {
+ console.error(error);
+ router.replace(whenDisabled);
+ }
+ };
+
+ initialize();
+ }, [ldReady, flagEnabled]);
+
+ return ;
+}
diff --git a/autogpt_platform/frontend/src/services/feature-flags/feature-flag-provider.tsx b/autogpt_platform/frontend/src/services/feature-flags/feature-flag-provider.tsx
index 47e4bd738a..8b78f4c589 100644
--- a/autogpt_platform/frontend/src/services/feature-flags/feature-flag-provider.tsx
+++ b/autogpt_platform/frontend/src/services/feature-flags/feature-flag-provider.tsx
@@ -7,14 +7,12 @@ import type { ReactNode } from "react";
import { useMemo } from "react";
import { environment } from "../environment";
-const clientId = process.env.NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID;
-const envEnabled = process.env.NEXT_PUBLIC_LAUNCHDARKLY_ENABLED === "true";
const LAUNCHDARKLY_INIT_TIMEOUT_MS = 5000;
export function LaunchDarklyProvider({ children }: { children: ReactNode }) {
const { user, isUserLoading } = useSupabase();
- const isCloud = environment.isCloud();
- const isLaunchDarklyConfigured = isCloud && envEnabled && clientId;
+ const envEnabled = environment.areFeatureFlagsEnabled();
+ const clientId = environment.getLaunchDarklyClientId();
const context = useMemo(() => {
if (isUserLoading || !user) {
@@ -36,7 +34,7 @@ export function LaunchDarklyProvider({ children }: { children: ReactNode }) {
};
}, [user, isUserLoading]);
- if (!isLaunchDarklyConfigured) {
+ if (!envEnabled) {
return <>{children}>;
}
@@ -44,7 +42,7 @@ export function LaunchDarklyProvider({ children }: { children: ReactNode }) {
(flag: T): FlagValues[T] | null {
+type FlagValues = typeof defaultFlags;
+
+export function useGetFlag(flag: T): FlagValues[T] {
const currentFlags = useFlags();
const flagValue = currentFlags[flag];
+ const areFlagsEnabled = environment.areFeatureFlagsEnabled();
- const envEnabled = process.env.NEXT_PUBLIC_LAUNCHDARKLY_ENABLED === "true";
- const clientId = process.env.NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID;
- const isLaunchDarklyConfigured = envEnabled && Boolean(clientId);
-
- if (!isLaunchDarklyConfigured || isPwMockEnabled) {
- return mockFlags[flag];
+ if (!areFlagsEnabled || isPwMockEnabled) {
+ return defaultFlags[flag];
}
- return flagValue ?? mockFlags[flag];
+ return flagValue ?? defaultFlags[flag];
}
diff --git a/classic/frontend/.gitignore b/classic/frontend/.gitignore
index 036283f834..eb060615c5 100644
--- a/classic/frontend/.gitignore
+++ b/classic/frontend/.gitignore
@@ -8,6 +8,7 @@
.buildlog/
.history
.svn/
+.next/
migrate_working_dir/
# IntelliJ related