refactor(frontend): Update Supabase and backend API management (#9036)
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<User> | 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: <!-- Put your test plan here: --> - [ ] ... <details> <summary>Example test plan</summary> - [ ] 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 </details> #### 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**) <details> <summary>Examples of configuration changes</summary> - Changing ports - Adding new services that need to communicate with each other - Secrets or environment variable changes - New or infrastructure changes such as databases </details> --------- Co-authored-by: Aarushi <50577581+aarushik93@users.noreply.github.com> Co-authored-by: Nicholas Tindle <nicholas.tindle@agpt.co>
This commit is contained in:
parent
95bd268de8
commit
6ec2bacb72
|
@ -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()
|
||||
|
|
|
@ -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`);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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 (
|
||||
<html lang="en">
|
||||
<body className={cn("antialiased transition-colors", inter.className)}>
|
||||
<Providers
|
||||
initialUser={user}
|
||||
attribute="class"
|
||||
defaultTheme="light"
|
||||
// Feel free to remove this line if you want to use the system theme by default
|
||||
|
@ -43,8 +35,6 @@ export default async function RootLayout({
|
|||
>
|
||||
<div className="flex min-h-screen flex-col items-center justify-center">
|
||||
<Navbar
|
||||
user={user}
|
||||
isLoggedIn={!!user}
|
||||
links={[
|
||||
{
|
||||
name: "Agent Store",
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
"use server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
import { createServerClient } from "@/lib/supabase/server";
|
||||
import { z } from "zod";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
import getServerSupabase from "@/lib/supabase/getServerSupabase";
|
||||
import BackendAPI from "@/lib/autogpt-server-api";
|
||||
|
||||
const loginFormSchema = z.object({
|
||||
email: z.string().email().min(2).max(64),
|
||||
|
@ -15,7 +16,7 @@ export async function logout() {
|
|||
"logout",
|
||||
{},
|
||||
async () => {
|
||||
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<typeof loginFormSchema>) {
|
||||
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<typeof loginFormSchema>) {
|
|||
// 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<typeof loginFormSchema>) {
|
|||
"signup",
|
||||
{},
|
||||
async () => {
|
||||
const supabase = createServerClient();
|
||||
const supabase = getServerSupabase();
|
||||
|
||||
if (!supabase) {
|
||||
redirect("/error");
|
||||
|
|
|
@ -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<string | null>(null);
|
||||
const router = useRouter();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const api = useBackendAPI();
|
||||
|
||||
const form = useForm<z.infer<typeof loginFormSchema>>({
|
||||
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 (
|
||||
<div className="flex h-[80vh] items-center justify-center">
|
||||
<FaSpinner className="mr-2 h-16 w-16 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
if (isUserLoading || user) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
if (!supabase) {
|
||||
|
@ -80,6 +77,8 @@ export default function LoginPage() {
|
|||
},
|
||||
});
|
||||
|
||||
await api.createUser();
|
||||
|
||||
if (!error) {
|
||||
setFeedback(null);
|
||||
return;
|
||||
|
|
|
@ -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<GraphMeta[]>([]);
|
||||
|
@ -25,8 +22,7 @@ const Monitor = () => {
|
|||
const [selectedRun, setSelectedRun] = useState<GraphExecution | null>(null);
|
||||
const [sortColumn, setSortColumn] = useState<keyof Schedule>("id");
|
||||
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc");
|
||||
|
||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
||||
const api = useBackendAPI();
|
||||
|
||||
const fetchSchedules = useCallback(async () => {
|
||||
setSchedules(await api.listSchedules());
|
||||
|
|
|
@ -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 (
|
||||
<div className="flex h-[80vh] items-center justify-center">
|
||||
<FaSpinner className="mr-2 h-16 w-16 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
if (isUserLoading) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="mx-auto max-w-3xl md:py-8">
|
||||
|
|
|
@ -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 (
|
||||
<NextThemesProvider {...props}>
|
||||
<SupabaseProvider initialUser={initialUser}>
|
||||
<BackendAPIProvider>
|
||||
<CredentialsProvider>
|
||||
<LaunchDarklyProvider>
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
</LaunchDarklyProvider>
|
||||
</CredentialsProvider>
|
||||
</BackendAPIProvider>
|
||||
</SupabaseProvider>
|
||||
<BackendAPIProvider>
|
||||
<CredentialsProvider>
|
||||
<LaunchDarklyProvider>
|
||||
<TooltipProvider>{children}</TooltipProvider>
|
||||
</LaunchDarklyProvider>
|
||||
</CredentialsProvider>
|
||||
</BackendAPIProvider>
|
||||
</NextThemesProvider>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<string | null>(null);
|
||||
|
@ -54,7 +52,7 @@ export default function ResetPasswordPage() {
|
|||
},
|
||||
});
|
||||
|
||||
if (isUserLoading || isSupabaseLoading) {
|
||||
if (isUserLoading) {
|
||||
return (
|
||||
<div className="flex h-[80vh] items-center justify-center">
|
||||
<FaSpinner className="mr-2 h-16 w-16 animate-spin" />
|
||||
|
|
|
@ -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<StoreSubmissionsResponse>();
|
||||
const [openPopout, setOpenPopout] = useState<boolean>(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(() => {
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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<Metadata> {
|
||||
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({
|
||||
|
|
|
@ -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<Metadata> {
|
||||
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);
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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<any[]>([]);
|
||||
const [creators, setCreators] = useState<any[]>([]);
|
||||
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([
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 = () => {
|
|||
<DropdownMenuItem onClick={() => router.push("/profile")}>
|
||||
Profile
|
||||
</DropdownMenuItem>
|
||||
{role === "admin" && (
|
||||
{user!.role === "admin" && (
|
||||
<DropdownMenuItem onClick={() => router.push("/admin/dashboard")}>
|
||||
Admin Dashboard
|
||||
</DropdownMenuItem>
|
||||
|
|
|
@ -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<RoleBasedAccessProps> = ({
|
|||
allowedRoles,
|
||||
children,
|
||||
}) => {
|
||||
const { role, isLoading } = useUser();
|
||||
const { user, isUserLoading } = useSupabase();
|
||||
|
||||
if (isLoading) {
|
||||
if (isUserLoading) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
if (!role || !allowedRoles.includes(role)) {
|
||||
if (!user!.role || !allowedRoles.includes(user!.role)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import { FaSpinner } from "react-icons/fa";
|
||||
|
||||
export default function Spinner() {
|
||||
return (
|
||||
<div className="flex h-[80vh] items-center justify-center">
|
||||
<FaSpinner className="mr-2 h-16 w-16 animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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<File>((val) => val instanceof File, {
|
||||
|
@ -75,7 +73,7 @@ export const AgentImportForm: React.FC<
|
|||
React.FormHTMLAttributes<HTMLFormElement>
|
||||
> = ({ className, ...props }) => {
|
||||
const [agentObject, setAgentObject] = useState<GraphCreatable | null>(null);
|
||||
const api = new AutoGPTServerAPI();
|
||||
const api = useBackendAPI();
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<PublishAgentInfoProps> = ({
|
|||
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(
|
||||
/^"(.*)"$/,
|
||||
|
|
|
@ -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<RatingCardProps> = ({
|
|||
const [rating, setRating] = React.useState<number>(0);
|
||||
const [hoveredRating, setHoveredRating] = React.useState<number>(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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<PublishAgentPopoutProps> = ({
|
|||
|
||||
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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<CredentialsProvidersContextType | null>(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,
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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 (
|
||||
<Card className={className}>
|
||||
<CardHeader className="flex-row items-center justify-between space-x-3 space-y-0">
|
||||
|
|
|
@ -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<HTMLDivElement> & {
|
||||
|
@ -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<Graph[] | null>(null);
|
||||
|
|
|
@ -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<HTMLDivElement> & {
|
||||
|
@ -22,7 +23,7 @@ export const FlowRunInfo: React.FC<
|
|||
> = ({ flow, execution, ...props }) => {
|
||||
const [isOutputOpen, setIsOutputOpen] = useState(false);
|
||||
const [blockOutputs, setBlockOutputs] = useState<BlockOutput[]>([]);
|
||||
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<
|
|||
<strong>Agent ID:</strong> <code>{flow.id}</code>
|
||||
</p>
|
||||
<p className="hidden">
|
||||
<strong>Run ID:</strong> <code>{flowRun.id}</code>
|
||||
<strong>Run ID:</strong> <code>{execution.execution_id}</code>
|
||||
</p>
|
||||
<div>
|
||||
<strong>Status:</strong>{" "}
|
||||
|
|
|
@ -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<number | null>(null);
|
||||
const api = useBackendAPI();
|
||||
|
||||
const fetchCredit = useCallback(async () => {
|
||||
try {
|
||||
|
|
|
@ -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<SupabaseContextType | undefined>(undefined);
|
||||
|
||||
export default function SupabaseProvider({
|
||||
children,
|
||||
initialUser,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
initialUser: User | null;
|
||||
}) {
|
||||
const [user, setUser] = useState<User | null>(initialUser);
|
||||
const [supabase, setSupabase] = useState<SupabaseClient | null>(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 (
|
||||
<Context.Provider value={{ supabase, isLoading, user }}>
|
||||
{children}
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useSupabase = () => {
|
||||
const context = useContext(Context);
|
||||
if (context === undefined) {
|
||||
throw new Error("useSupabase must be used inside SupabaseProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
|
@ -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<CustomEdge[]>([]);
|
||||
|
||||
const api = useMemo(
|
||||
() => new AutoGPTServerAPI(process.env.NEXT_PUBLIC_AGPT_SERVER_URL!),
|
||||
() => new BackendAPI(process.env.NEXT_PUBLIC_AGPT_SERVER_URL!),
|
||||
[],
|
||||
);
|
||||
|
||||
|
|
|
@ -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<User | null>(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 };
|
||||
}
|
|
@ -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<User | null>(null);
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [role, setRole] = useState<string | null>(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;
|
|
@ -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<void> | null = null;
|
||||
private wsMessageHandlers: Record<string, Set<(data: any) => 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<boolean> {
|
||||
if (!this.supabaseClient) return false;
|
||||
const {
|
||||
data: { session },
|
||||
} = await this.supabaseClient?.auth.getSession();
|
||||
return session != null;
|
||||
}
|
||||
|
||||
createUser(): Promise<User> {
|
||||
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<Block[]> {
|
||||
return this._get("/blocks");
|
||||
}
|
||||
|
||||
listGraphs(): Promise<GraphMeta[]> {
|
||||
return this._get(`/graphs`);
|
||||
}
|
||||
|
||||
getExecutions(): Promise<GraphExecution[]> {
|
||||
return this._get(`/executions`);
|
||||
}
|
||||
|
||||
getGraph(
|
||||
id: string,
|
||||
version?: number,
|
||||
hide_credentials?: boolean,
|
||||
): Promise<Graph> {
|
||||
let query: Record<string, any> = {};
|
||||
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<Graph[]> {
|
||||
return this._get(`/graphs/${id}/versions`);
|
||||
}
|
||||
|
||||
createGraph(graphCreateBody: GraphCreatable): Promise<Graph>;
|
||||
|
||||
createGraph(graphID: GraphCreatable | string): Promise<Graph> {
|
||||
let requestBody = { graph: graphID } as GraphCreateRequestBody;
|
||||
|
||||
return this._request("POST", "/graphs", requestBody);
|
||||
}
|
||||
|
||||
updateGraph(id: string, graph: GraphUpdateable): Promise<Graph> {
|
||||
return this._request("PUT", `/graphs/${id}`, graph);
|
||||
}
|
||||
|
||||
deleteGraph(id: string): Promise<void> {
|
||||
return this._request("DELETE", `/graphs/${id}`);
|
||||
}
|
||||
|
||||
setGraphActiveVersion(id: string, version: number): Promise<Graph> {
|
||||
return this._request("PUT", `/graphs/${id}/versions/active`, {
|
||||
active_graph_version: version,
|
||||
});
|
||||
}
|
||||
|
||||
executeGraph(
|
||||
id: string,
|
||||
inputData: { [key: string]: any } = {},
|
||||
): Promise<GraphExecuteResponse> {
|
||||
return this._request("POST", `/graphs/${id}/execute`, inputData);
|
||||
}
|
||||
|
||||
async getGraphExecutionInfo(
|
||||
graphID: string,
|
||||
runID: string,
|
||||
): Promise<NodeExecutionResult[]> {
|
||||
return (await this._get(`/graphs/${graphID}/executions/${runID}`)).map(
|
||||
parseNodeExecutionResultTimestamps,
|
||||
);
|
||||
}
|
||||
|
||||
async stopGraphExecution(
|
||||
graphID: string,
|
||||
runID: string,
|
||||
): Promise<NodeExecutionResult[]> {
|
||||
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<CredentialsMetaResponse> {
|
||||
return this._request("POST", `/integrations/${provider}/callback`, {
|
||||
code,
|
||||
state_token,
|
||||
});
|
||||
}
|
||||
|
||||
createAPIKeyCredentials(
|
||||
credentials: Omit<APIKeyCredentials, "id" | "type">,
|
||||
): Promise<APIKeyCredentials> {
|
||||
return this._request(
|
||||
"POST",
|
||||
`/integrations/${credentials.provider}/credentials`,
|
||||
credentials,
|
||||
);
|
||||
}
|
||||
|
||||
listCredentials(provider?: string): Promise<CredentialsMetaResponse[]> {
|
||||
return this._get(
|
||||
provider
|
||||
? `/integrations/${provider}/credentials`
|
||||
: "/integrations/credentials",
|
||||
);
|
||||
}
|
||||
|
||||
getCredentials(
|
||||
provider: string,
|
||||
id: string,
|
||||
): Promise<APIKeyCredentials | OAuth2Credentials> {
|
||||
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<boolean> {
|
||||
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<ProfileDetails | null> {
|
||||
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<StoreAgentsResponse> {
|
||||
return this._get("/store/agents", params);
|
||||
}
|
||||
|
||||
getStoreAgent(
|
||||
username: string,
|
||||
agentName: string,
|
||||
): Promise<StoreAgentDetails> {
|
||||
return this._get(`/store/agents/${username}/${agentName}`);
|
||||
}
|
||||
|
||||
getStoreCreators(params?: {
|
||||
featured?: boolean;
|
||||
search_query?: string;
|
||||
sorted_by?: string;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}): Promise<CreatorsResponse> {
|
||||
return this._get("/store/creators", params);
|
||||
}
|
||||
|
||||
getStoreCreator(username: string): Promise<CreatorDetails> {
|
||||
return this._get(`/store/creator/${username}`);
|
||||
}
|
||||
|
||||
getStoreSubmissions(params?: {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}): Promise<StoreSubmissionsResponse> {
|
||||
return this._get("/store/submissions", params);
|
||||
}
|
||||
|
||||
createStoreSubmission(
|
||||
submission: StoreSubmissionRequest,
|
||||
): Promise<StoreSubmission> {
|
||||
return this._request("POST", "/store/submissions", submission);
|
||||
}
|
||||
|
||||
deleteStoreSubmission(submission_id: string): Promise<boolean> {
|
||||
return this._request("DELETE", `/store/submissions/${submission_id}`);
|
||||
}
|
||||
|
||||
uploadStoreSubmissionMedia(file: File): Promise<string> {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return this._uploadFile("/store/submissions/media", file);
|
||||
}
|
||||
|
||||
updateStoreProfile(profile: ProfileDetails): Promise<ProfileDetails> {
|
||||
return this._request("POST", "/store/profile", profile);
|
||||
}
|
||||
|
||||
reviewAgent(
|
||||
username: string,
|
||||
agentName: string,
|
||||
review: StoreReviewCreate,
|
||||
): Promise<StoreReview> {
|
||||
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<MyAgentsResponse> {
|
||||
return this._get("/store/myagents", params);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
/////////// INTERNAL FUNCTIONS ////////////
|
||||
//////////////////////////////??///////////
|
||||
|
||||
private async _get(path: string, query?: Record<string, any>, page?: string) {
|
||||
return this._request("GET", path, query, page);
|
||||
}
|
||||
|
||||
async createSchedule(schedule: ScheduleCreatable): Promise<Schedule> {
|
||||
return this._request("POST", `/schedules`, schedule);
|
||||
}
|
||||
|
||||
async deleteSchedule(scheduleId: string): Promise<Schedule> {
|
||||
return this._request("DELETE", `/schedules/${scheduleId}`);
|
||||
}
|
||||
|
||||
async listSchedules(): Promise<Schedule[]> {
|
||||
return this._get(`/schedules`);
|
||||
}
|
||||
|
||||
private async _uploadFile(path: string, file: File): Promise<string> {
|
||||
// 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<string, any>,
|
||||
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<void> {
|
||||
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<M extends keyof WebsocketMessageTypeMap>(
|
||||
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<M extends keyof WebsocketMessageTypeMap>(
|
||||
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,
|
||||
};
|
||||
}
|
|
@ -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<void> | null = null;
|
||||
private wsMessageHandlers: Record<string, Set<(data: any) => 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<boolean> {
|
||||
if (!this.supabaseClient) return false;
|
||||
const {
|
||||
data: { user },
|
||||
} = await this.supabaseClient?.auth.getUser();
|
||||
return user != null;
|
||||
}
|
||||
|
||||
createUser(): Promise<User> {
|
||||
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<Block[]> {
|
||||
return this._get("/blocks");
|
||||
}
|
||||
|
||||
listGraphs(): Promise<GraphMeta[]> {
|
||||
return this._get(`/graphs`);
|
||||
}
|
||||
|
||||
getExecutions(): Promise<GraphExecution[]> {
|
||||
return this._get(`/executions`);
|
||||
}
|
||||
|
||||
getGraph(
|
||||
id: string,
|
||||
version?: number,
|
||||
hide_credentials?: boolean,
|
||||
): Promise<Graph> {
|
||||
let query: Record<string, any> = {};
|
||||
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<Graph[]> {
|
||||
return this._get(`/graphs/${id}/versions`);
|
||||
}
|
||||
|
||||
createGraph(graphCreateBody: GraphCreatable): Promise<Graph>;
|
||||
|
||||
createGraph(graphID: GraphCreatable | string): Promise<Graph> {
|
||||
let requestBody = { graph: graphID } as GraphCreateRequestBody;
|
||||
|
||||
return this._request("POST", "/graphs", requestBody);
|
||||
}
|
||||
|
||||
updateGraph(id: string, graph: GraphUpdateable): Promise<Graph> {
|
||||
return this._request("PUT", `/graphs/${id}`, graph);
|
||||
}
|
||||
|
||||
deleteGraph(id: string): Promise<void> {
|
||||
return this._request("DELETE", `/graphs/${id}`);
|
||||
}
|
||||
|
||||
setGraphActiveVersion(id: string, version: number): Promise<Graph> {
|
||||
return this._request("PUT", `/graphs/${id}/versions/active`, {
|
||||
active_graph_version: version,
|
||||
});
|
||||
}
|
||||
|
||||
executeGraph(
|
||||
id: string,
|
||||
inputData: { [key: string]: any } = {},
|
||||
): Promise<GraphExecuteResponse> {
|
||||
return this._request("POST", `/graphs/${id}/execute`, inputData);
|
||||
}
|
||||
|
||||
async getGraphExecutionInfo(
|
||||
graphID: string,
|
||||
runID: string,
|
||||
): Promise<NodeExecutionResult[]> {
|
||||
return (await this._get(`/graphs/${graphID}/executions/${runID}`)).map(
|
||||
parseNodeExecutionResultTimestamps,
|
||||
);
|
||||
}
|
||||
|
||||
async stopGraphExecution(
|
||||
graphID: string,
|
||||
runID: string,
|
||||
): Promise<NodeExecutionResult[]> {
|
||||
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<CredentialsMetaResponse> {
|
||||
return this._request("POST", `/integrations/${provider}/callback`, {
|
||||
code,
|
||||
state_token,
|
||||
});
|
||||
}
|
||||
|
||||
createAPIKeyCredentials(
|
||||
credentials: Omit<APIKeyCredentials, "id" | "type">,
|
||||
): Promise<APIKeyCredentials> {
|
||||
return this._request(
|
||||
"POST",
|
||||
`/integrations/${credentials.provider}/credentials`,
|
||||
credentials,
|
||||
);
|
||||
}
|
||||
|
||||
listCredentials(provider?: string): Promise<CredentialsMetaResponse[]> {
|
||||
return this._get(
|
||||
provider
|
||||
? `/integrations/${provider}/credentials`
|
||||
: "/integrations/credentials",
|
||||
);
|
||||
}
|
||||
|
||||
getCredentials(
|
||||
provider: string,
|
||||
id: string,
|
||||
): Promise<APIKeyCredentials | OAuth2Credentials> {
|
||||
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<boolean> {
|
||||
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<ProfileDetails | null> {
|
||||
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<StoreAgentsResponse> {
|
||||
return this._get("/store/agents", params);
|
||||
}
|
||||
|
||||
getStoreAgent(
|
||||
username: string,
|
||||
agentName: string,
|
||||
): Promise<StoreAgentDetails> {
|
||||
return this._get(`/store/agents/${username}/${agentName}`);
|
||||
}
|
||||
|
||||
getStoreCreators(params?: {
|
||||
featured?: boolean;
|
||||
search_query?: string;
|
||||
sorted_by?: string;
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}): Promise<CreatorsResponse> {
|
||||
return this._get("/store/creators", params);
|
||||
}
|
||||
|
||||
getStoreCreator(username: string): Promise<CreatorDetails> {
|
||||
return this._get(`/store/creator/${username}`);
|
||||
}
|
||||
|
||||
getStoreSubmissions(params?: {
|
||||
page?: number;
|
||||
page_size?: number;
|
||||
}): Promise<StoreSubmissionsResponse> {
|
||||
return this._get("/store/submissions", params);
|
||||
}
|
||||
|
||||
createStoreSubmission(
|
||||
submission: StoreSubmissionRequest,
|
||||
): Promise<StoreSubmission> {
|
||||
return this._request("POST", "/store/submissions", submission);
|
||||
}
|
||||
|
||||
deleteStoreSubmission(submission_id: string): Promise<boolean> {
|
||||
return this._request("DELETE", `/store/submissions/${submission_id}`);
|
||||
}
|
||||
|
||||
uploadStoreSubmissionMedia(file: File): Promise<string> {
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
return this._uploadFile("/store/submissions/media", file);
|
||||
}
|
||||
|
||||
updateStoreProfile(profile: ProfileDetails): Promise<ProfileDetails> {
|
||||
return this._request("POST", "/store/profile", profile);
|
||||
}
|
||||
|
||||
reviewAgent(
|
||||
username: string,
|
||||
agentName: string,
|
||||
review: StoreReviewCreate,
|
||||
): Promise<StoreReview> {
|
||||
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<MyAgentsResponse> {
|
||||
return this._get("/store/myagents", params);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
/////////// INTERNAL FUNCTIONS ////////////
|
||||
//////////////////////////////??///////////
|
||||
|
||||
private async _get(path: string, query?: Record<string, any>, page?: string) {
|
||||
return this._request("GET", path, query, page);
|
||||
}
|
||||
|
||||
async createSchedule(schedule: ScheduleCreatable): Promise<Schedule> {
|
||||
return this._request("POST", `/schedules`, schedule);
|
||||
}
|
||||
|
||||
async deleteSchedule(scheduleId: string): Promise<Schedule> {
|
||||
return this._request("DELETE", `/schedules/${scheduleId}`);
|
||||
}
|
||||
|
||||
async listSchedules(): Promise<Schedule[]> {
|
||||
return this._get(`/schedules`);
|
||||
}
|
||||
|
||||
private async _uploadFile(path: string, file: File): Promise<string> {
|
||||
// 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<string, any>,
|
||||
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<void> {
|
||||
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<M extends keyof WebsocketMessageTypeMap>(
|
||||
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<M extends keyof WebsocketMessageTypeMap>(
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
import { AutoGPTServerAPI } from "./client";
|
||||
import BackendAPI from "./client";
|
||||
import React, { createContext, useMemo } from "react";
|
||||
|
||||
const BackendAPIProviderContext = createContext<AutoGPTServerAPI | null>(null);
|
||||
const BackendAPIProviderContext = createContext<BackendAPI | null>(null);
|
||||
|
||||
export function BackendAPIProvider({
|
||||
children,
|
||||
}: {
|
||||
children?: React.ReactNode;
|
||||
}): React.ReactNode {
|
||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
||||
const api = useMemo(() => new BackendAPI(), []);
|
||||
|
||||
return (
|
||||
<BackendAPIProviderContext.Provider value={api}>
|
||||
|
@ -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(
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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" };
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
import { redirect } from "next/navigation";
|
||||
import getServerUser from "@/hooks/getServerUser";
|
||||
import React from "react";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
|
|
Loading…
Reference in New Issue