From 6ec2bacb725065966924c82844c33e633f2b51e8 Mon Sep 17 00:00:00 2001 From: Krzysztof Czerwinski <34861343+kcze@users.noreply.github.com> Date: Wed, 18 Dec 2024 09:55:23 +0000 Subject: [PATCH] refactor(frontend): Update Supabase and backend API management (#9036) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently there are random issues (logout, auth desync) and inconveniences with how Supabase and backend API works. Resolves: - https://github.com/Significant-Gravitas/AutoGPT/issues/9006 - https://github.com/Significant-Gravitas/AutoGPT/issues/8912 ### Changes 🏗️ This PR streamlines how the Supabase and backend API is used to fix current errors with auth, remove unnecessary code and make it easier to use Supabase and backend API. - Add `getServerSupabase` for server side that returns `SupabaseClient`. - Add `Spinner` component that is used for loading animation. - Remove redundant `useUser`, user is fetched in `useSupabase` already. - Replace most Supabase `create*Client` to `getSupabaseServer` and `useSupabase`. - Remove redundant `AutoGPTServerAPI` class and rename `BaseAutoGPTServerAPI` to `BackendAPI` and use it instead. - Remove `SupabaseProvider` context; supabase caches internally what's possible already. - Move `useSupabase` hook to its own file and update it. ### Helpful table | Next.js usage | Server | Client | |---|---|---| | API | `new BackendAPI();` | `new BackendAPI();`* or `useBackendAPI()` | | Supabase | `getServerSupabase();` | `useSupabase();` | | user, user.role | `getServerUser();`** | `useSupabase();` | \* `BackendAPI` automatically chooses correct Supabase client, so while it's recommended to use `useBackendAPI()`, it's ok to use `new BackendAPI();` in client components and even memoize it: `useMemo(() => new BackendAPI(), [])`. ** The reason user isn't returned in `getServerSupabase` is because it forces async fetch but creating supabase doesn't, so it'd force `getServerSupabase` to be async or return `{ supabase: SupabaseClient, user: Promise | null }`. For the same reason `useSupabase` provides access to `supabase` immediately but `user` *may* be loading, so there's `isUserLoading` provided as well. ### Checklist 📋 #### For code changes: - [ ] I have clearly listed my changes in the PR description - [ ] I have made a test plan - [ ] I have tested my changes according to the test plan: - [ ] ...
Example test plan - [ ] Create from scratch and execute an agent with at least 3 blocks - [ ] Import an agent from file upload, and confirm it executes correctly - [ ] Upload agent to marketplace - [ ] Import an agent from marketplace and confirm it executes correctly - [ ] Edit an agent from monitor, and confirm it executes correctly
#### For configuration changes: - [ ] `.env.example` is updated or already compatible with my changes - [ ] `docker-compose.yml` is updated or already compatible with my changes - [ ] I have included a list of my configuration changes in the PR description (under **Changes**)
Examples of configuration changes - Changing ports - Adding new services that need to communicate with each other - Secrets or environment variable changes - New or infrastructure changes such as databases
--------- Co-authored-by: Aarushi <50577581+aarushik93@users.noreply.github.com> Co-authored-by: Nicholas Tindle --- .../frontend/public/mockServiceWorker.js | 2 +- .../frontend/src/app/auth/callback/route.ts | 4 +- .../frontend/src/app/auth/confirm/route.ts | 4 +- autogpt_platform/frontend/src/app/layout.tsx | 10 - .../frontend/src/app/login/actions.ts | 12 +- .../frontend/src/app/login/page.tsx | 21 +- .../frontend/src/app/monitoring/page.tsx | 12 +- .../frontend/src/app/profile/page.tsx | 43 +- .../frontend/src/app/providers.tsx | 24 +- .../frontend/src/app/reset_password/page.tsx | 8 +- .../src/app/store/(user)/dashboard/page.tsx | 63 +- .../src/app/store/(user)/profile/page.tsx | 26 +- .../app/store/agent/[creator]/[slug]/page.tsx | 8 +- .../src/app/store/creator/[creator]/page.tsx | 8 +- .../frontend/src/app/store/page.tsx | 14 +- .../frontend/src/app/store/search/page.tsx | 4 +- .../frontend/src/components/NavBar.tsx | 2 +- .../src/components/ProfileDropdown.tsx | 10 +- .../src/components/RoleBasedAccess.tsx | 8 +- .../frontend/src/components/Spinner.tsx | 9 + .../components/admin/marketplace/actions.ts | 14 +- .../src/components/agent-import-form.tsx | 8 +- .../src/components/agptui/CreditsCard.tsx | 4 +- .../frontend/src/components/agptui/Navbar.tsx | 23 +- .../src/components/agptui/ProfileInfoForm.tsx | 16 +- .../agptui/PublishAgentSelectInfo.tsx | 9 +- .../src/components/agptui/RatingCard.tsx | 16 +- .../components/agptui/SettingsInputForm.tsx | 9 +- .../agptui/composite/PublishAgentPopout.tsx | 18 +- .../integrations/credentials-input.tsx | 6 +- .../integrations/credentials-provider.tsx | 19 +- .../marketplace/AgentDetailContent.tsx | 2 +- .../src/components/monitor/AgentFlowList.tsx | 4 +- .../src/components/monitor/FlowInfo.tsx | 9 +- .../src/components/monitor/FlowRunInfo.tsx | 9 +- .../src/components/nav/CreditButton.tsx | 5 +- .../components/providers/SupabaseProvider.tsx | 75 -- .../frontend/src/hooks/useAgentGraph.ts | 4 +- .../frontend/src/hooks/useSupabase.ts | 42 ++ .../frontend/src/hooks/useUser.ts | 67 -- .../src/lib/autogpt-server-api/baseClient.ts | 659 ----------------- .../src/lib/autogpt-server-api/client.ts | 664 +++++++++++++++++- .../lib/autogpt-server-api/clientServer.ts | 16 - .../src/lib/autogpt-server-api/context.tsx | 8 +- .../src/lib/autogpt-server-api/index.ts | 4 +- .../frontend/src/lib/supabase/client.ts | 13 - .../{server.ts => getServerSupabase.ts} | 27 +- .../{hooks => lib/supabase}/getServerUser.ts | 5 +- .../frontend/src/lib/withRoleAccess.ts | 2 - 49 files changed, 889 insertions(+), 1160 deletions(-) create mode 100644 autogpt_platform/frontend/src/components/Spinner.tsx delete mode 100644 autogpt_platform/frontend/src/components/providers/SupabaseProvider.tsx create mode 100644 autogpt_platform/frontend/src/hooks/useSupabase.ts delete mode 100644 autogpt_platform/frontend/src/hooks/useUser.ts delete mode 100644 autogpt_platform/frontend/src/lib/autogpt-server-api/baseClient.ts delete mode 100644 autogpt_platform/frontend/src/lib/autogpt-server-api/clientServer.ts delete mode 100644 autogpt_platform/frontend/src/lib/supabase/client.ts rename autogpt_platform/frontend/src/lib/supabase/{server.ts => getServerSupabase.ts} (58%) rename autogpt_platform/frontend/src/{hooks => lib/supabase}/getServerUser.ts (85%) diff --git a/autogpt_platform/frontend/public/mockServiceWorker.js b/autogpt_platform/frontend/public/mockServiceWorker.js index dbc5b1d0c..ec47a9a50 100644 --- a/autogpt_platform/frontend/public/mockServiceWorker.js +++ b/autogpt_platform/frontend/public/mockServiceWorker.js @@ -8,7 +8,7 @@ * - Please do NOT serve this file on production. */ -const PACKAGE_VERSION = '2.6.8' +const PACKAGE_VERSION = '2.7.0' const INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f' const IS_MOCKED_RESPONSE = Symbol('isMockedResponse') const activeClientIds = new Set() diff --git a/autogpt_platform/frontend/src/app/auth/callback/route.ts b/autogpt_platform/frontend/src/app/auth/callback/route.ts index 5d3e1e953..ba5bc0db1 100644 --- a/autogpt_platform/frontend/src/app/auth/callback/route.ts +++ b/autogpt_platform/frontend/src/app/auth/callback/route.ts @@ -1,5 +1,5 @@ +import getServerSupabase from "@/lib/supabase/getServerSupabase"; import { NextResponse } from "next/server"; -import { createServerClient } from "@/lib/supabase/server"; // Handle the callback to complete the user session login export async function GET(request: Request) { @@ -9,7 +9,7 @@ export async function GET(request: Request) { const next = searchParams.get("next") ?? "/"; if (code) { - const supabase = createServerClient(); + const supabase = getServerSupabase(); if (!supabase) { return NextResponse.redirect(`${origin}/error`); diff --git a/autogpt_platform/frontend/src/app/auth/confirm/route.ts b/autogpt_platform/frontend/src/app/auth/confirm/route.ts index 0fe20c311..e5d432b8f 100644 --- a/autogpt_platform/frontend/src/app/auth/confirm/route.ts +++ b/autogpt_platform/frontend/src/app/auth/confirm/route.ts @@ -2,7 +2,7 @@ import { type EmailOtpType } from "@supabase/supabase-js"; import { type NextRequest } from "next/server"; import { redirect } from "next/navigation"; -import { createServerClient } from "@/lib/supabase/server"; +import getServerSupabase from "@/lib/supabase/getServerSupabase"; // Email confirmation route export async function GET(request: NextRequest) { @@ -12,7 +12,7 @@ export async function GET(request: NextRequest) { const next = searchParams.get("next") ?? "/"; if (token_hash && type) { - const supabase = createServerClient(); + const supabase = getServerSupabase(); if (!supabase) { redirect("/error"); diff --git a/autogpt_platform/frontend/src/app/layout.tsx b/autogpt_platform/frontend/src/app/layout.tsx index f93edc7fd..5d1f399b3 100644 --- a/autogpt_platform/frontend/src/app/layout.tsx +++ b/autogpt_platform/frontend/src/app/layout.tsx @@ -10,7 +10,6 @@ import TallyPopupSimple from "@/components/TallyPopup"; import { GoogleAnalytics } from "@next/third-parties/google"; import { Toaster } from "@/components/ui/toaster"; import { IconType } from "@/components/ui/icons"; -import { createServerClient } from "@/lib/supabase/server"; const inter = Inter({ subsets: ["latin"] }); @@ -24,17 +23,10 @@ export default async function RootLayout({ }: Readonly<{ children: React.ReactNode; }>) { - const supabase = createServerClient(); - - const { - data: { user }, - } = await supabase.auth.getUser(); - return (
{ - const supabase = createServerClient(); + const supabase = getServerSupabase(); if (!supabase) { redirect("/error"); @@ -36,7 +37,8 @@ export async function logout() { export async function login(values: z.infer) { return await Sentry.withServerActionInstrumentation("login", {}, async () => { - const supabase = createServerClient(); + const supabase = getServerSupabase(); + const api = new BackendAPI(); if (!supabase) { redirect("/error"); @@ -45,6 +47,8 @@ export async function login(values: z.infer) { // We are sure that the values are of the correct type because zod validates the form const { data, error } = await supabase.auth.signInWithPassword(values); + await api.createUser(); + if (error) { console.log("Error logging in", error); if (error.status == 400) { @@ -70,7 +74,7 @@ export async function signup(values: z.infer) { "signup", {}, async () => { - const supabase = createServerClient(); + const supabase = getServerSupabase(); if (!supabase) { redirect("/error"); diff --git a/autogpt_platform/frontend/src/app/login/page.tsx b/autogpt_platform/frontend/src/app/login/page.tsx index dd1fd767c..969ac5dcb 100644 --- a/autogpt_platform/frontend/src/app/login/page.tsx +++ b/autogpt_platform/frontend/src/app/login/page.tsx @@ -1,5 +1,4 @@ "use client"; -import useUser from "@/hooks/useUser"; import { login, signup } from "./actions"; import { Button } from "@/components/ui/button"; import { @@ -18,10 +17,12 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { PasswordInput } from "@/components/PasswordInput"; import { FaGoogle, FaGithub, FaDiscord, FaSpinner } from "react-icons/fa"; import { useState } from "react"; -import { useSupabase } from "@/components/providers/SupabaseProvider"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { Checkbox } from "@/components/ui/checkbox"; +import useSupabase from "@/hooks/useSupabase"; +import Spinner from "@/components/Spinner"; +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; const loginFormSchema = z.object({ email: z.string().email().min(2).max(64), @@ -32,11 +33,11 @@ const loginFormSchema = z.object({ }); export default function LoginPage() { - const { supabase, isLoading: isSupabaseLoading } = useSupabase(); - const { user, isLoading: isUserLoading } = useUser(); + const { supabase, user, isUserLoading } = useSupabase(); const [feedback, setFeedback] = useState(null); const router = useRouter(); const [isLoading, setIsLoading] = useState(false); + const api = useBackendAPI(); const form = useForm>({ resolver: zodResolver(loginFormSchema), @@ -48,16 +49,12 @@ export default function LoginPage() { }); if (user) { - console.log("User exists, redirecting to home"); + console.debug("User exists, redirecting to /"); router.push("/"); } - if (isUserLoading || isSupabaseLoading || user) { - return ( -
- -
- ); + if (isUserLoading || user) { + return ; } if (!supabase) { @@ -80,6 +77,8 @@ export default function LoginPage() { }, }); + await api.createUser(); + if (!error) { setFeedback(null); return; diff --git a/autogpt_platform/frontend/src/app/monitoring/page.tsx b/autogpt_platform/frontend/src/app/monitoring/page.tsx index 33842a49f..bb04629ae 100644 --- a/autogpt_platform/frontend/src/app/monitoring/page.tsx +++ b/autogpt_platform/frontend/src/app/monitoring/page.tsx @@ -1,11 +1,7 @@ "use client"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { useCallback, useEffect, useState } from "react"; -import AutoGPTServerAPI, { - GraphExecution, - Schedule, - GraphMeta, -} from "@/lib/autogpt-server-api"; +import { GraphExecution, Schedule, GraphMeta } from "@/lib/autogpt-server-api"; import { Card } from "@/components/ui/card"; import { @@ -16,6 +12,7 @@ import { FlowRunsStats, } from "@/components/monitor"; import { SchedulesTable } from "@/components/monitor/scheduleTable"; +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; const Monitor = () => { const [flows, setFlows] = useState([]); @@ -25,8 +22,7 @@ const Monitor = () => { const [selectedRun, setSelectedRun] = useState(null); const [sortColumn, setSortColumn] = useState("id"); const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc"); - - const api = useMemo(() => new AutoGPTServerAPI(), []); + const api = useBackendAPI(); const fetchSchedules = useCallback(async () => { setSchedules(await api.listSchedules()); diff --git a/autogpt_platform/frontend/src/app/profile/page.tsx b/autogpt_platform/frontend/src/app/profile/page.tsx index 856bc7705..b3097577d 100644 --- a/autogpt_platform/frontend/src/app/profile/page.tsx +++ b/autogpt_platform/frontend/src/app/profile/page.tsx @@ -1,11 +1,7 @@ "use client"; - -import { useSupabase } from "@/components/providers/SupabaseProvider"; import { Button } from "@/components/ui/button"; -import useUser from "@/hooks/useUser"; import { useRouter } from "next/navigation"; import { useCallback, useContext, useMemo, useState } from "react"; -import { FaSpinner } from "react-icons/fa"; import { Separator } from "@/components/ui/separator"; import { useToast } from "@/components/ui/use-toast"; import { IconKey, IconUser } from "@/components/ui/icons"; @@ -31,10 +27,11 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; +import useSupabase from "@/hooks/useSupabase"; +import Spinner from "@/components/Spinner"; export default function PrivatePage() { - const { user, isLoading, error } = useUser(); - const { supabase } = useSupabase(); + const { supabase, user, isUserLoading } = useSupabase(); const router = useRouter(); const providers = useContext(CredentialsProvidersContext); const { toast } = useToast(); @@ -115,30 +112,28 @@ export default function PrivatePage() { [], ); - if (isLoading || !providers) { - return ( -
- -
- ); + if (isUserLoading) { + return ; } - if (error || !user || !supabase) { + if (!user || !supabase) { router.push("/login"); return null; } - const allCredentials = Object.values(providers).flatMap((provider) => - [...provider.savedOAuthCredentials, ...provider.savedApiKeys] - .filter((cred) => !hiddenCredentials.includes(cred.id)) - .map((credentials) => ({ - ...credentials, - provider: provider.provider, - providerName: provider.providerName, - ProviderIcon: providerIcons[provider.provider], - TypeIcon: { oauth2: IconUser, api_key: IconKey }[credentials.type], - })), - ); + const allCredentials = providers + ? Object.values(providers).flatMap((provider) => + [...provider.savedOAuthCredentials, ...provider.savedApiKeys] + .filter((cred) => !hiddenCredentials.includes(cred.id)) + .map((credentials) => ({ + ...credentials, + provider: provider.provider, + providerName: provider.providerName, + ProviderIcon: providerIcons[provider.provider], + TypeIcon: { oauth2: IconUser, api_key: IconKey }[credentials.type], + })), + ) + : []; return (
diff --git a/autogpt_platform/frontend/src/app/providers.tsx b/autogpt_platform/frontend/src/app/providers.tsx index 211b3c926..bae111b84 100644 --- a/autogpt_platform/frontend/src/app/providers.tsx +++ b/autogpt_platform/frontend/src/app/providers.tsx @@ -5,27 +5,19 @@ import { ThemeProvider as NextThemesProvider } from "next-themes"; import { ThemeProviderProps } from "next-themes"; import { BackendAPIProvider } from "@/lib/autogpt-server-api/context"; import { TooltipProvider } from "@/components/ui/tooltip"; -import SupabaseProvider from "@/components/providers/SupabaseProvider"; import CredentialsProvider from "@/components/integrations/credentials-provider"; -import { User } from "@supabase/supabase-js"; import { LaunchDarklyProvider } from "@/components/feature-flag/feature-flag-provider"; -export function Providers({ - children, - initialUser, - ...props -}: ThemeProviderProps & { initialUser: User | null }) { +export function Providers({ children, ...props }: ThemeProviderProps) { return ( - - - - - {children} - - - - + + + + {children} + + + ); } diff --git a/autogpt_platform/frontend/src/app/reset_password/page.tsx b/autogpt_platform/frontend/src/app/reset_password/page.tsx index 45a17cb49..b63f5a2ff 100644 --- a/autogpt_platform/frontend/src/app/reset_password/page.tsx +++ b/autogpt_platform/frontend/src/app/reset_password/page.tsx @@ -1,5 +1,4 @@ "use client"; -import { useSupabase } from "@/components/providers/SupabaseProvider"; import { Button } from "@/components/ui/button"; import { Form, @@ -10,7 +9,7 @@ import { FormMessage, } from "@/components/ui/form"; import { Input } from "@/components/ui/input"; -import useUser from "@/hooks/useUser"; +import useSupabase from "@/hooks/useSupabase"; import { zodResolver } from "@hookform/resolvers/zod"; import { useRouter } from "next/navigation"; import { useState } from "react"; @@ -33,8 +32,7 @@ const resetPasswordFormSchema = z }); export default function ResetPasswordPage() { - const { supabase, isLoading: isSupabaseLoading } = useSupabase(); - const { user, isLoading: isUserLoading } = useUser(); + const { supabase, user, isUserLoading } = useSupabase(); const router = useRouter(); const [isLoading, setIsLoading] = useState(false); const [feedback, setFeedback] = useState(null); @@ -54,7 +52,7 @@ export default function ResetPasswordPage() { }, }); - if (isUserLoading || isSupabaseLoading) { + if (isUserLoading) { return (
diff --git a/autogpt_platform/frontend/src/app/store/(user)/dashboard/page.tsx b/autogpt_platform/frontend/src/app/store/(user)/dashboard/page.tsx index e93747ce7..375375233 100644 --- a/autogpt_platform/frontend/src/app/store/(user)/dashboard/page.tsx +++ b/autogpt_platform/frontend/src/app/store/(user)/dashboard/page.tsx @@ -5,8 +5,6 @@ import { AgentTable } from "@/components/agptui/AgentTable"; import { AgentTableRowProps } from "@/components/agptui/AgentTableRow"; import { Button } from "@/components/agptui/Button"; import { Separator } from "@/components/ui/separator"; -import AutoGPTServerAPI from "@/lib/autogpt-server-api"; -import { createClient } from "@/lib/supabase/client"; import { StatusType } from "@/components/agptui/Status"; import { PublishAgentPopout } from "@/components/agptui/composite/PublishAgentPopout"; import { useCallback, useEffect, useState } from "react"; @@ -14,42 +12,12 @@ import { StoreSubmissionsResponse, StoreSubmissionRequest, } from "@/lib/autogpt-server-api/types"; - -async function getDashboardData() { - const supabase = createClient(); - if (!supabase) { - return { submissions: [] }; - } - - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (!session) { - console.warn("--- No session found in profile page"); - return { profile: null }; - } - - const api = new AutoGPTServerAPI( - process.env.NEXT_PUBLIC_AGPT_SERVER_URL, - process.env.NEXT_PUBLIC_AGPT_WS_SERVER_URL, - supabase, - ); - - try { - const submissions = await api.getStoreSubmissions(); - return { - submissions, - }; - } catch (error) { - console.error("Error fetching profile:", error); - return { - profile: null, - }; - } -} +import useSupabase from "@/hooks/useSupabase"; +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; export default function Page({}: {}) { + const { supabase } = useSupabase(); + const api = useBackendAPI(); const [submissions, setSubmissions] = useState(); const [openPopout, setOpenPopout] = useState(false); const [submissionData, setSubmissionData] = @@ -59,15 +27,20 @@ export default function Page({}: {}) { ); const fetchData = useCallback(async () => { - const { submissions } = await getDashboardData(); - if (submissions) { - setSubmissions(submissions as StoreSubmissionsResponse); + try { + const submissions = await api.getStoreSubmissions(); + setSubmissions(submissions); + } catch (error) { + console.error("Error fetching submissions:", error); } - }, []); + }, [api, supabase]); useEffect(() => { + if (!supabase) { + return; + } fetchData(); - }, [fetchData]); + }, [supabase]); const onEditSubmission = useCallback((submission: StoreSubmissionRequest) => { setSubmissionData(submission); @@ -77,19 +50,13 @@ export default function Page({}: {}) { const onDeleteSubmission = useCallback( (submission_id: string) => { - const supabase = createClient(); if (!supabase) { return; } - const api = new AutoGPTServerAPI( - process.env.NEXT_PUBLIC_AGPT_SERVER_URL, - process.env.NEXT_PUBLIC_AGPT_WS_SERVER_URL, - supabase, - ); api.deleteStoreSubmission(submission_id); fetchData(); }, - [fetchData], + [supabase], ); const onOpenPopout = useCallback(() => { diff --git a/autogpt_platform/frontend/src/app/store/(user)/profile/page.tsx b/autogpt_platform/frontend/src/app/store/(user)/profile/page.tsx index 2ab4b4387..c9c475e03 100644 --- a/autogpt_platform/frontend/src/app/store/(user)/profile/page.tsx +++ b/autogpt_platform/frontend/src/app/store/(user)/profile/page.tsx @@ -1,28 +1,9 @@ import * as React from "react"; import { ProfileInfoForm } from "@/components/agptui/ProfileInfoForm"; -import AutoGPTServerAPIServerSide from "@/lib/autogpt-server-api"; -import { createServerClient } from "@/lib/supabase/server"; +import BackendAPI from "@/lib/autogpt-server-api"; import { CreatorDetails } from "@/lib/autogpt-server-api/types"; -async function getProfileData() { - // Get the supabase client first - const supabase = createServerClient(); - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (!session) { - console.warn("--- No session found in profile page"); - return { profile: null }; - } - - // Create API client with the same supabase instance - const api = new AutoGPTServerAPIServerSide( - process.env.NEXT_PUBLIC_AGPT_SERVER_URL, - process.env.NEXT_PUBLIC_AGPT_WS_SERVER_URL, - supabase, // Pass the supabase client instance - ); - +async function getProfileData(api: BackendAPI) { try { const profile = await api.getStoreProfile("profile"); return { @@ -37,7 +18,8 @@ async function getProfileData() { } export default async function Page({}: {}) { - const { profile } = await getProfileData(); + const api = new BackendAPI(); + const { profile } = await getProfileData(api); if (!profile) { return ( diff --git a/autogpt_platform/frontend/src/app/store/agent/[creator]/[slug]/page.tsx b/autogpt_platform/frontend/src/app/store/agent/[creator]/[slug]/page.tsx index 9d80b91ba..4c4f1c303 100644 --- a/autogpt_platform/frontend/src/app/store/agent/[creator]/[slug]/page.tsx +++ b/autogpt_platform/frontend/src/app/store/agent/[creator]/[slug]/page.tsx @@ -1,4 +1,4 @@ -import AutoGPTServerAPI from "@/lib/autogpt-server-api"; +import BackendAPI from "@/lib/autogpt-server-api"; import { BreadCrumbs } from "@/components/agptui/BreadCrumbs"; import { AgentInfo } from "@/components/agptui/AgentInfo"; import { AgentImages } from "@/components/agptui/AgentImages"; @@ -12,7 +12,7 @@ export async function generateMetadata({ }: { params: { creator: string; slug: string }; }): Promise { - const api = new AutoGPTServerAPI(); + const api = new BackendAPI(); const agent = await api.getStoreAgent(params.creator, params.slug); return { @@ -22,7 +22,7 @@ export async function generateMetadata({ } // export async function generateStaticParams() { -// const api = new AutoGPTServerAPI(); +// const api = new BackendAPI(); // const agents = await api.getStoreAgents({ featured: true }); // return agents.agents.map((agent) => ({ // creator: agent.creator, @@ -35,7 +35,7 @@ export default async function Page({ }: { params: { creator: string; slug: string }; }) { - const api = new AutoGPTServerAPI(); + const api = new BackendAPI(); const agent = await api.getStoreAgent(params.creator, params.slug); const otherAgents = await api.getStoreAgents({ creator: params.creator }); const similarAgents = await api.getStoreAgents({ diff --git a/autogpt_platform/frontend/src/app/store/creator/[creator]/page.tsx b/autogpt_platform/frontend/src/app/store/creator/[creator]/page.tsx index 8f6a40de7..4a492a40f 100644 --- a/autogpt_platform/frontend/src/app/store/creator/[creator]/page.tsx +++ b/autogpt_platform/frontend/src/app/store/creator/[creator]/page.tsx @@ -1,4 +1,4 @@ -import AutoGPTServerAPI from "@/lib/autogpt-server-api"; +import BackendAPI from "@/lib/autogpt-server-api"; import { CreatorDetails as Creator, StoreAgent, @@ -14,7 +14,7 @@ export async function generateMetadata({ }: { params: { creator: string }; }): Promise { - const api = new AutoGPTServerAPI(); + const api = new BackendAPI(); const creator = await api.getStoreCreator(params.creator); return { @@ -24,7 +24,7 @@ export async function generateMetadata({ } // export async function generateStaticParams() { -// const api = new AutoGPTServerAPI(); +// const api = new BackendAPI(); // const creators = await api.getStoreCreators({ featured: true }); // return creators.creators.map((creator) => ({ // creator: creator.username, @@ -36,7 +36,7 @@ export default async function Page({ }: { params: { creator: string }; }) { - const api = new AutoGPTServerAPI(); + const api = new BackendAPI(); try { const creator = await api.getStoreCreator(params.creator); diff --git a/autogpt_platform/frontend/src/app/store/page.tsx b/autogpt_platform/frontend/src/app/store/page.tsx index 0be4af223..5c23ca476 100644 --- a/autogpt_platform/frontend/src/app/store/page.tsx +++ b/autogpt_platform/frontend/src/app/store/page.tsx @@ -14,28 +14,18 @@ import { FeaturedCreator, } from "@/components/agptui/composite/FeaturedCreators"; import { Separator } from "@/components/ui/separator"; -import AutoGPTServerAPIServerSide from "@/lib/autogpt-server-api/clientServer"; import { Metadata } from "next"; -import { createServerClient } from "@/lib/supabase/server"; import { StoreAgentsResponse, CreatorsResponse, } from "@/lib/autogpt-server-api/types"; +import BackendAPI from "@/lib/autogpt-server-api"; export const dynamic = "force-dynamic"; async function getStoreData() { try { - const supabase = createServerClient(); - const { - data: { session }, - } = await supabase.auth.getSession(); - - const api = new AutoGPTServerAPIServerSide( - process.env.NEXT_PUBLIC_AGPT_SERVER_URL, - process.env.NEXT_PUBLIC_AGPT_WS_SERVER_URL, - supabase, - ); + const api = new BackendAPI(); // Add error handling and default values let featuredAgents: StoreAgentsResponse = { diff --git a/autogpt_platform/frontend/src/app/store/search/page.tsx b/autogpt_platform/frontend/src/app/store/search/page.tsx index 5f7a9f7c9..2bb57cd47 100644 --- a/autogpt_platform/frontend/src/app/store/search/page.tsx +++ b/autogpt_platform/frontend/src/app/store/search/page.tsx @@ -1,13 +1,13 @@ "use client"; import { useState, useEffect } from "react"; -import { AutoGPTServerAPI } from "@/lib/autogpt-server-api/client"; import { AgentsSection } from "@/components/agptui/composite/AgentsSection"; import { SearchBar } from "@/components/agptui/SearchBar"; import { FeaturedCreators } from "@/components/agptui/composite/FeaturedCreators"; import { Separator } from "@/components/ui/separator"; import { SearchFilterChips } from "@/components/agptui/SearchFilterChips"; import { SortDropdown } from "@/components/agptui/SortDropdown"; +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; export default function Page({ searchParams, @@ -34,11 +34,11 @@ function SearchResults({ const [agents, setAgents] = useState([]); const [creators, setCreators] = useState([]); const [isLoading, setIsLoading] = useState(true); + const api = useBackendAPI(); useEffect(() => { const fetchData = async () => { setIsLoading(true); - const api = new AutoGPTServerAPI(); try { const [agentsRes, creatorsRes] = await Promise.all([ diff --git a/autogpt_platform/frontend/src/components/NavBar.tsx b/autogpt_platform/frontend/src/components/NavBar.tsx index d7b4c1ef7..aa54a84f7 100644 --- a/autogpt_platform/frontend/src/components/NavBar.tsx +++ b/autogpt_platform/frontend/src/components/NavBar.tsx @@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button"; import React from "react"; import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet"; import Image from "next/image"; -import getServerUser from "@/hooks/getServerUser"; +import getServerUser from "@/lib/supabase/getServerUser"; import ProfileDropdown from "./ProfileDropdown"; import { IconCircleUser, IconMenu } from "@/components/ui/icons"; import CreditButton from "@/components/nav/CreditButton"; diff --git a/autogpt_platform/frontend/src/components/ProfileDropdown.tsx b/autogpt_platform/frontend/src/components/ProfileDropdown.tsx index 60d8f0a76..0c8517e4e 100644 --- a/autogpt_platform/frontend/src/components/ProfileDropdown.tsx +++ b/autogpt_platform/frontend/src/components/ProfileDropdown.tsx @@ -7,16 +7,14 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Button } from "./ui/button"; -import { useSupabase } from "./providers/SupabaseProvider"; import { useRouter } from "next/navigation"; -import useUser from "@/hooks/useUser"; +import useSupabase from "@/hooks/useSupabase"; const ProfileDropdown = () => { - const { supabase } = useSupabase(); + const { supabase, user, isUserLoading } = useSupabase(); const router = useRouter(); - const { user, role, isLoading } = useUser(); - if (isLoading) { + if (isUserLoading) { return null; } @@ -37,7 +35,7 @@ const ProfileDropdown = () => { router.push("/profile")}> Profile - {role === "admin" && ( + {user!.role === "admin" && ( router.push("/admin/dashboard")}> Admin Dashboard diff --git a/autogpt_platform/frontend/src/components/RoleBasedAccess.tsx b/autogpt_platform/frontend/src/components/RoleBasedAccess.tsx index 83f660c4f..176c4761c 100644 --- a/autogpt_platform/frontend/src/components/RoleBasedAccess.tsx +++ b/autogpt_platform/frontend/src/components/RoleBasedAccess.tsx @@ -1,6 +1,6 @@ // components/RoleBasedAccess.tsx +import useSupabase from "@/hooks/useSupabase"; import React from "react"; -import useUser from "@/hooks/useUser"; interface RoleBasedAccessProps { allowedRoles: string[]; @@ -11,13 +11,13 @@ const RoleBasedAccess: React.FC = ({ allowedRoles, children, }) => { - const { role, isLoading } = useUser(); + const { user, isUserLoading } = useSupabase(); - if (isLoading) { + if (isUserLoading) { return
Loading...
; } - if (!role || !allowedRoles.includes(role)) { + if (!user!.role || !allowedRoles.includes(user!.role)) { return null; } diff --git a/autogpt_platform/frontend/src/components/Spinner.tsx b/autogpt_platform/frontend/src/components/Spinner.tsx new file mode 100644 index 000000000..cb75a99ab --- /dev/null +++ b/autogpt_platform/frontend/src/components/Spinner.tsx @@ -0,0 +1,9 @@ +import { FaSpinner } from "react-icons/fa"; + +export default function Spinner() { + return ( +
+ +
+ ); +} diff --git a/autogpt_platform/frontend/src/components/admin/marketplace/actions.ts b/autogpt_platform/frontend/src/components/admin/marketplace/actions.ts index 049aab69b..8a453335c 100644 --- a/autogpt_platform/frontend/src/components/admin/marketplace/actions.ts +++ b/autogpt_platform/frontend/src/components/admin/marketplace/actions.ts @@ -3,9 +3,19 @@ // import ServerSideMarketplaceAPI from "@/lib/marketplace-api/server-client"; // import { revalidatePath } from "next/cache"; // import * as Sentry from "@sentry/nextjs"; -// import { checkAuth, createServerClient } from "@/lib/supabase/server"; // import { redirect } from "next/navigation"; -// import { createClient } from "@/lib/supabase/client"; + +// export async function checkAuth() { +// const supabase = getServerSupabase(); +// if (!supabase) { +// console.error("No supabase client"); +// redirect("/login"); +// } +// const { data, error } = await supabase.auth.getUser(); +// if (error || !data?.user) { +// redirect("/login"); +// } +// } // export async function approveAgent( // agentId: string, diff --git a/autogpt_platform/frontend/src/components/agent-import-form.tsx b/autogpt_platform/frontend/src/components/agent-import-form.tsx index 33c35204d..a17b9963e 100644 --- a/autogpt_platform/frontend/src/components/agent-import-form.tsx +++ b/autogpt_platform/frontend/src/components/agent-import-form.tsx @@ -14,12 +14,10 @@ import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; -import AutoGPTServerAPI, { - Graph, - GraphCreatable, -} from "@/lib/autogpt-server-api"; +import { Graph, GraphCreatable } from "@/lib/autogpt-server-api"; import { cn } from "@/lib/utils"; import { EnterIcon } from "@radix-ui/react-icons"; +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; // Add this custom schema for File type const fileSchema = z.custom((val) => val instanceof File, { @@ -75,7 +73,7 @@ export const AgentImportForm: React.FC< React.FormHTMLAttributes > = ({ className, ...props }) => { const [agentObject, setAgentObject] = useState(null); - const api = new AutoGPTServerAPI(); + const api = useBackendAPI(); const form = useForm>({ resolver: zodResolver(formSchema), diff --git a/autogpt_platform/frontend/src/components/agptui/CreditsCard.tsx b/autogpt_platform/frontend/src/components/agptui/CreditsCard.tsx index f41f78684..55cde3a46 100644 --- a/autogpt_platform/frontend/src/components/agptui/CreditsCard.tsx +++ b/autogpt_platform/frontend/src/components/agptui/CreditsCard.tsx @@ -1,13 +1,13 @@ "use client"; import { IconRefresh } from "@/components/ui/icons"; -import AutoGPTServerAPI from "@/lib/autogpt-server-api"; import { useState } from "react"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; interface CreditsCardProps { credits: number; @@ -15,7 +15,7 @@ interface CreditsCardProps { const CreditsCard = ({ credits }: CreditsCardProps) => { const [currentCredits, setCurrentCredits] = useState(credits); - const api = new AutoGPTServerAPI(); + const api = useBackendAPI(); const onRefresh = async () => { const { credits } = await api.getUserCredit("credits-card"); diff --git a/autogpt_platform/frontend/src/components/agptui/Navbar.tsx b/autogpt_platform/frontend/src/components/agptui/Navbar.tsx index 30d9eddb6..8217b05fc 100644 --- a/autogpt_platform/frontend/src/components/agptui/Navbar.tsx +++ b/autogpt_platform/frontend/src/components/agptui/Navbar.tsx @@ -6,10 +6,10 @@ import { MobileNavBar } from "./MobileNavBar"; import { Button } from "./Button"; import CreditsCard from "./CreditsCard"; import { ProfileDetails } from "@/lib/autogpt-server-api/types"; -import { User } from "@supabase/supabase-js"; -import AutoGPTServerAPIServerSide from "@/lib/autogpt-server-api/clientServer"; import { ThemeToggle } from "./ThemeToggle"; import { NavbarLink } from "./NavbarLink"; +import getServerUser from "@/lib/supabase/getServerUser"; +import BackendAPI from "@/lib/autogpt-server-api"; interface NavLink { name: string; @@ -17,8 +17,6 @@ interface NavLink { } interface NavbarProps { - user: User | null; - isLoggedIn: boolean; links: NavLink[]; menuItemGroups: { groupName?: string; @@ -31,8 +29,8 @@ interface NavbarProps { }[]; } -async function getProfileData(user: User | null) { - const api = new AutoGPTServerAPIServerSide(); +async function getProfileData() { + const api = new BackendAPI(); const [profile, credits] = await Promise.all([ api.getStoreProfile("navbar"), api.getUserCredit("navbar"), @@ -43,17 +41,14 @@ async function getProfileData(user: User | null) { credits, }; } -export const Navbar = async ({ - user, - isLoggedIn, - links, - menuItemGroups, -}: NavbarProps) => { + +export const Navbar = async ({ links, menuItemGroups }: NavbarProps) => { + const { user } = await getServerUser(); + const isLoggedIn = user !== null; let profile: ProfileDetails | null = null; let credits: { credits: number } = { credits: 0 }; if (isLoggedIn) { - const { profile: t_profile, credits: t_credits } = - await getProfileData(user); + const { profile: t_profile, credits: t_credits } = await getProfileData(); profile = t_profile; credits = t_credits; } diff --git a/autogpt_platform/frontend/src/components/agptui/ProfileInfoForm.tsx b/autogpt_platform/frontend/src/components/agptui/ProfileInfoForm.tsx index 4079fc672..0a02fd644 100644 --- a/autogpt_platform/frontend/src/components/agptui/ProfileInfoForm.tsx +++ b/autogpt_platform/frontend/src/components/agptui/ProfileInfoForm.tsx @@ -6,25 +6,17 @@ import { useState } from "react"; import Image from "next/image"; import { Button } from "./Button"; - import { IconPersonFill } from "@/components/ui/icons"; - -import { AutoGPTServerAPI } from "@/lib/autogpt-server-api/client"; import { CreatorDetails, ProfileDetails } from "@/lib/autogpt-server-api/types"; -import { createClient } from "@/lib/supabase/client"; import { Separator } from "@/components/ui/separator"; +import useSupabase from "@/hooks/useSupabase"; +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; export const ProfileInfoForm = ({ profile }: { profile: CreatorDetails }) => { const [isSubmitting, setIsSubmitting] = useState(false); const [profileData, setProfileData] = useState(profile); - - const supabase = createClient(); - - const api = new AutoGPTServerAPI( - process.env.NEXT_PUBLIC_AGPT_SERVER_URL, - process.env.NEXT_PUBLIC_AGPT_WS_SERVER_URL, - supabase, - ); + const { supabase } = useSupabase(); + const api = useBackendAPI(); const submitForm = async () => { try { diff --git a/autogpt_platform/frontend/src/components/agptui/PublishAgentSelectInfo.tsx b/autogpt_platform/frontend/src/components/agptui/PublishAgentSelectInfo.tsx index b97a83032..7353a532f 100644 --- a/autogpt_platform/frontend/src/components/agptui/PublishAgentSelectInfo.tsx +++ b/autogpt_platform/frontend/src/components/agptui/PublishAgentSelectInfo.tsx @@ -4,8 +4,7 @@ import * as React from "react"; import Image from "next/image"; import { Button } from "../agptui/Button"; import { IconClose, IconPlus } from "../ui/icons"; -import AutoGPTServerAPI from "@/lib/autogpt-server-api"; -import { createClient } from "@/lib/supabase/client"; +import BackendAPI from "@/lib/autogpt-server-api"; interface PublishAgentInfoProps { onBack: () => void; @@ -96,11 +95,7 @@ export const PublishAgentInfo: React.FC = ({ if (!file) return; try { - const api = new AutoGPTServerAPI( - process.env.NEXT_PUBLIC_AGPT_SERVER_URL, - process.env.NEXT_PUBLIC_AGPT_WS_SERVER_URL, - createClient(), - ); + const api = new BackendAPI(); const imageUrl = (await api.uploadStoreSubmissionMedia(file)).replace( /^"(.*)"$/, diff --git a/autogpt_platform/frontend/src/components/agptui/RatingCard.tsx b/autogpt_platform/frontend/src/components/agptui/RatingCard.tsx index bc7c1bb24..3486d5482 100644 --- a/autogpt_platform/frontend/src/components/agptui/RatingCard.tsx +++ b/autogpt_platform/frontend/src/components/agptui/RatingCard.tsx @@ -3,8 +3,7 @@ import * as React from "react"; import { Cross1Icon } from "@radix-ui/react-icons"; import { IconStar, IconStarFilled } from "@/components/ui/icons"; -import { createClient } from "@/lib/supabase/client"; -import { AutoGPTServerAPI } from "@/lib/autogpt-server-api/client"; +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; interface RatingCardProps { agentName: string; @@ -18,18 +17,7 @@ export const RatingCard: React.FC = ({ const [rating, setRating] = React.useState(0); const [hoveredRating, setHoveredRating] = React.useState(0); const [isVisible, setIsVisible] = React.useState(true); - - const supabase = React.useMemo(() => createClient(), []); - - const api = React.useMemo( - () => - new AutoGPTServerAPI( - process.env.NEXT_PUBLIC_AGPT_SERVER_URL, - process.env.NEXT_PUBLIC_AGPT_WS_SERVER_URL, - supabase, - ), - [supabase], - ); + const api = useBackendAPI(); const handleClose = () => { setIsVisible(false); diff --git a/autogpt_platform/frontend/src/components/agptui/SettingsInputForm.tsx b/autogpt_platform/frontend/src/components/agptui/SettingsInputForm.tsx index 699fbb1ea..9ea01bc68 100644 --- a/autogpt_platform/frontend/src/components/agptui/SettingsInputForm.tsx +++ b/autogpt_platform/frontend/src/components/agptui/SettingsInputForm.tsx @@ -2,8 +2,7 @@ import * as React from "react"; import { Button } from "@/components/ui/button"; -import { Switch } from "@/components/ui/switch"; -import { createClient } from "@/lib/supabase/client"; +import useSupabase from "@/hooks/useSupabase"; interface SettingsInputFormProps { email?: string; @@ -20,6 +19,7 @@ export const SettingsInputForm = ({ const [password, setPassword] = React.useState(""); const [confirmPassword, setConfirmPassword] = React.useState(""); const [passwordsMatch, setPasswordsMatch] = React.useState(true); + const { supabase } = useSupabase(); const handleSaveChanges = async () => { if (password !== confirmPassword) { @@ -27,10 +27,9 @@ export const SettingsInputForm = ({ return; } setPasswordsMatch(true); - const client = createClient(); - if (client) { + if (supabase) { try { - const { error } = await client.auth.updateUser({ + const { error } = await supabase.auth.updateUser({ password: password, }); if (error) { diff --git a/autogpt_platform/frontend/src/components/agptui/composite/PublishAgentPopout.tsx b/autogpt_platform/frontend/src/components/agptui/composite/PublishAgentPopout.tsx index 07efa13d5..5a86e6545 100644 --- a/autogpt_platform/frontend/src/components/agptui/composite/PublishAgentPopout.tsx +++ b/autogpt_platform/frontend/src/components/agptui/composite/PublishAgentPopout.tsx @@ -7,7 +7,7 @@ import { PopoverContent, PopoverAnchor, } from "@/components/ui/popover"; -import { PublishAgentSelect, Agent } from "../PublishAgentSelect"; +import { PublishAgentSelect } from "../PublishAgentSelect"; import { PublishAgentInfo } from "../PublishAgentSelectInfo"; import { PublishAgentAwaitingReview } from "../PublishAgentAwaitingReview"; import { Button } from "../Button"; @@ -15,9 +15,8 @@ import { StoreSubmissionRequest, MyAgentsResponse, } from "@/lib/autogpt-server-api"; -import { createClient } from "@/lib/supabase/client"; -import { AutoGPTServerAPI } from "@/lib/autogpt-server-api/client"; import { useRouter } from "next/navigation"; +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; interface PublishAgentPopoutProps { trigger?: React.ReactNode; openPopout?: boolean; @@ -57,18 +56,7 @@ export const PublishAgentPopout: React.FC = ({ const popupId = React.useId(); const router = useRouter(); - - const supabase = React.useMemo(() => createClient(), []); - - const api = React.useMemo( - () => - new AutoGPTServerAPI( - process.env.NEXT_PUBLIC_AGPT_SERVER_URL, - process.env.NEXT_PUBLIC_AGPT_WS_SERVER_URL, - supabase, - ), - [supabase], - ); + const api = useBackendAPI(); React.useEffect(() => { console.log("PublishAgentPopout Effect"); diff --git a/autogpt_platform/frontend/src/components/integrations/credentials-input.tsx b/autogpt_platform/frontend/src/components/integrations/credentials-input.tsx index 879f70aff..afd5949a5 100644 --- a/autogpt_platform/frontend/src/components/integrations/credentials-input.tsx +++ b/autogpt_platform/frontend/src/components/integrations/credentials-input.tsx @@ -6,10 +6,9 @@ import { Button } from "@/components/ui/button"; import SchemaTooltip from "@/components/SchemaTooltip"; import useCredentials from "@/hooks/useCredentials"; import { zodResolver } from "@hookform/resolvers/zod"; -import AutoGPTServerAPI from "@/lib/autogpt-server-api"; import { NotionLogoIcon } from "@radix-ui/react-icons"; import { FaDiscord, FaGithub, FaGoogle, FaMedium, FaKey } from "react-icons/fa"; -import { FC, useMemo, useState } from "react"; +import { FC, useState } from "react"; import { CredentialsMetaInput, CredentialsProviderName, @@ -39,6 +38,7 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; const fallbackIcon = FaKey; @@ -91,7 +91,7 @@ export const CredentialsInput: FC<{ selectedCredentials?: CredentialsMetaInput; onSelectCredentials: (newValue?: CredentialsMetaInput) => void; }> = ({ className, selectedCredentials, onSelectCredentials }) => { - const api = useMemo(() => new AutoGPTServerAPI(), []); + const api = useBackendAPI(); const credentials = useCredentials(); const [isAPICredentialsModalOpen, setAPICredentialsModalOpen] = useState(false); diff --git a/autogpt_platform/frontend/src/components/integrations/credentials-provider.tsx b/autogpt_platform/frontend/src/components/integrations/credentials-provider.tsx index 0b965aa2e..6762f99ad 100644 --- a/autogpt_platform/frontend/src/components/integrations/credentials-provider.tsx +++ b/autogpt_platform/frontend/src/components/integrations/credentials-provider.tsx @@ -1,4 +1,4 @@ -import AutoGPTServerAPI, { +import { APIKeyCredentials, CredentialsDeleteNeedConfirmationResponse, CredentialsDeleteResponse, @@ -6,13 +6,8 @@ import AutoGPTServerAPI, { CredentialsProviderName, PROVIDER_NAMES, } from "@/lib/autogpt-server-api"; -import { - createContext, - useCallback, - useEffect, - useMemo, - useState, -} from "react"; +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; +import { createContext, useCallback, useEffect, useState } from "react"; // Get keys from CredentialsProviderName type const CREDENTIALS_PROVIDER_NAMES = Object.values( @@ -87,7 +82,7 @@ export default function CredentialsProvider({ }) { const [providers, setProviders] = useState(null); - const api = useMemo(() => new AutoGPTServerAPI(), []); + const api = useBackendAPI(); const addCredentials = useCallback( ( @@ -120,7 +115,7 @@ export default function CredentialsProvider({ [setProviders], ); - /** Wraps `AutoGPTServerAPI.oAuthCallback`, and adds the result to the internal credentials store. */ + /** Wraps `BackendAPI.oAuthCallback`, and adds the result to the internal credentials store. */ const oAuthCallback = useCallback( async ( provider: CredentialsProviderName, @@ -134,7 +129,7 @@ export default function CredentialsProvider({ [api, addCredentials], ); - /** Wraps `AutoGPTServerAPI.createAPIKeyCredentials`, and adds the result to the internal credentials store. */ + /** Wraps `BackendAPI.createAPIKeyCredentials`, and adds the result to the internal credentials store. */ const createAPIKeyCredentials = useCallback( async ( provider: CredentialsProviderName, @@ -150,7 +145,7 @@ export default function CredentialsProvider({ [api, addCredentials], ); - /** Wraps `AutoGPTServerAPI.deleteCredentials`, and removes the credentials from the internal store. */ + /** Wraps `BackendAPI.deleteCredentials`, and removes the credentials from the internal store. */ const deleteCredentials = useCallback( async ( provider: CredentialsProviderName, diff --git a/autogpt_platform/frontend/src/components/marketplace/AgentDetailContent.tsx b/autogpt_platform/frontend/src/components/marketplace/AgentDetailContent.tsx index 08355836d..cbaa72e99 100644 --- a/autogpt_platform/frontend/src/components/marketplace/AgentDetailContent.tsx +++ b/autogpt_platform/frontend/src/components/marketplace/AgentDetailContent.tsx @@ -2,7 +2,7 @@ // import Link from "next/link"; // import { ArrowLeft, Download, Calendar, Tag } from "lucide-react"; // import { Button } from "@/components/ui/button"; -// import AutoGPTServerAPI, { GraphCreatable } from "@/lib/autogpt-server-api"; +// import BackendAPI, { GraphCreatable } from "@/lib/autogpt-server-api"; // import "@xyflow/react/dist/style.css"; // import { useToast } from "../ui/use-toast"; diff --git a/autogpt_platform/frontend/src/components/monitor/AgentFlowList.tsx b/autogpt_platform/frontend/src/components/monitor/AgentFlowList.tsx index 56a7e96d3..8a87af4c8 100644 --- a/autogpt_platform/frontend/src/components/monitor/AgentFlowList.tsx +++ b/autogpt_platform/frontend/src/components/monitor/AgentFlowList.tsx @@ -1,4 +1,4 @@ -import AutoGPTServerAPI, { +import BackendAPI, { GraphExecution, GraphMeta, } from "@/lib/autogpt-server-api"; @@ -45,8 +45,6 @@ export const AgentFlowList = ({ onSelectFlow: (f: GraphMeta) => void; className?: string; }) => { - const api = useMemo(() => new AutoGPTServerAPI(), []); - return ( diff --git a/autogpt_platform/frontend/src/components/monitor/FlowInfo.tsx b/autogpt_platform/frontend/src/components/monitor/FlowInfo.tsx index a9ee6cd35..220a8e59e 100644 --- a/autogpt_platform/frontend/src/components/monitor/FlowInfo.tsx +++ b/autogpt_platform/frontend/src/components/monitor/FlowInfo.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useMemo, useState, useCallback } from "react"; -import AutoGPTServerAPI, { +import React, { useEffect, useState, useCallback } from "react"; +import { GraphExecution, Graph, GraphMeta, @@ -22,7 +22,7 @@ import { ClockIcon, ExitIcon, Pencil2Icon } from "@radix-ui/react-icons"; import Link from "next/link"; import { exportAsJSONFile, filterBlocksByType } from "@/lib/utils"; import { FlowRunsStats } from "@/components/monitor/index"; -import { Trash2Icon, Timer } from "lucide-react"; +import { Trash2Icon } from "lucide-react"; import { Dialog, DialogContent, @@ -35,6 +35,7 @@ import { useToast } from "@/components/ui/use-toast"; import { CronScheduler } from "@/components/cronScheduler"; import RunnerInputUI from "@/components/runner-ui/RunnerInputUI"; import useAgentGraph from "@/hooks/useAgentGraph"; +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; export const FlowInfo: React.FC< React.HTMLAttributes & { @@ -66,7 +67,7 @@ export const FlowInfo: React.FC< setEdges, } = useAgentGraph(flow.id, false); - const api = useMemo(() => new AutoGPTServerAPI(), []); + const api = useBackendAPI(); const { toast } = useToast(); const [flowVersions, setFlowVersions] = useState(null); diff --git a/autogpt_platform/frontend/src/components/monitor/FlowRunInfo.tsx b/autogpt_platform/frontend/src/components/monitor/FlowRunInfo.tsx index b0839f5d3..2d6dd41a9 100644 --- a/autogpt_platform/frontend/src/components/monitor/FlowRunInfo.tsx +++ b/autogpt_platform/frontend/src/components/monitor/FlowRunInfo.tsx @@ -1,5 +1,5 @@ -import React, { useCallback, useEffect, useMemo, useState } from "react"; -import AutoGPTServerAPI, { +import React, { useCallback, useEffect, useState } from "react"; +import { GraphExecution, GraphMeta, NodeExecutionResult, @@ -13,6 +13,7 @@ import { ExitIcon, Pencil2Icon } from "@radix-ui/react-icons"; import moment from "moment/moment"; import { FlowRunStatusBadge } from "@/components/monitor/FlowRunStatusBadge"; import RunnerOutputUI, { BlockOutput } from "../runner-ui/RunnerOutputUI"; +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; export const FlowRunInfo: React.FC< React.HTMLAttributes & { @@ -22,7 +23,7 @@ export const FlowRunInfo: React.FC< > = ({ flow, execution, ...props }) => { const [isOutputOpen, setIsOutputOpen] = useState(false); const [blockOutputs, setBlockOutputs] = useState([]); - const api = useMemo(() => new AutoGPTServerAPI(), []); + const api = useBackendAPI(); const fetchBlockResults = useCallback(async () => { const executionResults = await api.getGraphExecutionInfo( @@ -119,7 +120,7 @@ export const FlowRunInfo: React.FC< Agent ID: {flow.id}

- Run ID: {flowRun.id} + Run ID: {execution.execution_id}

Status:{" "} diff --git a/autogpt_platform/frontend/src/components/nav/CreditButton.tsx b/autogpt_platform/frontend/src/components/nav/CreditButton.tsx index be9951666..8fa56aa66 100644 --- a/autogpt_platform/frontend/src/components/nav/CreditButton.tsx +++ b/autogpt_platform/frontend/src/components/nav/CreditButton.tsx @@ -3,12 +3,11 @@ import { useState, useEffect, useCallback } from "react"; import { Button } from "@/components/ui/button"; import { IconRefresh } from "@/components/ui/icons"; -import AutoGPTServerAPI from "@/lib/autogpt-server-api"; - -const api = new AutoGPTServerAPI(); +import { useBackendAPI } from "@/lib/autogpt-server-api/context"; export default function CreditButton() { const [credit, setCredit] = useState(null); + const api = useBackendAPI(); const fetchCredit = useCallback(async () => { try { diff --git a/autogpt_platform/frontend/src/components/providers/SupabaseProvider.tsx b/autogpt_platform/frontend/src/components/providers/SupabaseProvider.tsx deleted file mode 100644 index 6ceb01dad..000000000 --- a/autogpt_platform/frontend/src/components/providers/SupabaseProvider.tsx +++ /dev/null @@ -1,75 +0,0 @@ -"use client"; - -import { createClient } from "@/lib/supabase/client"; -import { SupabaseClient, User } from "@supabase/supabase-js"; -import { Session } from "@supabase/supabase-js"; -import { useRouter } from "next/navigation"; -import { createContext, useContext, useEffect, useState } from "react"; -import AutoGPTServerAPI from "@/lib/autogpt-server-api"; - -type SupabaseContextType = { - supabase: SupabaseClient | null; - isLoading: boolean; - user: User | null; -}; - -const Context = createContext(undefined); - -export default function SupabaseProvider({ - children, - initialUser, -}: { - children: React.ReactNode; - initialUser: User | null; -}) { - const [user, setUser] = useState(initialUser); - const [supabase, setSupabase] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const router = useRouter(); - - useEffect(() => { - const initializeSupabase = async () => { - setIsLoading(true); - const client = createClient(); - const api = new AutoGPTServerAPI(); - setSupabase(client); - setIsLoading(false); - - if (client) { - const { - data: { subscription }, - } = client.auth.onAuthStateChange((event, session) => { - client.auth.getUser().then((user) => { - setUser(user.data.user); - }); - if (event === "SIGNED_IN") { - api.createUser(); - } - if (event === "SIGNED_OUT") { - router.refresh(); - } - }); - - return () => { - subscription.unsubscribe(); - }; - } - }; - - initializeSupabase(); - }, [router]); - - return ( - - {children} - - ); -} - -export const useSupabase = () => { - const context = useContext(Context); - if (context === undefined) { - throw new Error("useSupabase must be used inside SupabaseProvider"); - } - return context; -}; diff --git a/autogpt_platform/frontend/src/hooks/useAgentGraph.ts b/autogpt_platform/frontend/src/hooks/useAgentGraph.ts index f0ce56bd2..ea530051e 100644 --- a/autogpt_platform/frontend/src/hooks/useAgentGraph.ts +++ b/autogpt_platform/frontend/src/hooks/useAgentGraph.ts @@ -1,6 +1,6 @@ import { CustomEdge } from "@/components/CustomEdge"; import { CustomNode } from "@/components/CustomNode"; -import AutoGPTServerAPI, { +import BackendAPI, { Block, BlockIOSubSchema, BlockUIType, @@ -74,7 +74,7 @@ export default function useAgentGraph( const [edges, setEdges] = useState([]); const api = useMemo( - () => new AutoGPTServerAPI(process.env.NEXT_PUBLIC_AGPT_SERVER_URL!), + () => new BackendAPI(process.env.NEXT_PUBLIC_AGPT_SERVER_URL!), [], ); diff --git a/autogpt_platform/frontend/src/hooks/useSupabase.ts b/autogpt_platform/frontend/src/hooks/useSupabase.ts new file mode 100644 index 000000000..562fff97d --- /dev/null +++ b/autogpt_platform/frontend/src/hooks/useSupabase.ts @@ -0,0 +1,42 @@ +import { createBrowserClient } from "@supabase/ssr"; +import { User } from "@supabase/supabase-js"; +import { useEffect, useMemo, useState } from "react"; + +export default function useSupabase() { + const [user, setUser] = useState(null); + const [isUserLoading, setIsUserLoading] = useState(true); + + const supabase = useMemo(() => { + try { + return createBrowserClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + ); + } catch (error) { + console.error("Error creating Supabase client", error); + return null; + } + }, []); + + useEffect(() => { + if (!supabase) { + setIsUserLoading(false); + return; + } + + const fetchUser = async () => { + const response = await supabase.auth.getUser(); + + if (response.error) { + console.error("Error fetching user", response.error); + } else { + setUser(response.data.user); + } + setIsUserLoading(false); + }; + + fetchUser(); + }, [supabase]); + + return { supabase, user, isUserLoading }; +} diff --git a/autogpt_platform/frontend/src/hooks/useUser.ts b/autogpt_platform/frontend/src/hooks/useUser.ts deleted file mode 100644 index f9768b5f8..000000000 --- a/autogpt_platform/frontend/src/hooks/useUser.ts +++ /dev/null @@ -1,67 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; -import { User, Session } from "@supabase/supabase-js"; -import { useSupabase } from "@/components/providers/SupabaseProvider"; - -const useUser = () => { - const { supabase, isLoading: isSupabaseLoading } = useSupabase(); - const [user, setUser] = useState(null); - const [session, setSession] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - const [role, setRole] = useState(null); - - useEffect(() => { - if (isSupabaseLoading || !supabase) { - return; - } - - const fetchUser = async () => { - try { - setIsLoading(true); - const { data: userData, error: userError } = - await supabase.auth.getUser(); - const { data: sessionData, error: sessionError } = - await supabase.auth.getSession(); - - if (userError) throw new Error(`User error: ${userError.message}`); - if (sessionError) - throw new Error(`Session error: ${sessionError.message}`); - - setUser(userData.user); - setSession(sessionData.session); - setRole(userData.user?.role || null); - } catch (e) { - setError(e instanceof Error ? e.message : "Failed to fetch user data"); - console.error("Error in useUser hook:", e); - } finally { - setIsLoading(false); - } - }; - - fetchUser(); - - const { - data: { subscription }, - } = supabase.auth.onAuthStateChange((_event, session) => { - setSession(session); - setUser(session?.user ?? null); - setRole(session?.user?.role || null); - - setIsLoading(false); - }); - - return () => subscription.unsubscribe(); - }, [supabase, isSupabaseLoading]); - - return { - user, - session, - role, - isLoading: isLoading || isSupabaseLoading, - error, - }; -}; - -export default useUser; diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/baseClient.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/baseClient.ts deleted file mode 100644 index e1ca7e318..000000000 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/baseClient.ts +++ /dev/null @@ -1,659 +0,0 @@ -import { SupabaseClient } from "@supabase/supabase-js"; -import { - AnalyticsDetails, - AnalyticsMetrics, - APIKeyCredentials, - Block, - CredentialsDeleteNeedConfirmationResponse, - CredentialsDeleteResponse, - CredentialsMetaResponse, - GraphExecution, - Graph, - GraphCreatable, - GraphExecuteResponse, - GraphMeta, - GraphUpdateable, - NodeExecutionResult, - MyAgentsResponse, - OAuth2Credentials, - ProfileDetails, - User, - StoreAgentsResponse, - StoreAgentDetails, - CreatorsResponse, - CreatorDetails, - StoreSubmissionsResponse, - StoreSubmissionRequest, - StoreSubmission, - StoreReviewCreate, - StoreReview, - ScheduleCreatable, - Schedule, -} from "./types"; - -export default class BaseAutoGPTServerAPI { - private baseUrl: string; - private wsUrl: string; - private webSocket: WebSocket | null = null; - private wsConnecting: Promise | null = null; - private wsMessageHandlers: Record void>> = {}; - private supabaseClient: SupabaseClient | null = null; - heartbeatInterval: number | null = null; - readonly HEARTBEAT_INTERVAL = 10_0000; // 30 seconds - readonly HEARTBEAT_TIMEOUT = 10_000; // 10 seconds - heartbeatTimeoutId: number | null = null; - - constructor( - baseUrl: string = process.env.NEXT_PUBLIC_AGPT_SERVER_URL || - "http://localhost:8006/api/", - wsUrl: string = process.env.NEXT_PUBLIC_AGPT_WS_SERVER_URL || - "ws://localhost:8001/ws", - supabaseClient: SupabaseClient | null = null, - ) { - this.baseUrl = baseUrl; - this.wsUrl = wsUrl; - this.supabaseClient = supabaseClient; - } - - async isAuthenticated(): Promise { - if (!this.supabaseClient) return false; - const { - data: { session }, - } = await this.supabaseClient?.auth.getSession(); - return session != null; - } - - createUser(): Promise { - return this._request("POST", "/auth/user", {}); - } - - getUserCredit(page?: string): Promise<{ credits: number }> { - try { - return this._get(`/credits`, undefined, page); - } catch (error) { - return Promise.resolve({ credits: 0 }); - } - } - - getBlocks(): Promise { - return this._get("/blocks"); - } - - listGraphs(): Promise { - return this._get(`/graphs`); - } - - getExecutions(): Promise { - return this._get(`/executions`); - } - - getGraph( - id: string, - version?: number, - hide_credentials?: boolean, - ): Promise { - let query: Record = {}; - if (version !== undefined) { - query["version"] = version; - } - if (hide_credentials !== undefined) { - query["hide_credentials"] = hide_credentials; - } - return this._get(`/graphs/${id}`, query); - } - - getGraphAllVersions(id: string): Promise { - return this._get(`/graphs/${id}/versions`); - } - - createGraph(graphCreateBody: GraphCreatable): Promise; - - createGraph(graphID: GraphCreatable | string): Promise { - let requestBody = { graph: graphID } as GraphCreateRequestBody; - - return this._request("POST", "/graphs", requestBody); - } - - updateGraph(id: string, graph: GraphUpdateable): Promise { - return this._request("PUT", `/graphs/${id}`, graph); - } - - deleteGraph(id: string): Promise { - return this._request("DELETE", `/graphs/${id}`); - } - - setGraphActiveVersion(id: string, version: number): Promise { - return this._request("PUT", `/graphs/${id}/versions/active`, { - active_graph_version: version, - }); - } - - executeGraph( - id: string, - inputData: { [key: string]: any } = {}, - ): Promise { - return this._request("POST", `/graphs/${id}/execute`, inputData); - } - - async getGraphExecutionInfo( - graphID: string, - runID: string, - ): Promise { - return (await this._get(`/graphs/${graphID}/executions/${runID}`)).map( - parseNodeExecutionResultTimestamps, - ); - } - - async stopGraphExecution( - graphID: string, - runID: string, - ): Promise { - return ( - await this._request("POST", `/graphs/${graphID}/executions/${runID}/stop`) - ).map(parseNodeExecutionResultTimestamps); - } - - oAuthLogin( - provider: string, - scopes?: string[], - ): Promise<{ login_url: string; state_token: string }> { - const query = scopes ? { scopes: scopes.join(",") } : undefined; - return this._get(`/integrations/${provider}/login`, query); - } - - oAuthCallback( - provider: string, - code: string, - state_token: string, - ): Promise { - return this._request("POST", `/integrations/${provider}/callback`, { - code, - state_token, - }); - } - - createAPIKeyCredentials( - credentials: Omit, - ): Promise { - return this._request( - "POST", - `/integrations/${credentials.provider}/credentials`, - credentials, - ); - } - - listCredentials(provider?: string): Promise { - return this._get( - provider - ? `/integrations/${provider}/credentials` - : "/integrations/credentials", - ); - } - - getCredentials( - provider: string, - id: string, - ): Promise { - return this._get(`/integrations/${provider}/credentials/${id}`); - } - - deleteCredentials( - provider: string, - id: string, - force: boolean = true, - ): Promise< - CredentialsDeleteResponse | CredentialsDeleteNeedConfirmationResponse - > { - return this._request( - "DELETE", - `/integrations/${provider}/credentials/${id}`, - force ? { force: true } : undefined, - ); - } - - /** - * @returns `true` if a ping event was received, `false` if provider doesn't support pinging but the webhook exists. - * @throws `Error` if the webhook does not exist. - * @throws `Error` if the attempt to ping timed out. - */ - async pingWebhook(webhook_id: string): Promise { - return this._request("POST", `/integrations/webhooks/${webhook_id}/ping`); - } - - logMetric(metric: AnalyticsMetrics) { - return this._request("POST", "/analytics/log_raw_metric", metric); - } - - logAnalytic(analytic: AnalyticsDetails) { - return this._request("POST", "/analytics/log_raw_analytics", analytic); - } - - /////////////////////////////////////////// - /////////// V2 STORE API ///////////////// - ///////////////////////////////////////// - - getStoreProfile(page?: string): Promise { - try { - console.log("+++ Making API from: ", page); - const result = this._get("/store/profile", undefined, page); - return result; - } catch (error) { - console.error("Error fetching store profile:", error); - return Promise.resolve(null); - } - } - - getStoreAgents(params?: { - featured?: boolean; - creator?: string; - sorted_by?: string; - search_query?: string; - category?: string; - page?: number; - page_size?: number; - }): Promise { - return this._get("/store/agents", params); - } - - getStoreAgent( - username: string, - agentName: string, - ): Promise { - return this._get(`/store/agents/${username}/${agentName}`); - } - - getStoreCreators(params?: { - featured?: boolean; - search_query?: string; - sorted_by?: string; - page?: number; - page_size?: number; - }): Promise { - return this._get("/store/creators", params); - } - - getStoreCreator(username: string): Promise { - return this._get(`/store/creator/${username}`); - } - - getStoreSubmissions(params?: { - page?: number; - page_size?: number; - }): Promise { - return this._get("/store/submissions", params); - } - - createStoreSubmission( - submission: StoreSubmissionRequest, - ): Promise { - return this._request("POST", "/store/submissions", submission); - } - - deleteStoreSubmission(submission_id: string): Promise { - return this._request("DELETE", `/store/submissions/${submission_id}`); - } - - uploadStoreSubmissionMedia(file: File): Promise { - const formData = new FormData(); - formData.append("file", file); - return this._uploadFile("/store/submissions/media", file); - } - - updateStoreProfile(profile: ProfileDetails): Promise { - return this._request("POST", "/store/profile", profile); - } - - reviewAgent( - username: string, - agentName: string, - review: StoreReviewCreate, - ): Promise { - console.log("Reviewing agent: ", username, agentName, review); - return this._request( - "POST", - `/store/agents/${username}/${agentName}/review`, - review, - ); - } - - getMyAgents(params?: { - page?: number; - page_size?: number; - }): Promise { - return this._get("/store/myagents", params); - } - - /////////////////////////////////////////// - /////////// INTERNAL FUNCTIONS //////////// - //////////////////////////////??/////////// - - private async _get(path: string, query?: Record, page?: string) { - return this._request("GET", path, query, page); - } - - async createSchedule(schedule: ScheduleCreatable): Promise { - return this._request("POST", `/schedules`, schedule); - } - - async deleteSchedule(scheduleId: string): Promise { - return this._request("DELETE", `/schedules/${scheduleId}`); - } - - async listSchedules(): Promise { - return this._get(`/schedules`); - } - - private async _uploadFile(path: string, file: File): Promise { - // Get session with retry logic - let token = "no-token-found"; - let retryCount = 0; - const maxRetries = 3; - - while (retryCount < maxRetries) { - const { - data: { session }, - } = (await this.supabaseClient?.auth.getSession()) || { - data: { session: null }, - }; - - if (session?.access_token) { - token = session.access_token; - break; - } - - retryCount++; - if (retryCount < maxRetries) { - await new Promise((resolve) => setTimeout(resolve, 100 * retryCount)); - } - } - - // Create a FormData object and append the file - const formData = new FormData(); - formData.append("file", file); - - const response = await fetch(this.baseUrl + path, { - method: "POST", - headers: { - ...(token && { Authorization: `Bearer ${token}` }), - }, - body: formData, - }); - - if (!response.ok) { - throw new Error(`Error uploading file: ${response.statusText}`); - } - - // Parse the response appropriately - const media_url = await response.text(); - return media_url; - } - - private async _request( - method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", - path: string, - payload?: Record, - page?: string, - ) { - if (method !== "GET") { - console.debug(`${method} ${path} payload:`, payload); - } - - // Get session with retry logic - let token = "no-token-found"; - let retryCount = 0; - const maxRetries = 3; - - while (retryCount < maxRetries) { - const { - data: { session }, - } = (await this.supabaseClient?.auth.getSession()) || { - data: { session: null }, - }; - - if (session?.access_token) { - token = session.access_token; - break; - } - - retryCount++; - if (retryCount < maxRetries) { - await new Promise((resolve) => setTimeout(resolve, 100 * retryCount)); - } - } - console.log("Request: ", method, path, "from: ", page); - if (token === "no-token-found") { - console.warn( - "No auth token found after retries. This may indicate a session sync issue between client and server.", - ); - console.debug("Last session attempt:", retryCount); - } else { - console.log("Auth token found"); - } - console.log("--------------------------------"); - - let url = this.baseUrl + path; - const payloadAsQuery = ["GET", "DELETE"].includes(method); - if (payloadAsQuery && payload) { - // For GET requests, use payload as query - const queryParams = new URLSearchParams(payload); - url += `?${queryParams.toString()}`; - } - - const hasRequestBody = !payloadAsQuery && payload !== undefined; - const response = await fetch(url, { - method, - headers: { - ...(hasRequestBody && { "Content-Type": "application/json" }), - ...(token && { Authorization: `Bearer ${token}` }), - }, - body: hasRequestBody ? JSON.stringify(payload) : undefined, - }); - - if (!response.ok) { - console.warn(`${method} ${path} returned non-OK response:`, response); - - // console.warn("baseClient is attempting to redirect by changing window location") - // if ( - // response.status === 403 && - // response.statusText === "Not authenticated" && - // typeof window !== "undefined" // Check if in browser environment - // ) { - // window.location.href = "/login"; - // } - - let errorDetail; - try { - const errorData = await response.json(); - errorDetail = errorData.detail || response.statusText; - } catch (e) { - errorDetail = response.statusText; - } - - throw new Error(errorDetail); - } - - // Handle responses with no content (like DELETE requests) - if ( - response.status === 204 || - response.headers.get("Content-Length") === "0" - ) { - return null; - } - - try { - return await response.json(); - } catch (e) { - if (e instanceof SyntaxError) { - console.warn(`${method} ${path} returned invalid JSON:`, e); - return null; - } - throw e; - } - } - - startHeartbeat() { - this.stopHeartbeat(); - this.heartbeatInterval = window.setInterval(() => { - if (this.webSocket?.readyState === WebSocket.OPEN) { - this.webSocket.send( - JSON.stringify({ - method: "heartbeat", - data: "ping", - success: true, - }), - ); - - this.heartbeatTimeoutId = window.setTimeout(() => { - console.log("Heartbeat timeout - reconnecting"); - this.webSocket?.close(); - this.connectWebSocket(); - }, this.HEARTBEAT_TIMEOUT); - } - }, this.HEARTBEAT_INTERVAL); - } - - stopHeartbeat() { - if (this.heartbeatInterval) { - clearInterval(this.heartbeatInterval); - this.heartbeatInterval = null; - } - if (this.heartbeatTimeoutId) { - clearTimeout(this.heartbeatTimeoutId); - this.heartbeatTimeoutId = null; - } - } - - handleHeartbeatResponse() { - if (this.heartbeatTimeoutId) { - clearTimeout(this.heartbeatTimeoutId); - this.heartbeatTimeoutId = null; - } - } - - async connectWebSocket(): Promise { - this.wsConnecting ??= new Promise(async (resolve, reject) => { - try { - const token = - (await this.supabaseClient?.auth.getSession())?.data.session - ?.access_token || ""; - const wsUrlWithToken = `${this.wsUrl}?token=${token}`; - this.webSocket = new WebSocket(wsUrlWithToken); - - this.webSocket.onopen = () => { - console.log("WebSocket connection established"); - this.startHeartbeat(); // Start heartbeat when connection opens - resolve(); - }; - - this.webSocket.onclose = (event) => { - console.log("WebSocket connection closed", event); - this.stopHeartbeat(); // Stop heartbeat when connection closes - this.webSocket = null; - // Attempt to reconnect after a delay - setTimeout(() => this.connectWebSocket(), 1000); - }; - - this.webSocket.onerror = (error) => { - console.error("WebSocket error:", error); - this.stopHeartbeat(); // Stop heartbeat on error - reject(error); - }; - - this.webSocket.onmessage = (event) => { - const message: WebsocketMessage = JSON.parse(event.data); - - // Handle heartbeat response - if (message.method === "heartbeat" && message.data === "pong") { - this.handleHeartbeatResponse(); - return; - } - - if (message.method === "execution_event") { - message.data = parseNodeExecutionResultTimestamps(message.data); - } - this.wsMessageHandlers[message.method]?.forEach((handler) => - handler(message.data), - ); - }; - } catch (error) { - console.error("Error connecting to WebSocket:", error); - reject(error); - } - }); - return this.wsConnecting; - } - - disconnectWebSocket() { - this.stopHeartbeat(); // Stop heartbeat when disconnecting - if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) { - this.webSocket.close(); - } - } - - sendWebSocketMessage( - method: M, - data: WebsocketMessageTypeMap[M], - callCount = 0, - ) { - if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) { - this.webSocket.send(JSON.stringify({ method, data })); - } else { - this.connectWebSocket().then(() => { - callCount == 0 - ? this.sendWebSocketMessage(method, data, callCount + 1) - : setTimeout( - () => { - this.sendWebSocketMessage(method, data, callCount + 1); - }, - 2 ** (callCount - 1) * 1000, - ); - }); - } - } - - onWebSocketMessage( - method: M, - handler: (data: WebsocketMessageTypeMap[M]) => void, - ): () => void { - this.wsMessageHandlers[method] ??= new Set(); - this.wsMessageHandlers[method].add(handler); - - // Return detacher - return () => this.wsMessageHandlers[method].delete(handler); - } - - subscribeToExecution(graphId: string) { - this.sendWebSocketMessage("subscribe", { graph_id: graphId }); - } -} - -/* *** UTILITY TYPES *** */ - -type GraphCreateRequestBody = { - graph: GraphCreatable; -}; - -type WebsocketMessageTypeMap = { - subscribe: { graph_id: string }; - execution_event: NodeExecutionResult; - heartbeat: "ping" | "pong"; -}; - -type WebsocketMessage = { - [M in keyof WebsocketMessageTypeMap]: { - method: M; - data: WebsocketMessageTypeMap[M]; - }; -}[keyof WebsocketMessageTypeMap]; - -/* *** HELPER FUNCTIONS *** */ - -function parseNodeExecutionResultTimestamps(result: any): NodeExecutionResult { - return { - ...result, - add_time: new Date(result.add_time), - queue_time: result.queue_time ? new Date(result.queue_time) : undefined, - start_time: result.start_time ? new Date(result.start_time) : undefined, - end_time: result.end_time ? new Date(result.end_time) : undefined, - }; -} diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts index 4f71cae07..13d26505a 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/client.ts @@ -1,15 +1,669 @@ import { SupabaseClient } from "@supabase/supabase-js"; -import { createClient } from "../supabase/client"; -import BaseAutoGPTServerAPI from "./baseClient"; +import { + AnalyticsDetails, + AnalyticsMetrics, + APIKeyCredentials, + Block, + CredentialsDeleteNeedConfirmationResponse, + CredentialsDeleteResponse, + CredentialsMetaResponse, + GraphExecution, + Graph, + GraphCreatable, + GraphExecuteResponse, + GraphMeta, + GraphUpdateable, + NodeExecutionResult, + MyAgentsResponse, + OAuth2Credentials, + ProfileDetails, + User, + StoreAgentsResponse, + StoreAgentDetails, + CreatorsResponse, + CreatorDetails, + StoreSubmissionsResponse, + StoreSubmissionRequest, + StoreSubmission, + StoreReviewCreate, + StoreReview, + ScheduleCreatable, + Schedule, +} from "./types"; +import { createBrowserClient } from "@supabase/ssr"; +import getServerSupabase from "../supabase/getServerSupabase"; + +const isClient = typeof window !== "undefined"; + +export default class BackendAPI { + private baseUrl: string; + private wsUrl: string; + private webSocket: WebSocket | null = null; + private wsConnecting: Promise | null = null; + private wsMessageHandlers: Record void>> = {}; + heartbeatInterval: number | null = null; + readonly HEARTBEAT_INTERVAL = 10_0000; // 100 seconds + readonly HEARTBEAT_TIMEOUT = 10_000; // 10 seconds + heartbeatTimeoutId: number | null = null; -export class AutoGPTServerAPI extends BaseAutoGPTServerAPI { constructor( baseUrl: string = process.env.NEXT_PUBLIC_AGPT_SERVER_URL || "http://localhost:8006/api", wsUrl: string = process.env.NEXT_PUBLIC_AGPT_WS_SERVER_URL || "ws://localhost:8001/ws", - supabaseClient: SupabaseClient | null = createClient(), ) { - super(baseUrl, wsUrl, supabaseClient); + this.baseUrl = baseUrl; + this.wsUrl = wsUrl; + } + + private get supabaseClient(): SupabaseClient | null { + return isClient + ? createBrowserClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + ) + : getServerSupabase(); + } + + async isAuthenticated(): Promise { + if (!this.supabaseClient) return false; + const { + data: { user }, + } = await this.supabaseClient?.auth.getUser(); + return user != null; + } + + createUser(): Promise { + return this._request("POST", "/auth/user", {}); + } + + getUserCredit(page?: string): Promise<{ credits: number }> { + try { + return this._get(`/credits`, undefined, page); + } catch (error) { + return Promise.resolve({ credits: 0 }); + } + } + + getBlocks(): Promise { + return this._get("/blocks"); + } + + listGraphs(): Promise { + return this._get(`/graphs`); + } + + getExecutions(): Promise { + return this._get(`/executions`); + } + + getGraph( + id: string, + version?: number, + hide_credentials?: boolean, + ): Promise { + let query: Record = {}; + if (version !== undefined) { + query["version"] = version; + } + if (hide_credentials !== undefined) { + query["hide_credentials"] = hide_credentials; + } + return this._get(`/graphs/${id}`, query); + } + + getGraphAllVersions(id: string): Promise { + return this._get(`/graphs/${id}/versions`); + } + + createGraph(graphCreateBody: GraphCreatable): Promise; + + createGraph(graphID: GraphCreatable | string): Promise { + let requestBody = { graph: graphID } as GraphCreateRequestBody; + + return this._request("POST", "/graphs", requestBody); + } + + updateGraph(id: string, graph: GraphUpdateable): Promise { + return this._request("PUT", `/graphs/${id}`, graph); + } + + deleteGraph(id: string): Promise { + return this._request("DELETE", `/graphs/${id}`); + } + + setGraphActiveVersion(id: string, version: number): Promise { + return this._request("PUT", `/graphs/${id}/versions/active`, { + active_graph_version: version, + }); + } + + executeGraph( + id: string, + inputData: { [key: string]: any } = {}, + ): Promise { + return this._request("POST", `/graphs/${id}/execute`, inputData); + } + + async getGraphExecutionInfo( + graphID: string, + runID: string, + ): Promise { + return (await this._get(`/graphs/${graphID}/executions/${runID}`)).map( + parseNodeExecutionResultTimestamps, + ); + } + + async stopGraphExecution( + graphID: string, + runID: string, + ): Promise { + return ( + await this._request("POST", `/graphs/${graphID}/executions/${runID}/stop`) + ).map(parseNodeExecutionResultTimestamps); + } + + oAuthLogin( + provider: string, + scopes?: string[], + ): Promise<{ login_url: string; state_token: string }> { + const query = scopes ? { scopes: scopes.join(",") } : undefined; + return this._get(`/integrations/${provider}/login`, query); + } + + oAuthCallback( + provider: string, + code: string, + state_token: string, + ): Promise { + return this._request("POST", `/integrations/${provider}/callback`, { + code, + state_token, + }); + } + + createAPIKeyCredentials( + credentials: Omit, + ): Promise { + return this._request( + "POST", + `/integrations/${credentials.provider}/credentials`, + credentials, + ); + } + + listCredentials(provider?: string): Promise { + return this._get( + provider + ? `/integrations/${provider}/credentials` + : "/integrations/credentials", + ); + } + + getCredentials( + provider: string, + id: string, + ): Promise { + return this._get(`/integrations/${provider}/credentials/${id}`); + } + + deleteCredentials( + provider: string, + id: string, + force: boolean = true, + ): Promise< + CredentialsDeleteResponse | CredentialsDeleteNeedConfirmationResponse + > { + return this._request( + "DELETE", + `/integrations/${provider}/credentials/${id}`, + force ? { force: true } : undefined, + ); + } + + /** + * @returns `true` if a ping event was received, `false` if provider doesn't support pinging but the webhook exists. + * @throws `Error` if the webhook does not exist. + * @throws `Error` if the attempt to ping timed out. + */ + async pingWebhook(webhook_id: string): Promise { + return this._request("POST", `/integrations/webhooks/${webhook_id}/ping`); + } + + logMetric(metric: AnalyticsMetrics) { + return this._request("POST", "/analytics/log_raw_metric", metric); + } + + logAnalytic(analytic: AnalyticsDetails) { + return this._request("POST", "/analytics/log_raw_analytics", analytic); + } + + /////////////////////////////////////////// + /////////// V2 STORE API ///////////////// + ///////////////////////////////////////// + + getStoreProfile(page?: string): Promise { + try { + console.log("+++ Making API from: ", page); + const result = this._get("/store/profile", undefined, page); + return result; + } catch (error) { + console.error("Error fetching store profile:", error); + return Promise.resolve(null); + } + } + + getStoreAgents(params?: { + featured?: boolean; + creator?: string; + sorted_by?: string; + search_query?: string; + category?: string; + page?: number; + page_size?: number; + }): Promise { + return this._get("/store/agents", params); + } + + getStoreAgent( + username: string, + agentName: string, + ): Promise { + return this._get(`/store/agents/${username}/${agentName}`); + } + + getStoreCreators(params?: { + featured?: boolean; + search_query?: string; + sorted_by?: string; + page?: number; + page_size?: number; + }): Promise { + return this._get("/store/creators", params); + } + + getStoreCreator(username: string): Promise { + return this._get(`/store/creator/${username}`); + } + + getStoreSubmissions(params?: { + page?: number; + page_size?: number; + }): Promise { + return this._get("/store/submissions", params); + } + + createStoreSubmission( + submission: StoreSubmissionRequest, + ): Promise { + return this._request("POST", "/store/submissions", submission); + } + + deleteStoreSubmission(submission_id: string): Promise { + return this._request("DELETE", `/store/submissions/${submission_id}`); + } + + uploadStoreSubmissionMedia(file: File): Promise { + const formData = new FormData(); + formData.append("file", file); + return this._uploadFile("/store/submissions/media", file); + } + + updateStoreProfile(profile: ProfileDetails): Promise { + return this._request("POST", "/store/profile", profile); + } + + reviewAgent( + username: string, + agentName: string, + review: StoreReviewCreate, + ): Promise { + console.log("Reviewing agent: ", username, agentName, review); + return this._request( + "POST", + `/store/agents/${username}/${agentName}/review`, + review, + ); + } + + getMyAgents(params?: { + page?: number; + page_size?: number; + }): Promise { + return this._get("/store/myagents", params); + } + + /////////////////////////////////////////// + /////////// INTERNAL FUNCTIONS //////////// + //////////////////////////////??/////////// + + private async _get(path: string, query?: Record, page?: string) { + return this._request("GET", path, query, page); + } + + async createSchedule(schedule: ScheduleCreatable): Promise { + return this._request("POST", `/schedules`, schedule); + } + + async deleteSchedule(scheduleId: string): Promise { + return this._request("DELETE", `/schedules/${scheduleId}`); + } + + async listSchedules(): Promise { + return this._get(`/schedules`); + } + + private async _uploadFile(path: string, file: File): Promise { + // Get session with retry logic + let token = "no-token-found"; + let retryCount = 0; + const maxRetries = 3; + + while (retryCount < maxRetries) { + const { + data: { session }, + } = (await this.supabaseClient?.auth.getSession()) || { + data: { session: null }, + }; + + if (session?.access_token) { + token = session.access_token; + break; + } + + retryCount++; + if (retryCount < maxRetries) { + await new Promise((resolve) => setTimeout(resolve, 100 * retryCount)); + } + } + + // Create a FormData object and append the file + const formData = new FormData(); + formData.append("file", file); + + const response = await fetch(this.baseUrl + path, { + method: "POST", + headers: { + ...(token && { Authorization: `Bearer ${token}` }), + }, + body: formData, + }); + + if (!response.ok) { + throw new Error(`Error uploading file: ${response.statusText}`); + } + + // Parse the response appropriately + const media_url = await response.text(); + return media_url; + } + + private async _request( + method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", + path: string, + payload?: Record, + page?: string, + ) { + if (method !== "GET") { + console.debug(`${method} ${path} payload:`, payload); + } + + // Get session with retry logic + let token = "no-token-found"; + let retryCount = 0; + const maxRetries = 3; + + while (retryCount < maxRetries) { + const { + data: { session }, + } = (await this.supabaseClient?.auth.getSession()) || { + data: { session: null }, + }; + + if (session?.access_token) { + token = session.access_token; + break; + } + + retryCount++; + if (retryCount < maxRetries) { + await new Promise((resolve) => setTimeout(resolve, 100 * retryCount)); + } + } + console.log("Request: ", method, path, "from: ", page); + if (token === "no-token-found") { + console.warn( + "No auth token found after retries. This may indicate a session sync issue between client and server.", + ); + console.debug("Last session attempt:", retryCount); + } else { + console.log("Auth token found"); + } + console.log("--------------------------------"); + + let url = this.baseUrl + path; + const payloadAsQuery = ["GET", "DELETE"].includes(method); + if (payloadAsQuery && payload) { + // For GET requests, use payload as query + const queryParams = new URLSearchParams(payload); + url += `?${queryParams.toString()}`; + } + + const hasRequestBody = !payloadAsQuery && payload !== undefined; + const response = await fetch(url, { + method, + headers: { + ...(hasRequestBody && { "Content-Type": "application/json" }), + ...(token && { Authorization: `Bearer ${token}` }), + }, + body: hasRequestBody ? JSON.stringify(payload) : undefined, + }); + + if (!response.ok) { + console.warn(`${method} ${path} returned non-OK response:`, response); + + // console.warn("baseClient is attempting to redirect by changing window location") + // if ( + // response.status === 403 && + // response.statusText === "Not authenticated" && + // typeof window !== "undefined" // Check if in browser environment + // ) { + // window.location.href = "/login"; + // } + + let errorDetail; + try { + const errorData = await response.json(); + errorDetail = errorData.detail || response.statusText; + } catch (e) { + errorDetail = response.statusText; + } + + throw new Error(errorDetail); + } + + // Handle responses with no content (like DELETE requests) + if ( + response.status === 204 || + response.headers.get("Content-Length") === "0" + ) { + return null; + } + + try { + return await response.json(); + } catch (e) { + if (e instanceof SyntaxError) { + console.warn(`${method} ${path} returned invalid JSON:`, e); + return null; + } + throw e; + } + } + + startHeartbeat() { + this.stopHeartbeat(); + this.heartbeatInterval = window.setInterval(() => { + if (this.webSocket?.readyState === WebSocket.OPEN) { + this.webSocket.send( + JSON.stringify({ + method: "heartbeat", + data: "ping", + success: true, + }), + ); + + this.heartbeatTimeoutId = window.setTimeout(() => { + console.log("Heartbeat timeout - reconnecting"); + this.webSocket?.close(); + this.connectWebSocket(); + }, this.HEARTBEAT_TIMEOUT); + } + }, this.HEARTBEAT_INTERVAL); + } + + stopHeartbeat() { + if (this.heartbeatInterval) { + clearInterval(this.heartbeatInterval); + this.heartbeatInterval = null; + } + if (this.heartbeatTimeoutId) { + clearTimeout(this.heartbeatTimeoutId); + this.heartbeatTimeoutId = null; + } + } + + handleHeartbeatResponse() { + if (this.heartbeatTimeoutId) { + clearTimeout(this.heartbeatTimeoutId); + this.heartbeatTimeoutId = null; + } + } + + async connectWebSocket(): Promise { + this.wsConnecting ??= new Promise(async (resolve, reject) => { + try { + const token = + (await this.supabaseClient?.auth.getSession())?.data.session + ?.access_token || ""; + const wsUrlWithToken = `${this.wsUrl}?token=${token}`; + this.webSocket = new WebSocket(wsUrlWithToken); + + this.webSocket.onopen = () => { + console.log("WebSocket connection established"); + this.startHeartbeat(); // Start heartbeat when connection opens + resolve(); + }; + + this.webSocket.onclose = (event) => { + console.log("WebSocket connection closed", event); + this.stopHeartbeat(); // Stop heartbeat when connection closes + this.webSocket = null; + // Attempt to reconnect after a delay + setTimeout(() => this.connectWebSocket(), 1000); + }; + + this.webSocket.onerror = (error) => { + console.error("WebSocket error:", error); + this.stopHeartbeat(); // Stop heartbeat on error + reject(error); + }; + + this.webSocket.onmessage = (event) => { + const message: WebsocketMessage = JSON.parse(event.data); + + // Handle heartbeat response + if (message.method === "heartbeat" && message.data === "pong") { + this.handleHeartbeatResponse(); + return; + } + + if (message.method === "execution_event") { + message.data = parseNodeExecutionResultTimestamps(message.data); + } + this.wsMessageHandlers[message.method]?.forEach((handler) => + handler(message.data), + ); + }; + } catch (error) { + console.error("Error connecting to WebSocket:", error); + reject(error); + } + }); + return this.wsConnecting; + } + + disconnectWebSocket() { + this.stopHeartbeat(); // Stop heartbeat when disconnecting + if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) { + this.webSocket.close(); + } + } + + sendWebSocketMessage( + method: M, + data: WebsocketMessageTypeMap[M], + callCount = 0, + ) { + if (this.webSocket && this.webSocket.readyState === WebSocket.OPEN) { + this.webSocket.send(JSON.stringify({ method, data })); + } else { + this.connectWebSocket().then(() => { + callCount == 0 + ? this.sendWebSocketMessage(method, data, callCount + 1) + : setTimeout( + () => { + this.sendWebSocketMessage(method, data, callCount + 1); + }, + 2 ** (callCount - 1) * 1000, + ); + }); + } + } + + onWebSocketMessage( + method: M, + handler: (data: WebsocketMessageTypeMap[M]) => void, + ): () => void { + this.wsMessageHandlers[method] ??= new Set(); + this.wsMessageHandlers[method].add(handler); + + // Return detacher + return () => this.wsMessageHandlers[method].delete(handler); + } + + subscribeToExecution(graphId: string) { + this.sendWebSocketMessage("subscribe", { graph_id: graphId }); } } + +/* *** UTILITY TYPES *** */ + +type GraphCreateRequestBody = { + graph: GraphCreatable; +}; + +type WebsocketMessageTypeMap = { + subscribe: { graph_id: string }; + execution_event: NodeExecutionResult; + heartbeat: "ping" | "pong"; +}; + +type WebsocketMessage = { + [M in keyof WebsocketMessageTypeMap]: { + method: M; + data: WebsocketMessageTypeMap[M]; + }; +}[keyof WebsocketMessageTypeMap]; + +/* *** HELPER FUNCTIONS *** */ + +function parseNodeExecutionResultTimestamps(result: any): NodeExecutionResult { + return { + ...result, + add_time: new Date(result.add_time), + queue_time: result.queue_time ? new Date(result.queue_time) : undefined, + start_time: result.start_time ? new Date(result.start_time) : undefined, + end_time: result.end_time ? new Date(result.end_time) : undefined, + }; +} diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/clientServer.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/clientServer.ts deleted file mode 100644 index 097a18149..000000000 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/clientServer.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createServerClient } from "../supabase/server"; -import BaseAutoGPTServerAPI from "./baseClient"; - -export default class AutoGPTServerAPIServerSide extends BaseAutoGPTServerAPI { - private cachedToken: string | null = null; - - constructor( - baseUrl: string = process.env.NEXT_PUBLIC_AGPT_SERVER_URL || - "http://localhost:8006/api", - wsUrl: string = process.env.NEXT_PUBLIC_AGPT_WS_SERVER_URL || - "ws://localhost:8001/ws", - supabaseClient = createServerClient(), - ) { - super(baseUrl, wsUrl, supabaseClient); - } -} diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/context.tsx b/autogpt_platform/frontend/src/lib/autogpt-server-api/context.tsx index 938e226dc..05b2c2207 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/context.tsx +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/context.tsx @@ -1,14 +1,14 @@ -import { AutoGPTServerAPI } from "./client"; +import BackendAPI from "./client"; import React, { createContext, useMemo } from "react"; -const BackendAPIProviderContext = createContext(null); +const BackendAPIProviderContext = createContext(null); export function BackendAPIProvider({ children, }: { children?: React.ReactNode; }): React.ReactNode { - const api = useMemo(() => new AutoGPTServerAPI(), []); + const api = useMemo(() => new BackendAPI(), []); return ( @@ -17,7 +17,7 @@ export function BackendAPIProvider({ ); } -export function useBackendAPI(): AutoGPTServerAPI { +export function useBackendAPI(): BackendAPI { const context = React.useContext(BackendAPIProviderContext); if (!context) { throw new Error( diff --git a/autogpt_platform/frontend/src/lib/autogpt-server-api/index.ts b/autogpt_platform/frontend/src/lib/autogpt-server-api/index.ts index d1575b7ef..d25d101d2 100644 --- a/autogpt_platform/frontend/src/lib/autogpt-server-api/index.ts +++ b/autogpt_platform/frontend/src/lib/autogpt-server-api/index.ts @@ -1,6 +1,6 @@ -import { AutoGPTServerAPI } from "./client"; +import BackendAPI from "./client"; -export default AutoGPTServerAPI; +export default BackendAPI; export * from "./client"; export * from "./types"; export * from "./utils"; diff --git a/autogpt_platform/frontend/src/lib/supabase/client.ts b/autogpt_platform/frontend/src/lib/supabase/client.ts deleted file mode 100644 index f1e63694e..000000000 --- a/autogpt_platform/frontend/src/lib/supabase/client.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createBrowserClient } from "@supabase/ssr"; - -export function createClient() { - try { - return createBrowserClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, - ); - } catch (error) { - console.error("error creating client", error); - return null; - } -} diff --git a/autogpt_platform/frontend/src/lib/supabase/server.ts b/autogpt_platform/frontend/src/lib/supabase/getServerSupabase.ts similarity index 58% rename from autogpt_platform/frontend/src/lib/supabase/server.ts rename to autogpt_platform/frontend/src/lib/supabase/getServerSupabase.ts index a3d3070bd..7122b2e65 100644 --- a/autogpt_platform/frontend/src/lib/supabase/server.ts +++ b/autogpt_platform/frontend/src/lib/supabase/getServerSupabase.ts @@ -1,15 +1,12 @@ -import { - createServerClient as createClient, - type CookieOptions, -} from "@supabase/ssr"; -import { cookies } from "next/headers"; -import { redirect } from "next/navigation"; +import { createServerClient } from "@supabase/ssr"; -export function createServerClient() { +export default function getServerSupabase() { + // Need require here, so Next.js doesn't complain about importing this on client side + const { cookies } = require("next/headers"); const cookieStore = cookies(); try { - return createClient( + const supabase = createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { @@ -31,19 +28,9 @@ export function createServerClient() { }, }, ); + + return supabase; } catch (error) { throw error; } } - -export async function checkAuth() { - const supabase = createServerClient(); - if (!supabase) { - console.error("No supabase client"); - redirect("/login"); - } - const { data, error } = await supabase.auth.getUser(); - if (error || !data?.user) { - redirect("/login"); - } -} diff --git a/autogpt_platform/frontend/src/hooks/getServerUser.ts b/autogpt_platform/frontend/src/lib/supabase/getServerUser.ts similarity index 85% rename from autogpt_platform/frontend/src/hooks/getServerUser.ts rename to autogpt_platform/frontend/src/lib/supabase/getServerUser.ts index 8ae1cdf00..d46cd8b85 100644 --- a/autogpt_platform/frontend/src/hooks/getServerUser.ts +++ b/autogpt_platform/frontend/src/lib/supabase/getServerUser.ts @@ -1,10 +1,9 @@ -import { createServerClient } from "@/lib/supabase/server"; +import getServerSupabase from "./getServerSupabase"; const getServerUser = async () => { - const supabase = createServerClient(); + const supabase = getServerSupabase(); if (!supabase) { - console.log(">>> failed to create supabase client"); return { user: null, error: "Failed to create Supabase client" }; } diff --git a/autogpt_platform/frontend/src/lib/withRoleAccess.ts b/autogpt_platform/frontend/src/lib/withRoleAccess.ts index 38ccd0945..fdf59a0cf 100644 --- a/autogpt_platform/frontend/src/lib/withRoleAccess.ts +++ b/autogpt_platform/frontend/src/lib/withRoleAccess.ts @@ -1,5 +1,3 @@ -import { redirect } from "next/navigation"; -import getServerUser from "@/hooks/getServerUser"; import React from "react"; import * as Sentry from "@sentry/nextjs";