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";