From c7fdfa0f777dd28a600db4b7f4b20e27f5746525 Mon Sep 17 00:00:00 2001 From: Swifty Date: Mon, 5 Aug 2024 09:18:08 +0200 Subject: [PATCH] fix(builder): Apply Prettier Formatting (#7695) formatting --- rnd/autogpt_builder/README.md | 2 +- rnd/autogpt_builder/components.json | 2 +- rnd/autogpt_builder/next.config.mjs | 26 +- .../src/app/auth/auth-code-error/page.tsx | 14 +- .../src/app/auth/callback/route.ts | 28 +- .../src/app/auth/confirm/route.ts | 26 +- rnd/autogpt_builder/src/app/error/page.tsx | 2 +- rnd/autogpt_builder/src/app/globals.css | 3 - rnd/autogpt_builder/src/app/layout.tsx | 47 +- rnd/autogpt_builder/src/app/login/actions.ts | 36 +- rnd/autogpt_builder/src/app/login/page.tsx | 138 ++- rnd/autogpt_builder/src/app/monitor/page.tsx | 939 +++++++++++------- rnd/autogpt_builder/src/app/profile/page.tsx | 22 +- rnd/autogpt_builder/src/app/providers.tsx | 14 +- .../src/components/ConnectionLine.tsx | 28 +- .../src/components/CustomEdge.tsx | 71 +- .../src/components/CustomNode.tsx | 240 +++-- rnd/autogpt_builder/src/components/Flow.tsx | 765 ++++++++------ .../src/components/InputModalComponent.tsx | 17 +- rnd/autogpt_builder/src/components/NavBar.tsx | 18 +- .../src/components/NodeHandle.tsx | 56 +- .../src/components/NodeInputField.tsx | 233 +++-- .../src/components/OutputModalComponent.tsx | 20 +- .../src/components/PasswordInput.tsx | 89 +- .../src/components/ProfileDropdown.tsx | 17 +- .../src/components/SchemaTooltip.tsx | 22 +- .../src/components/SupabaseProvider.tsx | 14 +- .../src/components/agent-import-form.tsx | 94 +- .../src/components/customedge.css | 6 +- .../src/components/edit/ControlPanel.tsx | 84 +- .../components/edit/control/BlocksControl.tsx | 127 +-- .../components/edit/control/ControlPanel.tsx | 84 +- .../components/edit/control/SaveControl.tsx | 165 +-- rnd/autogpt_builder/src/components/flow.css | 12 +- rnd/autogpt_builder/src/components/history.ts | 111 ++- .../src/components/ui/avatar.tsx | 26 +- .../src/components/ui/badge.tsx | 14 +- .../src/components/ui/button.tsx | 29 +- .../src/components/ui/calendar.tsx | 29 +- .../src/components/ui/card.tsx | 39 +- .../src/components/ui/collapsible.tsx | 12 +- .../src/components/ui/dialog.tsx | 54 +- .../src/components/ui/dropdown-menu.tsx | 87 +- .../src/components/ui/form.tsx | 113 ++- .../src/components/ui/input.tsx | 18 +- .../src/components/ui/label.tsx | 20 +- .../src/components/ui/popover.tsx | 22 +- .../src/components/ui/scroll-area.tsx | 20 +- .../src/components/ui/separator.tsx | 20 +- .../src/components/ui/switch.tsx | 18 +- .../src/components/ui/table.tsx | 51 +- .../src/components/ui/textarea.tsx | 16 +- .../src/components/ui/tooltip.tsx | 20 +- .../src/hooks/getServerUser.ts | 2 +- rnd/autogpt_builder/src/hooks/useUser.ts | 20 +- .../src/lib/autogpt-server-api/client.ts | 128 ++- .../src/lib/autogpt-server-api/types.ts | 93 +- .../src/lib/autogpt-server-api/utils.ts | 12 +- .../src/lib/supabase/client.ts | 9 +- .../src/lib/supabase/middleware.ts | 44 +- .../src/lib/supabase/server.ts | 22 +- rnd/autogpt_builder/src/lib/utils.ts | 148 +-- rnd/autogpt_builder/src/middleware.ts | 10 +- rnd/autogpt_builder/tailwind.config.ts | 76 +- rnd/autogpt_builder/webpack.config.js | 8 +- 65 files changed, 2722 insertions(+), 2030 deletions(-) diff --git a/rnd/autogpt_builder/README.md b/rnd/autogpt_builder/README.md index 1c61952b1..c7fe91a26 100644 --- a/rnd/autogpt_builder/README.md +++ b/rnd/autogpt_builder/README.md @@ -36,6 +36,6 @@ If the project is updated via git, you will need to `npm install` after each upd This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. -## Deploy +## Deploy TODO diff --git a/rnd/autogpt_builder/components.json b/rnd/autogpt_builder/components.json index cb30231b8..1d586e3d7 100644 --- a/rnd/autogpt_builder/components.json +++ b/rnd/autogpt_builder/components.json @@ -14,4 +14,4 @@ "components": "@/components", "utils": "@/lib/utils" } -} \ No newline at end of file +} diff --git a/rnd/autogpt_builder/next.config.mjs b/rnd/autogpt_builder/next.config.mjs index 977e8dfa0..5c47b636d 100644 --- a/rnd/autogpt_builder/next.config.mjs +++ b/rnd/autogpt_builder/next.config.mjs @@ -1,22 +1,22 @@ -import dotenv from 'dotenv'; +import dotenv from "dotenv"; // Load environment variables dotenv.config(); /** @type {import('next').NextConfig} */ const nextConfig = { - env: { - AGPT_SERVER_URL: process.env.AGPT_SERVER_URL, - }, - async redirects() { - return [ - { - source: '/', - destination: '/build', - permanent: false, - }, - ]; - }, + env: { + AGPT_SERVER_URL: process.env.AGPT_SERVER_URL, + }, + async redirects() { + return [ + { + source: "/", + destination: "/build", + permanent: false, + }, + ]; + }, }; export default nextConfig; diff --git a/rnd/autogpt_builder/src/app/auth/auth-code-error/page.tsx b/rnd/autogpt_builder/src/app/auth/auth-code-error/page.tsx index 97ceec546..797cad062 100644 --- a/rnd/autogpt_builder/src/app/auth/auth-code-error/page.tsx +++ b/rnd/autogpt_builder/src/app/auth/auth-code-error/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from 'react'; +import { useEffect, useState } from "react"; export default function AuthErrorPage() { const [errorType, setErrorType] = useState(null); @@ -9,13 +9,15 @@ export default function AuthErrorPage() { useEffect(() => { // This code only runs on the client side - if (typeof window !== 'undefined') { + if (typeof window !== "undefined") { const hash = window.location.hash.substring(1); // Remove the leading '#' const params = new URLSearchParams(hash); - - setErrorType(params.get('error')); - setErrorCode(params.get('error_code')); - setErrorDescription(params.get('error_description')?.replace(/\+/g, ' ') ?? null); // Replace '+' with space + + setErrorType(params.get("error")); + setErrorCode(params.get("error_code")); + setErrorDescription( + params.get("error_description")?.replace(/\+/g, " ") ?? null, + ); // Replace '+' with space } }, []); diff --git a/rnd/autogpt_builder/src/app/auth/callback/route.ts b/rnd/autogpt_builder/src/app/auth/callback/route.ts index d26b7a524..c81341523 100644 --- a/rnd/autogpt_builder/src/app/auth/callback/route.ts +++ b/rnd/autogpt_builder/src/app/auth/callback/route.ts @@ -1,36 +1,36 @@ -import { NextResponse } from 'next/server' -import { createServerClient } from '@/lib/supabase/server' +import { NextResponse } from "next/server"; +import { createServerClient } from "@/lib/supabase/server"; // Handle the callback to complete the user session login export async function GET(request: Request) { - const { searchParams, origin } = new URL(request.url) - const code = searchParams.get('code') + const { searchParams, origin } = new URL(request.url); + const code = searchParams.get("code"); // if "next" is in param, use it as the redirect URL - const next = searchParams.get('next') ?? '/profile' + const next = searchParams.get("next") ?? "/profile"; if (code) { - const supabase = createServerClient() + const supabase = createServerClient(); if (!supabase) { - return NextResponse.redirect(`${origin}/error`) + return NextResponse.redirect(`${origin}/error`); } - const { data, error } = await supabase.auth.exchangeCodeForSession(code) + const { data, error } = await supabase.auth.exchangeCodeForSession(code); // data.session?.refresh_token is available if you need to store it for later use if (!error) { - const forwardedHost = request.headers.get('x-forwarded-host') // original origin before load balancer - const isLocalEnv = process.env.NODE_ENV === 'development' + const forwardedHost = request.headers.get("x-forwarded-host"); // original origin before load balancer + const isLocalEnv = process.env.NODE_ENV === "development"; if (isLocalEnv) { // we can be sure that there is no load balancer in between, so no need to watch for X-Forwarded-Host - return NextResponse.redirect(`${origin}${next}`) + return NextResponse.redirect(`${origin}${next}`); } else if (forwardedHost) { - return NextResponse.redirect(`https://${forwardedHost}${next}`) + return NextResponse.redirect(`https://${forwardedHost}${next}`); } else { - return NextResponse.redirect(`${origin}${next}`) + return NextResponse.redirect(`${origin}${next}`); } } } // return the user to an error page with instructions - return NextResponse.redirect(`${origin}/auth/auth-code-error`) + return NextResponse.redirect(`${origin}/auth/auth-code-error`); } diff --git a/rnd/autogpt_builder/src/app/auth/confirm/route.ts b/rnd/autogpt_builder/src/app/auth/confirm/route.ts index dc341adab..0fe20c311 100644 --- a/rnd/autogpt_builder/src/app/auth/confirm/route.ts +++ b/rnd/autogpt_builder/src/app/auth/confirm/route.ts @@ -1,33 +1,33 @@ -import { type EmailOtpType } from '@supabase/supabase-js' -import { type NextRequest } from 'next/server' +import { type EmailOtpType } from "@supabase/supabase-js"; +import { type NextRequest } from "next/server"; -import { redirect } from 'next/navigation' -import { createServerClient } from '@/lib/supabase/server' +import { redirect } from "next/navigation"; +import { createServerClient } from "@/lib/supabase/server"; // Email confirmation route export async function GET(request: NextRequest) { - const { searchParams } = new URL(request.url) - const token_hash = searchParams.get('token_hash') - const type = searchParams.get('type') as EmailOtpType | null - const next = searchParams.get('next') ?? '/' + const { searchParams } = new URL(request.url); + const token_hash = searchParams.get("token_hash"); + const type = searchParams.get("type") as EmailOtpType | null; + const next = searchParams.get("next") ?? "/"; if (token_hash && type) { - const supabase = createServerClient() + const supabase = createServerClient(); if (!supabase) { - redirect('/error') + redirect("/error"); } const { error } = await supabase.auth.verifyOtp({ type, token_hash, - }) + }); if (!error) { // redirect user to specified redirect URL or root of app - redirect(next) + redirect(next); } } // redirect the user to an error page with some instructions - redirect('/error') + redirect("/error"); } diff --git a/rnd/autogpt_builder/src/app/error/page.tsx b/rnd/autogpt_builder/src/app/error/page.tsx index 752b8bac1..f984f8431 100644 --- a/rnd/autogpt_builder/src/app/error/page.tsx +++ b/rnd/autogpt_builder/src/app/error/page.tsx @@ -1,3 +1,3 @@ export default function ErrorPage() { - return

Sorry, something went wrong

+ return

Sorry, something went wrong

; } diff --git a/rnd/autogpt_builder/src/app/globals.css b/rnd/autogpt_builder/src/app/globals.css index 9df147e69..130a1e630 100644 --- a/rnd/autogpt_builder/src/app/globals.css +++ b/rnd/autogpt_builder/src/app/globals.css @@ -8,8 +8,6 @@ } } - - @layer base { :root { --background: 0 0% 100%; @@ -67,7 +65,6 @@ } } - @layer base { * { @apply border-border; diff --git a/rnd/autogpt_builder/src/app/layout.tsx b/rnd/autogpt_builder/src/app/layout.tsx index 423f8e860..c9483a8e2 100644 --- a/rnd/autogpt_builder/src/app/layout.tsx +++ b/rnd/autogpt_builder/src/app/layout.tsx @@ -1,9 +1,9 @@ -import React from 'react'; +import React from "react"; import type { Metadata } from "next"; import { Inter } from "next/font/google"; import { Providers } from "@/app/providers"; -import {NavBar} from "@/components/NavBar"; -import {cn} from "@/lib/utils"; +import { NavBar } from "@/components/NavBar"; +import { cn } from "@/lib/utils"; import "./globals.css"; @@ -15,33 +15,26 @@ export const metadata: Metadata = { }; export default function RootLayout({ - children, + children, }: Readonly<{ - children: React.ReactNode; + children: React.ReactNode; }>) { - return ( - - + return ( + + -
- -
- {children} -
-
+
+ +
{children}
+
- - - ); + + + ); } diff --git a/rnd/autogpt_builder/src/app/login/actions.ts b/rnd/autogpt_builder/src/app/login/actions.ts index 114da537a..dadd1de91 100644 --- a/rnd/autogpt_builder/src/app/login/actions.ts +++ b/rnd/autogpt_builder/src/app/login/actions.ts @@ -1,54 +1,54 @@ -'use server' -import { revalidatePath } from 'next/cache' -import { redirect } from 'next/navigation' -import { createServerClient } from '@/lib/supabase/server' -import { z } from 'zod' +"use server"; +import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; +import { createServerClient } from "@/lib/supabase/server"; +import { z } from "zod"; const loginFormSchema = z.object({ email: z.string().email().min(2).max(64), password: z.string().min(6).max(64), -}) +}); export async function login(values: z.infer) { - const supabase = createServerClient() + const supabase = createServerClient(); if (!supabase) { - redirect('/error') + redirect("/error"); } // 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); if (error) { - return error.message + return error.message; } if (data.session) { await supabase.auth.setSession(data.session); } - revalidatePath('/', 'layout') - redirect('/profile') + revalidatePath("/", "layout"); + redirect("/profile"); } export async function signup(values: z.infer) { - const supabase = createServerClient() + const supabase = createServerClient(); if (!supabase) { - redirect('/error') + redirect("/error"); } // We are sure that the values are of the correct type because zod validates the form - const { data, error } = await supabase.auth.signUp(values) + const { data, error } = await supabase.auth.signUp(values); if (error) { - return error.message + return error.message; } if (data.session) { await supabase.auth.setSession(data.session); } - revalidatePath('/', 'layout') - redirect('/profile') + revalidatePath("/", "layout"); + redirect("/profile"); } diff --git a/rnd/autogpt_builder/src/app/login/page.tsx b/rnd/autogpt_builder/src/app/login/page.tsx index 9900d3fc4..36b2bf1d1 100644 --- a/rnd/autogpt_builder/src/app/login/page.tsx +++ b/rnd/autogpt_builder/src/app/login/page.tsx @@ -1,22 +1,30 @@ "use client"; -import useUser from '@/hooks/useUser'; -import { login, signup } from './actions' -import { Button } from '@/components/ui/button'; -import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; -import { useForm } from 'react-hook-form'; -import { Input } from '@/components/ui/input'; -import { z } from "zod" -import { zodResolver } from "@hookform/resolvers/zod" -import { PasswordInput } from '@/components/PasswordInput'; +import useUser from "@/hooks/useUser"; +import { login, signup } from "./actions"; +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { useForm } from "react-hook-form"; +import { Input } from "@/components/ui/input"; +import { z } from "zod"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { PasswordInput } from "@/components/PasswordInput"; import { FaGoogle, FaGithub, FaDiscord, FaSpinner } from "react-icons/fa"; -import { useState } from 'react'; -import { useSupabase } from '@/components/SupabaseProvider'; -import { useRouter } from 'next/navigation'; +import { useState } from "react"; +import { useSupabase } from "@/components/SupabaseProvider"; +import { useRouter } from "next/navigation"; const loginFormSchema = z.object({ email: z.string().email().min(2).max(64), password: z.string().min(6).max(64), -}) +}); export default function LoginPage() { const { supabase, isLoading: isSupabaseLoading } = useSupabase(); @@ -31,82 +39,108 @@ export default function LoginPage() { email: "", password: "", }, - }) + }); if (user) { - console.log('User exists, redirecting to profile') - router.push('/profile') + console.log("User exists, redirecting to profile"); + router.push("/profile"); } if (isUserLoading || isSupabaseLoading || user) { return (
- +
); } if (!supabase) { - return
User accounts are disabled because Supabase client is unavailable
+ return ( +
+ User accounts are disabled because Supabase client is unavailable +
+ ); } - async function handleSignInWithProvider(provider: 'google' | 'github' | 'discord') { + async function handleSignInWithProvider( + provider: "google" | "github" | "discord", + ) { const { data, error } = await supabase!.auth.signInWithOAuth({ provider: provider, options: { - redirectTo: process.env.AUTH_CALLBACK_URL ?? `http://localhost:3000/auth/callback`, + redirectTo: + process.env.AUTH_CALLBACK_URL ?? + `http://localhost:3000/auth/callback`, // Get Google provider_refresh_token // queryParams: { // access_type: 'offline', // prompt: 'consent', // }, }, - }) + }); if (!error) { - setFeedback(null) - return + setFeedback(null); + return; } - setFeedback(error.message) + setFeedback(error.message); } const onLogin = async (data: z.infer) => { - setIsLoading(true) - const error = await login(data) - setIsLoading(false) + setIsLoading(true); + const error = await login(data); + setIsLoading(false); if (error) { - setFeedback(error) - return + setFeedback(error); + return; } - setFeedback(null) - } + setFeedback(null); + }; const onSignup = async (data: z.infer) => { if (await form.trigger()) { - setIsLoading(true) - const error = await signup(data) - setIsLoading(false) + setIsLoading(true); + const error = await signup(data); + setIsLoading(false); if (error) { - setFeedback(error) - return + setFeedback(error); + return; } - setFeedback(null) + setFeedback(null); } - } + }; return (
-
- - - @@ -117,7 +151,7 @@ export default function LoginPage() { control={form.control} name="email" render={({ field }) => ( - + Email @@ -142,13 +176,17 @@ export default function LoginPage() { )} /> -
-
-

{feedback}

-

+

{feedback}

+

By continuing you agree to everything

- ) + ); } diff --git a/rnd/autogpt_builder/src/app/monitor/page.tsx b/rnd/autogpt_builder/src/app/monitor/page.tsx index 7aebc6e83..2a7f7e998 100644 --- a/rnd/autogpt_builder/src/app/monitor/page.tsx +++ b/rnd/autogpt_builder/src/app/monitor/page.tsx @@ -1,7 +1,7 @@ "use client"; -import React, { useEffect, useState } from 'react'; -import Link from 'next/link'; -import moment from 'moment'; +import React, { useEffect, useState } from "react"; +import Link from "next/link"; +import moment from "moment"; import { ComposedChart, DefaultLegendContentProps, @@ -12,7 +12,7 @@ import { Tooltip, XAxis, YAxis, -} from 'recharts'; +} from "recharts"; import { DropdownMenu, DropdownMenuContent, @@ -22,23 +22,45 @@ import { DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" +} from "@/components/ui/dropdown-menu"; import AutoGPTServerAPI, { Graph, GraphMeta, NodeExecutionResult, safeCopyGraph, -} from '@/lib/autogpt-server-api'; -import { ChevronDownIcon, ClockIcon, EnterIcon, ExitIcon, Pencil2Icon } from '@radix-ui/react-icons'; -import { cn, exportAsJSONFile, hashString } from '@/lib/utils'; +} from "@/lib/autogpt-server-api"; +import { + ChevronDownIcon, + ClockIcon, + EnterIcon, + ExitIcon, + Pencil2Icon, +} from "@radix-ui/react-icons"; +import { cn, exportAsJSONFile, hashString } from "@/lib/utils"; import { Badge } from "@/components/ui/badge"; import { Button, buttonVariants } from "@/components/ui/button"; import { Calendar } from "@/components/ui/calendar"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; -import { Dialog, DialogContent, DialogHeader, DialogTrigger } from '@/components/ui/dialog'; -import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; -import { AgentImportForm } from '@/components/agent-import-form'; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTrigger, +} from "@/components/ui/dialog"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { AgentImportForm } from "@/components/agent-import-form"; const Monitor = () => { const [flows, setFlows] = useState([]); @@ -50,46 +72,49 @@ const Monitor = () => { useEffect(() => fetchFlowsAndRuns(), []); useEffect(() => { - const intervalId = setInterval(() => flows.map(f => refreshFlowRuns(f.id)), 5000); + const intervalId = setInterval( + () => flows.map((f) => refreshFlowRuns(f.id)), + 5000, + ); return () => clearInterval(intervalId); }, []); function fetchFlowsAndRuns() { - api.listGraphs() - .then(flows => { + api.listGraphs().then((flows) => { setFlows(flows); - flows.map(flow => refreshFlowRuns(flow.id)); + flows.map((flow) => refreshFlowRuns(flow.id)); }); } function refreshFlowRuns(flowID: string) { // Fetch flow run IDs - api.listGraphRunIDs(flowID) - .then(runIDs => runIDs.map(runID => { - let run; - if ( - (run = flowRuns.find(fr => fr.id == runID)) - && !["waiting", "running"].includes(run.status) - ) { - return - } - - // Fetch flow run - api.getGraphExecutionInfo(flowID, runID) - .then(execInfo => setFlowRuns(flowRuns => { - if (execInfo.length == 0) return flowRuns; - - const flowRunIndex = flowRuns.findIndex(fr => fr.id == runID); - const flowRun = flowRunFromNodeExecutionResults(execInfo); - if (flowRunIndex > -1) { - flowRuns.splice(flowRunIndex, 1, flowRun) + api.listGraphRunIDs(flowID).then((runIDs) => + runIDs.map((runID) => { + let run; + if ( + (run = flowRuns.find((fr) => fr.id == runID)) && + !["waiting", "running"].includes(run.status) + ) { + return; } - else { - flowRuns.push(flowRun) - } - return [...flowRuns] - })); - })); + + // Fetch flow run + api.getGraphExecutionInfo(flowID, runID).then((execInfo) => + setFlowRuns((flowRuns) => { + if (execInfo.length == 0) return flowRuns; + + const flowRunIndex = flowRuns.findIndex((fr) => fr.id == runID); + const flowRun = flowRunFromNodeExecutionResults(execInfo); + if (flowRunIndex > -1) { + flowRuns.splice(flowRunIndex, 1, flowRun); + } else { + flowRuns.push(flowRun); + } + return [...flowRuns]; + }), + ); + }), + ); } const column1 = "md:col-span-2 xl:col-span-3 xxl:col-span-2"; @@ -103,7 +128,7 @@ const Monitor = () => { flows={flows} flowRuns={flowRuns} selectedFlow={selectedFlow} - onSelectFlow={f => { + onSelectFlow={(f) => { setSelectedRun(null); setSelectedFlow(f.id == selectedFlow?.id ? null : f); }} @@ -111,84 +136,86 @@ const Monitor = () => { v.graphID == selectedFlow.id) - : flowRuns - ) - .toSorted((a, b) => Number(a.startTime) - Number(b.startTime)) - } + runs={(selectedFlow + ? flowRuns.filter((v) => v.graphID == selectedFlow.id) + : flowRuns + ).toSorted((a, b) => Number(a.startTime) - Number(b.startTime))} selectedRun={selectedRun} - onSelectRun={r => setSelectedRun(r.id == selectedRun?.id ? null : r)} + onSelectRun={(r) => setSelectedRun(r.id == selectedRun?.id ? null : r)} /> - {selectedRun && ( + {(selectedRun && ( f.id == selectedRun.graphID)!} + flow={selectedFlow || flows.find((f) => f.id == selectedRun.graphID)!} flowRun={selectedRun} className={column3} /> - ) || selectedFlow && ( - r.graphID == selectedFlow.id)} - className={column3} - /> - ) || ( - - - - )} + )) || + (selectedFlow && ( + r.graphID == selectedFlow.id)} + className={column3} + /> + )) || ( + + + + )}
); }; type FlowRun = { - id: string - graphID: string - graphVersion: number - status: 'running' | 'waiting' | 'success' | 'failed' - startTime: number // unix timestamp (ms) - endTime: number // unix timestamp (ms) - duration: number // seconds - totalRunTime: number // seconds + id: string; + graphID: string; + graphVersion: number; + status: "running" | "waiting" | "success" | "failed"; + startTime: number; // unix timestamp (ms) + endTime: number; // unix timestamp (ms) + duration: number; // seconds + totalRunTime: number; // seconds - nodeExecutionResults: NodeExecutionResult[] + nodeExecutionResults: NodeExecutionResult[]; }; function flowRunFromNodeExecutionResults( - nodeExecutionResults: NodeExecutionResult[] + nodeExecutionResults: NodeExecutionResult[], ): FlowRun { // Determine overall status - let status: 'running' | 'waiting' | 'success' | 'failed' = 'success'; + let status: "running" | "waiting" | "success" | "failed" = "success"; for (const execution of nodeExecutionResults) { - if (execution.status === 'FAILED') { - status = 'failed'; + if (execution.status === "FAILED") { + status = "failed"; break; - } else if (['QUEUED', 'RUNNING'].includes(execution.status)) { - status = 'running'; + } else if (["QUEUED", "RUNNING"].includes(execution.status)) { + status = "running"; break; - } else if (execution.status === 'INCOMPLETE') { - status = 'waiting'; + } else if (execution.status === "INCOMPLETE") { + status = "waiting"; } } // Determine aggregate startTime, endTime, and totalRunTime const now = Date.now(); const startTime = Math.min( - ...nodeExecutionResults.map(ner => ner.add_time.getTime()), now + ...nodeExecutionResults.map((ner) => ner.add_time.getTime()), + now, ); - const endTime = ( - ['success', 'failed'].includes(status) - ? Math.max( - ...nodeExecutionResults.map(ner => ner.end_time?.getTime() || 0), startTime + const endTime = ["success", "failed"].includes(status) + ? Math.max( + ...nodeExecutionResults.map((ner) => ner.end_time?.getTime() || 0), + startTime, ) - : now - ); - const duration = (endTime - startTime) / 1000; // Convert to seconds - const totalRunTime = nodeExecutionResults.reduce((cum, node) => ( - cum + ((node.end_time?.getTime() ?? now) - (node.start_time?.getTime() ?? now)) - ), 0) / 1000; + : now; + const duration = (endTime - startTime) / 1000; // Convert to seconds + const totalRunTime = + nodeExecutionResults.reduce( + (cum, node) => + cum + + ((node.end_time?.getTime() ?? now) - + (node.start_time?.getTime() ?? now)), + 0, + ) / 1000; return { id: nodeExecutionResults[0].graph_exec_id, @@ -203,136 +230,174 @@ function flowRunFromNodeExecutionResults( }; } -const AgentFlowList = ( - { flows, flowRuns, selectedFlow, onSelectFlow, className }: { - flows: GraphMeta[], - flowRuns?: FlowRun[], - selectedFlow: GraphMeta | null, - onSelectFlow: (f: GraphMeta) => void, - className?: string, - } -) => { +const AgentFlowList = ({ + flows, + flowRuns, + selectedFlow, + onSelectFlow, + className, +}: { + flows: GraphMeta[]; + flowRuns?: FlowRun[]; + selectedFlow: GraphMeta | null; + onSelectFlow: (f: GraphMeta) => void; + className?: string; +}) => { const [templates, setTemplates] = useState([]); const api = new AutoGPTServerAPI(); useEffect(() => { - api.listTemplates().then(templates => setTemplates(templates)) + api.listTemplates().then((templates) => setTemplates(templates)); }, []); - return - - Agents + return ( + + + Agents -
{/* Split "Create" button */} - - {/* https://ui.shadcn.com/docs/components/dialog#notes */} - - - - +
+ {/* Split "Create" button */} + + + {/* https://ui.shadcn.com/docs/components/dialog#notes */} + + + + - - - - Import from file - - - {templates.length > 0 && <>{/* List of templates */} - - Use a template - {templates.map(template => ( - { - api.createGraph(template.id, template.version) - .then(newGraph => { - window.location.href = `/build?flowID=${newGraph.id}`; - }); - }} - > - {template.name} + + + + Import from file - ))} - } - - + + {templates.length > 0 && ( + <> + {/* List of templates */} + + Use a template + {templates.map((template) => ( + { + api + .createGraph(template.id, template.version) + .then((newGraph) => { + window.location.href = `/build?flowID=${newGraph.id}`; + }); + }} + > + {template.name} + + ))} + + )} + + - - - Import an Agent (template) from a file - - - - -
- + + + Import an Agent (template) from a file + + + +
+
+
- - - - - Name - {/* Status */} - {/* Last updated */} - {flowRuns && # of runs} - {flowRuns && Last run} - - - - {flows - .map((flow) => { - let runCount = 0, lastRun: FlowRun | null = null; - if (flowRuns) { - const _flowRuns = flowRuns.filter(r => r.graphID == flow.id); - runCount = _flowRuns.length; - lastRun = runCount == 0 ? null : _flowRuns.reduce( - (a, c) => a.startTime > c.startTime ? a : c - ); - } - return { flow, runCount, lastRun }; - }) - .sort((a, b) => { - if (!a.lastRun && !b.lastRun) return 0; - if (!a.lastRun) return 1; - if (!b.lastRun) return -1; - return b.lastRun.startTime - a.lastRun.startTime; - }) - .map(({ flow, runCount, lastRun }) => ( - onSelectFlow(flow)} - data-state={selectedFlow?.id == flow.id ? "selected" : null} - > - {flow.name} - {/* */} - {/* + +
+ + + Name + {/* Status */} + {/* Last updated */} + {flowRuns && ( + + # of runs + + )} + {flowRuns && Last run} + + + + {flows + .map((flow) => { + let runCount = 0, + lastRun: FlowRun | null = null; + if (flowRuns) { + const _flowRuns = flowRuns.filter( + (r) => r.graphID == flow.id, + ); + runCount = _flowRuns.length; + lastRun = + runCount == 0 + ? null + : _flowRuns.reduce((a, c) => + a.startTime > c.startTime ? a : c, + ); + } + return { flow, runCount, lastRun }; + }) + .sort((a, b) => { + if (!a.lastRun && !b.lastRun) return 0; + if (!a.lastRun) return 1; + if (!b.lastRun) return -1; + return b.lastRun.startTime - a.lastRun.startTime; + }) + .map(({ flow, runCount, lastRun }) => ( + onSelectFlow(flow)} + data-state={selectedFlow?.id == flow.id ? "selected" : null} + > + {flow.name} + {/* */} + {/* {flow.updatedAt ?? "???"} */} - {flowRuns && {runCount}} - {flowRuns && (!lastRun ? : - - {moment(lastRun.startTime).fromNow()} - )} - - )) - } - -
-
-
+ {flowRuns && ( + + {runCount} + + )} + {flowRuns && + (!lastRun ? ( + + ) : ( + + {moment(lastRun.startTime).fromNow()} + + ))} + + ))} + + + +
+ ); }; -const FlowStatusBadge = ({ status }: { status: "active" | "disabled" | "failing" }) => ( +const FlowStatusBadge = ({ + status, +}: { + status: "active" | "disabled" | "failing"; +}) => ( {status} @@ -368,9 +433,13 @@ const FlowRunsList: React.FC<{ onClick={() => onSelectRun(run)} data-state={selectedRun?.id == run.id ? "selected" : null} > - {flows.find(f => f.id == run.graphID)!.name} + + {flows.find((f) => f.id == run.graphID)!.name} + {moment(run.startTime).format("HH:mm")} - + + + {formatDuration(run.duration)} ))} @@ -381,16 +450,19 @@ const FlowRunsList: React.FC<{ ); const FlowRunStatusBadge: React.FC<{ - status: FlowRun['status']; + status: FlowRun["status"]; className?: string; }> = ({ status, className }) => ( @@ -398,188 +470,285 @@ const FlowRunStatusBadge: React.FC<{ ); -const FlowInfo: React.FC & { - flow: GraphMeta; - flowRuns: FlowRun[]; - flowVersion?: number | "all"; -}> = ({ flow, flowRuns, flowVersion, ...props }) => { +const FlowInfo: React.FC< + React.HTMLAttributes & { + flow: GraphMeta; + flowRuns: FlowRun[]; + flowVersion?: number | "all"; + } +> = ({ flow, flowRuns, flowVersion, ...props }) => { const api = new AutoGPTServerAPI(); const [flowVersions, setFlowVersions] = useState(null); - const [selectedVersion, setSelectedFlowVersion] = useState(flowVersion ?? "all"); - const selectedFlowVersion: Graph | undefined = flowVersions?.find(v => ( - v.version == (selectedVersion == "all" ? flow.version : selectedVersion) - )); + const [selectedVersion, setSelectedFlowVersion] = useState( + flowVersion ?? "all", + ); + const selectedFlowVersion: Graph | undefined = flowVersions?.find( + (v) => + v.version == (selectedVersion == "all" ? flow.version : selectedVersion), + ); useEffect(() => { - api.getGraphAllVersions(flow.id).then(result => setFlowVersions(result)); + api.getGraphAllVersions(flow.id).then((result) => setFlowVersions(result)); }, [flow.id]); - return - -
- - {flow.name} v{flow.version} - -

Agent ID: {flow.id}

-
-
- {(flowVersions?.length ?? 0) > 1 && - - - - - - Choose a version - - setSelectedFlowVersion( - choice == "all" ? choice : Number(choice) - )} - > - All versions - {flowVersions?.map(v => - - Version {v.version}{v.is_active ? " (active)" : ""} - - )} - - - } - - Edit - - + + + Choose a version + + + setSelectedFlowVersion( + choice == "all" ? choice : Number(choice), + ) + } + > + + All versions + + {flowVersions?.map((v) => ( + + Version {v.version} + {v.is_active ? " (active)" : ""} + + ))} + + + )} - > - - -
-
- - - r.graphID == flow.id - && (selectedVersion == "all" || r.graphVersion == selectedVersion) - )} - /> - -
; + + Edit + + + + + + + r.graphID == flow.id && + (selectedVersion == "all" || r.graphVersion == selectedVersion), + )} + /> + + + ); }; -const FlowRunInfo: React.FC & { - flow: GraphMeta; - flowRun: FlowRun; -}> = ({ flow, flowRun, ...props }) => { +const FlowRunInfo: React.FC< + React.HTMLAttributes & { + flow: GraphMeta; + flowRun: FlowRun; + } +> = ({ flow, flowRun, ...props }) => { if (flowRun.graphID != flow.id) { - throw new Error(`FlowRunInfo can't be used with non-matching flowRun.flowID and flow.id`) + throw new Error( + `FlowRunInfo can't be used with non-matching flowRun.flowID and flow.id`, + ); } - return - -
- - {flow.name} v{flow.version} - -

Agent ID: {flow.id}

-

Run ID: {flowRun.id}

-
- - Edit Agent - -
- -

Status:

-

Started: {moment(flowRun.startTime).format('YYYY-MM-DD HH:mm:ss')}

-

Finished: {moment(flowRun.endTime).format('YYYY-MM-DD HH:mm:ss')}

-

Duration (run time): {flowRun.duration} ({flowRun.totalRunTime}) seconds

- {/*

Total cost: €1,23

*/} -
-
; + return ( + + +
+ + {flow.name} v{flow.version} + +

+ Agent ID: {flow.id} +

+

+ Run ID: {flowRun.id} +

+
+ + Edit Agent + +
+ +

+ Status:{" "} + +

+

+ Started:{" "} + {moment(flowRun.startTime).format("YYYY-MM-DD HH:mm:ss")} +

+

+ Finished:{" "} + {moment(flowRun.endTime).format("YYYY-MM-DD HH:mm:ss")} +

+

+ Duration (run time): {flowRun.duration} ( + {flowRun.totalRunTime}) seconds +

+ {/*

Total cost: €1,23

*/} +
+
+ ); }; const FlowRunsStats: React.FC<{ - flows: GraphMeta[], - flowRuns: FlowRun[], - title?: string, - className?: string, + flows: GraphMeta[]; + flowRuns: FlowRun[]; + title?: string; + className?: string; }> = ({ flows, flowRuns, title, className }) => { /* "dateMin": since the first flow in the dataset * number > 0: custom date (unix timestamp) * number < 0: offset relative to Date.now() (in seconds) */ - const [statsSince, setStatsSince] = useState(-24*3600) - const statsSinceTimestamp = ( // unix timestamp or null - typeof(statsSince) == "string" + const [statsSince, setStatsSince] = useState(-24 * 3600); + const statsSinceTimestamp = // unix timestamp or null + typeof statsSince == "string" ? null : statsSince < 0 - ? Date.now() + (statsSince*1000) - : statsSince - ) - const filteredFlowRuns = statsSinceTimestamp != null - ? flowRuns.filter(fr => fr.startTime > statsSinceTimestamp) - : flowRuns; + ? Date.now() + statsSince * 1000 + : statsSince; + const filteredFlowRuns = + statsSinceTimestamp != null + ? flowRuns.filter((fr) => fr.startTime > statsSinceTimestamp) + : flowRuns; return (
- { title || "Stats" } + {title || "Stats"}
- - - - + + + + - + setStatsSince(selectedDay.getTime())} + onSelect={(_, selectedDay) => + setStatsSince(selectedDay.getTime()) + } initialFocus /> - +
- +
-

Total runs: {filteredFlowRuns.length}

- Total run time: { - filteredFlowRuns.reduce((total, run) => total + run.totalRunTime, 0) - } seconds + Total runs: {filteredFlowRuns.length} +

+

+ Total run time:{" "} + {filteredFlowRuns.reduce((total, run) => total + run.totalRunTime, 0)}{" "} + seconds

{/*

Total cost: €1,23

*/}
- ) -} + ); +}; -const FlowRunsTimeline = ( - { flows, flowRuns, dataMin, className }: { - flows: GraphMeta[], - flowRuns: FlowRun[], - dataMin: "dataMin" | number, - className?: string, - } -) => ( +const FlowRunsTimeline = ({ + flows, + flowRuns, + dataMin, + className, +}: { + flows: GraphMeta[]; + flowRuns: FlowRun[]; + dataMin: "dataMin" | number; + className?: string; +}) => ( /* TODO: make logarithmic? */ @@ -587,20 +756,20 @@ const FlowRunsTimeline = ( dataKey="time" type="number" domain={[ - typeof(dataMin) == "string" + typeof dataMin == "string" ? dataMin : dataMin < 0 - ? Date.now() + (dataMin*1000) + ? Date.now() + dataMin * 1000 : dataMin, - Date.now() + Date.now(), ]} allowDataOverflow={true} tickFormatter={(unixTime) => { const now = moment(); const time = moment(unixTime); - return now.diff(time, 'hours') < 24 - ? time.format('HH:mm') - : time.format('YYYY-MM-DD HH:mm'); + return now.diff(time, "hours") < 24 + ? time.format("HH:mm") + : time.format("YYYY-MM-DD HH:mm"); }} name="Time" scale="time" @@ -608,24 +777,35 @@ const FlowRunsTimeline = ( s > 90 ? `${Math.round(s / 60)}m` : `${s}s`} + tickFormatter={(s) => (s > 90 ? `${Math.round(s / 60)}m` : `${s}s`)} /> { if (payload && payload.length) { - const data: FlowRun & { time: number, _duration: number } = payload[0].payload; - const flow = flows.find(f => f.id === data.graphID); + const data: FlowRun & { time: number; _duration: number } = + payload[0].payload; + const flow = flows.find((f) => f.id === data.graphID); return ( -

Agent: {flow ? flow.name : 'Unknown'}

+

+ Agent: {flow ? flow.name : "Unknown"} +

Status:  - + +

+

+ Started:{" "} + {moment(data.startTime).format("YYYY-MM-DD HH:mm:ss")} +

+

+ Duration / run time:{" "} + {formatDuration(data.duration)} /{" "} + {formatDuration(data.totalRunTime)}

-

Started: {moment(data.startTime).format('YYYY-MM-DD HH:mm:ss')}

-

Duration / run time: { - formatDuration(data.duration)} / {formatDuration(data.totalRunTime) - }

); } @@ -635,13 +815,15 @@ const FlowRunsTimeline = ( {flows.map((flow) => ( fr.graphID == flow.id).map(fr => ({ - ...fr, - time: fr.startTime + (fr.totalRunTime * 1000), - _duration: fr.totalRunTime, - }))} + data={flowRuns + .filter((fr) => fr.graphID == flow.id) + .map((fr) => ({ + ...fr, + time: fr.startTime + fr.totalRunTime * 1000, + _duration: fr.totalRunTime, + }))} name={flow.name} - fill={`hsl(${hashString(flow.id) * 137.5 % 360}, 70%, 50%)`} + fill={`hsl(${(hashString(flow.id) * 137.5) % 360}, 70%, 50%)`} /> ))} {flowRuns.map((run) => ( @@ -651,9 +833,9 @@ const FlowRunsTimeline = ( dataKey="_duration" data={[ { ...run, time: run.startTime, _duration: 0 }, - { ...run, time: run.endTime, _duration: run.totalRunTime } + { ...run, time: run.endTime, _duration: run.totalRunTime }, ]} - stroke={`hsl(${hashString(run.graphID) * 137.5 % 360}, 70%, 50%)`} + stroke={`hsl(${(hashString(run.graphID) * 137.5) % 360}, 70%, 50%)`} strokeWidth={2} dot={false} legendType="none" @@ -674,9 +856,9 @@ const FlowRunsTimeline = (
); -const ScrollableLegend: React.FC = ( - { payload, className } -) => { +const ScrollableLegend: React.FC< + DefaultLegendContentProps & { className?: string } +> = ({ payload, className }) => { return (
{entry.value} - ) + ); })}
); @@ -703,10 +885,9 @@ const ScrollableLegend: React.FCHello {user.email}

- ) + ); } diff --git a/rnd/autogpt_builder/src/app/providers.tsx b/rnd/autogpt_builder/src/app/providers.tsx index 2758c5beb..ce2731227 100644 --- a/rnd/autogpt_builder/src/app/providers.tsx +++ b/rnd/autogpt_builder/src/app/providers.tsx @@ -1,10 +1,10 @@ -'use client' +"use client"; -import * as React from 'react' -import { ThemeProvider as NextThemesProvider } from 'next-themes' -import { ThemeProviderProps } from 'next-themes/dist/types' -import { TooltipProvider } from '@/components/ui/tooltip' -import SupabaseProvider from '@/components/SupabaseProvider' +import * as React from "react"; +import { ThemeProvider as NextThemesProvider } from "next-themes"; +import { ThemeProviderProps } from "next-themes/dist/types"; +import { TooltipProvider } from "@/components/ui/tooltip"; +import SupabaseProvider from "@/components/SupabaseProvider"; export function Providers({ children, ...props }: ThemeProviderProps) { return ( @@ -13,5 +13,5 @@ export function Providers({ children, ...props }: ThemeProviderProps) { {children} - ) + ); } diff --git a/rnd/autogpt_builder/src/components/ConnectionLine.tsx b/rnd/autogpt_builder/src/components/ConnectionLine.tsx index 988d2dd0b..43eecbb3c 100644 --- a/rnd/autogpt_builder/src/components/ConnectionLine.tsx +++ b/rnd/autogpt_builder/src/components/ConnectionLine.tsx @@ -1,9 +1,23 @@ -import { BaseEdge, ConnectionLineComponentProps, getBezierPath, Position } from "reactflow"; +import { + BaseEdge, + ConnectionLineComponentProps, + getBezierPath, + Position, +} from "reactflow"; -const ConnectionLine: React.FC = ({ fromPosition, fromHandle, fromX, fromY, toPosition, toX, toY }) => { - - const sourceX = fromPosition === Position.Right ? - fromX + (fromHandle?.width! / 2 - 5) : fromX - (fromHandle?.width! / 2 - 5); +const ConnectionLine: React.FC = ({ + fromPosition, + fromHandle, + fromX, + fromY, + toPosition, + toX, + toY, +}) => { + const sourceX = + fromPosition === Position.Right + ? fromX + (fromHandle?.width! / 2 - 5) + : fromX - (fromHandle?.width! / 2 - 5); const [path] = getBezierPath({ sourceX: sourceX, @@ -14,9 +28,7 @@ const ConnectionLine: React.FC = ({ fromPosition, targetPosition: toPosition, }); - return ( - - ); + return ; }; export default ConnectionLine; diff --git a/rnd/autogpt_builder/src/components/CustomEdge.tsx b/rnd/autogpt_builder/src/components/CustomEdge.tsx index cc98998d0..25ac6c329 100644 --- a/rnd/autogpt_builder/src/components/CustomEdge.tsx +++ b/rnd/autogpt_builder/src/components/CustomEdge.tsx @@ -1,21 +1,41 @@ import React, { FC, memo, useMemo, useState } from "react"; -import { BaseEdge, EdgeLabelRenderer, EdgeProps, getBezierPath, useReactFlow, XYPosition } from "reactflow"; -import './customedge.css'; -import { X } from 'lucide-react'; +import { + BaseEdge, + EdgeLabelRenderer, + EdgeProps, + getBezierPath, + useReactFlow, + XYPosition, +} from "reactflow"; +import "./customedge.css"; +import { X } from "lucide-react"; export type CustomEdgeData = { edgeColor: string; sourcePos?: XYPosition; -} +}; -const CustomEdgeFC: FC> = ({ id, data, selected, source, sourcePosition, sourceX, sourceY, target, targetPosition, targetX, targetY, markerEnd }) => { +const CustomEdgeFC: FC> = ({ + id, + data, + selected, + source, + sourcePosition, + sourceX, + sourceY, + target, + targetPosition, + targetX, + targetY, + markerEnd, +}) => { const [isHovered, setIsHovered] = useState(false); const { setEdges } = useReactFlow(); const onEdgeClick = () => { setEdges((edges) => edges.filter((edge) => edge.id !== id)); data.clearNodesStatusAndOutput(); - } + }; const [path, labelX, labelY] = getBezierPath({ sourceX: sourceX - 5, @@ -27,14 +47,29 @@ const CustomEdgeFC: FC> = ({ id, data, selected, sourc }); // Calculate y difference between source and source node, to adjust self-loop edge - const yDifference = useMemo(() => sourceY - (data?.sourcePos?.y || 0), [data?.sourcePos?.y]); + const yDifference = useMemo( + () => sourceY - (data?.sourcePos?.y || 0), + [data?.sourcePos?.y], + ); // Define special edge path for self-loop - const edgePath = source === target ? - `M ${sourceX - 5} ${sourceY} C ${sourceX + 128} ${sourceY - yDifference - 128} ${targetX - 128} ${sourceY - yDifference - 128} ${targetX + 3}, ${targetY}` : - path; + const edgePath = + source === target + ? `M ${sourceX - 5} ${sourceY} C ${sourceX + 128} ${sourceY - yDifference - 128} ${targetX - 128} ${sourceY - yDifference - 128} ${targetX + 3}, ${targetY}` + : path; - console.table({ id, sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, path, labelX, labelY }); + console.table({ + id, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + path, + labelX, + labelY, + }); return ( <> @@ -43,7 +78,9 @@ const CustomEdgeFC: FC> = ({ id, data, selected, sourc markerEnd={markerEnd} style={{ strokeWidth: isHovered ? 3 : 2, - stroke: (data?.edgeColor ?? '#555555') + (selected || isHovered ? '' : '80') + stroke: + (data?.edgeColor ?? "#555555") + + (selected || isHovered ? "" : "80"), }} /> > = ({ id, data, selected, sourc
- ) + ); }; -export const CustomEdge = memo(CustomEdgeFC); \ No newline at end of file +export const CustomEdge = memo(CustomEdgeFC); diff --git a/rnd/autogpt_builder/src/components/CustomNode.tsx b/rnd/autogpt_builder/src/components/CustomNode.tsx index 8ed9844ea..63aeb222a 100644 --- a/rnd/autogpt_builder/src/components/CustomNode.tsx +++ b/rnd/autogpt_builder/src/components/CustomNode.tsx @@ -1,17 +1,27 @@ -import React, { useState, useEffect, FC, memo, useCallback, useRef } from 'react'; -import { NodeProps, useReactFlow } from 'reactflow'; -import 'reactflow/dist/style.css'; -import './customnode.css'; -import InputModalComponent from './InputModalComponent'; -import OutputModalComponent from './OutputModalComponent'; -import { BlockIORootSchema, NodeExecutionResult } from '@/lib/autogpt-server-api/types'; -import { BlockSchema } from '@/lib/types'; -import { beautifyString, setNestedProperty } from '@/lib/utils'; -import { Switch } from "@/components/ui/switch" -import NodeHandle from './NodeHandle'; -import NodeInputField from './NodeInputField'; -import { Copy, Trash2 } from 'lucide-react'; -import { history } from './history'; +import React, { + useState, + useEffect, + FC, + memo, + useCallback, + useRef, +} from "react"; +import { NodeProps, useReactFlow } from "reactflow"; +import "reactflow/dist/style.css"; +import "./customnode.css"; +import InputModalComponent from "./InputModalComponent"; +import OutputModalComponent from "./OutputModalComponent"; +import { + BlockIORootSchema, + NodeExecutionResult, +} from "@/lib/autogpt-server-api/types"; +import { BlockSchema } from "@/lib/types"; +import { beautifyString, setNestedProperty } from "@/lib/utils"; +import { Switch } from "@/components/ui/switch"; +import NodeHandle from "./NodeHandle"; +import NodeInputField from "./NodeInputField"; +import { Copy, Trash2 } from "lucide-react"; +import { history } from "./history"; export type CustomNodeData = { blockType: string; @@ -20,7 +30,12 @@ export type CustomNodeData = { outputSchema: BlockIORootSchema; hardcodedValues: { [key: string]: any }; setHardcodedValues: (values: { [key: string]: any }) => void; - connections: Array<{ source: string; sourceHandle: string; target: string; targetHandle: string }>; + connections: Array<{ + source: string; + sourceHandle: string; + target: string; + targetHandle: string; + }>; isOutputOpen: boolean; status?: string; output_data?: any; @@ -36,11 +51,10 @@ const CustomNode: FC> = ({ data, id }) => { const [isAdvancedOpen, setIsAdvancedOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false); const [activeKey, setActiveKey] = useState(null); - const [modalValue, setModalValue] = useState(''); + const [modalValue, setModalValue] = useState(""); const [isOutputModalOpen, setIsOutputModalOpen] = useState(false); const [isHovered, setIsHovered] = useState(false); - const { getNode, setNodes, getEdges, setEdges } = useReactFlow(); const outputDataRef = useRef(null); @@ -73,9 +87,12 @@ const CustomNode: FC> = ({ data, id }) => { }; const hasOptionalFields = () => { - return data.inputSchema && Object.keys(data.inputSchema.properties).some((key) => { - return !(data.inputSchema.required?.includes(key)); - }); + return ( + data.inputSchema && + Object.keys(data.inputSchema.properties).some((key) => { + return !data.inputSchema.required?.includes(key); + }) + ); }; const generateOutputHandles = (schema: BlockIORootSchema) => { @@ -83,13 +100,18 @@ const CustomNode: FC> = ({ data, id }) => { const keys = Object.keys(schema.properties); return keys.map((key) => (
- +
)); }; const handleInputChange = (key: string, value: any) => { - const keys = key.split('.'); + const keys = key.split("."); const newValues = JSON.parse(JSON.stringify(data.hardcodedValues)); let current = newValues; @@ -103,7 +125,7 @@ const CustomNode: FC> = ({ data, id }) => { if (!isInitialSetup.current) { history.push({ - type: 'UPDATE_INPUT', + type: "UPDATE_INPUT", payload: { nodeId: id, oldValues: data.hardcodedValues, newValues }, undo: () => data.setHardcodedValues(data.hardcodedValues), redo: () => data.setHardcodedValues(newValues), @@ -118,27 +140,39 @@ const CustomNode: FC> = ({ data, id }) => { }; const getValue = (key: string) => { - const keys = key.split('.'); - return keys.reduce((acc, k) => (acc && acc[k] !== undefined) ? acc[k] : '', data.hardcodedValues); + const keys = key.split("."); + return keys.reduce( + (acc, k) => (acc && acc[k] !== undefined ? acc[k] : ""), + data.hardcodedValues, + ); }; const isHandleConnected = (key: string) => { - return data.connections && data.connections.some((conn: any) => { - if (typeof conn === 'string') { - const [source, target] = conn.split(' -> '); - return (target.includes(key) && target.includes(data.title)) || - (source.includes(key) && source.includes(data.title)); - } - return (conn.target === id && conn.targetHandle === key) || - (conn.source === id && conn.sourceHandle === key); - }); + return ( + data.connections && + data.connections.some((conn: any) => { + if (typeof conn === "string") { + const [source, target] = conn.split(" -> "); + return ( + (target.includes(key) && target.includes(data.title)) || + (source.includes(key) && source.includes(data.title)) + ); + } + return ( + (conn.target === id && conn.targetHandle === key) || + (conn.source === id && conn.sourceHandle === key) + ); + }) + ); }; const handleInputClick = (key: string) => { console.log(`Opening modal for key: ${key}`); setActiveKey(key); const value = getValue(key); - setModalValue(typeof value === 'object' ? JSON.stringify(value, null, 2) : value); + setModalValue( + typeof value === "object" ? JSON.stringify(value, null, 2) : value, + ); setIsModalOpen(true); }; @@ -158,75 +192,88 @@ const CustomNode: FC> = ({ data, id }) => { const handleOutputClick = () => { setIsOutputModalOpen(true); setModalValue( - data.output_data ? JSON.stringify(data.output_data, null, 2) : "[no output (yet)]" + data.output_data + ? JSON.stringify(data.output_data, null, 2) + : "[no output (yet)]", ); }; const isTextTruncated = (element: HTMLElement | null): boolean => { if (!element) return false; - return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth; + return ( + element.scrollHeight > element.clientHeight || + element.scrollWidth > element.clientWidth + ); }; const handleHovered = () => { setIsHovered(true); - console.log('isHovered', isHovered); - } + console.log("isHovered", isHovered); + }; const handleMouseLeave = () => { setIsHovered(false); - console.log('isHovered', isHovered); - } + console.log("isHovered", isHovered); + }; const deleteNode = useCallback(() => { - console.log('Deleting node:', id); + console.log("Deleting node:", id); // Get all edges connected to this node - const connectedEdges = getEdges().filter(edge => edge.source === id || edge.target === id); + const connectedEdges = getEdges().filter( + (edge) => edge.source === id || edge.target === id, + ); // For each connected edge, update the connected node's state - connectedEdges.forEach(edge => { + connectedEdges.forEach((edge) => { const connectedNodeId = edge.source === id ? edge.target : edge.source; const connectedNode = getNode(connectedNodeId); if (connectedNode) { - setNodes(nodes => nodes.map(node => { - if (node.id === connectedNodeId) { - // Update the node's data to reflect the disconnection - const updatedConnections = node.data.connections.filter( - conn => !(conn.source === id || conn.target === id) - ); - return { - ...node, - data: { - ...node.data, - connections: updatedConnections - } - }; - } - return node; - })); + setNodes((nodes) => + nodes.map((node) => { + if (node.id === connectedNodeId) { + // Update the node's data to reflect the disconnection + const updatedConnections = node.data.connections.filter( + (conn) => !(conn.source === id || conn.target === id), + ); + return { + ...node, + data: { + ...node.data, + connections: updatedConnections, + }, + }; + } + return node; + }), + ); } }); // Remove the node and its connected edges - setNodes(nodes => nodes.filter(node => node.id !== id)); - setEdges(edges => edges.filter(edge => edge.source !== id && edge.target !== id)); + setNodes((nodes) => nodes.filter((node) => node.id !== id)); + setEdges((edges) => + edges.filter((edge) => edge.source !== id && edge.target !== id), + ); }, [id, setNodes, setEdges, getNode, getEdges]); const copyNode = useCallback(() => { // This is a placeholder function. The actual copy functionality // will be implemented by another team member. - console.log('Copy node:', id); + console.log("Copy node:", id); }, [id]); return ( -
+ >
-
{beautifyString(data.blockType?.replace(/Block$/, '') || data.title)}
+
+ {beautifyString(data.blockType?.replace(/Block$/, "") || data.title)} +
{isHovered && ( <> @@ -253,19 +300,28 @@ const CustomNode: FC> = ({ data, id }) => { {data.inputSchema && Object.entries(data.inputSchema.properties).map(([key, schema]) => { const isRequired = data.inputSchema.required?.includes(key); - return (isRequired || isAdvancedOpen) && ( -
{ }}> - - {!isHandleConnected(key) && - {}}> + } -
+ side="left" + /> + {!isHandleConnected(key) && ( + + )} +
+ ) ); })}
@@ -276,17 +332,20 @@ const CustomNode: FC> = ({ data, id }) => { {isOutputOpen && (

- Status:{' '} - {typeof data.status === 'object' ? JSON.stringify(data.status) : data.status || 'N/A'} + Status:{" "} + {typeof data.status === "object" + ? JSON.stringify(data.status) + : data.status || "N/A"}

- Output Data:{' '} + Output Data:{" "} {(() => { - const outputText = typeof data.output_data === 'object' - ? JSON.stringify(data.output_data) - : data.output_data; + const outputText = + typeof data.output_data === "object" + ? JSON.stringify(data.output_data) + : data.output_data; - if (!outputText) return 'No output data'; + if (!outputText) return "No output data"; return outputText.length > 100 ? `${outputText.slice(0, 100)}... Press To Read More` @@ -296,12 +355,15 @@ const CustomNode: FC> = ({ data, id }) => {

)}
- - Output + + Output {hasOptionalFields() && ( <> - - Advanced + + Advanced )}
diff --git a/rnd/autogpt_builder/src/components/Flow.tsx b/rnd/autogpt_builder/src/components/Flow.tsx index 431557e86..8bc7f5a7c 100644 --- a/rnd/autogpt_builder/src/components/Flow.tsx +++ b/rnd/autogpt_builder/src/components/Flow.tsx @@ -1,5 +1,11 @@ "use client"; -import React, { useState, useCallback, useEffect, useMemo, useRef } from 'react'; +import React, { + useState, + useCallback, + useEffect, + useMemo, + useRef, +} from "react"; import ReactFlow, { addEdge, useNodesState, @@ -11,20 +17,30 @@ import ReactFlow, { Connection, EdgeTypes, MarkerType, -} from 'reactflow'; -import 'reactflow/dist/style.css'; -import CustomNode, { CustomNodeData } from './CustomNode'; -import './flow.css'; -import AutoGPTServerAPI, { Block, BlockIOSchema, Graph, NodeExecutionResult } from '@/lib/autogpt-server-api'; -import { Play, Undo2, Redo2} from "lucide-react"; -import { deepEquals, getTypeColor, removeEmptyStringsAndNulls, setNestedProperty } from '@/lib/utils'; -import { history } from './history'; -import { CustomEdge, CustomEdgeData } from './CustomEdge'; -import ConnectionLine from './ConnectionLine'; -import Ajv from 'ajv'; -import {Control, ControlPanel} from "@/components/edit/control/ControlPanel"; -import {SaveControl} from "@/components/edit/control/SaveControl"; -import {BlocksControl} from "@/components/edit/control/BlocksControl"; +} from "reactflow"; +import "reactflow/dist/style.css"; +import CustomNode, { CustomNodeData } from "./CustomNode"; +import "./flow.css"; +import AutoGPTServerAPI, { + Block, + BlockIOSchema, + Graph, + NodeExecutionResult, +} from "@/lib/autogpt-server-api"; +import { Play, Undo2, Redo2 } from "lucide-react"; +import { + deepEquals, + getTypeColor, + removeEmptyStringsAndNulls, + setNestedProperty, +} from "@/lib/utils"; +import { history } from "./history"; +import { CustomEdge, CustomEdgeData } from "./CustomEdge"; +import ConnectionLine from "./ConnectionLine"; +import Ajv from "ajv"; +import { Control, ControlPanel } from "@/components/edit/control/ControlPanel"; +import { SaveControl } from "@/components/edit/control/SaveControl"; +import { BlocksControl } from "@/components/edit/control/BlocksControl"; // This is for the history, this is the minimum distance a block must move before it is logged // It helps to prevent spamming the history with small movements especially when pressing on a input in a block @@ -42,27 +58,30 @@ const FlowEditor: React.FC<{ const [nodeId, setNodeId] = useState(1); const [availableNodes, setAvailableNodes] = useState([]); const [savedAgent, setSavedAgent] = useState(null); - const [agentDescription, setAgentDescription] = useState(''); - const [agentName, setAgentName] = useState(''); + const [agentDescription, setAgentDescription] = useState(""); + const [agentName, setAgentName] = useState(""); const [copiedNodes, setCopiedNodes] = useState[]>([]); const [copiedEdges, setCopiedEdges] = useState[]>([]); const [isAnyModalOpen, setIsAnyModalOpen] = useState(false); // Track if any modal is open const apiUrl = process.env.AGPT_SERVER_URL!; const api = useMemo(() => new AutoGPTServerAPI(apiUrl), [apiUrl]); - const initialPositionRef = useRef<{ [key: string]: { x: number; y: number } }>({}); + const initialPositionRef = useRef<{ + [key: string]: { x: number; y: number }; + }>({}); const isDragging = useRef(false); useEffect(() => { - api.connectWebSocket() + api + .connectWebSocket() .then(() => { - console.log('WebSocket connected'); - api.onWebSocketMessage('execution_event', (data) => { + console.log("WebSocket connected"); + api.onWebSocketMessage("execution_event", (data) => { updateNodesWithExecutionData([data]); }); }) .catch((error) => { - console.error('Failed to connect WebSocket:', error); + console.error("Failed to connect WebSocket:", error); }); return () => { @@ -71,8 +90,9 @@ const FlowEditor: React.FC<{ }, [api]); useEffect(() => { - api.getBlocks() - .then(blocks => setAvailableNodes(blocks)) + api + .getBlocks() + .then((blocks) => setAvailableNodes(blocks)) .catch(); }, []); @@ -80,34 +100,37 @@ const FlowEditor: React.FC<{ useEffect(() => { if (!flowID || availableNodes.length == 0) return; - (template ? api.getTemplate(flowID) : api.getGraph(flowID)) - .then(graph => loadGraph(graph)); + (template ? api.getTemplate(flowID) : api.getGraph(flowID)).then((graph) => + loadGraph(graph), + ); }, [flowID, template, availableNodes]); useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { - const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; - const isUndo = (isMac ? event.metaKey : event.ctrlKey) && event.key === 'z'; - const isRedo = (isMac ? event.metaKey : event.ctrlKey) && (event.key === 'y' || (event.shiftKey && event.key === 'Z')); - + const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0; + const isUndo = + (isMac ? event.metaKey : event.ctrlKey) && event.key === "z"; + const isRedo = + (isMac ? event.metaKey : event.ctrlKey) && + (event.key === "y" || (event.shiftKey && event.key === "Z")); + if (isUndo) { event.preventDefault(); handleUndo(); } - + if (isRedo) { event.preventDefault(); handleRedo(); } }; - - window.addEventListener('keydown', handleKeyDown); - + + window.addEventListener("keydown", handleKeyDown); + return () => { - window.removeEventListener('keydown', handleKeyDown); + window.removeEventListener("keydown", handleKeyDown); }; }, []); - const nodeTypes: NodeTypes = useMemo(() => ({ custom: CustomNode }), []); const edgeTypes: EdgeTypes = useMemo(() => ({ custom: CustomEdge }), []); @@ -119,7 +142,7 @@ const FlowEditor: React.FC<{ const onNodesChangeEnd = (event: MouseEvent, node: Node | null) => { if (!node) return; - + isDragging.current = false; const oldPosition = initialPositionRef.current[node.id]; const newPosition = node.position; @@ -129,39 +152,58 @@ const FlowEditor: React.FC<{ const distanceMoved = Math.sqrt( Math.pow(newPosition.x - oldPosition.x, 2) + - Math.pow(newPosition.y - oldPosition.y, 2) + Math.pow(newPosition.y - oldPosition.y, 2), ); - if (distanceMoved > MINIMUM_MOVE_BEFORE_LOG) { // Minimum movement threshold + if (distanceMoved > MINIMUM_MOVE_BEFORE_LOG) { + // Minimum movement threshold history.push({ - type: 'UPDATE_NODE_POSITION', + type: "UPDATE_NODE_POSITION", payload: { nodeId: node.id, oldPosition, newPosition }, - undo: () => setNodes((nds) => nds.map(n => n.id === node.id ? { ...n, position: oldPosition } : n)), - redo: () => setNodes((nds) => nds.map(n => n.id === node.id ? { ...n, position: newPosition } : n)), + undo: () => + setNodes((nds) => + nds.map((n) => + n.id === node.id ? { ...n, position: oldPosition } : n, + ), + ), + redo: () => + setNodes((nds) => + nds.map((n) => + n.id === node.id ? { ...n, position: newPosition } : n, + ), + ), }); } delete initialPositionRef.current[node.id]; }; - - const updateNodesOnEdgeChange = (edge: Edge, action: 'add' | 'remove') => { + const updateNodesOnEdgeChange = ( + edge: Edge, + action: "add" | "remove", + ) => { setNodes((nds) => nds.map((node) => { if (node.id === edge.source || node.id === edge.target) { - const connections = action === 'add' - ? [ - ...node.data.connections, - { - source: edge.source, - sourceHandle: edge.sourceHandle!, - target: edge.target, - targetHandle: edge.targetHandle!, - } - ] - : node.data.connections.filter( - (conn) => - !(conn.source === edge.source && conn.target === edge.target && conn.sourceHandle === edge.sourceHandle && conn.targetHandle === edge.targetHandle) - ); + const connections = + action === "add" + ? [ + ...node.data.connections, + { + source: edge.source, + sourceHandle: edge.sourceHandle!, + target: edge.target, + targetHandle: edge.targetHandle!, + }, + ] + : node.data.connections.filter( + (conn) => + !( + conn.source === edge.source && + conn.target === edge.target && + conn.sourceHandle === edge.sourceHandle && + conn.targetHandle === edge.targetHandle + ), + ); return { ...node, data: { @@ -171,28 +213,28 @@ const FlowEditor: React.FC<{ }; } return node; - }) + }), ); }; const getOutputType = (id: string, handleId: string) => { const node = nodes.find((node) => node.id === id); - if (!node) return 'unknown'; + if (!node) return "unknown"; const outputSchema = node.data.outputSchema; - if (!outputSchema) return 'unknown'; + if (!outputSchema) return "unknown"; const outputHandle = outputSchema.properties[handleId]; if (!("type" in outputHandle)) return "unknown"; return outputHandle.type; - } + }; const getNodePos = (id: string) => { const node = nodes.find((node) => node.id === id); if (!node) return 0; return node.position; - } + }; // Function to clear status, output, and close the output info dropdown of all nodes const clearNodesStatusAndOutput = useCallback(() => { @@ -205,38 +247,46 @@ const FlowEditor: React.FC<{ output_data: undefined, isOutputOpen: false, // Close the output info dropdown }, - })) + })), ); }, [setNodes]); const onConnect: OnConnect = useCallback( (connection: Connection) => { - const edgeColor = getTypeColor(getOutputType(connection.source!, connection.sourceHandle!)); - const sourcePos = getNodePos(connection.source!) - console.log('sourcePos', sourcePos); + const edgeColor = getTypeColor( + getOutputType(connection.source!, connection.sourceHandle!), + ); + const sourcePos = getNodePos(connection.source!); + console.log("sourcePos", sourcePos); const newEdge = { id: `${connection.source}_${connection.sourceHandle}_${connection.target}_${connection.targetHandle}`, - type: 'custom', - markerEnd: { type: MarkerType.ArrowClosed, strokeWidth: 2, color: edgeColor }, + type: "custom", + markerEnd: { + type: MarkerType.ArrowClosed, + strokeWidth: 2, + color: edgeColor, + }, data: { edgeColor, sourcePos }, - ...connection + ...connection, }; - + setEdges((eds) => { const newEdges = addEdge(newEdge, eds); history.push({ - type: 'ADD_EDGE', + type: "ADD_EDGE", payload: newEdge, undo: () => { - setEdges((prevEdges) => prevEdges.filter(edge => edge.id !== newEdge.id)); - updateNodesOnEdgeChange(newEdge, 'remove'); + setEdges((prevEdges) => + prevEdges.filter((edge) => edge.id !== newEdge.id), + ); + updateNodesOnEdgeChange(newEdge, "remove"); }, redo: () => { setEdges((prevEdges) => addEdge(newEdge, prevEdges)); - updateNodesOnEdgeChange(newEdge, 'add'); - } + updateNodesOnEdgeChange(newEdge, "add"); + }, }); - updateNodesOnEdgeChange(newEdge, 'add'); + updateNodesOnEdgeChange(newEdge, "add"); return newEdges; }); setNodes((nds) => @@ -253,17 +303,22 @@ const FlowEditor: React.FC<{ sourceHandle: connection.sourceHandle, target: connection.target, targetHandle: connection.targetHandle, - } as { source: string; sourceHandle: string; target: string; targetHandle: string }, + } as { + source: string; + sourceHandle: string; + target: string; + targetHandle: string; + }, ], }, }; } return node; - }) + }), ); - clearNodesStatusAndOutput(); // Clear status and output on connection change + clearNodesStatusAndOutput(); // Clear status and output on connection change }, - [nodes] + [nodes], ); const onEdgesDelete = useCallback( @@ -280,72 +335,79 @@ const FlowEditor: React.FC<{ edge.source === conn.source && edge.target === conn.target && edge.sourceHandle === edge.sourceHandle && - edge.targetHandle === edge.targetHandle - ) + edge.targetHandle === edge.targetHandle, + ), ), }, - })) + })), ); clearNodesStatusAndOutput(); // Clear status and output on edge deletion }, - [setNodes, clearNodesStatusAndOutput] + [setNodes, clearNodesStatusAndOutput], ); - const addNode = useCallback((blockId: string, nodeType: string) => { - const nodeSchema = availableNodes.find(node => node.id === blockId); - if (!nodeSchema) { - console.error(`Schema not found for block ID: ${blockId}`); - return; - } - - const newNode: Node = { - id: nodeId.toString(), - type: 'custom', - position: { x: Math.random() * 400, y: Math.random() * 400 }, - data: { - blockType: nodeType, - title: `${nodeType} ${nodeId}`, - inputSchema: nodeSchema.inputSchema, - outputSchema: nodeSchema.outputSchema, - hardcodedValues: {}, - setHardcodedValues: (values) => { - setNodes((nds) => - nds.map((node) => - node.id === newNode.id ? { ...node, data: { ...node.data, hardcodedValues: values } } : node - ) - ); + const addNode = useCallback( + (blockId: string, nodeType: string) => { + const nodeSchema = availableNodes.find((node) => node.id === blockId); + if (!nodeSchema) { + console.error(`Schema not found for block ID: ${blockId}`); + return; + } + + const newNode: Node = { + id: nodeId.toString(), + type: "custom", + position: { x: Math.random() * 400, y: Math.random() * 400 }, + data: { + blockType: nodeType, + title: `${nodeType} ${nodeId}`, + inputSchema: nodeSchema.inputSchema, + outputSchema: nodeSchema.outputSchema, + hardcodedValues: {}, + setHardcodedValues: (values) => { + setNodes((nds) => + nds.map((node) => + node.id === newNode.id + ? { ...node, data: { ...node.data, hardcodedValues: values } } + : node, + ), + ); + }, + connections: [], + isOutputOpen: false, + block_id: blockId, + setIsAnyModalOpen: setIsAnyModalOpen, // Pass setIsAnyModalOpen function + setErrors: (errors: { [key: string]: string | null }) => { + setNodes((nds) => + nds.map((node) => + node.id === newNode.id + ? { ...node, data: { ...node.data, errors } } + : node, + ), + ); + }, }, - connections: [], - isOutputOpen: false, - block_id: blockId, - setIsAnyModalOpen: setIsAnyModalOpen, // Pass setIsAnyModalOpen function - setErrors: (errors: { [key: string]: string | null }) => { - setNodes((nds) => nds.map((node) => - node.id === newNode.id - ? { ...node, data: { ...node.data, errors } } - : node - )); - } - }, - }; - - setNodes((nds) => [...nds, newNode]); - setNodeId((prevId) => prevId + 1); - clearNodesStatusAndOutput(); // Clear status and output when a new node is added - - history.push({ - type: 'ADD_NODE', - payload: newNode, - undo: () => setNodes((nds) => nds.filter(node => node.id !== newNode.id)), - redo: () => setNodes((nds) => [...nds, newNode]) - }); - }, [nodeId, availableNodes]); - + }; + + setNodes((nds) => [...nds, newNode]); + setNodeId((prevId) => prevId + 1); + clearNodesStatusAndOutput(); // Clear status and output when a new node is added + + history.push({ + type: "ADD_NODE", + payload: newNode, + undo: () => + setNodes((nds) => nds.filter((node) => node.id !== newNode.id)), + redo: () => setNodes((nds) => [...nds, newNode]), + }); + }, + [nodeId, availableNodes], + ); const handleUndo = () => { history.undo(); }; - + const handleRedo = () => { history.redo(); }; @@ -355,67 +417,101 @@ const FlowEditor: React.FC<{ setAgentName(graph.name); setAgentDescription(graph.description); - setNodes(graph.nodes.map(node => { - const block = availableNodes.find(block => block.id === node.block_id)!; - const newNode: Node = { - id: node.id, - type: 'custom', - position: { x: node.metadata.position.x, y: node.metadata.position.y }, - data: { - setIsAnyModalOpen: setIsAnyModalOpen, - block_id: block.id, - blockType: block.name, - title: `${block.name} ${node.id}`, - inputSchema: block.inputSchema, - outputSchema: block.outputSchema, - hardcodedValues: node.input_default, - setHardcodedValues: (values: { [key: string]: any; }) => { - setNodes((nds) => nds.map((node) => node.id === newNode.id - ? { ...node, data: { ...node.data, hardcodedValues: values } } - : node - )); + setNodes( + graph.nodes.map((node) => { + const block = availableNodes.find( + (block) => block.id === node.block_id, + )!; + const newNode: Node = { + id: node.id, + type: "custom", + position: { + x: node.metadata.position.x, + y: node.metadata.position.y, }, - connections: graph.links - .filter(l => [l.source_id, l.sink_id].includes(node.id)) - .map(link => ({ - source: link.source_id, - sourceHandle: link.source_name, - target: link.sink_id, - targetHandle: link.sink_name, - })), - isOutputOpen: false, - setIsAnyModalOpen: setIsAnyModalOpen, // Pass setIsAnyModalOpen function - setErrors: (errors: { [key: string]: string | null }) => { - setNodes((nds) => nds.map((node) => - node.id === newNode.id - ? { ...node, data: { ...node.data, errors } } - : node - )); - } - }, - }; - return newNode; - })); + data: { + setIsAnyModalOpen: setIsAnyModalOpen, + block_id: block.id, + blockType: block.name, + title: `${block.name} ${node.id}`, + inputSchema: block.inputSchema, + outputSchema: block.outputSchema, + hardcodedValues: node.input_default, + setHardcodedValues: (values: { [key: string]: any }) => { + setNodes((nds) => + nds.map((node) => + node.id === newNode.id + ? { + ...node, + data: { ...node.data, hardcodedValues: values }, + } + : node, + ), + ); + }, + connections: graph.links + .filter((l) => [l.source_id, l.sink_id].includes(node.id)) + .map((link) => ({ + source: link.source_id, + sourceHandle: link.source_name, + target: link.sink_id, + targetHandle: link.sink_name, + })), + isOutputOpen: false, + setIsAnyModalOpen: setIsAnyModalOpen, // Pass setIsAnyModalOpen function + setErrors: (errors: { [key: string]: string | null }) => { + setNodes((nds) => + nds.map((node) => + node.id === newNode.id + ? { ...node, data: { ...node.data, errors } } + : node, + ), + ); + }, + }, + }; + return newNode; + }), + ); - setEdges(graph.links.map(link => ({ - id: `${link.source_id}_${link.source_name}_${link.sink_id}_${link.sink_name}`, - type: 'custom', - data: { - edgeColor: getTypeColor(getOutputType(link.source_id, link.source_name!)), - sourcePos: getNodePos(link.source_id) - }, - markerEnd: { type: MarkerType.ArrowClosed, strokeWidth: 2, color: getTypeColor(getOutputType(link.source_id, link.source_name!)) }, - source: link.source_id, - target: link.sink_id, - sourceHandle: link.source_name || undefined, - targetHandle: link.sink_name || undefined - }) as Edge)); + setEdges( + graph.links.map( + (link) => + ({ + id: `${link.source_id}_${link.source_name}_${link.sink_id}_${link.sink_name}`, + type: "custom", + data: { + edgeColor: getTypeColor( + getOutputType(link.source_id, link.source_name!), + ), + sourcePos: getNodePos(link.source_id), + }, + markerEnd: { + type: MarkerType.ArrowClosed, + strokeWidth: 2, + color: getTypeColor( + getOutputType(link.source_id, link.source_name!), + ), + }, + source: link.source_id, + target: link.sink_id, + sourceHandle: link.source_name || undefined, + targetHandle: link.sink_name || undefined, + }) as Edge, + ), + ); } - const prepareNodeInputData = (node: Node, allNodes: Node[], allEdges: Edge[]) => { + const prepareNodeInputData = ( + node: Node, + allNodes: Node[], + allEdges: Edge[], + ) => { console.log("Preparing input data for node:", node.id, node.data.blockType); - const blockSchema = availableNodes.find(n => n.id === node.data.block_id)?.inputSchema; + const blockSchema = availableNodes.find( + (n) => n.id === node.data.block_id, + )?.inputSchema; if (!blockSchema) { console.error(`Schema not found for block ID: ${node.data.block_id}`); @@ -423,7 +519,8 @@ const FlowEditor: React.FC<{ } const getNestedData = ( - schema: BlockIOSchema, values: { [key: string]: any } + schema: BlockIOSchema, + values: { [key: string]: any }, ): { [key: string]: any } => { let inputData: { [key: string]: any } = {}; @@ -431,10 +528,13 @@ const FlowEditor: React.FC<{ Object.keys(schema.properties).forEach((key) => { if (values[key] !== undefined) { if ( - "properties" in schema.properties[key] - || "additionalProperties" in schema.properties[key] + "properties" in schema.properties[key] || + "additionalProperties" in schema.properties[key] ) { - inputData[key] = getNestedData(schema.properties[key], values[key]); + inputData[key] = getNestedData( + schema.properties[key], + values[key], + ); } else { inputData[key] = values[key]; } @@ -451,7 +551,10 @@ const FlowEditor: React.FC<{ let inputData = getNestedData(blockSchema, node.data.hardcodedValues); - console.log(`Final prepared input for ${node.data.blockType} (${node.id}):`, inputData); + console.log( + `Final prepared input for ${node.data.blockType} (${node.id}):`, + inputData, + ); return inputData; }; @@ -461,32 +564,34 @@ const FlowEditor: React.FC<{ ...node, data: { ...node.data, - hardcodedValues: removeEmptyStringsAndNulls(node.data.hardcodedValues), + hardcodedValues: removeEmptyStringsAndNulls( + node.data.hardcodedValues, + ), status: undefined, }, - })) + })), ); await new Promise((resolve) => setTimeout(resolve, 100)); console.log("All nodes before formatting:", nodes); const blockIdToNodeIdMap = {}; - const formattedNodes = nodes.map(node => { - nodes.forEach(node => { + const formattedNodes = nodes.map((node) => { + nodes.forEach((node) => { const key = `${node.data.block_id}_${node.position.x}_${node.position.y}`; blockIdToNodeIdMap[key] = node.id; }); const inputDefault = prepareNodeInputData(node, nodes, edges); const inputNodes = edges - .filter(edge => edge.target === node.id) - .map(edge => ({ - name: edge.targetHandle || '', + .filter((edge) => edge.target === node.id) + .map((edge) => ({ + name: edge.targetHandle || "", node_id: edge.source, })); const outputNodes = edges - .filter(edge => edge.source === node.id) - .map(edge => ({ - name: edge.sourceHandle || '', + .filter((edge) => edge.source === node.id) + .map((edge) => ({ + name: edge.sourceHandle || "", node_id: edge.target, })); @@ -498,71 +603,79 @@ const FlowEditor: React.FC<{ output_nodes: outputNodes, data: { ...node.data, - hardcodedValues: removeEmptyStringsAndNulls(node.data.hardcodedValues), + hardcodedValues: removeEmptyStringsAndNulls( + node.data.hardcodedValues, + ), }, - metadata: { position: node.position } + metadata: { position: node.position }, }; }); - const links = edges.map(edge => ({ + const links = edges.map((edge) => ({ source_id: edge.source, sink_id: edge.target, - source_name: edge.sourceHandle || '', - sink_name: edge.targetHandle || '' + source_name: edge.sourceHandle || "", + sink_name: edge.targetHandle || "", })); const payload = { id: savedAgent?.id!, - name: agentName || 'Agent Name', - description: agentDescription || 'Agent Description', + name: agentName || "Agent Name", + description: agentDescription || "Agent Description", nodes: formattedNodes, - links: links // Ensure this field is included + links: links, // Ensure this field is included }; if (savedAgent && deepEquals(payload, savedAgent)) { console.debug("No need to save: Graph is the same as version on server"); return; } else { - console.debug("Saving new Graph version; old vs new:", savedAgent, payload); + console.debug( + "Saving new Graph version; old vs new:", + savedAgent, + payload, + ); } const newSavedAgent = savedAgent ? await (savedAgent.is_template - ? api.updateTemplate(savedAgent.id, payload) - : api.updateGraph(savedAgent.id, payload)) + ? api.updateTemplate(savedAgent.id, payload) + : api.updateGraph(savedAgent.id, payload)) : await (asTemplate - ? api.createTemplate(payload) - : api.createGraph(payload)); - console.debug('Response from the API:', newSavedAgent); + ? api.createTemplate(payload) + : api.createGraph(payload)); + console.debug("Response from the API:", newSavedAgent); setSavedAgent(newSavedAgent); // Update the node IDs in the frontend - const updatedNodes = newSavedAgent.nodes.map(backendNode => { - const key = `${backendNode.block_id}_${backendNode.metadata.position.x}_${backendNode.metadata.position.y}`; - const frontendNodeId = blockIdToNodeIdMap[key]; - const frontendNode = nodes.find(node => node.id === frontendNodeId); + const updatedNodes = newSavedAgent.nodes + .map((backendNode) => { + const key = `${backendNode.block_id}_${backendNode.metadata.position.x}_${backendNode.metadata.position.y}`; + const frontendNodeId = blockIdToNodeIdMap[key]; + const frontendNode = nodes.find((node) => node.id === frontendNodeId); - return frontendNode - ? { - ...frontendNode, - position: backendNode.metadata.position, - data: { - ...frontendNode.data, - backend_id: backendNode.id, - }, - } - : null; - }).filter(node => node !== null); + return frontendNode + ? { + ...frontendNode, + position: backendNode.metadata.position, + data: { + ...frontendNode.data, + backend_id: backendNode.id, + }, + } + : null; + }) + .filter((node) => node !== null); setNodes(updatedNodes); return newSavedAgent.id; - }; + } const validateNodes = (): boolean => { let isValid = true; - nodes.forEach(node => { + nodes.forEach((node) => { const validate = ajv.compile(node.data.inputSchema); const errors = {} as { [key: string]: string | null }; @@ -574,14 +687,22 @@ const FlowEditor: React.FC<{ // Skip error if there's an edge connected const path = error.instancePath || error.schemaPath; const handle = path.split(/[\/.]/)[0]; - if (node.data.connections.some(conn => conn.target === node.id || conn.targetHandle === handle)) { + if ( + node.data.connections.some( + (conn) => conn.target === node.id || conn.targetHandle === handle, + ) + ) { return; } isValid = false; if (path && error.message) { const key = path.slice(1); console.log("Error", key, error.message); - setNestedProperty(errors, key, error.message[0].toUpperCase() + error.message.slice(1)); + setNestedProperty( + errors, + key, + error.message[0].toUpperCase() + error.message.slice(1), + ); } else if (error.keyword === "required") { const key = error.params.missingProperty; setNestedProperty(errors, key, "This field is required"); @@ -598,27 +719,30 @@ const FlowEditor: React.FC<{ try { const newAgentId = await saveAgent(); if (!newAgentId) { - console.error('Error saving agent; aborting run'); + console.error("Error saving agent; aborting run"); return; } if (!validateNodes()) { - console.error('Validation failed; aborting run'); + console.error("Validation failed; aborting run"); return; } api.subscribeToExecution(newAgentId); api.runGraph(newAgentId); - } catch (error) { - console.error('Error running agent:', error); + console.error("Error running agent:", error); } }; - const updateNodesWithExecutionData = (executionData: NodeExecutionResult[]) => { + const updateNodesWithExecutionData = ( + executionData: NodeExecutionResult[], + ) => { setNodes((nds) => nds.map((node) => { - const nodeExecution = executionData.find((exec) => exec.node_id === node.data.backend_id); + const nodeExecution = executionData.find( + (exec) => exec.node_id === node.data.backend_id, + ); if (nodeExecution) { return { ...node, @@ -631,71 +755,86 @@ const FlowEditor: React.FC<{ }; } return node; - }) + }), ); }; - const handleKeyDown = useCallback((event: KeyboardEvent) => { - if (isAnyModalOpen) return; // Prevent copy/paste if any modal is open + const handleKeyDown = useCallback( + (event: KeyboardEvent) => { + if (isAnyModalOpen) return; // Prevent copy/paste if any modal is open - if (event.ctrlKey || event.metaKey) { - if (event.key === 'c' || event.key === 'C') { - // Copy selected nodes - const selectedNodes = nodes.filter(node => node.selected); - const selectedEdges = edges.filter(edge => edge.selected); - setCopiedNodes(selectedNodes); - setCopiedEdges(selectedEdges); - } - if (event.key === 'v' || event.key === 'V') { - // Paste copied nodes - if (copiedNodes.length > 0) { - const newNodes = copiedNodes.map((node, index) => { - const newNodeId = (nodeId + index).toString(); - return { - ...node, - id: newNodeId, - position: { - x: node.position.x + 20, // Offset pasted nodes - y: node.position.y + 20, - }, - data: { - ...node.data, - status: undefined, // Reset status - output_data: undefined, // Clear output data - setHardcodedValues: (values: { [key: string]: any }) => { - setNodes((nds) => nds.map((n) => - n.id === newNodeId - ? { ...n, data: { ...n.data, hardcodedValues: values } } - : n - )); + if (event.ctrlKey || event.metaKey) { + if (event.key === "c" || event.key === "C") { + // Copy selected nodes + const selectedNodes = nodes.filter((node) => node.selected); + const selectedEdges = edges.filter((edge) => edge.selected); + setCopiedNodes(selectedNodes); + setCopiedEdges(selectedEdges); + } + if (event.key === "v" || event.key === "V") { + // Paste copied nodes + if (copiedNodes.length > 0) { + const newNodes = copiedNodes.map((node, index) => { + const newNodeId = (nodeId + index).toString(); + return { + ...node, + id: newNodeId, + position: { + x: node.position.x + 20, // Offset pasted nodes + y: node.position.y + 20, }, - }, - }; - }); - const updatedNodes = nodes.map(node => ({ ...node, selected: false })); // Deselect old nodes - setNodes([...updatedNodes, ...newNodes]); - setNodeId(prevId => prevId + copiedNodes.length); + data: { + ...node.data, + status: undefined, // Reset status + output_data: undefined, // Clear output data + setHardcodedValues: (values: { [key: string]: any }) => { + setNodes((nds) => + nds.map((n) => + n.id === newNodeId + ? { + ...n, + data: { ...n.data, hardcodedValues: values }, + } + : n, + ), + ); + }, + }, + }; + }); + const updatedNodes = nodes.map((node) => ({ + ...node, + selected: false, + })); // Deselect old nodes + setNodes([...updatedNodes, ...newNodes]); + setNodeId((prevId) => prevId + copiedNodes.length); - const newEdges = copiedEdges.map(edge => { - const newSourceId = newNodes.find(n => n.data.title === edge.source)?.id || edge.source; - const newTargetId = newNodes.find(n => n.data.title === edge.target)?.id || edge.target; - return { - ...edge, - id: `${newSourceId}_${edge.sourceHandle}_${newTargetId}_${edge.targetHandle}_${Date.now()}`, - source: newSourceId, - target: newTargetId, - }; - }); - setEdges([...edges, ...newEdges]); + const newEdges = copiedEdges.map((edge) => { + const newSourceId = + newNodes.find((n) => n.data.title === edge.source)?.id || + edge.source; + const newTargetId = + newNodes.find((n) => n.data.title === edge.target)?.id || + edge.target; + return { + ...edge, + id: `${newSourceId}_${edge.sourceHandle}_${newTargetId}_${edge.targetHandle}_${Date.now()}`, + source: newSourceId, + target: newTargetId, + }; + }); + setEdges([...edges, ...newEdges]); + } } } - } - }, [nodes, edges, copiedNodes, copiedEdges, nodeId, isAnyModalOpen]); + }, + [nodes, edges, copiedNodes, copiedEdges, nodeId, isAnyModalOpen], + ); useEffect(() => { - window.addEventListener('keydown', handleKeyDown); + window.addEventListener("keydown", handleKeyDown); return () => { - window.removeEventListener('keydown', handleKeyDown); + window.removeEventListener("keydown", handleKeyDown); }; }, [handleKeyDown]); @@ -705,27 +844,33 @@ const FlowEditor: React.FC<{ const editorControls: Control[] = [ { - label: 'Undo', + label: "Undo", icon: , onClick: handleUndo, }, { - label: 'Redo', + label: "Redo", icon: , onClick: handleRedo, }, { - label: 'Run', - icon: , + label: "Run", + icon: , onClick: runAgent, - } + }, ]; return (
({ ...node, data: { ...node.data, setIsAnyModalOpen } }))} - edges={edges.map(edge => ({...edge, data: { ...edge.data, clearNodesStatusAndOutput } }))} + nodes={nodes.map((node) => ({ + ...node, + data: { ...node.data, setIsAnyModalOpen }, + }))} + edges={edges.map((edge) => ({ + ...edge, + data: { ...edge.data, clearNodesStatusAndOutput }, + }))} onNodesChange={onNodesChange} onEdgesChange={onEdgesChange} onConnect={onConnect} @@ -739,13 +884,13 @@ const FlowEditor: React.FC<{ onNodeDragStop={onNodesChangeEnd} >
- +
diff --git a/rnd/autogpt_builder/src/components/InputModalComponent.tsx b/rnd/autogpt_builder/src/components/InputModalComponent.tsx index a80299758..799fdbeec 100644 --- a/rnd/autogpt_builder/src/components/InputModalComponent.tsx +++ b/rnd/autogpt_builder/src/components/InputModalComponent.tsx @@ -1,6 +1,6 @@ -import React, { FC, useEffect, useRef } from 'react'; -import { Button } from './ui/button'; -import { Textarea } from './ui/textarea'; +import React, { FC, useEffect, useRef } from "react"; +import { Button } from "./ui/button"; +import { Textarea } from "./ui/textarea"; interface ModalProps { isOpen: boolean; @@ -9,7 +9,12 @@ interface ModalProps { value: string; } -const InputModalComponent: FC = ({ isOpen, onClose, onSave, value }) => { +const InputModalComponent: FC = ({ + isOpen, + onClose, + onSave, + value, +}) => { const [tempValue, setTempValue] = React.useState(value); const textAreaRef = useRef(null); @@ -34,7 +39,9 @@ const InputModalComponent: FC = ({ isOpen, onClose, onSave, value }) return (
-

Enter input text

+
+

Enter input text

+