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.
|
* - 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 INTEGRITY_CHECKSUM = '00729d72e3b82faf54ca8b9621dbb96f'
|
||||||
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
|
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
|
||||||
const activeClientIds = new Set()
|
const activeClientIds = new Set()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import getServerSupabase from "@/lib/supabase/getServerSupabase";
|
||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
import { createServerClient } from "@/lib/supabase/server";
|
|
||||||
|
|
||||||
// Handle the callback to complete the user session login
|
// Handle the callback to complete the user session login
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
|
@ -9,7 +9,7 @@ export async function GET(request: Request) {
|
||||||
const next = searchParams.get("next") ?? "/";
|
const next = searchParams.get("next") ?? "/";
|
||||||
|
|
||||||
if (code) {
|
if (code) {
|
||||||
const supabase = createServerClient();
|
const supabase = getServerSupabase();
|
||||||
|
|
||||||
if (!supabase) {
|
if (!supabase) {
|
||||||
return NextResponse.redirect(`${origin}/error`);
|
return NextResponse.redirect(`${origin}/error`);
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { type EmailOtpType } from "@supabase/supabase-js";
|
||||||
import { type NextRequest } from "next/server";
|
import { type NextRequest } from "next/server";
|
||||||
|
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { createServerClient } from "@/lib/supabase/server";
|
import getServerSupabase from "@/lib/supabase/getServerSupabase";
|
||||||
|
|
||||||
// Email confirmation route
|
// Email confirmation route
|
||||||
export async function GET(request: NextRequest) {
|
export async function GET(request: NextRequest) {
|
||||||
|
@ -12,7 +12,7 @@ export async function GET(request: NextRequest) {
|
||||||
const next = searchParams.get("next") ?? "/";
|
const next = searchParams.get("next") ?? "/";
|
||||||
|
|
||||||
if (token_hash && type) {
|
if (token_hash && type) {
|
||||||
const supabase = createServerClient();
|
const supabase = getServerSupabase();
|
||||||
|
|
||||||
if (!supabase) {
|
if (!supabase) {
|
||||||
redirect("/error");
|
redirect("/error");
|
||||||
|
|
|
@ -10,7 +10,6 @@ import TallyPopupSimple from "@/components/TallyPopup";
|
||||||
import { GoogleAnalytics } from "@next/third-parties/google";
|
import { GoogleAnalytics } from "@next/third-parties/google";
|
||||||
import { Toaster } from "@/components/ui/toaster";
|
import { Toaster } from "@/components/ui/toaster";
|
||||||
import { IconType } from "@/components/ui/icons";
|
import { IconType } from "@/components/ui/icons";
|
||||||
import { createServerClient } from "@/lib/supabase/server";
|
|
||||||
|
|
||||||
const inter = Inter({ subsets: ["latin"] });
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
|
@ -24,17 +23,10 @@ export default async function RootLayout({
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
const supabase = createServerClient();
|
|
||||||
|
|
||||||
const {
|
|
||||||
data: { user },
|
|
||||||
} = await supabase.auth.getUser();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body className={cn("antialiased transition-colors", inter.className)}>
|
<body className={cn("antialiased transition-colors", inter.className)}>
|
||||||
<Providers
|
<Providers
|
||||||
initialUser={user}
|
|
||||||
attribute="class"
|
attribute="class"
|
||||||
defaultTheme="light"
|
defaultTheme="light"
|
||||||
// Feel free to remove this line if you want to use the system theme by default
|
// 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">
|
<div className="flex min-h-screen flex-col items-center justify-center">
|
||||||
<Navbar
|
<Navbar
|
||||||
user={user}
|
|
||||||
isLoggedIn={!!user}
|
|
||||||
links={[
|
links={[
|
||||||
{
|
{
|
||||||
name: "Agent Store",
|
name: "Agent Store",
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
"use server";
|
"use server";
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { createServerClient } from "@/lib/supabase/server";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import * as Sentry from "@sentry/nextjs";
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
import getServerSupabase from "@/lib/supabase/getServerSupabase";
|
||||||
|
import BackendAPI from "@/lib/autogpt-server-api";
|
||||||
|
|
||||||
const loginFormSchema = z.object({
|
const loginFormSchema = z.object({
|
||||||
email: z.string().email().min(2).max(64),
|
email: z.string().email().min(2).max(64),
|
||||||
|
@ -15,7 +16,7 @@ export async function logout() {
|
||||||
"logout",
|
"logout",
|
||||||
{},
|
{},
|
||||||
async () => {
|
async () => {
|
||||||
const supabase = createServerClient();
|
const supabase = getServerSupabase();
|
||||||
|
|
||||||
if (!supabase) {
|
if (!supabase) {
|
||||||
redirect("/error");
|
redirect("/error");
|
||||||
|
@ -36,7 +37,8 @@ export async function logout() {
|
||||||
|
|
||||||
export async function login(values: z.infer<typeof loginFormSchema>) {
|
export async function login(values: z.infer<typeof loginFormSchema>) {
|
||||||
return await Sentry.withServerActionInstrumentation("login", {}, async () => {
|
return await Sentry.withServerActionInstrumentation("login", {}, async () => {
|
||||||
const supabase = createServerClient();
|
const supabase = getServerSupabase();
|
||||||
|
const api = new BackendAPI();
|
||||||
|
|
||||||
if (!supabase) {
|
if (!supabase) {
|
||||||
redirect("/error");
|
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
|
// We are sure that the values are of the correct type because zod validates the form
|
||||||
const { data, error } = await supabase.auth.signInWithPassword(values);
|
const { data, error } = await supabase.auth.signInWithPassword(values);
|
||||||
|
|
||||||
|
await api.createUser();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.log("Error logging in", error);
|
console.log("Error logging in", error);
|
||||||
if (error.status == 400) {
|
if (error.status == 400) {
|
||||||
|
@ -70,7 +74,7 @@ export async function signup(values: z.infer<typeof loginFormSchema>) {
|
||||||
"signup",
|
"signup",
|
||||||
{},
|
{},
|
||||||
async () => {
|
async () => {
|
||||||
const supabase = createServerClient();
|
const supabase = getServerSupabase();
|
||||||
|
|
||||||
if (!supabase) {
|
if (!supabase) {
|
||||||
redirect("/error");
|
redirect("/error");
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
"use client";
|
"use client";
|
||||||
import useUser from "@/hooks/useUser";
|
|
||||||
import { login, signup } from "./actions";
|
import { login, signup } from "./actions";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
|
@ -18,10 +17,12 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { PasswordInput } from "@/components/PasswordInput";
|
import { PasswordInput } from "@/components/PasswordInput";
|
||||||
import { FaGoogle, FaGithub, FaDiscord, FaSpinner } from "react-icons/fa";
|
import { FaGoogle, FaGithub, FaDiscord, FaSpinner } from "react-icons/fa";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useSupabase } from "@/components/providers/SupabaseProvider";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
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({
|
const loginFormSchema = z.object({
|
||||||
email: z.string().email().min(2).max(64),
|
email: z.string().email().min(2).max(64),
|
||||||
|
@ -32,11 +33,11 @@ const loginFormSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const { supabase, isLoading: isSupabaseLoading } = useSupabase();
|
const { supabase, user, isUserLoading } = useSupabase();
|
||||||
const { user, isLoading: isUserLoading } = useUser();
|
|
||||||
const [feedback, setFeedback] = useState<string | null>(null);
|
const [feedback, setFeedback] = useState<string | null>(null);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const api = useBackendAPI();
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof loginFormSchema>>({
|
const form = useForm<z.infer<typeof loginFormSchema>>({
|
||||||
resolver: zodResolver(loginFormSchema),
|
resolver: zodResolver(loginFormSchema),
|
||||||
|
@ -48,16 +49,12 @@ export default function LoginPage() {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
console.log("User exists, redirecting to home");
|
console.debug("User exists, redirecting to /");
|
||||||
router.push("/");
|
router.push("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUserLoading || isSupabaseLoading || user) {
|
if (isUserLoading || user) {
|
||||||
return (
|
return <Spinner />;
|
||||||
<div className="flex h-[80vh] items-center justify-center">
|
|
||||||
<FaSpinner className="mr-2 h-16 w-16 animate-spin" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!supabase) {
|
if (!supabase) {
|
||||||
|
@ -80,6 +77,8 @@ export default function LoginPage() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await api.createUser();
|
||||||
|
|
||||||
if (!error) {
|
if (!error) {
|
||||||
setFeedback(null);
|
setFeedback(null);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
|
||||||
import AutoGPTServerAPI, {
|
import { GraphExecution, Schedule, GraphMeta } from "@/lib/autogpt-server-api";
|
||||||
GraphExecution,
|
|
||||||
Schedule,
|
|
||||||
GraphMeta,
|
|
||||||
} from "@/lib/autogpt-server-api";
|
|
||||||
|
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import {
|
import {
|
||||||
|
@ -16,6 +12,7 @@ import {
|
||||||
FlowRunsStats,
|
FlowRunsStats,
|
||||||
} from "@/components/monitor";
|
} from "@/components/monitor";
|
||||||
import { SchedulesTable } from "@/components/monitor/scheduleTable";
|
import { SchedulesTable } from "@/components/monitor/scheduleTable";
|
||||||
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
|
|
||||||
const Monitor = () => {
|
const Monitor = () => {
|
||||||
const [flows, setFlows] = useState<GraphMeta[]>([]);
|
const [flows, setFlows] = useState<GraphMeta[]>([]);
|
||||||
|
@ -25,8 +22,7 @@ const Monitor = () => {
|
||||||
const [selectedRun, setSelectedRun] = useState<GraphExecution | null>(null);
|
const [selectedRun, setSelectedRun] = useState<GraphExecution | null>(null);
|
||||||
const [sortColumn, setSortColumn] = useState<keyof Schedule>("id");
|
const [sortColumn, setSortColumn] = useState<keyof Schedule>("id");
|
||||||
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc");
|
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc");
|
||||||
|
const api = useBackendAPI();
|
||||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
|
||||||
|
|
||||||
const fetchSchedules = useCallback(async () => {
|
const fetchSchedules = useCallback(async () => {
|
||||||
setSchedules(await api.listSchedules());
|
setSchedules(await api.listSchedules());
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useSupabase } from "@/components/providers/SupabaseProvider";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import useUser from "@/hooks/useUser";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useCallback, useContext, useMemo, useState } from "react";
|
import { useCallback, useContext, useMemo, useState } from "react";
|
||||||
import { FaSpinner } from "react-icons/fa";
|
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { useToast } from "@/components/ui/use-toast";
|
import { useToast } from "@/components/ui/use-toast";
|
||||||
import { IconKey, IconUser } from "@/components/ui/icons";
|
import { IconKey, IconUser } from "@/components/ui/icons";
|
||||||
|
@ -31,10 +27,11 @@ import {
|
||||||
AlertDialogHeader,
|
AlertDialogHeader,
|
||||||
AlertDialogTitle,
|
AlertDialogTitle,
|
||||||
} from "@/components/ui/alert-dialog";
|
} from "@/components/ui/alert-dialog";
|
||||||
|
import useSupabase from "@/hooks/useSupabase";
|
||||||
|
import Spinner from "@/components/Spinner";
|
||||||
|
|
||||||
export default function PrivatePage() {
|
export default function PrivatePage() {
|
||||||
const { user, isLoading, error } = useUser();
|
const { supabase, user, isUserLoading } = useSupabase();
|
||||||
const { supabase } = useSupabase();
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const providers = useContext(CredentialsProvidersContext);
|
const providers = useContext(CredentialsProvidersContext);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
@ -115,30 +112,28 @@ export default function PrivatePage() {
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isLoading || !providers) {
|
if (isUserLoading) {
|
||||||
return (
|
return <Spinner />;
|
||||||
<div className="flex h-[80vh] items-center justify-center">
|
|
||||||
<FaSpinner className="mr-2 h-16 w-16 animate-spin" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (error || !user || !supabase) {
|
if (!user || !supabase) {
|
||||||
router.push("/login");
|
router.push("/login");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const allCredentials = Object.values(providers).flatMap((provider) =>
|
const allCredentials = providers
|
||||||
[...provider.savedOAuthCredentials, ...provider.savedApiKeys]
|
? Object.values(providers).flatMap((provider) =>
|
||||||
.filter((cred) => !hiddenCredentials.includes(cred.id))
|
[...provider.savedOAuthCredentials, ...provider.savedApiKeys]
|
||||||
.map((credentials) => ({
|
.filter((cred) => !hiddenCredentials.includes(cred.id))
|
||||||
...credentials,
|
.map((credentials) => ({
|
||||||
provider: provider.provider,
|
...credentials,
|
||||||
providerName: provider.providerName,
|
provider: provider.provider,
|
||||||
ProviderIcon: providerIcons[provider.provider],
|
providerName: provider.providerName,
|
||||||
TypeIcon: { oauth2: IconUser, api_key: IconKey }[credentials.type],
|
ProviderIcon: providerIcons[provider.provider],
|
||||||
})),
|
TypeIcon: { oauth2: IconUser, api_key: IconKey }[credentials.type],
|
||||||
);
|
})),
|
||||||
|
)
|
||||||
|
: [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto max-w-3xl md:py-8">
|
<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 { ThemeProviderProps } from "next-themes";
|
||||||
import { BackendAPIProvider } from "@/lib/autogpt-server-api/context";
|
import { BackendAPIProvider } from "@/lib/autogpt-server-api/context";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import SupabaseProvider from "@/components/providers/SupabaseProvider";
|
|
||||||
import CredentialsProvider from "@/components/integrations/credentials-provider";
|
import CredentialsProvider from "@/components/integrations/credentials-provider";
|
||||||
import { User } from "@supabase/supabase-js";
|
|
||||||
import { LaunchDarklyProvider } from "@/components/feature-flag/feature-flag-provider";
|
import { LaunchDarklyProvider } from "@/components/feature-flag/feature-flag-provider";
|
||||||
|
|
||||||
export function Providers({
|
export function Providers({ children, ...props }: ThemeProviderProps) {
|
||||||
children,
|
|
||||||
initialUser,
|
|
||||||
...props
|
|
||||||
}: ThemeProviderProps & { initialUser: User | null }) {
|
|
||||||
return (
|
return (
|
||||||
<NextThemesProvider {...props}>
|
<NextThemesProvider {...props}>
|
||||||
<SupabaseProvider initialUser={initialUser}>
|
<BackendAPIProvider>
|
||||||
<BackendAPIProvider>
|
<CredentialsProvider>
|
||||||
<CredentialsProvider>
|
<LaunchDarklyProvider>
|
||||||
<LaunchDarklyProvider>
|
<TooltipProvider>{children}</TooltipProvider>
|
||||||
<TooltipProvider>{children}</TooltipProvider>
|
</LaunchDarklyProvider>
|
||||||
</LaunchDarklyProvider>
|
</CredentialsProvider>
|
||||||
</CredentialsProvider>
|
</BackendAPIProvider>
|
||||||
</BackendAPIProvider>
|
|
||||||
</SupabaseProvider>
|
|
||||||
</NextThemesProvider>
|
</NextThemesProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
"use client";
|
"use client";
|
||||||
import { useSupabase } from "@/components/providers/SupabaseProvider";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
|
@ -10,7 +9,7 @@ import {
|
||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import useUser from "@/hooks/useUser";
|
import useSupabase from "@/hooks/useSupabase";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
@ -33,8 +32,7 @@ const resetPasswordFormSchema = z
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function ResetPasswordPage() {
|
export default function ResetPasswordPage() {
|
||||||
const { supabase, isLoading: isSupabaseLoading } = useSupabase();
|
const { supabase, user, isUserLoading } = useSupabase();
|
||||||
const { user, isLoading: isUserLoading } = useUser();
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [feedback, setFeedback] = useState<string | null>(null);
|
const [feedback, setFeedback] = useState<string | null>(null);
|
||||||
|
@ -54,7 +52,7 @@ export default function ResetPasswordPage() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isUserLoading || isSupabaseLoading) {
|
if (isUserLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex h-[80vh] items-center justify-center">
|
<div className="flex h-[80vh] items-center justify-center">
|
||||||
<FaSpinner className="mr-2 h-16 w-16 animate-spin" />
|
<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 { AgentTableRowProps } from "@/components/agptui/AgentTableRow";
|
||||||
import { Button } from "@/components/agptui/Button";
|
import { Button } from "@/components/agptui/Button";
|
||||||
import { Separator } from "@/components/ui/separator";
|
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 { StatusType } from "@/components/agptui/Status";
|
||||||
import { PublishAgentPopout } from "@/components/agptui/composite/PublishAgentPopout";
|
import { PublishAgentPopout } from "@/components/agptui/composite/PublishAgentPopout";
|
||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
@ -14,42 +12,12 @@ import {
|
||||||
StoreSubmissionsResponse,
|
StoreSubmissionsResponse,
|
||||||
StoreSubmissionRequest,
|
StoreSubmissionRequest,
|
||||||
} from "@/lib/autogpt-server-api/types";
|
} from "@/lib/autogpt-server-api/types";
|
||||||
|
import useSupabase from "@/hooks/useSupabase";
|
||||||
async function getDashboardData() {
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Page({}: {}) {
|
export default function Page({}: {}) {
|
||||||
|
const { supabase } = useSupabase();
|
||||||
|
const api = useBackendAPI();
|
||||||
const [submissions, setSubmissions] = useState<StoreSubmissionsResponse>();
|
const [submissions, setSubmissions] = useState<StoreSubmissionsResponse>();
|
||||||
const [openPopout, setOpenPopout] = useState<boolean>(false);
|
const [openPopout, setOpenPopout] = useState<boolean>(false);
|
||||||
const [submissionData, setSubmissionData] =
|
const [submissionData, setSubmissionData] =
|
||||||
|
@ -59,15 +27,20 @@ export default function Page({}: {}) {
|
||||||
);
|
);
|
||||||
|
|
||||||
const fetchData = useCallback(async () => {
|
const fetchData = useCallback(async () => {
|
||||||
const { submissions } = await getDashboardData();
|
try {
|
||||||
if (submissions) {
|
const submissions = await api.getStoreSubmissions();
|
||||||
setSubmissions(submissions as StoreSubmissionsResponse);
|
setSubmissions(submissions);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching submissions:", error);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [api, supabase]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!supabase) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
fetchData();
|
fetchData();
|
||||||
}, [fetchData]);
|
}, [supabase]);
|
||||||
|
|
||||||
const onEditSubmission = useCallback((submission: StoreSubmissionRequest) => {
|
const onEditSubmission = useCallback((submission: StoreSubmissionRequest) => {
|
||||||
setSubmissionData(submission);
|
setSubmissionData(submission);
|
||||||
|
@ -77,19 +50,13 @@ export default function Page({}: {}) {
|
||||||
|
|
||||||
const onDeleteSubmission = useCallback(
|
const onDeleteSubmission = useCallback(
|
||||||
(submission_id: string) => {
|
(submission_id: string) => {
|
||||||
const supabase = createClient();
|
|
||||||
if (!supabase) {
|
if (!supabase) {
|
||||||
return;
|
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);
|
api.deleteStoreSubmission(submission_id);
|
||||||
fetchData();
|
fetchData();
|
||||||
},
|
},
|
||||||
[fetchData],
|
[supabase],
|
||||||
);
|
);
|
||||||
|
|
||||||
const onOpenPopout = useCallback(() => {
|
const onOpenPopout = useCallback(() => {
|
||||||
|
|
|
@ -1,28 +1,9 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { ProfileInfoForm } from "@/components/agptui/ProfileInfoForm";
|
import { ProfileInfoForm } from "@/components/agptui/ProfileInfoForm";
|
||||||
import AutoGPTServerAPIServerSide from "@/lib/autogpt-server-api";
|
import BackendAPI from "@/lib/autogpt-server-api";
|
||||||
import { createServerClient } from "@/lib/supabase/server";
|
|
||||||
import { CreatorDetails } from "@/lib/autogpt-server-api/types";
|
import { CreatorDetails } from "@/lib/autogpt-server-api/types";
|
||||||
|
|
||||||
async function getProfileData() {
|
async function getProfileData(api: BackendAPI) {
|
||||||
// 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
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const profile = await api.getStoreProfile("profile");
|
const profile = await api.getStoreProfile("profile");
|
||||||
return {
|
return {
|
||||||
|
@ -37,7 +18,8 @@ async function getProfileData() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page({}: {}) {
|
export default async function Page({}: {}) {
|
||||||
const { profile } = await getProfileData();
|
const api = new BackendAPI();
|
||||||
|
const { profile } = await getProfileData(api);
|
||||||
|
|
||||||
if (!profile) {
|
if (!profile) {
|
||||||
return (
|
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 { BreadCrumbs } from "@/components/agptui/BreadCrumbs";
|
||||||
import { AgentInfo } from "@/components/agptui/AgentInfo";
|
import { AgentInfo } from "@/components/agptui/AgentInfo";
|
||||||
import { AgentImages } from "@/components/agptui/AgentImages";
|
import { AgentImages } from "@/components/agptui/AgentImages";
|
||||||
|
@ -12,7 +12,7 @@ export async function generateMetadata({
|
||||||
}: {
|
}: {
|
||||||
params: { creator: string; slug: string };
|
params: { creator: string; slug: string };
|
||||||
}): Promise<Metadata> {
|
}): Promise<Metadata> {
|
||||||
const api = new AutoGPTServerAPI();
|
const api = new BackendAPI();
|
||||||
const agent = await api.getStoreAgent(params.creator, params.slug);
|
const agent = await api.getStoreAgent(params.creator, params.slug);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -22,7 +22,7 @@ export async function generateMetadata({
|
||||||
}
|
}
|
||||||
|
|
||||||
// export async function generateStaticParams() {
|
// export async function generateStaticParams() {
|
||||||
// const api = new AutoGPTServerAPI();
|
// const api = new BackendAPI();
|
||||||
// const agents = await api.getStoreAgents({ featured: true });
|
// const agents = await api.getStoreAgents({ featured: true });
|
||||||
// return agents.agents.map((agent) => ({
|
// return agents.agents.map((agent) => ({
|
||||||
// creator: agent.creator,
|
// creator: agent.creator,
|
||||||
|
@ -35,7 +35,7 @@ export default async function Page({
|
||||||
}: {
|
}: {
|
||||||
params: { creator: string; slug: string };
|
params: { creator: string; slug: string };
|
||||||
}) {
|
}) {
|
||||||
const api = new AutoGPTServerAPI();
|
const api = new BackendAPI();
|
||||||
const agent = await api.getStoreAgent(params.creator, params.slug);
|
const agent = await api.getStoreAgent(params.creator, params.slug);
|
||||||
const otherAgents = await api.getStoreAgents({ creator: params.creator });
|
const otherAgents = await api.getStoreAgents({ creator: params.creator });
|
||||||
const similarAgents = await api.getStoreAgents({
|
const similarAgents = await api.getStoreAgents({
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import AutoGPTServerAPI from "@/lib/autogpt-server-api";
|
import BackendAPI from "@/lib/autogpt-server-api";
|
||||||
import {
|
import {
|
||||||
CreatorDetails as Creator,
|
CreatorDetails as Creator,
|
||||||
StoreAgent,
|
StoreAgent,
|
||||||
|
@ -14,7 +14,7 @@ export async function generateMetadata({
|
||||||
}: {
|
}: {
|
||||||
params: { creator: string };
|
params: { creator: string };
|
||||||
}): Promise<Metadata> {
|
}): Promise<Metadata> {
|
||||||
const api = new AutoGPTServerAPI();
|
const api = new BackendAPI();
|
||||||
const creator = await api.getStoreCreator(params.creator);
|
const creator = await api.getStoreCreator(params.creator);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -24,7 +24,7 @@ export async function generateMetadata({
|
||||||
}
|
}
|
||||||
|
|
||||||
// export async function generateStaticParams() {
|
// export async function generateStaticParams() {
|
||||||
// const api = new AutoGPTServerAPI();
|
// const api = new BackendAPI();
|
||||||
// const creators = await api.getStoreCreators({ featured: true });
|
// const creators = await api.getStoreCreators({ featured: true });
|
||||||
// return creators.creators.map((creator) => ({
|
// return creators.creators.map((creator) => ({
|
||||||
// creator: creator.username,
|
// creator: creator.username,
|
||||||
|
@ -36,7 +36,7 @@ export default async function Page({
|
||||||
}: {
|
}: {
|
||||||
params: { creator: string };
|
params: { creator: string };
|
||||||
}) {
|
}) {
|
||||||
const api = new AutoGPTServerAPI();
|
const api = new BackendAPI();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const creator = await api.getStoreCreator(params.creator);
|
const creator = await api.getStoreCreator(params.creator);
|
||||||
|
|
|
@ -14,28 +14,18 @@ import {
|
||||||
FeaturedCreator,
|
FeaturedCreator,
|
||||||
} from "@/components/agptui/composite/FeaturedCreators";
|
} from "@/components/agptui/composite/FeaturedCreators";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import AutoGPTServerAPIServerSide from "@/lib/autogpt-server-api/clientServer";
|
|
||||||
import { Metadata } from "next";
|
import { Metadata } from "next";
|
||||||
import { createServerClient } from "@/lib/supabase/server";
|
|
||||||
import {
|
import {
|
||||||
StoreAgentsResponse,
|
StoreAgentsResponse,
|
||||||
CreatorsResponse,
|
CreatorsResponse,
|
||||||
} from "@/lib/autogpt-server-api/types";
|
} from "@/lib/autogpt-server-api/types";
|
||||||
|
import BackendAPI from "@/lib/autogpt-server-api";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
async function getStoreData() {
|
async function getStoreData() {
|
||||||
try {
|
try {
|
||||||
const supabase = createServerClient();
|
const api = new BackendAPI();
|
||||||
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add error handling and default values
|
// Add error handling and default values
|
||||||
let featuredAgents: StoreAgentsResponse = {
|
let featuredAgents: StoreAgentsResponse = {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { AutoGPTServerAPI } from "@/lib/autogpt-server-api/client";
|
|
||||||
import { AgentsSection } from "@/components/agptui/composite/AgentsSection";
|
import { AgentsSection } from "@/components/agptui/composite/AgentsSection";
|
||||||
import { SearchBar } from "@/components/agptui/SearchBar";
|
import { SearchBar } from "@/components/agptui/SearchBar";
|
||||||
import { FeaturedCreators } from "@/components/agptui/composite/FeaturedCreators";
|
import { FeaturedCreators } from "@/components/agptui/composite/FeaturedCreators";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { SearchFilterChips } from "@/components/agptui/SearchFilterChips";
|
import { SearchFilterChips } from "@/components/agptui/SearchFilterChips";
|
||||||
import { SortDropdown } from "@/components/agptui/SortDropdown";
|
import { SortDropdown } from "@/components/agptui/SortDropdown";
|
||||||
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
|
|
||||||
export default function Page({
|
export default function Page({
|
||||||
searchParams,
|
searchParams,
|
||||||
|
@ -34,11 +34,11 @@ function SearchResults({
|
||||||
const [agents, setAgents] = useState<any[]>([]);
|
const [agents, setAgents] = useState<any[]>([]);
|
||||||
const [creators, setCreators] = useState<any[]>([]);
|
const [creators, setCreators] = useState<any[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const api = useBackendAPI();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const api = new AutoGPTServerAPI();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [agentsRes, creatorsRes] = await Promise.all([
|
const [agentsRes, creatorsRes] = await Promise.all([
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { Button } from "@/components/ui/button";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import getServerUser from "@/hooks/getServerUser";
|
import getServerUser from "@/lib/supabase/getServerUser";
|
||||||
import ProfileDropdown from "./ProfileDropdown";
|
import ProfileDropdown from "./ProfileDropdown";
|
||||||
import { IconCircleUser, IconMenu } from "@/components/ui/icons";
|
import { IconCircleUser, IconMenu } from "@/components/ui/icons";
|
||||||
import CreditButton from "@/components/nav/CreditButton";
|
import CreditButton from "@/components/nav/CreditButton";
|
||||||
|
|
|
@ -7,16 +7,14 @@ import {
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import { useSupabase } from "./providers/SupabaseProvider";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import useUser from "@/hooks/useUser";
|
import useSupabase from "@/hooks/useSupabase";
|
||||||
|
|
||||||
const ProfileDropdown = () => {
|
const ProfileDropdown = () => {
|
||||||
const { supabase } = useSupabase();
|
const { supabase, user, isUserLoading } = useSupabase();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { user, role, isLoading } = useUser();
|
|
||||||
|
|
||||||
if (isLoading) {
|
if (isUserLoading) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +35,7 @@ const ProfileDropdown = () => {
|
||||||
<DropdownMenuItem onClick={() => router.push("/profile")}>
|
<DropdownMenuItem onClick={() => router.push("/profile")}>
|
||||||
Profile
|
Profile
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
{role === "admin" && (
|
{user!.role === "admin" && (
|
||||||
<DropdownMenuItem onClick={() => router.push("/admin/dashboard")}>
|
<DropdownMenuItem onClick={() => router.push("/admin/dashboard")}>
|
||||||
Admin Dashboard
|
Admin Dashboard
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// components/RoleBasedAccess.tsx
|
// components/RoleBasedAccess.tsx
|
||||||
|
import useSupabase from "@/hooks/useSupabase";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import useUser from "@/hooks/useUser";
|
|
||||||
|
|
||||||
interface RoleBasedAccessProps {
|
interface RoleBasedAccessProps {
|
||||||
allowedRoles: string[];
|
allowedRoles: string[];
|
||||||
|
@ -11,13 +11,13 @@ const RoleBasedAccess: React.FC<RoleBasedAccessProps> = ({
|
||||||
allowedRoles,
|
allowedRoles,
|
||||||
children,
|
children,
|
||||||
}) => {
|
}) => {
|
||||||
const { role, isLoading } = useUser();
|
const { user, isUserLoading } = useSupabase();
|
||||||
|
|
||||||
if (isLoading) {
|
if (isUserLoading) {
|
||||||
return <div>Loading...</div>;
|
return <div>Loading...</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!role || !allowedRoles.includes(role)) {
|
if (!user!.role || !allowedRoles.includes(user!.role)) {
|
||||||
return null;
|
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 ServerSideMarketplaceAPI from "@/lib/marketplace-api/server-client";
|
||||||
// import { revalidatePath } from "next/cache";
|
// import { revalidatePath } from "next/cache";
|
||||||
// import * as Sentry from "@sentry/nextjs";
|
// import * as Sentry from "@sentry/nextjs";
|
||||||
// import { checkAuth, createServerClient } from "@/lib/supabase/server";
|
|
||||||
// import { redirect } from "next/navigation";
|
// 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(
|
// export async function approveAgent(
|
||||||
// agentId: string,
|
// agentId: string,
|
||||||
|
|
|
@ -14,12 +14,10 @@ import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
import AutoGPTServerAPI, {
|
import { Graph, GraphCreatable } from "@/lib/autogpt-server-api";
|
||||||
Graph,
|
|
||||||
GraphCreatable,
|
|
||||||
} from "@/lib/autogpt-server-api";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { EnterIcon } from "@radix-ui/react-icons";
|
import { EnterIcon } from "@radix-ui/react-icons";
|
||||||
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
|
|
||||||
// Add this custom schema for File type
|
// Add this custom schema for File type
|
||||||
const fileSchema = z.custom<File>((val) => val instanceof File, {
|
const fileSchema = z.custom<File>((val) => val instanceof File, {
|
||||||
|
@ -75,7 +73,7 @@ export const AgentImportForm: React.FC<
|
||||||
React.FormHTMLAttributes<HTMLFormElement>
|
React.FormHTMLAttributes<HTMLFormElement>
|
||||||
> = ({ className, ...props }) => {
|
> = ({ className, ...props }) => {
|
||||||
const [agentObject, setAgentObject] = useState<GraphCreatable | null>(null);
|
const [agentObject, setAgentObject] = useState<GraphCreatable | null>(null);
|
||||||
const api = new AutoGPTServerAPI();
|
const api = useBackendAPI();
|
||||||
|
|
||||||
const form = useForm<z.infer<typeof formSchema>>({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { IconRefresh } from "@/components/ui/icons";
|
import { IconRefresh } from "@/components/ui/icons";
|
||||||
import AutoGPTServerAPI from "@/lib/autogpt-server-api";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
|
|
||||||
interface CreditsCardProps {
|
interface CreditsCardProps {
|
||||||
credits: number;
|
credits: number;
|
||||||
|
@ -15,7 +15,7 @@ interface CreditsCardProps {
|
||||||
|
|
||||||
const CreditsCard = ({ credits }: CreditsCardProps) => {
|
const CreditsCard = ({ credits }: CreditsCardProps) => {
|
||||||
const [currentCredits, setCurrentCredits] = useState(credits);
|
const [currentCredits, setCurrentCredits] = useState(credits);
|
||||||
const api = new AutoGPTServerAPI();
|
const api = useBackendAPI();
|
||||||
|
|
||||||
const onRefresh = async () => {
|
const onRefresh = async () => {
|
||||||
const { credits } = await api.getUserCredit("credits-card");
|
const { credits } = await api.getUserCredit("credits-card");
|
||||||
|
|
|
@ -6,10 +6,10 @@ import { MobileNavBar } from "./MobileNavBar";
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
import CreditsCard from "./CreditsCard";
|
import CreditsCard from "./CreditsCard";
|
||||||
import { ProfileDetails } from "@/lib/autogpt-server-api/types";
|
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 { ThemeToggle } from "./ThemeToggle";
|
||||||
import { NavbarLink } from "./NavbarLink";
|
import { NavbarLink } from "./NavbarLink";
|
||||||
|
import getServerUser from "@/lib/supabase/getServerUser";
|
||||||
|
import BackendAPI from "@/lib/autogpt-server-api";
|
||||||
|
|
||||||
interface NavLink {
|
interface NavLink {
|
||||||
name: string;
|
name: string;
|
||||||
|
@ -17,8 +17,6 @@ interface NavLink {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NavbarProps {
|
interface NavbarProps {
|
||||||
user: User | null;
|
|
||||||
isLoggedIn: boolean;
|
|
||||||
links: NavLink[];
|
links: NavLink[];
|
||||||
menuItemGroups: {
|
menuItemGroups: {
|
||||||
groupName?: string;
|
groupName?: string;
|
||||||
|
@ -31,8 +29,8 @@ interface NavbarProps {
|
||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getProfileData(user: User | null) {
|
async function getProfileData() {
|
||||||
const api = new AutoGPTServerAPIServerSide();
|
const api = new BackendAPI();
|
||||||
const [profile, credits] = await Promise.all([
|
const [profile, credits] = await Promise.all([
|
||||||
api.getStoreProfile("navbar"),
|
api.getStoreProfile("navbar"),
|
||||||
api.getUserCredit("navbar"),
|
api.getUserCredit("navbar"),
|
||||||
|
@ -43,17 +41,14 @@ async function getProfileData(user: User | null) {
|
||||||
credits,
|
credits,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
export const Navbar = async ({
|
|
||||||
user,
|
export const Navbar = async ({ links, menuItemGroups }: NavbarProps) => {
|
||||||
isLoggedIn,
|
const { user } = await getServerUser();
|
||||||
links,
|
const isLoggedIn = user !== null;
|
||||||
menuItemGroups,
|
|
||||||
}: NavbarProps) => {
|
|
||||||
let profile: ProfileDetails | null = null;
|
let profile: ProfileDetails | null = null;
|
||||||
let credits: { credits: number } = { credits: 0 };
|
let credits: { credits: number } = { credits: 0 };
|
||||||
if (isLoggedIn) {
|
if (isLoggedIn) {
|
||||||
const { profile: t_profile, credits: t_credits } =
|
const { profile: t_profile, credits: t_credits } = await getProfileData();
|
||||||
await getProfileData(user);
|
|
||||||
profile = t_profile;
|
profile = t_profile;
|
||||||
credits = t_credits;
|
credits = t_credits;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,25 +6,17 @@ import { useState } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
import { Button } from "./Button";
|
import { Button } from "./Button";
|
||||||
|
|
||||||
import { IconPersonFill } from "@/components/ui/icons";
|
import { IconPersonFill } from "@/components/ui/icons";
|
||||||
|
|
||||||
import { AutoGPTServerAPI } from "@/lib/autogpt-server-api/client";
|
|
||||||
import { CreatorDetails, ProfileDetails } from "@/lib/autogpt-server-api/types";
|
import { CreatorDetails, ProfileDetails } from "@/lib/autogpt-server-api/types";
|
||||||
import { createClient } from "@/lib/supabase/client";
|
|
||||||
import { Separator } from "@/components/ui/separator";
|
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 }) => {
|
export const ProfileInfoForm = ({ profile }: { profile: CreatorDetails }) => {
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
const [profileData, setProfileData] = useState(profile);
|
const [profileData, setProfileData] = useState(profile);
|
||||||
|
const { supabase } = useSupabase();
|
||||||
const supabase = createClient();
|
const api = useBackendAPI();
|
||||||
|
|
||||||
const api = new AutoGPTServerAPI(
|
|
||||||
process.env.NEXT_PUBLIC_AGPT_SERVER_URL,
|
|
||||||
process.env.NEXT_PUBLIC_AGPT_WS_SERVER_URL,
|
|
||||||
supabase,
|
|
||||||
);
|
|
||||||
|
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -4,8 +4,7 @@ import * as React from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Button } from "../agptui/Button";
|
import { Button } from "../agptui/Button";
|
||||||
import { IconClose, IconPlus } from "../ui/icons";
|
import { IconClose, IconPlus } from "../ui/icons";
|
||||||
import AutoGPTServerAPI from "@/lib/autogpt-server-api";
|
import BackendAPI from "@/lib/autogpt-server-api";
|
||||||
import { createClient } from "@/lib/supabase/client";
|
|
||||||
|
|
||||||
interface PublishAgentInfoProps {
|
interface PublishAgentInfoProps {
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
|
@ -96,11 +95,7 @@ export const PublishAgentInfo: React.FC<PublishAgentInfoProps> = ({
|
||||||
if (!file) return;
|
if (!file) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const api = new AutoGPTServerAPI(
|
const api = new BackendAPI();
|
||||||
process.env.NEXT_PUBLIC_AGPT_SERVER_URL,
|
|
||||||
process.env.NEXT_PUBLIC_AGPT_WS_SERVER_URL,
|
|
||||||
createClient(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const imageUrl = (await api.uploadStoreSubmissionMedia(file)).replace(
|
const imageUrl = (await api.uploadStoreSubmissionMedia(file)).replace(
|
||||||
/^"(.*)"$/,
|
/^"(.*)"$/,
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Cross1Icon } from "@radix-ui/react-icons";
|
import { Cross1Icon } from "@radix-ui/react-icons";
|
||||||
import { IconStar, IconStarFilled } from "@/components/ui/icons";
|
import { IconStar, IconStarFilled } from "@/components/ui/icons";
|
||||||
import { createClient } from "@/lib/supabase/client";
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
import { AutoGPTServerAPI } from "@/lib/autogpt-server-api/client";
|
|
||||||
|
|
||||||
interface RatingCardProps {
|
interface RatingCardProps {
|
||||||
agentName: string;
|
agentName: string;
|
||||||
|
@ -18,18 +17,7 @@ export const RatingCard: React.FC<RatingCardProps> = ({
|
||||||
const [rating, setRating] = React.useState<number>(0);
|
const [rating, setRating] = React.useState<number>(0);
|
||||||
const [hoveredRating, setHoveredRating] = React.useState<number>(0);
|
const [hoveredRating, setHoveredRating] = React.useState<number>(0);
|
||||||
const [isVisible, setIsVisible] = React.useState(true);
|
const [isVisible, setIsVisible] = React.useState(true);
|
||||||
|
const api = useBackendAPI();
|
||||||
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 handleClose = () => {
|
const handleClose = () => {
|
||||||
setIsVisible(false);
|
setIsVisible(false);
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import useSupabase from "@/hooks/useSupabase";
|
||||||
import { createClient } from "@/lib/supabase/client";
|
|
||||||
|
|
||||||
interface SettingsInputFormProps {
|
interface SettingsInputFormProps {
|
||||||
email?: string;
|
email?: string;
|
||||||
|
@ -20,6 +19,7 @@ export const SettingsInputForm = ({
|
||||||
const [password, setPassword] = React.useState("");
|
const [password, setPassword] = React.useState("");
|
||||||
const [confirmPassword, setConfirmPassword] = React.useState("");
|
const [confirmPassword, setConfirmPassword] = React.useState("");
|
||||||
const [passwordsMatch, setPasswordsMatch] = React.useState(true);
|
const [passwordsMatch, setPasswordsMatch] = React.useState(true);
|
||||||
|
const { supabase } = useSupabase();
|
||||||
|
|
||||||
const handleSaveChanges = async () => {
|
const handleSaveChanges = async () => {
|
||||||
if (password !== confirmPassword) {
|
if (password !== confirmPassword) {
|
||||||
|
@ -27,10 +27,9 @@ export const SettingsInputForm = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setPasswordsMatch(true);
|
setPasswordsMatch(true);
|
||||||
const client = createClient();
|
if (supabase) {
|
||||||
if (client) {
|
|
||||||
try {
|
try {
|
||||||
const { error } = await client.auth.updateUser({
|
const { error } = await supabase.auth.updateUser({
|
||||||
password: password,
|
password: password,
|
||||||
});
|
});
|
||||||
if (error) {
|
if (error) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverAnchor,
|
PopoverAnchor,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import { PublishAgentSelect, Agent } from "../PublishAgentSelect";
|
import { PublishAgentSelect } from "../PublishAgentSelect";
|
||||||
import { PublishAgentInfo } from "../PublishAgentSelectInfo";
|
import { PublishAgentInfo } from "../PublishAgentSelectInfo";
|
||||||
import { PublishAgentAwaitingReview } from "../PublishAgentAwaitingReview";
|
import { PublishAgentAwaitingReview } from "../PublishAgentAwaitingReview";
|
||||||
import { Button } from "../Button";
|
import { Button } from "../Button";
|
||||||
|
@ -15,9 +15,8 @@ import {
|
||||||
StoreSubmissionRequest,
|
StoreSubmissionRequest,
|
||||||
MyAgentsResponse,
|
MyAgentsResponse,
|
||||||
} from "@/lib/autogpt-server-api";
|
} 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 { useRouter } from "next/navigation";
|
||||||
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
interface PublishAgentPopoutProps {
|
interface PublishAgentPopoutProps {
|
||||||
trigger?: React.ReactNode;
|
trigger?: React.ReactNode;
|
||||||
openPopout?: boolean;
|
openPopout?: boolean;
|
||||||
|
@ -57,18 +56,7 @@ export const PublishAgentPopout: React.FC<PublishAgentPopoutProps> = ({
|
||||||
|
|
||||||
const popupId = React.useId();
|
const popupId = React.useId();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const api = useBackendAPI();
|
||||||
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],
|
|
||||||
);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
console.log("PublishAgentPopout Effect");
|
console.log("PublishAgentPopout Effect");
|
||||||
|
|
|
@ -6,10 +6,9 @@ import { Button } from "@/components/ui/button";
|
||||||
import SchemaTooltip from "@/components/SchemaTooltip";
|
import SchemaTooltip from "@/components/SchemaTooltip";
|
||||||
import useCredentials from "@/hooks/useCredentials";
|
import useCredentials from "@/hooks/useCredentials";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import AutoGPTServerAPI from "@/lib/autogpt-server-api";
|
|
||||||
import { NotionLogoIcon } from "@radix-ui/react-icons";
|
import { NotionLogoIcon } from "@radix-ui/react-icons";
|
||||||
import { FaDiscord, FaGithub, FaGoogle, FaMedium, FaKey } from "react-icons/fa";
|
import { FaDiscord, FaGithub, FaGoogle, FaMedium, FaKey } from "react-icons/fa";
|
||||||
import { FC, useMemo, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import {
|
import {
|
||||||
CredentialsMetaInput,
|
CredentialsMetaInput,
|
||||||
CredentialsProviderName,
|
CredentialsProviderName,
|
||||||
|
@ -39,6 +38,7 @@ import {
|
||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
|
|
||||||
const fallbackIcon = FaKey;
|
const fallbackIcon = FaKey;
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ export const CredentialsInput: FC<{
|
||||||
selectedCredentials?: CredentialsMetaInput;
|
selectedCredentials?: CredentialsMetaInput;
|
||||||
onSelectCredentials: (newValue?: CredentialsMetaInput) => void;
|
onSelectCredentials: (newValue?: CredentialsMetaInput) => void;
|
||||||
}> = ({ className, selectedCredentials, onSelectCredentials }) => {
|
}> = ({ className, selectedCredentials, onSelectCredentials }) => {
|
||||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
const api = useBackendAPI();
|
||||||
const credentials = useCredentials();
|
const credentials = useCredentials();
|
||||||
const [isAPICredentialsModalOpen, setAPICredentialsModalOpen] =
|
const [isAPICredentialsModalOpen, setAPICredentialsModalOpen] =
|
||||||
useState(false);
|
useState(false);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import AutoGPTServerAPI, {
|
import {
|
||||||
APIKeyCredentials,
|
APIKeyCredentials,
|
||||||
CredentialsDeleteNeedConfirmationResponse,
|
CredentialsDeleteNeedConfirmationResponse,
|
||||||
CredentialsDeleteResponse,
|
CredentialsDeleteResponse,
|
||||||
|
@ -6,13 +6,8 @@ import AutoGPTServerAPI, {
|
||||||
CredentialsProviderName,
|
CredentialsProviderName,
|
||||||
PROVIDER_NAMES,
|
PROVIDER_NAMES,
|
||||||
} from "@/lib/autogpt-server-api";
|
} from "@/lib/autogpt-server-api";
|
||||||
import {
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
createContext,
|
import { createContext, useCallback, useEffect, useState } from "react";
|
||||||
useCallback,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from "react";
|
|
||||||
|
|
||||||
// Get keys from CredentialsProviderName type
|
// Get keys from CredentialsProviderName type
|
||||||
const CREDENTIALS_PROVIDER_NAMES = Object.values(
|
const CREDENTIALS_PROVIDER_NAMES = Object.values(
|
||||||
|
@ -87,7 +82,7 @@ export default function CredentialsProvider({
|
||||||
}) {
|
}) {
|
||||||
const [providers, setProviders] =
|
const [providers, setProviders] =
|
||||||
useState<CredentialsProvidersContextType | null>(null);
|
useState<CredentialsProvidersContextType | null>(null);
|
||||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
const api = useBackendAPI();
|
||||||
|
|
||||||
const addCredentials = useCallback(
|
const addCredentials = useCallback(
|
||||||
(
|
(
|
||||||
|
@ -120,7 +115,7 @@ export default function CredentialsProvider({
|
||||||
[setProviders],
|
[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(
|
const oAuthCallback = useCallback(
|
||||||
async (
|
async (
|
||||||
provider: CredentialsProviderName,
|
provider: CredentialsProviderName,
|
||||||
|
@ -134,7 +129,7 @@ export default function CredentialsProvider({
|
||||||
[api, addCredentials],
|
[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(
|
const createAPIKeyCredentials = useCallback(
|
||||||
async (
|
async (
|
||||||
provider: CredentialsProviderName,
|
provider: CredentialsProviderName,
|
||||||
|
@ -150,7 +145,7 @@ export default function CredentialsProvider({
|
||||||
[api, addCredentials],
|
[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(
|
const deleteCredentials = useCallback(
|
||||||
async (
|
async (
|
||||||
provider: CredentialsProviderName,
|
provider: CredentialsProviderName,
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
// import Link from "next/link";
|
// import Link from "next/link";
|
||||||
// import { ArrowLeft, Download, Calendar, Tag } from "lucide-react";
|
// import { ArrowLeft, Download, Calendar, Tag } from "lucide-react";
|
||||||
// import { Button } from "@/components/ui/button";
|
// 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 "@xyflow/react/dist/style.css";
|
||||||
// import { useToast } from "../ui/use-toast";
|
// import { useToast } from "../ui/use-toast";
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import AutoGPTServerAPI, {
|
import BackendAPI, {
|
||||||
GraphExecution,
|
GraphExecution,
|
||||||
GraphMeta,
|
GraphMeta,
|
||||||
} from "@/lib/autogpt-server-api";
|
} from "@/lib/autogpt-server-api";
|
||||||
|
@ -45,8 +45,6 @@ export const AgentFlowList = ({
|
||||||
onSelectFlow: (f: GraphMeta) => void;
|
onSelectFlow: (f: GraphMeta) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={className}>
|
<Card className={className}>
|
||||||
<CardHeader className="flex-row items-center justify-between space-x-3 space-y-0">
|
<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 React, { useEffect, useState, useCallback } from "react";
|
||||||
import AutoGPTServerAPI, {
|
import {
|
||||||
GraphExecution,
|
GraphExecution,
|
||||||
Graph,
|
Graph,
|
||||||
GraphMeta,
|
GraphMeta,
|
||||||
|
@ -22,7 +22,7 @@ import { ClockIcon, ExitIcon, Pencil2Icon } from "@radix-ui/react-icons";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { exportAsJSONFile, filterBlocksByType } from "@/lib/utils";
|
import { exportAsJSONFile, filterBlocksByType } from "@/lib/utils";
|
||||||
import { FlowRunsStats } from "@/components/monitor/index";
|
import { FlowRunsStats } from "@/components/monitor/index";
|
||||||
import { Trash2Icon, Timer } from "lucide-react";
|
import { Trash2Icon } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
|
@ -35,6 +35,7 @@ import { useToast } from "@/components/ui/use-toast";
|
||||||
import { CronScheduler } from "@/components/cronScheduler";
|
import { CronScheduler } from "@/components/cronScheduler";
|
||||||
import RunnerInputUI from "@/components/runner-ui/RunnerInputUI";
|
import RunnerInputUI from "@/components/runner-ui/RunnerInputUI";
|
||||||
import useAgentGraph from "@/hooks/useAgentGraph";
|
import useAgentGraph from "@/hooks/useAgentGraph";
|
||||||
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
|
|
||||||
export const FlowInfo: React.FC<
|
export const FlowInfo: React.FC<
|
||||||
React.HTMLAttributes<HTMLDivElement> & {
|
React.HTMLAttributes<HTMLDivElement> & {
|
||||||
|
@ -66,7 +67,7 @@ export const FlowInfo: React.FC<
|
||||||
setEdges,
|
setEdges,
|
||||||
} = useAgentGraph(flow.id, false);
|
} = useAgentGraph(flow.id, false);
|
||||||
|
|
||||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
const api = useBackendAPI();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const [flowVersions, setFlowVersions] = useState<Graph[] | null>(null);
|
const [flowVersions, setFlowVersions] = useState<Graph[] | null>(null);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import AutoGPTServerAPI, {
|
import {
|
||||||
GraphExecution,
|
GraphExecution,
|
||||||
GraphMeta,
|
GraphMeta,
|
||||||
NodeExecutionResult,
|
NodeExecutionResult,
|
||||||
|
@ -13,6 +13,7 @@ import { ExitIcon, Pencil2Icon } from "@radix-ui/react-icons";
|
||||||
import moment from "moment/moment";
|
import moment from "moment/moment";
|
||||||
import { FlowRunStatusBadge } from "@/components/monitor/FlowRunStatusBadge";
|
import { FlowRunStatusBadge } from "@/components/monitor/FlowRunStatusBadge";
|
||||||
import RunnerOutputUI, { BlockOutput } from "../runner-ui/RunnerOutputUI";
|
import RunnerOutputUI, { BlockOutput } from "../runner-ui/RunnerOutputUI";
|
||||||
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
|
|
||||||
export const FlowRunInfo: React.FC<
|
export const FlowRunInfo: React.FC<
|
||||||
React.HTMLAttributes<HTMLDivElement> & {
|
React.HTMLAttributes<HTMLDivElement> & {
|
||||||
|
@ -22,7 +23,7 @@ export const FlowRunInfo: React.FC<
|
||||||
> = ({ flow, execution, ...props }) => {
|
> = ({ flow, execution, ...props }) => {
|
||||||
const [isOutputOpen, setIsOutputOpen] = useState(false);
|
const [isOutputOpen, setIsOutputOpen] = useState(false);
|
||||||
const [blockOutputs, setBlockOutputs] = useState<BlockOutput[]>([]);
|
const [blockOutputs, setBlockOutputs] = useState<BlockOutput[]>([]);
|
||||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
const api = useBackendAPI();
|
||||||
|
|
||||||
const fetchBlockResults = useCallback(async () => {
|
const fetchBlockResults = useCallback(async () => {
|
||||||
const executionResults = await api.getGraphExecutionInfo(
|
const executionResults = await api.getGraphExecutionInfo(
|
||||||
|
@ -119,7 +120,7 @@ export const FlowRunInfo: React.FC<
|
||||||
<strong>Agent ID:</strong> <code>{flow.id}</code>
|
<strong>Agent ID:</strong> <code>{flow.id}</code>
|
||||||
</p>
|
</p>
|
||||||
<p className="hidden">
|
<p className="hidden">
|
||||||
<strong>Run ID:</strong> <code>{flowRun.id}</code>
|
<strong>Run ID:</strong> <code>{execution.execution_id}</code>
|
||||||
</p>
|
</p>
|
||||||
<div>
|
<div>
|
||||||
<strong>Status:</strong>{" "}
|
<strong>Status:</strong>{" "}
|
||||||
|
|
|
@ -3,12 +3,11 @@
|
||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { IconRefresh } from "@/components/ui/icons";
|
import { IconRefresh } from "@/components/ui/icons";
|
||||||
import AutoGPTServerAPI from "@/lib/autogpt-server-api";
|
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
|
||||||
|
|
||||||
const api = new AutoGPTServerAPI();
|
|
||||||
|
|
||||||
export default function CreditButton() {
|
export default function CreditButton() {
|
||||||
const [credit, setCredit] = useState<number | null>(null);
|
const [credit, setCredit] = useState<number | null>(null);
|
||||||
|
const api = useBackendAPI();
|
||||||
|
|
||||||
const fetchCredit = useCallback(async () => {
|
const fetchCredit = useCallback(async () => {
|
||||||
try {
|
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 { CustomEdge } from "@/components/CustomEdge";
|
||||||
import { CustomNode } from "@/components/CustomNode";
|
import { CustomNode } from "@/components/CustomNode";
|
||||||
import AutoGPTServerAPI, {
|
import BackendAPI, {
|
||||||
Block,
|
Block,
|
||||||
BlockIOSubSchema,
|
BlockIOSubSchema,
|
||||||
BlockUIType,
|
BlockUIType,
|
||||||
|
@ -74,7 +74,7 @@ export default function useAgentGraph(
|
||||||
const [edges, setEdges] = useState<CustomEdge[]>([]);
|
const [edges, setEdges] = useState<CustomEdge[]>([]);
|
||||||
|
|
||||||
const api = useMemo(
|
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 { SupabaseClient } from "@supabase/supabase-js";
|
||||||
import { createClient } from "../supabase/client";
|
import {
|
||||||
import BaseAutoGPTServerAPI from "./baseClient";
|
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(
|
constructor(
|
||||||
baseUrl: string = process.env.NEXT_PUBLIC_AGPT_SERVER_URL ||
|
baseUrl: string = process.env.NEXT_PUBLIC_AGPT_SERVER_URL ||
|
||||||
"http://localhost:8006/api",
|
"http://localhost:8006/api",
|
||||||
wsUrl: string = process.env.NEXT_PUBLIC_AGPT_WS_SERVER_URL ||
|
wsUrl: string = process.env.NEXT_PUBLIC_AGPT_WS_SERVER_URL ||
|
||||||
"ws://localhost:8001/ws",
|
"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";
|
import React, { createContext, useMemo } from "react";
|
||||||
|
|
||||||
const BackendAPIProviderContext = createContext<AutoGPTServerAPI | null>(null);
|
const BackendAPIProviderContext = createContext<BackendAPI | null>(null);
|
||||||
|
|
||||||
export function BackendAPIProvider({
|
export function BackendAPIProvider({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}): React.ReactNode {
|
}): React.ReactNode {
|
||||||
const api = useMemo(() => new AutoGPTServerAPI(), []);
|
const api = useMemo(() => new BackendAPI(), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BackendAPIProviderContext.Provider value={api}>
|
<BackendAPIProviderContext.Provider value={api}>
|
||||||
|
@ -17,7 +17,7 @@ export function BackendAPIProvider({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useBackendAPI(): AutoGPTServerAPI {
|
export function useBackendAPI(): BackendAPI {
|
||||||
const context = React.useContext(BackendAPIProviderContext);
|
const context = React.useContext(BackendAPIProviderContext);
|
||||||
if (!context) {
|
if (!context) {
|
||||||
throw new Error(
|
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 "./client";
|
||||||
export * from "./types";
|
export * from "./types";
|
||||||
export * from "./utils";
|
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 {
|
import { createServerClient } from "@supabase/ssr";
|
||||||
createServerClient as createClient,
|
|
||||||
type CookieOptions,
|
|
||||||
} from "@supabase/ssr";
|
|
||||||
import { cookies } from "next/headers";
|
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
|
|
||||||
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();
|
const cookieStore = cookies();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return createClient(
|
const supabase = createServerClient(
|
||||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||||
{
|
{
|
||||||
|
@ -31,19 +28,9 @@ export function createServerClient() {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return supabase;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw 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 getServerUser = async () => {
|
||||||
const supabase = createServerClient();
|
const supabase = getServerSupabase();
|
||||||
|
|
||||||
if (!supabase) {
|
if (!supabase) {
|
||||||
console.log(">>> failed to create supabase client");
|
|
||||||
return { user: null, error: "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 React from "react";
|
||||||
import * as Sentry from "@sentry/nextjs";
|
import * as Sentry from "@sentry/nextjs";
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue