fix(builder): Apply Prettier Formatting (#7695)

formatting
pull/7697/head
Swifty 2024-08-05 09:18:08 +02:00 committed by GitHub
parent 6fa7d22c91
commit c7fdfa0f77
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
65 changed files with 2722 additions and 2030 deletions

View File

@ -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

View File

@ -14,4 +14,4 @@
"components": "@/components",
"utils": "@/lib/utils"
}
}
}

View File

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

View File

@ -1,6 +1,6 @@
"use client";
import { useEffect, useState } from 'react';
import { useEffect, useState } from "react";
export default function AuthErrorPage() {
const [errorType, setErrorType] = useState<string | null>(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
}
}, []);

View File

@ -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`);
}

View File

@ -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");
}

View File

@ -1,3 +1,3 @@
export default function ErrorPage() {
return <p>Sorry, something went wrong</p>
return <p>Sorry, something went wrong</p>;
}

View File

@ -8,8 +8,6 @@
}
}
@layer base {
:root {
--background: 0 0% 100%;
@ -67,7 +65,6 @@
}
}
@layer base {
* {
@apply border-border;

View File

@ -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 (
<html lang="en">
<body className={
cn(
'antialiased transition-colors',
inter.className
)
}>
return (
<html lang="en">
<body className={cn("antialiased transition-colors", inter.className)}>
<Providers
attribute="class"
defaultTheme="light"
// Feel free to remove this line if you want to use the system theme by default
// enableSystem
disableTransitionOnChange
attribute="class"
defaultTheme="light"
// Feel free to remove this line if you want to use the system theme by default
// enableSystem
disableTransitionOnChange
>
<div className="flex flex-col min-h-screen ">
<NavBar/>
<main className="flex-1 p-4 overflow-hidden">
{children}
</main>
</div>
<div className="flex flex-col min-h-screen ">
<NavBar />
<main className="flex-1 p-4 overflow-hidden">{children}</main>
</div>
</Providers>
</body>
</html>
);
</body>
</html>
);
}

View File

@ -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<typeof loginFormSchema>) {
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<typeof loginFormSchema>) {
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");
}

View File

@ -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 (
<div className="flex justify-center items-center h-[80vh]">
<FaSpinner className="mr-2 h-16 w-16 animate-spin"/>
<FaSpinner className="mr-2 h-16 w-16 animate-spin" />
</div>
);
}
if (!supabase) {
return <div>User accounts are disabled because Supabase client is unavailable</div>
return (
<div>
User accounts are disabled because Supabase client is unavailable
</div>
);
}
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<typeof loginFormSchema>) => {
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<typeof loginFormSchema>) => {
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 (
<div className="flex items-center justify-center h-[80vh]">
<div className="w-full max-w-md p-8 rounded-lg shadow-md space-y-6">
<div className='mb-6 space-y-2'>
<Button className="w-full" onClick={() => handleSignInWithProvider('google')} variant="outline" type="button" disabled={isLoading}>
<div className="mb-6 space-y-2">
<Button
className="w-full"
onClick={() => handleSignInWithProvider("google")}
variant="outline"
type="button"
disabled={isLoading}
>
<FaGoogle className="mr-2 h-4 w-4" />
Sign in with Google
</Button>
<Button className="w-full" onClick={() => handleSignInWithProvider('github')} variant="outline" type="button" disabled={isLoading}>
<Button
className="w-full"
onClick={() => handleSignInWithProvider("github")}
variant="outline"
type="button"
disabled={isLoading}
>
<FaGithub className="mr-2 h-4 w-4" />
Sign in with GitHub
</Button>
<Button className="w-full" onClick={() => handleSignInWithProvider('discord')} variant="outline" type="button" disabled={isLoading}>
<Button
className="w-full"
onClick={() => handleSignInWithProvider("discord")}
variant="outline"
type="button"
disabled={isLoading}
>
<FaDiscord className="mr-2 h-4 w-4" />
Sign in with Discord
</Button>
@ -117,7 +151,7 @@ export default function LoginPage() {
control={form.control}
name="email"
render={({ field }) => (
<FormItem className='mb-4'>
<FormItem className="mb-4">
<FormLabel>Email</FormLabel>
<FormControl>
<Input placeholder="user@email.com" {...field} />
@ -142,13 +176,17 @@ export default function LoginPage() {
</FormItem>
)}
/>
<div className='flex w-full space-x-4 mt-6 mb-6'>
<Button className='w-1/2 flex justify-center' type="submit" disabled={isLoading}>
<div className="flex w-full space-x-4 mt-6 mb-6">
<Button
className="w-1/2 flex justify-center"
type="submit"
disabled={isLoading}
>
Log in
</Button>
<Button
className='w-1/2 flex justify-center'
variant='outline'
className="w-1/2 flex justify-center"
variant="outline"
type="button"
onClick={form.handleSubmit(onSignup)}
disabled={isLoading}
@ -157,12 +195,12 @@ export default function LoginPage() {
</Button>
</div>
</form>
<p className='text-red-500 text-sm'>{feedback}</p>
<p className='text-primary text-center text-sm'>
<p className="text-red-500 text-sm">{feedback}</p>
<p className="text-primary text-center text-sm">
By continuing you agree to everything
</p>
</Form>
</div>
</div>
)
);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,15 @@
"use client";
import { useSupabase } from '@/components/SupabaseProvider';
import { Button } from '@/components/ui/button'
import useUser from '@/hooks/useUser';
import { useRouter } from 'next/navigation';
import { FaSpinner } from 'react-icons/fa';
import { useSupabase } from "@/components/SupabaseProvider";
import { Button } from "@/components/ui/button";
import useUser from "@/hooks/useUser";
import { useRouter } from "next/navigation";
import { FaSpinner } from "react-icons/fa";
export default function PrivatePage() {
const { user, isLoading, error } = useUser()
const { supabase } = useSupabase()
const router = useRouter()
const { user, isLoading, error } = useUser();
const { supabase } = useSupabase();
const router = useRouter();
if (isLoading) {
return (
@ -20,8 +20,8 @@ export default function PrivatePage() {
}
if (error || !user || !supabase) {
router.push('/login')
return null
router.push("/login");
return null;
}
return (
@ -29,5 +29,5 @@ export default function PrivatePage() {
<p>Hello {user.email}</p>
<Button onClick={() => supabase.auth.signOut()}>Log out</Button>
</div>
)
);
}

View File

@ -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) {
<TooltipProvider>{children}</TooltipProvider>
</SupabaseProvider>
</NextThemesProvider>
)
);
}

View File

@ -1,9 +1,23 @@
import { BaseEdge, ConnectionLineComponentProps, getBezierPath, Position } from "reactflow";
import {
BaseEdge,
ConnectionLineComponentProps,
getBezierPath,
Position,
} from "reactflow";
const ConnectionLine: React.FC<ConnectionLineComponentProps> = ({ 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<ConnectionLineComponentProps> = ({
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<ConnectionLineComponentProps> = ({ fromPosition,
targetPosition: toPosition,
});
return (
<BaseEdge path={path} style={{ strokeWidth: 2, stroke: '#555' }} />
);
return <BaseEdge path={path} style={{ strokeWidth: 2, stroke: "#555" }} />;
};
export default ConnectionLine;

View File

@ -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<EdgeProps<CustomEdgeData>> = ({ id, data, selected, source, sourcePosition, sourceX, sourceY, target, targetPosition, targetX, targetY, markerEnd }) => {
const CustomEdgeFC: FC<EdgeProps<CustomEdgeData>> = ({
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<EdgeProps<CustomEdgeData>> = ({ 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<EdgeProps<CustomEdgeData>> = ({ 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"),
}}
/>
<path
@ -58,16 +95,16 @@ const CustomEdgeFC: FC<EdgeProps<CustomEdgeData>> = ({ id, data, selected, sourc
<EdgeLabelRenderer>
<div
style={{
position: 'absolute',
position: "absolute",
transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`,
pointerEvents: 'all',
pointerEvents: "all",
}}
className="edge-label-renderer"
>
<button
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
className={`edge-label-button ${isHovered ? 'visible' : ''}`}
className={`edge-label-button ${isHovered ? "visible" : ""}`}
onClick={onEdgeClick}
>
<X className="size-4" />
@ -75,7 +112,7 @@ const CustomEdgeFC: FC<EdgeProps<CustomEdgeData>> = ({ id, data, selected, sourc
</div>
</EdgeLabelRenderer>
</>
)
);
};
export const CustomEdge = memo(CustomEdgeFC);
export const CustomEdge = memo(CustomEdgeFC);

View File

@ -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<NodeProps<CustomNodeData>> = ({ data, id }) => {
const [isAdvancedOpen, setIsAdvancedOpen] = useState(false);
const [isModalOpen, setIsModalOpen] = useState(false);
const [activeKey, setActiveKey] = useState<string | null>(null);
const [modalValue, setModalValue] = useState<string>('');
const [modalValue, setModalValue] = useState<string>("");
const [isOutputModalOpen, setIsOutputModalOpen] = useState(false);
const [isHovered, setIsHovered] = useState(false);
const { getNode, setNodes, getEdges, setEdges } = useReactFlow();
const outputDataRef = useRef<HTMLDivElement>(null);
@ -73,9 +87,12 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ 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<NodeProps<CustomNodeData>> = ({ data, id }) => {
const keys = Object.keys(schema.properties);
return keys.map((key) => (
<div key={key}>
<NodeHandle keyName={key} isConnected={isHandleConnected(key)} schema={schema.properties[key]} side="right" />
<NodeHandle
keyName={key}
isConnected={isHandleConnected(key)}
schema={schema.properties[key]}
side="right"
/>
</div>
));
};
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<NodeProps<CustomNodeData>> = ({ 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<NodeProps<CustomNodeData>> = ({ 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<NodeProps<CustomNodeData>> = ({ 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 (
<div
className={`custom-node dark-theme ${data.status?.toLowerCase() ?? ''}`}
<div
className={`custom-node dark-theme ${data.status?.toLowerCase() ?? ""}`}
onMouseEnter={handleHovered}
onMouseLeave={handleMouseLeave}
>
>
<div className="mb-2">
<div className="text-lg font-bold">{beautifyString(data.blockType?.replace(/Block$/, '') || data.title)}</div>
<div className="text-lg font-bold">
{beautifyString(data.blockType?.replace(/Block$/, "") || data.title)}
</div>
<div className="node-actions">
{isHovered && (
<>
@ -253,19 +300,28 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
{data.inputSchema &&
Object.entries(data.inputSchema.properties).map(([key, schema]) => {
const isRequired = data.inputSchema.required?.includes(key);
return (isRequired || isAdvancedOpen) && (
<div key={key} onMouseOver={() => { }}>
<NodeHandle keyName={key} isConnected={isHandleConnected(key)} isRequired={isRequired} schema={schema} side="left" />
{!isHandleConnected(key) &&
<NodeInputField
return (
(isRequired || isAdvancedOpen) && (
<div key={key} onMouseOver={() => {}}>
<NodeHandle
keyName={key}
isConnected={isHandleConnected(key)}
isRequired={isRequired}
schema={schema}
value={getValue(key)}
handleInputClick={handleInputClick}
handleInputChange={handleInputChange}
errors={data.errors?.[key]}
/>}
</div>
side="left"
/>
{!isHandleConnected(key) && (
<NodeInputField
keyName={key}
schema={schema}
value={getValue(key)}
handleInputClick={handleInputClick}
handleInputChange={handleInputChange}
errors={data.errors?.[key]}
/>
)}
</div>
)
);
})}
</div>
@ -276,17 +332,20 @@ const CustomNode: FC<NodeProps<CustomNodeData>> = ({ data, id }) => {
{isOutputOpen && (
<div className="node-output" onClick={handleOutputClick}>
<p>
<strong>Status:</strong>{' '}
{typeof data.status === 'object' ? JSON.stringify(data.status) : data.status || 'N/A'}
<strong>Status:</strong>{" "}
{typeof data.status === "object"
? JSON.stringify(data.status)
: data.status || "N/A"}
</p>
<p>
<strong>Output Data:</strong>{' '}
<strong>Output Data:</strong>{" "}
{(() => {
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<NodeProps<CustomNodeData>> = ({ data, id }) => {
</div>
)}
<div className="flex items-center mt-2.5">
<Switch onCheckedChange={toggleOutput} className='custom-switch' />
<span className='m-1 mr-4'>Output</span>
<Switch onCheckedChange={toggleOutput} className="custom-switch" />
<span className="m-1 mr-4">Output</span>
{hasOptionalFields() && (
<>
<Switch onCheckedChange={toggleAdvancedSettings} className='custom-switch' />
<span className='m-1'>Advanced</span>
<Switch
onCheckedChange={toggleAdvancedSettings}
className="custom-switch"
/>
<span className="m-1">Advanced</span>
</>
)}
</div>

File diff suppressed because it is too large Load Diff

View File

@ -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<ModalProps> = ({ isOpen, onClose, onSave, value }) => {
const InputModalComponent: FC<ModalProps> = ({
isOpen,
onClose,
onSave,
value,
}) => {
const [tempValue, setTempValue] = React.useState(value);
const textAreaRef = useRef<HTMLTextAreaElement>(null);
@ -34,7 +39,9 @@ const InputModalComponent: FC<ModalProps> = ({ isOpen, onClose, onSave, value })
return (
<div className="nodrag fixed inset-0 bg-white bg-opacity-60 flex justify-center items-center">
<div className="bg-white p-5 rounded-lg w-[500px] max-w-[90%]">
<center><h1>Enter input text</h1></center>
<center>
<h1>Enter input text</h1>
</center>
<Textarea
ref={textAreaRef}
className="w-full h-[200px] p-2.5 rounded border border-[#dfdfdf] text-black bg-[#dfdfdf]"

View File

@ -1,7 +1,8 @@
import {
DropdownMenu,
DropdownMenuContent, DropdownMenuItem,
DropdownMenuTrigger
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import Link from "next/link";
import { CircleUser, Menu, SquareActivity, Workflow } from "lucide-react";
@ -16,10 +17,11 @@ import ProfileDropdown from "./ProfileDropdown";
export async function NavBar() {
const isAvailable = Boolean(
process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
process.env.NEXT_PUBLIC_SUPABASE_URL &&
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
);
const { user } = await getServerUser();
return (
<header className="sticky top-0 flex h-16 items-center gap-4 border-b bg-background px-4 md:px-6">
<div className="flex items-center gap-4 flex-1">
@ -84,13 +86,15 @@ export async function NavBar() {
</a>
</div>
<div className="flex items-center gap-4 flex-1 justify-end">
{isAvailable && !user &&
{isAvailable && !user && (
<Link
href="/login"
className="text-muted-foreground hover:text-foreground flex flex-row gap-2 items-center"
>
Log In<CircleUser className="size-5" />
</Link>}
Log In
<CircleUser className="size-5" />
</Link>
)}
{isAvailable && user && <ProfileDropdown />}
</div>
</header>

View File

@ -5,47 +5,55 @@ import { Handle, Position } from "reactflow";
import SchemaTooltip from "./SchemaTooltip";
type HandleProps = {
keyName: string,
schema: BlockIOSchema,
isConnected: boolean,
isRequired?: boolean,
side: 'left' | 'right'
}
const NodeHandle: FC<HandleProps> = ({ keyName, schema, isConnected, isRequired, side }) => {
keyName: string;
schema: BlockIOSchema;
isConnected: boolean;
isRequired?: boolean;
side: "left" | "right";
};
const NodeHandle: FC<HandleProps> = ({
keyName,
schema,
isConnected,
isRequired,
side,
}) => {
const typeName: Record<string, string> = {
string: 'text',
number: 'number',
boolean: 'true/false',
object: 'complex',
array: 'list',
null: 'null',
string: "text",
number: "number",
boolean: "true/false",
object: "complex",
array: "list",
null: "null",
};
const typeClass = `text-sm ${getTypeTextColor(schema.type)} ${side === 'left' ? 'text-left' : 'text-right'}`;
const typeClass = `text-sm ${getTypeTextColor(schema.type)} ${side === "left" ? "text-left" : "text-right"}`;
const label = (
<div className="flex flex-col flex-grow">
<span className="text-m text-gray-900 -mb-1 green">
{schema.title || beautifyString(keyName)}{isRequired ? '*' : ''}
{schema.title || beautifyString(keyName)}
{isRequired ? "*" : ""}
</span>
<span className={typeClass}>{typeName[schema.type]}</span>
</div>
);
const dot = (
<div className={`w-4 h-4 m-1 ${isConnected ? getTypeBgColor(schema.type) : 'bg-gray-600'} rounded-full transition-colors duration-100 group-hover:bg-gray-300`} />
<div
className={`w-4 h-4 m-1 ${isConnected ? getTypeBgColor(schema.type) : "bg-gray-600"} rounded-full transition-colors duration-100 group-hover:bg-gray-300`}
/>
);
if (side === 'left') {
if (side === "left") {
return (
<div key={keyName} className="handle-container">
<Handle
type="target"
position={Position.Left}
id={keyName}
className='group -ml-[29px]'
className="group -ml-[29px]"
>
<div className="pointer-events-none flex items-center">
{dot}
@ -54,7 +62,7 @@ const NodeHandle: FC<HandleProps> = ({ keyName, schema, isConnected, isRequired,
</Handle>
<SchemaTooltip schema={schema} />
</div>
)
);
} else {
return (
<div key={keyName} className="handle-container justify-end">
@ -62,16 +70,16 @@ const NodeHandle: FC<HandleProps> = ({ keyName, schema, isConnected, isRequired,
type="source"
position={Position.Right}
id={keyName}
className='group -mr-[29px]'
className="group -mr-[29px]"
>
<div className="pointer-events-none flex items-center">
{label}
{dot}
</div>
</Handle>
</div >
)
</div>
);
}
}
};
export default NodeHandle;

View File

@ -6,48 +6,64 @@ import { Button } from "./ui/button";
import { Input } from "./ui/input";
type BlockInputFieldProps = {
keyName: string
schema: BlockIOSchema
parentKey?: string
value: string | Array<string> | { [key: string]: string }
handleInputClick: (key: string) => void
handleInputChange: (key: string, value: any) => void
errors?: { [key: string]: string } | string | null
}
keyName: string;
schema: BlockIOSchema;
parentKey?: string;
value: string | Array<string> | { [key: string]: string };
handleInputClick: (key: string) => void;
handleInputChange: (key: string, value: any) => void;
errors?: { [key: string]: string } | string | null;
};
const NodeInputField: FC<BlockInputFieldProps> = ({
keyName: key,
schema,
parentKey = '',
parentKey = "",
value,
handleInputClick,
handleInputChange,
errors
errors,
}) => {
const fullKey = parentKey ? `${parentKey}.${key}` : key;
const error = typeof errors === 'string' ? errors : errors?.[key] ?? "";
const error = typeof errors === "string" ? errors : (errors?.[key] ?? "");
const displayKey = schema.title || beautifyString(key);
const [keyValuePairs, _setKeyValuePairs] = useState<{ key: string, value: string }[]>(
const [keyValuePairs, _setKeyValuePairs] = useState<
{ key: string; value: string }[]
>(
"additionalProperties" in schema && value
? Object.entries(value).map(([key, value]) => ({ key: key, value: value }))
: []
? Object.entries(value).map(([key, value]) => ({
key: key,
value: value,
}))
: [],
);
function setKeyValuePairs(newKVPairs: typeof keyValuePairs): void {
_setKeyValuePairs(newKVPairs);
handleInputChange(
fullKey,
newKVPairs.reduce((obj, { key, value }) => ({ ...obj, [key]: value }), {})
newKVPairs.reduce(
(obj, { key, value }) => ({ ...obj, [key]: value }),
{},
),
);
}
const renderClickableInput = (value: string | null = null, placeholder: string = "", secret: boolean = false) => {
const className = `clickable-input ${error ? 'border-error' : ''}`;
const renderClickableInput = (
value: string | null = null,
placeholder: string = "",
secret: boolean = false,
) => {
const className = `clickable-input ${error ? "border-error" : ""}`;
return secret ? (
<div className={className} onClick={() => handleInputClick(fullKey)}>
{value ? <span>********</span> : <i className="text-gray-500">{placeholder}</i>}
{value ? (
<span>********</span>
) : (
<i className="text-gray-500">{placeholder}</i>
)}
</div>
) : (
<div className={className} onClick={() => handleInputClick(fullKey)}>
@ -77,43 +93,57 @@ const NodeInputField: FC<BlockInputFieldProps> = ({
);
}
if (schema.type === 'object' && schema.additionalProperties) {
if (schema.type === "object" && schema.additionalProperties) {
return (
<div key={fullKey}>
<div>
{keyValuePairs.map(({ key, value }, index) => (
<div key={index} className="flex items-center w-[325px] space-x-2 mb-2">
<div
key={index}
className="flex items-center w-[325px] space-x-2 mb-2"
>
<Input
type="text"
placeholder="Key"
value={key}
onChange={(e) => setKeyValuePairs(
keyValuePairs.toSpliced(index, 1, {
key: e.target.value, value: value
})
)}
onChange={(e) =>
setKeyValuePairs(
keyValuePairs.toSpliced(index, 1, {
key: e.target.value,
value: value,
}),
)
}
/>
<Input
type="text"
placeholder="Value"
value={value}
onChange={(e) => setKeyValuePairs(
keyValuePairs.toSpliced(index, 1, {
key: key, value: e.target.value
})
)}
onChange={(e) =>
setKeyValuePairs(
keyValuePairs.toSpliced(index, 1, {
key: key,
value: e.target.value,
}),
)
}
/>
<Button variant="ghost" className="px-2"
onClick={() => setKeyValuePairs(keyValuePairs.toSpliced(index, 1))}
<Button
variant="ghost"
className="px-2"
onClick={() =>
setKeyValuePairs(keyValuePairs.toSpliced(index, 1))
}
>
<Cross2Icon />
</Button>
</div>
))}
<Button className="w-full"
onClick={() => setKeyValuePairs(
keyValuePairs.concat({ key: "", value: "" })
)}
<Button
className="w-full"
onClick={() =>
setKeyValuePairs(keyValuePairs.concat({ key: "", value: "" }))
}
>
<PlusIcon className="mr-2" /> Add Property
</Button>
@ -124,11 +154,14 @@ const NodeInputField: FC<BlockInputFieldProps> = ({
}
if ("anyOf" in schema) {
const types = schema.anyOf.map(s => "type" in s ? s.type : undefined);
if (types.includes('string') && types.includes('null')) {
const types = schema.anyOf.map((s) => ("type" in s ? s.type : undefined));
if (types.includes("string") && types.includes("null")) {
return (
<div key={fullKey} className="input-container">
{renderClickableInput(value as string, schema.placeholder || `Enter ${displayKey} (optional)`)}
{renderClickableInput(
value as string,
schema.placeholder || `Enter ${displayKey} (optional)`,
)}
{error && <span className="error-message">{error}</span>}
</div>
);
@ -140,19 +173,21 @@ const NodeInputField: FC<BlockInputFieldProps> = ({
<div key={fullKey} className="object-input">
<strong>{displayKey}:</strong>
{"properties" in schema.allOf[0] &&
Object.entries(schema.allOf[0].properties).map(([propKey, propSchema]) => (
<div key={`${fullKey}.${propKey}`} className="nested-input">
<NodeInputField
keyName={propKey}
schema={propSchema}
parentKey={fullKey}
value={(value as { [key: string]: string })[propKey]}
handleInputClick={handleInputClick}
handleInputChange={handleInputChange}
errors={errors}
/>
</div>
))}
Object.entries(schema.allOf[0].properties).map(
([propKey, propSchema]) => (
<div key={`${fullKey}.${propKey}`} className="nested-input">
<NodeInputField
keyName={propKey}
schema={propSchema}
parentKey={fullKey}
value={(value as { [key: string]: string })[propKey]}
handleInputClick={handleInputClick}
handleInputChange={handleInputChange}
errors={errors}
/>
</div>
),
)}
</div>
);
}
@ -162,19 +197,21 @@ const NodeInputField: FC<BlockInputFieldProps> = ({
<div key={fullKey} className="object-input">
<strong>{displayKey}:</strong>
{"properties" in schema.oneOf[0] &&
Object.entries(schema.oneOf[0].properties).map(([propKey, propSchema]) => (
<div key={`${fullKey}.${propKey}`} className="nested-input">
<NodeInputField
keyName={propKey}
schema={propSchema}
parentKey={fullKey}
value={(value as { [key: string]: string })[propKey]}
handleInputClick={handleInputClick}
handleInputChange={handleInputChange}
errors={errors}
/>
</div>
))}
Object.entries(schema.oneOf[0].properties).map(
([propKey, propSchema]) => (
<div key={`${fullKey}.${propKey}`} className="nested-input">
<NodeInputField
keyName={propKey}
schema={propSchema}
parentKey={fullKey}
value={(value as { [key: string]: string })[propKey]}
handleInputClick={handleInputClick}
handleInputChange={handleInputChange}
errors={errors}
/>
</div>
),
)}
</div>
);
}
@ -183,19 +220,22 @@ const NodeInputField: FC<BlockInputFieldProps> = ({
console.warn(`Schema for input ${key} does not specify a type:`, schema);
return (
<div key={fullKey} className="input-container">
{renderClickableInput(value as string, schema.placeholder || `Enter ${beautifyString(displayKey)} (Complex)`)}
{renderClickableInput(
value as string,
schema.placeholder || `Enter ${beautifyString(displayKey)} (Complex)`,
)}
{error && <span className="error-message">{error}</span>}
</div>
);
}
switch (schema.type) {
case 'string':
case "string":
if (schema.enum) {
return (
<div key={fullKey} className="input-container">
<select
value={value as string || ''}
value={(value as string) || ""}
onChange={(e) => handleInputChange(fullKey, e.target.value)}
className="select-input"
>
@ -214,7 +254,11 @@ const NodeInputField: FC<BlockInputFieldProps> = ({
if (schema.secret) {
return (
<div key={fullKey} className="input-container">
{renderClickableInput(value as string, schema.placeholder || `Enter ${displayKey}`, true)}
{renderClickableInput(
value as string,
schema.placeholder || `Enter ${displayKey}`,
true,
)}
{error && <span className="error-message">{error}</span>}
</div>
);
@ -222,16 +266,21 @@ const NodeInputField: FC<BlockInputFieldProps> = ({
return (
<div key={fullKey} className="input-container">
{renderClickableInput(value as string, schema.placeholder || `Enter ${displayKey}`)}
{renderClickableInput(
value as string,
schema.placeholder || `Enter ${displayKey}`,
)}
{error && <span className="error-message">{error}</span>}
</div>
);
case 'boolean':
case "boolean":
return (
<div key={fullKey} className="input-container">
<select
value={value === undefined ? '' : value.toString()}
onChange={(e) => handleInputChange(fullKey, e.target.value === 'true')}
value={value === undefined ? "" : value.toString()}
onChange={(e) =>
handleInputChange(fullKey, e.target.value === "true")
}
className="select-input"
>
<option value="">Select {displayKey}</option>
@ -241,22 +290,24 @@ const NodeInputField: FC<BlockInputFieldProps> = ({
{error && <span className="error-message">{error}</span>}
</div>
);
case 'number':
case 'integer':
case "number":
case "integer":
return (
<div key={fullKey} className="input-container">
<Input
type="number"
value={value as string || ''}
onChange={(e) => handleInputChange(fullKey, parseFloat(e.target.value))}
className={`number-input ${error ? 'border-error' : ''}`}
value={(value as string) || ""}
onChange={(e) =>
handleInputChange(fullKey, parseFloat(e.target.value))
}
className={`number-input ${error ? "border-error" : ""}`}
/>
{error && <span className="error-message">{error}</span>}
</div>
);
case 'array':
if (schema.items && schema.items.type === 'string') {
const arrayValues = value as Array<string> || [];
case "array":
if (schema.items && schema.items.type === "string") {
const arrayValues = (value as Array<string>) || [];
return (
<div key={fullKey} className="input-container">
{arrayValues.map((item: string, index: number) => (
@ -264,15 +315,23 @@ const NodeInputField: FC<BlockInputFieldProps> = ({
<Input
type="text"
value={item}
onChange={(e) => handleInputChange(`${fullKey}.${index}`, e.target.value)}
onChange={(e) =>
handleInputChange(`${fullKey}.${index}`, e.target.value)
}
className="array-item-input"
/>
<Button onClick={() => handleInputChange(`${fullKey}.${index}`, '')} className="array-item-remove">
<Button
onClick={() => handleInputChange(`${fullKey}.${index}`, "")}
className="array-item-remove"
>
&times;
</Button>
</div>
))}
<Button onClick={() => handleInputChange(fullKey, [...arrayValues, ''])} className="array-item-add">
<Button
onClick={() => handleInputChange(fullKey, [...arrayValues, ""])}
className="array-item-add"
>
Add Item
</Button>
{error && <span className="error-message ml-2">{error}</span>}
@ -284,7 +343,11 @@ const NodeInputField: FC<BlockInputFieldProps> = ({
console.warn(`Schema for input ${key} specifies unknown type:`, schema);
return (
<div key={fullKey} className="input-container">
{renderClickableInput(value as string, schema.placeholder || `Enter ${beautifyString(displayKey)} (Complex)`)}
{renderClickableInput(
value as string,
schema.placeholder ||
`Enter ${beautifyString(displayKey)} (Complex)`,
)}
{error && <span className="error-message">{error}</span>}
</div>
);

View File

@ -1,7 +1,7 @@
import React, { FC, useEffect } from 'react';
import { createPortal } from 'react-dom';
import { Button } from './ui/button';
import { Textarea } from './ui/textarea';
import React, { FC, useEffect } from "react";
import { createPortal } from "react-dom";
import { Button } from "./ui/button";
import { Textarea } from "./ui/textarea";
interface OutputModalProps {
isOpen: boolean;
@ -9,7 +9,11 @@ interface OutputModalProps {
value: string;
}
const OutputModalComponent: FC<OutputModalProps> = ({ isOpen, onClose, value }) => {
const OutputModalComponent: FC<OutputModalProps> = ({
isOpen,
onClose,
value,
}) => {
const [tempValue, setTempValue] = React.useState(value);
useEffect(() => {
@ -25,7 +29,9 @@ const OutputModalComponent: FC<OutputModalProps> = ({ isOpen, onClose, value })
return createPortal(
<div className="fixed inset-0 bg-white bg-opacity-60 flex justify-center items-center z-50">
<div className="bg-white p-5 rounded-lg w-[1000px] max-w-[100%]">
<center><h1 style={{ color: 'black' }}>Full Output</h1></center>
<center>
<h1 style={{ color: "black" }}>Full Output</h1>
</center>
<Textarea
className="w-full h-[400px] p-2.5 rounded border border-[#dfdfdf] text-black bg-[#dfdfdf]"
value={tempValue}
@ -36,7 +42,7 @@ const OutputModalComponent: FC<OutputModalProps> = ({ isOpen, onClose, value })
</div>
</div>
</div>,
document.body
document.body,
);
};

View File

@ -1,48 +1,43 @@
import { forwardRef, useState } from "react"
import { EyeIcon, EyeOffIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input, InputProps } from "@/components/ui/input"
import { cn } from "@/lib/utils"
import { forwardRef, useState } from "react";
import { EyeIcon, EyeOffIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input, InputProps } from "@/components/ui/input";
import { cn } from "@/lib/utils";
const PasswordInput = forwardRef<HTMLInputElement, InputProps>(
({ className, ...props }, ref) => {
const [showPassword, setShowPassword] = useState(false)
const disabled = props.value === "" || props.value === undefined || props.disabled
({ className, ...props }, ref) => {
const [showPassword, setShowPassword] = useState(false);
const disabled =
props.value === "" || props.value === undefined || props.disabled;
return (
<div className="relative">
<Input
type={showPassword ? "text" : "password"}
className={cn("hide-password-toggle pr-10", className)}
ref={ref}
{...props}
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute top-0 right-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowPassword((prev) => !prev)}
disabled={disabled}
>
{showPassword && !disabled ? (
<EyeIcon
className="w-4 h-4"
aria-hidden="true"
/>
) : (
<EyeOffIcon
className="w-4 h-4"
aria-hidden="true"
/>
)}
<span className="sr-only">
{showPassword ? "Hide password" : "Show password"}
</span>
</Button>
return (
<div className="relative">
<Input
type={showPassword ? "text" : "password"}
className={cn("hide-password-toggle pr-10", className)}
ref={ref}
{...props}
/>
<Button
type="button"
variant="ghost"
size="sm"
className="absolute top-0 right-0 h-full px-3 py-2 hover:bg-transparent"
onClick={() => setShowPassword((prev) => !prev)}
disabled={disabled}
>
{showPassword && !disabled ? (
<EyeIcon className="w-4 h-4" aria-hidden="true" />
) : (
<EyeOffIcon className="w-4 h-4" aria-hidden="true" />
)}
<span className="sr-only">
{showPassword ? "Hide password" : "Show password"}
</span>
</Button>
{/* hides browsers password toggles */}
<style>{`
{/* hides browsers password toggles */}
<style>{`
.hide-password-toggle::-ms-reveal,
.hide-password-toggle::-ms-clear {
visibility: hidden;
@ -50,10 +45,10 @@ const PasswordInput = forwardRef<HTMLInputElement, InputProps>(
display: none;
}
`}</style>
</div>
)
},
)
PasswordInput.displayName = "PasswordInput"
</div>
);
},
);
PasswordInput.displayName = "PasswordInput";
export { PasswordInput }
export { PasswordInput };

View File

@ -1,7 +1,10 @@
"use client";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import {
DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button } from "./ui/button";
import { useSupabase } from "./SupabaseProvider";
@ -23,11 +26,15 @@ const ProfileDropdown = () => {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => router.push('profile')}>Profile</DropdownMenuItem>
<DropdownMenuItem onClick={() => supabase?.auth.signOut()}>Log out</DropdownMenuItem>
<DropdownMenuItem onClick={() => router.push("profile")}>
Profile
</DropdownMenuItem>
<DropdownMenuItem onClick={() => supabase?.auth.signOut()}>
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}
);
};
export default ProfileDropdown;

View File

@ -3,10 +3,10 @@ import {
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
} from "@/components/ui/tooltip";
import { BlockIOSchema } from "@/lib/autogpt-server-api/types";
import { Info } from 'lucide-react';
import ReactMarkdown from 'react-markdown';
import { Info } from "lucide-react";
import ReactMarkdown from "react-markdown";
const SchemaTooltip: React.FC<{ schema: BlockIOSchema }> = ({ schema }) => {
if (!schema.description) return null;
@ -18,13 +18,19 @@ const SchemaTooltip: React.FC<{ schema: BlockIOSchema }> = ({ schema }) => {
<Info className="p-1 rounded-full hover:bg-gray-300" size={24} />
</TooltipTrigger>
<TooltipContent className="max-w-xs tooltip-content">
<ReactMarkdown components={{
a: ({ node, ...props }) => <a className="text-blue-400 underline" {...props} />,
}}>{schema.description}</ReactMarkdown>
<ReactMarkdown
components={{
a: ({ node, ...props }) => (
<a className="text-blue-400 underline" {...props} />
),
}}
>
{schema.description}
</ReactMarkdown>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)
}
);
};
export default SchemaTooltip;

View File

@ -1,9 +1,9 @@
"use client";
import { createClient } from '@/lib/supabase/client';
import { SupabaseClient } from '@supabase/supabase-js';
import { useRouter } from 'next/navigation';
import { createContext, useContext, useEffect, useState } from 'react';
import { createClient } from "@/lib/supabase/client";
import { SupabaseClient } from "@supabase/supabase-js";
import { useRouter } from "next/navigation";
import { createContext, useContext, useEffect, useState } from "react";
type SupabaseContextType = {
supabase: SupabaseClient | null;
@ -13,9 +13,9 @@ type SupabaseContextType = {
const Context = createContext<SupabaseContextType | undefined>(undefined);
export default function SupabaseProvider({
children
children,
}: {
children: React.ReactNode
children: React.ReactNode;
}) {
const [supabase, setSupabase] = useState<SupabaseClient | null>(null);
const [isLoading, setIsLoading] = useState(true);
@ -54,7 +54,7 @@ export default function SupabaseProvider({
export const useSupabase = () => {
const context = useContext(Context);
if (context === undefined) {
throw new Error('useSupabase must be used inside SupabaseProvider');
throw new Error("useSupabase must be used inside SupabaseProvider");
}
return context;
};

View File

@ -1,7 +1,7 @@
import { z } from "zod"
import { useForm } from "react-hook-form"
import { zodResolver } from "@hookform/resolvers/zod"
import React, { useState } from "react"
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import React, { useState } from "react";
import {
Form,
FormControl,
@ -9,28 +9,30 @@ import {
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Switch } from "@/components/ui/switch"
import { Textarea } from "@/components/ui/textarea"
import AutoGPTServerAPI, { Graph, GraphCreatable } from "@/lib/autogpt-server-api"
import { cn } from "@/lib/utils"
import { EnterIcon } from "@radix-ui/react-icons"
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea";
import AutoGPTServerAPI, {
Graph,
GraphCreatable,
} from "@/lib/autogpt-server-api";
import { cn } from "@/lib/utils";
import { EnterIcon } from "@radix-ui/react-icons";
const formSchema = z.object({
agentFile: z.instanceof(File),
agentName: z.string().min(1, "Agent name is required"),
agentDescription: z.string(),
importAsTemplate: z.boolean(),
})
});
export const AgentImportForm: React.FC<React.FormHTMLAttributes<HTMLFormElement>> = (
{ className, ...props }
) => {
const [agentObject, setAgentObject] = useState<GraphCreatable | null>(null)
const api = new AutoGPTServerAPI()
export const AgentImportForm: React.FC<
React.FormHTMLAttributes<HTMLFormElement>
> = ({ className, ...props }) => {
const [agentObject, setAgentObject] = useState<GraphCreatable | null>(null);
const api = new AutoGPTServerAPI();
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
@ -39,12 +41,12 @@ export const AgentImportForm: React.FC<React.FormHTMLAttributes<HTMLFormElement>
agentDescription: "",
importAsTemplate: false,
},
})
});
function onSubmit(values: z.infer<typeof formSchema>) {
if (!agentObject) {
form.setError("root", { message: "No Agent object to save" })
return
form.setError("root", { message: "No Agent object to save" });
return;
}
const payload: GraphCreatable = {
...agentObject,
@ -54,15 +56,20 @@ export const AgentImportForm: React.FC<React.FormHTMLAttributes<HTMLFormElement>
is_template: values.importAsTemplate,
};
(values.importAsTemplate ? api.createTemplate(payload) : api.createGraph(payload))
(values.importAsTemplate
? api.createTemplate(payload)
: api.createGraph(payload)
)
.then((response) => {
const qID = values.importAsTemplate ? "templateID" : "flowID";
window.location.href = `/build?${qID}=${response.id}`;
})
.catch(error => {
const entity_type = values.importAsTemplate ? 'template' : 'agent';
form.setError("root", { message: `Could not create ${entity_type}: ${error}` });
})
.catch((error) => {
const entity_type = values.importAsTemplate ? "template" : "agent";
form.setError("root", {
message: `Could not create ${entity_type}: ${error}`,
});
});
}
return (
@ -85,21 +92,22 @@ export const AgentImportForm: React.FC<React.FormHTMLAttributes<HTMLFormElement>
onChange={(e) => {
const file = e.target.files?.[0];
if (file) {
field.onChange(file)
field.onChange(file);
const reader = new FileReader();
// Attach parser to file reader
reader.onload = (event) => {
try {
const obj = JSON.parse(
event.target?.result as string
event.target?.result as string,
);
if (
!["name", "description", "nodes", "links"]
.every(key => !!obj[key])
!["name", "description", "nodes", "links"].every(
(key) => !!obj[key],
)
) {
throw new Error(
"Invalid agent object in file: "
+ JSON.stringify(obj, null, 2)
"Invalid agent object in file: " +
JSON.stringify(obj, null, 2),
);
}
const agent = obj as Graph;
@ -158,13 +166,25 @@ export const AgentImportForm: React.FC<React.FormHTMLAttributes<HTMLFormElement>
<FormLabel>Import as</FormLabel>
<FormControl>
<div className="flex space-x-2 items-center">
<span className={field.value ? "text-gray-400 dark:text-gray-600" : ""}>Agent</span>
<span
className={
field.value ? "text-gray-400 dark:text-gray-600" : ""
}
>
Agent
</span>
<Switch
disabled={field.disabled}
checked={field.value}
onCheckedChange={field.onChange}
/>
<span className={field.value ? "" : "text-gray-400 dark:text-gray-600"}>Template</span>
<span
className={
field.value ? "" : "text-gray-400 dark:text-gray-600"
}
>
Template
</span>
</div>
</FormControl>
<FormMessage />
@ -176,5 +196,5 @@ export const AgentImportForm: React.FC<React.FormHTMLAttributes<HTMLFormElement>
</Button>
</form>
</Form>
)
}
);
};

View File

@ -16,7 +16,9 @@
padding: 0;
color: #555;
opacity: 0;
transition: opacity 0.2s ease-in-out, background-color 0.2s ease-in-out;
transition:
opacity 0.2s ease-in-out,
background-color 0.2s ease-in-out;
}
.edge-label-button.visible {
@ -35,4 +37,4 @@
.react-flow__edge-interaction {
cursor: pointer;
}
}

View File

@ -1,7 +1,11 @@
import {Card, CardContent} from "@/components/ui/card";
import {Tooltip, TooltipContent, TooltipTrigger} from "@/components/ui/tooltip";
import {Button} from "@/components/ui/button";
import {Separator} from "@/components/ui/separator";
import { Card, CardContent } from "@/components/ui/card";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import React from "react";
/**
@ -12,14 +16,14 @@ import React from "react";
* @property {onclick} onClick - The function to be executed when the control is clicked.
*/
export type Control = {
icon: React.ReactNode;
label: string;
onClick: () => void;
}
icon: React.ReactNode;
label: string;
onClick: () => void;
};
interface ControlPanelProps {
controls: Control[];
children?: React.ReactNode;
controls: Control[];
children?: React.ReactNode;
}
/**
@ -29,33 +33,33 @@ interface ControlPanelProps {
* @param {Array} ControlPanelProps.children - The child components of the control panel.
* @returns The rendered control panel component.
*/
export const ControlPanel= ( {controls, children}: ControlPanelProps) => {
return (
<aside className="hidden w-14 flex-col sm:flex">
<Card>
<CardContent className="p-0">
<div className="flex flex-col items-center gap-4 px-2 sm:py-5 rounded-radius">
{controls.map((control, index) => (
<Tooltip key={index} delayDuration={500}>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={() => control.onClick()}
>
{control.icon}
<span className="sr-only">{control.label}</span>
</Button>
</TooltipTrigger>
<TooltipContent side="right">{control.label}</TooltipContent>
</Tooltip>
))}
<Separator />
{children}
</div>
</CardContent>
</Card>
</aside>
);
}
export default ControlPanel;
export const ControlPanel = ({ controls, children }: ControlPanelProps) => {
return (
<aside className="hidden w-14 flex-col sm:flex">
<Card>
<CardContent className="p-0">
<div className="flex flex-col items-center gap-4 px-2 sm:py-5 rounded-radius">
{controls.map((control, index) => (
<Tooltip key={index} delayDuration={500}>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={() => control.onClick()}
>
{control.icon}
<span className="sr-only">{control.label}</span>
</Button>
</TooltipTrigger>
<TooltipContent side="right">{control.label}</TooltipContent>
</Tooltip>
))}
<Separator />
{children}
</div>
</CardContent>
</Card>
</aside>
);
};
export default ControlPanel;

View File

@ -6,13 +6,17 @@ import { ToyBrick } from "lucide-react";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import { beautifyString } from "@/lib/utils";
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
import { Block } from '@/lib/autogpt-server-api';
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Block } from "@/lib/autogpt-server-api";
import { PlusIcon } from "@radix-ui/react-icons";
interface BlocksControlProps {
blocks: Block[];
addBlock: (id: string, name: string) => void;
blocks: Block[];
addBlock: (id: string, name: string) => void;
}
/**
@ -24,60 +28,67 @@ interface BlocksControlProps {
* @param {(id: string, name: string) => void} BlocksControlProps.addBlock - A function to call when a block is added.
* @returns The rendered BlocksControl component.
*/
export const BlocksControl: React.FC<BlocksControlProps> = ({ blocks, addBlock }) => {
const [searchQuery, setSearchQuery] = useState('');
export const BlocksControl: React.FC<BlocksControlProps> = ({
blocks,
addBlock,
}) => {
const [searchQuery, setSearchQuery] = useState("");
const filteredBlocks = blocks.filter((block: Block) =>
block.name.toLowerCase().includes(searchQuery.toLowerCase())
);
const filteredBlocks = blocks.filter((block: Block) =>
block.name.toLowerCase().includes(searchQuery.toLowerCase()),
);
return (
<Popover>
<PopoverTrigger className="hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-50 dark:text-white">
<ToyBrick className="size-4"/>
</PopoverTrigger>
<PopoverContent side="right" sideOffset={15} align="start" className="w-80 p-0">
<Card className="border-none shadow-none">
<CardHeader className="p-4">
<div className="flex flex-row justify-between items-center">
<Label htmlFor="search-blocks">Blocks</Label>
</div>
<Input
id="search-blocks"
type="text"
placeholder="Search blocks..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</CardHeader>
<CardContent className="p-1">
<ScrollArea className="h-[60vh]">
{filteredBlocks.map((block) => (
<Card
key={block.id}
className="m-2"
>
<div className="flex items-center justify-between m-3">
<div className="flex-1 min-w-0 mr-2">
<span className="font-medium truncate block">{beautifyString(block.name)}</span>
</div>
<div className="flex items-center gap-1 flex-shrink-0">
<Button
variant="ghost"
size="icon"
onClick={() => addBlock(block.id, block.name)}
aria-label="Add block"
>
<PlusIcon />
</Button>
</div>
</div>
</Card>
))}
</ScrollArea>
</CardContent>
return (
<Popover>
<PopoverTrigger className="hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-50 dark:text-white">
<ToyBrick className="size-4" />
</PopoverTrigger>
<PopoverContent
side="right"
sideOffset={15}
align="start"
className="w-80 p-0"
>
<Card className="border-none shadow-none">
<CardHeader className="p-4">
<div className="flex flex-row justify-between items-center">
<Label htmlFor="search-blocks">Blocks</Label>
</div>
<Input
id="search-blocks"
type="text"
placeholder="Search blocks..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/>
</CardHeader>
<CardContent className="p-1">
<ScrollArea className="h-[60vh]">
{filteredBlocks.map((block) => (
<Card key={block.id} className="m-2">
<div className="flex items-center justify-between m-3">
<div className="flex-1 min-w-0 mr-2">
<span className="font-medium truncate block">
{beautifyString(block.name)}
</span>
</div>
<div className="flex items-center gap-1 flex-shrink-0">
<Button
variant="ghost"
size="icon"
onClick={() => addBlock(block.id, block.name)}
aria-label="Add block"
>
<PlusIcon />
</Button>
</div>
</div>
</Card>
</PopoverContent>
</Popover>
);
};
))}
</ScrollArea>
</CardContent>
</Card>
</PopoverContent>
</Popover>
);
};

View File

@ -1,7 +1,11 @@
import {Card, CardContent} from "@/components/ui/card";
import {Tooltip, TooltipContent, TooltipTrigger} from "@/components/ui/tooltip";
import {Button} from "@/components/ui/button";
import {Separator} from "@/components/ui/separator";
import { Card, CardContent } from "@/components/ui/card";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import React from "react";
/**
@ -12,14 +16,14 @@ import React from "react";
* @property {onclick} onClick - The function to be executed when the control is clicked.
*/
export type Control = {
icon: React.ReactNode;
label: string;
onClick: () => void;
}
icon: React.ReactNode;
label: string;
onClick: () => void;
};
interface ControlPanelProps {
controls: Control[];
children?: React.ReactNode;
controls: Control[];
children?: React.ReactNode;
}
/**
@ -29,33 +33,33 @@ interface ControlPanelProps {
* @param {Array} ControlPanelProps.children - The child components of the control panel.
* @returns The rendered control panel component.
*/
export const ControlPanel= ( {controls, children}: ControlPanelProps) => {
return (
<aside className="hidden w-14 flex-col sm:flex">
<Card>
<CardContent className="p-0">
<div className="flex flex-col items-center gap-4 px-2 sm:py-5 rounded-radius">
{children}
<Separator />
{controls.map((control, index) => (
<Tooltip key={index} delayDuration={500}>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={() => control.onClick()}
>
{control.icon}
<span className="sr-only">{control.label}</span>
</Button>
</TooltipTrigger>
<TooltipContent side="right">{control.label}</TooltipContent>
</Tooltip>
))}
</div>
</CardContent>
</Card>
</aside>
);
}
export default ControlPanel;
export const ControlPanel = ({ controls, children }: ControlPanelProps) => {
return (
<aside className="hidden w-14 flex-col sm:flex">
<Card>
<CardContent className="p-0">
<div className="flex flex-col items-center gap-4 px-2 sm:py-5 rounded-radius">
{children}
<Separator />
{controls.map((control, index) => (
<Tooltip key={index} delayDuration={500}>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={() => control.onClick()}
>
{control.icon}
<span className="sr-only">{control.label}</span>
</Button>
</TooltipTrigger>
<TooltipContent side="right">{control.label}</TooltipContent>
</Tooltip>
))}
</div>
</CardContent>
</Card>
</aside>
);
};
export default ControlPanel;

View File

@ -1,17 +1,21 @@
import React from "react";
import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover";
import {Card, CardContent, CardFooter} from "@/components/ui/card";
import {Input} from "@/components/ui/input";
import {Button} from "@/components/ui/button";
import {GraphMeta} from "@/lib/autogpt-server-api";
import {Label} from "@/components/ui/label";
import {Save} from "lucide-react";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { GraphMeta } from "@/lib/autogpt-server-api";
import { Label } from "@/components/ui/label";
import { Save } from "lucide-react";
interface SaveControlProps {
agentMeta: GraphMeta | null;
onSave: (isTemplate: boolean | undefined) => void;
onNameChange: (name: string) => void;
onDescriptionChange: (description: string) => void;
agentMeta: GraphMeta | null;
onSave: (isTemplate: boolean | undefined) => void;
onNameChange: (name: string) => void;
onDescriptionChange: (description: string) => void;
}
/**
@ -23,77 +27,74 @@ interface SaveControlProps {
* @param {(description: string) => void} SaveControlProps.onDescriptionChange - Function to handle description changes.
* @returns The SaveControl component.
*/
export const SaveControl= ({
agentMeta,
onSave,
onNameChange,
onDescriptionChange
}: SaveControlProps) => {
export const SaveControl = ({
agentMeta,
onSave,
onNameChange,
onDescriptionChange,
}: SaveControlProps) => {
/**
* Note for improvement:
* At the moment we are leveraging onDescriptionChange and onNameChange to handle the changes in the description and name of the agent.
* We should migrate this to be handled with form controls and a form library.
*/
/**
* Note for improvement:
* At the moment we are leveraging onDescriptionChange and onNameChange to handle the changes in the description and name of the agent.
* We should migrate this to be handled with form controls and a form library.
*/
// Determines if we're saving a template or an agent
let isTemplate = agentMeta?.is_template ? true : undefined;
const handleSave = () => {
onSave(isTemplate);
};
// Determines if we're saving a template or an agent
let isTemplate = agentMeta?.is_template ? true : undefined;
const handleSave = () => {
onSave(isTemplate);
};
const getType = () => {
return agentMeta?.is_template ? "template" : "agent";
};
const getType = () => {
return agentMeta?.is_template ? 'template' : 'agent';
}
return (
<Popover >
<PopoverTrigger
className="hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-50 dark:text-white"
>
<Save className="size-4"/>
</PopoverTrigger>
<PopoverContent side="right" sideOffset={15} align="start">
<Card className="border-none shadow-none">
<CardContent className="p-4">
<div className="grid gap-3">
<Label htmlFor="name">
Name
</Label>
<Input
id="name"
placeholder="Enter your agent name"
className="col-span-3"
defaultValue={agentMeta?.name || ''}
onChange={(e) => onNameChange(e.target.value)}
/>
<Label htmlFor="description">
Description
</Label>
<Input
id="description"
placeholder="Your agent description"
className="col-span-3"
defaultValue={agentMeta?.description || ''}
onChange={(e) => onDescriptionChange(e.target.value)}
/>
</div>
</CardContent>
<CardFooter className="flex flex-col items-stretch gap-2 ">
<Button className="w-full" onClick={handleSave}>
Save {getType()}
</Button>
{!agentMeta && (
<Button variant="secondary" className="w-full" onClick={() => {
isTemplate = true;
handleSave();
}}>
Save as Template
</Button>
)}
</CardFooter>
</Card>
</PopoverContent>
</Popover>
);
}
return (
<Popover>
<PopoverTrigger className="hover:bg-gray-100 hover:text-gray-900 dark:hover:bg-gray-800 dark:hover:text-gray-50 dark:text-white">
<Save className="size-4" />
</PopoverTrigger>
<PopoverContent side="right" sideOffset={15} align="start">
<Card className="border-none shadow-none">
<CardContent className="p-4">
<div className="grid gap-3">
<Label htmlFor="name">Name</Label>
<Input
id="name"
placeholder="Enter your agent name"
className="col-span-3"
defaultValue={agentMeta?.name || ""}
onChange={(e) => onNameChange(e.target.value)}
/>
<Label htmlFor="description">Description</Label>
<Input
id="description"
placeholder="Your agent description"
className="col-span-3"
defaultValue={agentMeta?.description || ""}
onChange={(e) => onDescriptionChange(e.target.value)}
/>
</div>
</CardContent>
<CardFooter className="flex flex-col items-stretch gap-2 ">
<Button className="w-full" onClick={handleSave}>
Save {getType()}
</Button>
{!agentMeta && (
<Button
variant="secondary"
className="w-full"
onClick={() => {
isTemplate = true;
handleSave();
}}
>
Save as Template
</Button>
)}
</CardFooter>
</Card>
</PopoverContent>
</Popover>
);
};

View File

@ -1,13 +1,13 @@
/* flow.css or index.css */
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
@ -25,7 +25,8 @@ button:hover {
background-color: #666;
}
input, textarea {
input,
textarea {
background-color: #ffffff;
color: #000000;
border: 1px solid #555;
@ -35,7 +36,8 @@ input, textarea {
box-sizing: border-box;
}
input::placeholder, textarea::placeholder {
input::placeholder,
textarea::placeholder {
color: #aaa;
}

View File

@ -1,17 +1,17 @@
// history.ts
import { CustomNodeData } from './CustomNode';
import { CustomEdgeData } from './CustomEdge';
import { Edge } from 'reactflow';
import { CustomNodeData } from "./CustomNode";
import { CustomEdgeData } from "./CustomEdge";
import { Edge } from "reactflow";
type ActionType =
| 'ADD_NODE'
| 'DELETE_NODE'
| 'ADD_EDGE'
| 'DELETE_EDGE'
| 'UPDATE_NODE'
| 'MOVE_NODE'
| 'UPDATE_INPUT'
| 'UPDATE_NODE_POSITION';
type ActionType =
| "ADD_NODE"
| "DELETE_NODE"
| "ADD_EDGE"
| "DELETE_EDGE"
| "UPDATE_NODE"
| "MOVE_NODE"
| "UPDATE_INPUT"
| "UPDATE_NODE_POSITION";
type AddNodePayload = { node: CustomNodeData };
type DeleteNodePayload = { nodeId: string };
@ -19,8 +19,16 @@ type AddEdgePayload = { edge: Edge<CustomEdgeData> };
type DeleteEdgePayload = { edgeId: string };
type UpdateNodePayload = { nodeId: string; newData: Partial<CustomNodeData> };
type MoveNodePayload = { nodeId: string; position: { x: number; y: number } };
type UpdateInputPayload = { nodeId: string; oldValues: { [key: string]: any }; newValues: { [key: string]: any } };
type UpdateNodePositionPayload = { nodeId: string; oldPosition: { x: number; y: number }; newPosition: { x: number; y: number } };
type UpdateInputPayload = {
nodeId: string;
oldValues: { [key: string]: any };
newValues: { [key: string]: any };
};
type UpdateNodePositionPayload = {
nodeId: string;
oldPosition: { x: number; y: number };
newPosition: { x: number; y: number };
};
type ActionPayload =
| AddNodePayload
@ -40,50 +48,49 @@ type Action = {
};
class History {
private past: Action[] = [];
private future: Action[] = [];
private past: Action[] = [];
private future: Action[] = [];
push(action: Action) {
push(action: Action) {
this.past.push(action);
this.future = [];
}
undo() {
const action = this.past.pop();
if (action) {
action.undo();
this.future.push(action);
}
}
redo() {
const action = this.future.pop();
if (action) {
action.redo();
this.past.push(action);
this.future = [];
}
}
undo() {
const action = this.past.pop();
if (action) {
action.undo();
this.future.push(action);
}
}
canUndo(): boolean {
return this.past.length > 0;
}
redo() {
const action = this.future.pop();
if (action) {
action.redo();
this.past.push(action);
}
}
canRedo(): boolean {
return this.future.length > 0;
}
canUndo(): boolean {
return this.past.length > 0;
}
canRedo(): boolean {
return this.future.length > 0;
}
clear() {
this.past = [];
this.future = [];
}
getHistoryState() {
return {
past: [...this.past],
future: [...this.future],
};
}
clear() {
this.past = [];
this.future = [];
}
getHistoryState() {
return {
past: [...this.past],
future: [...this.future],
};
}
}
export const history = new History();
export const history = new History();

View File

@ -1,9 +1,9 @@
"use client"
"use client";
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import * as React from "react";
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
@ -13,12 +13,12 @@ const Avatar = React.forwardRef<
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
className,
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
));
Avatar.displayName = AvatarPrimitive.Root.displayName;
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
@ -29,8 +29,8 @@ const AvatarImage = React.forwardRef<
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
@ -40,11 +40,11 @@ const AvatarFallback = React.forwardRef<
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-neutral-100 dark:bg-neutral-800",
className
className,
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
export { Avatar, AvatarImage, AvatarFallback }
export { Avatar, AvatarImage, AvatarFallback };

View File

@ -1,7 +1,7 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center rounded-md border border-neutral-200 px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 dark:border-neutral-800 dark:focus:ring-neutral-300 cursor-default",
@ -20,8 +20,8 @@ const badgeVariants = cva(
defaultVariants: {
variant: "default",
},
}
)
},
);
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
@ -30,7 +30,7 @@ export interface BadgeProps
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
);
}
export { Badge, badgeVariants }
export { Badge, badgeVariants };

View File

@ -1,8 +1,8 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-950 disabled:pointer-events-none disabled:opacity-50 dark:focus-visible:ring-neutral-300",
@ -17,7 +17,8 @@ const buttonVariants = cva(
"border border-neutral-200 bg-white shadow-sm hover:bg-neutral-100 hover:text-neutral-900 dark:border-neutral-800 dark:bg-neutral-950 dark:hover:bg-neutral-800 dark:hover:text-neutral-50",
secondary:
"bg-neutral-100 text-neutral-900 shadow-sm hover:bg-neutral-100/80 dark:bg-neutral-800 dark:text-neutral-50 dark:hover:bg-neutral-800/80",
ghost: "hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50",
ghost:
"hover:bg-neutral-100 hover:text-neutral-900 dark:hover:bg-neutral-800 dark:hover:text-neutral-50",
link: "text-neutral-900 underline-offset-4 hover:underline dark:text-neutral-50",
},
size: {
@ -31,27 +32,27 @@ const buttonVariants = cva(
variant: "default",
size: "default",
},
}
)
},
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
asChild?: boolean;
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
const Comp = asChild ? Slot : "button";
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
);
},
);
Button.displayName = "Button";
export { Button, buttonVariants }
export { Button, buttonVariants };

View File

@ -1,13 +1,13 @@
"use client"
"use client";
import * as React from "react"
import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons"
import { DayPicker } from "react-day-picker"
import * as React from "react";
import { ChevronLeftIcon, ChevronRightIcon } from "@radix-ui/react-icons";
import { DayPicker } from "react-day-picker";
import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"
import { cn } from "@/lib/utils";
import { buttonVariants } from "@/components/ui/button";
export type CalendarProps = React.ComponentProps<typeof DayPicker>
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
function Calendar({
className,
@ -27,7 +27,7 @@ function Calendar({
nav: "space-x-1 flex items-center",
nav_button: cn(
buttonVariants({ variant: "outline" }),
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
"h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100",
),
nav_button_previous: "absolute left-1",
nav_button_next: "absolute right-1",
@ -40,17 +40,18 @@ function Calendar({
"relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-neutral-100 [&:has([aria-selected].day-outside)]:bg-neutral-100/50 [&:has([aria-selected].day-range-end)]:rounded-r-md dark:[&:has([aria-selected])]:bg-neutral-800 dark:[&:has([aria-selected].day-outside)]:bg-neutral-800/50",
props.mode === "range"
? "[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md"
: "[&:has([aria-selected])]:rounded-md"
: "[&:has([aria-selected])]:rounded-md",
),
day: cn(
buttonVariants({ variant: "ghost" }),
"h-8 w-8 p-0 font-normal aria-selected:opacity-100"
"h-8 w-8 p-0 font-normal aria-selected:opacity-100",
),
day_range_start: "day-range-start",
day_range_end: "day-range-end",
day_selected:
"bg-neutral-900 text-neutral-50 hover:bg-neutral-900 hover:text-neutral-50 focus:bg-neutral-900 focus:text-neutral-50 dark:bg-neutral-50 dark:text-neutral-900 dark:hover:bg-neutral-50 dark:hover:text-neutral-900 dark:focus:bg-neutral-50 dark:focus:text-neutral-900",
day_today: "bg-neutral-100 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-50",
day_today:
"bg-neutral-100 text-neutral-900 dark:bg-neutral-800 dark:text-neutral-50",
day_outside:
"day-outside text-neutral-500 opacity-50 aria-selected:bg-neutral-100/50 aria-selected:text-neutral-500 aria-selected:opacity-30 dark:text-neutral-400 dark:aria-selected:bg-neutral-800/50 dark:aria-selected:text-neutral-400",
day_disabled: "text-neutral-500 opacity-50 dark:text-neutral-400",
@ -65,8 +66,8 @@ function Calendar({
}}
{...props}
/>
)
);
}
Calendar.displayName = "Calendar"
Calendar.displayName = "Calendar";
export { Calendar }
export { Calendar };

View File

@ -1,6 +1,6 @@
import * as React from "react"
import * as React from "react";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const Card = React.forwardRef<
HTMLDivElement,
@ -10,12 +10,12 @@ const Card = React.forwardRef<
ref={ref}
className={cn(
"rounded-xl border border-neutral-200 bg-white text-neutral-950 shadow dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
className
className,
)}
{...props}
/>
))
Card.displayName = "Card"
));
Card.displayName = "Card";
const CardHeader = React.forwardRef<
HTMLDivElement,
@ -26,8 +26,8 @@ const CardHeader = React.forwardRef<
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
));
CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef<
HTMLParagraphElement,
@ -38,8 +38,8 @@ const CardTitle = React.forwardRef<
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
));
CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef<
HTMLParagraphElement,
@ -50,16 +50,16 @@ const CardDescription = React.forwardRef<
className={cn("text-sm text-neutral-500 dark:text-neutral-400", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
));
CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
));
CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef<
HTMLDivElement,
@ -70,7 +70,14 @@ const CardFooter = React.forwardRef<
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
));
CardFooter.displayName = "CardFooter";
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
};

View File

@ -1,11 +1,11 @@
"use client"
"use client";
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible";
const Collapsible = CollapsiblePrimitive.Root
const Collapsible = CollapsiblePrimitive.Root;
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
export { Collapsible, CollapsibleTrigger, CollapsibleContent };

View File

@ -1,18 +1,18 @@
"use client"
"use client";
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { Cross2Icon } from "@radix-ui/react-icons"
import * as React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
import { Cross2Icon } from "@radix-ui/react-icons";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const Dialog = DialogPrimitive.Root
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close
const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
@ -22,12 +22,12 @@ const DialogOverlay = React.forwardRef<
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
className,
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
@ -39,7 +39,7 @@ const DialogContent = React.forwardRef<
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-neutral-200 bg-white p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg dark:border-neutral-800 dark:bg-neutral-950",
className
className,
)}
{...props}
>
@ -50,8 +50,8 @@ const DialogContent = React.forwardRef<
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({
className,
@ -60,12 +60,12 @@ const DialogHeader = ({
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
className,
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
);
DialogHeader.displayName = "DialogHeader";
const DialogFooter = ({
className,
@ -74,12 +74,12 @@ const DialogFooter = ({
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
className,
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
);
DialogFooter.displayName = "DialogFooter";
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
@ -89,12 +89,12 @@ const DialogTitle = React.forwardRef<
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
className,
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
@ -105,8 +105,8 @@ const DialogDescription = React.forwardRef<
className={cn("text-sm text-neutral-500 dark:text-neutral-400", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
@ -119,4 +119,4 @@ export {
DialogFooter,
DialogTitle,
DialogDescription,
}
};

View File

@ -1,31 +1,31 @@
"use client"
"use client";
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import * as React from "react";
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import {
CheckIcon,
ChevronRightIcon,
DotFilledIcon,
} from "@radix-ui/react-icons"
} from "@radix-ui/react-icons";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenu = DropdownMenuPrimitive.Root;
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
inset?: boolean;
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
@ -33,16 +33,16 @@ const DropdownMenuSubTrigger = React.forwardRef<
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-neutral-100 data-[state=open]:bg-neutral-100 dark:focus:bg-neutral-800 dark:data-[state=open]:bg-neutral-800",
inset && "pl-8",
className
className,
)}
{...props}
>
{children}
<ChevronRightIcon className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
));
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
DropdownMenuPrimitive.SubTrigger.displayName;
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
@ -52,13 +52,13 @@ const DropdownMenuSubContent = React.forwardRef<
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
className
className,
)}
{...props}
/>
))
));
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
DropdownMenuPrimitive.SubContent.displayName;
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
@ -71,18 +71,18 @@ const DropdownMenuContent = React.forwardRef<
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-md dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
className,
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
));
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
@ -90,12 +90,12 @@ const DropdownMenuItem = React.forwardRef<
className={cn(
"relative flex cursor-pointer select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
inset && "pl-8",
className
className,
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
));
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
@ -105,7 +105,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
ref={ref}
className={cn(
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
className
className,
)}
checked={checked}
{...props}
@ -117,9 +117,9 @@ const DropdownMenuCheckboxItem = React.forwardRef<
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
));
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
DropdownMenuPrimitive.CheckboxItem.displayName;
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
@ -129,7 +129,7 @@ const DropdownMenuRadioItem = React.forwardRef<
ref={ref}
className={cn(
"relative flex cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-neutral-100 focus:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 dark:focus:bg-neutral-800 dark:focus:text-neutral-50",
className
className,
)}
{...props}
>
@ -140,13 +140,13 @@ const DropdownMenuRadioItem = React.forwardRef<
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
));
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
inset?: boolean;
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
@ -154,12 +154,12 @@ const DropdownMenuLabel = React.forwardRef<
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
className,
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
));
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
@ -167,11 +167,14 @@ const DropdownMenuSeparator = React.forwardRef<
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-neutral-100 dark:bg-neutral-800", className)}
className={cn(
"-mx-1 my-1 h-px bg-neutral-100 dark:bg-neutral-800",
className,
)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
));
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
const DropdownMenuShortcut = ({
className,
@ -182,9 +185,9 @@ const DropdownMenuShortcut = ({
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
);
};
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
export {
DropdownMenu,
@ -202,4 +205,4 @@ export {
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}
};

View File

@ -1,8 +1,8 @@
"use client"
"use client";
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { Slot } from "@radix-ui/react-slot";
import {
Controller,
ControllerProps,
@ -10,27 +10,27 @@ import {
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form"
} from "react-hook-form";
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
import { cn } from "@/lib/utils";
import { Label } from "@/components/ui/label";
const Form = FormProvider
const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
name: TName
}
name: TName;
};
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
{} as FormFieldContextValue,
);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
@ -38,21 +38,21 @@ const FormField = <
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
);
};
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
const fieldContext = React.useContext(FormFieldContext);
const itemContext = React.useContext(FormItemContext);
const { getFieldState, formState } = useFormContext();
const fieldState = getFieldState(fieldContext.name, formState)
const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
throw new Error("useFormField should be used within <FormField>");
}
const { id } = itemContext
const { id } = itemContext;
return {
id,
@ -61,36 +61,36 @@ const useFormField = () => {
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
};
};
type FormItemContextValue = {
id: string
}
id: string;
};
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)
{} as FormItemContextValue,
);
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId()
const id = React.useId();
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
)
})
FormItem.displayName = "FormItem"
);
});
FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()
const { error, formItemId } = useFormField();
return (
<Label
@ -99,15 +99,16 @@ const FormLabel = React.forwardRef<
htmlFor={formItemId}
{...props}
/>
)
})
FormLabel.displayName = "FormLabel"
);
});
FormLabel.displayName = "FormLabel";
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
const { error, formItemId, formDescriptionId, formMessageId } =
useFormField();
return (
<Slot
@ -121,50 +122,56 @@ const FormControl = React.forwardRef<
aria-invalid={!!error}
{...props}
/>
)
})
FormControl.displayName = "FormControl"
);
});
FormControl.displayName = "FormControl";
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()
const { formDescriptionId } = useFormField();
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-[0.8rem] text-neutral-500 dark:text-neutral-400", className)}
className={cn(
"text-[0.8rem] text-neutral-500 dark:text-neutral-400",
className,
)}
{...props}
/>
)
})
FormDescription.displayName = "FormDescription"
);
});
FormDescription.displayName = "FormDescription";
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children
const { error, formMessageId } = useFormField();
const body = error ? String(error?.message) : children;
if (!body) {
return null
return null;
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-[0.8rem] font-medium text-red-500 dark:text-red-900", className)}
className={cn(
"text-[0.8rem] font-medium text-red-500 dark:text-red-900",
className,
)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = "FormMessage"
);
});
FormMessage.displayName = "FormMessage";
export {
useFormField,
@ -175,4 +182,4 @@ export {
FormDescription,
FormMessage,
FormField,
}
};

View File

@ -1,6 +1,6 @@
import * as React from "react"
import * as React from "react";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
@ -12,15 +12,15 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-gray-200 bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-gray-500 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-gray-950 disabled:cursor-not-allowed disabled:opacity-50 dark:border-gray-800 dark:placeholder:text-gray-400 dark:focus-visible:ring-gray-300",
type == "file" ? "pt-1.5 pb-0.5" : "", // fix alignment
className
type == "file" ? "pt-1.5 pb-0.5" : "", // fix alignment
className,
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
);
},
);
Input.displayName = "Input";
export { Input }
export { Input };

View File

@ -1,14 +1,14 @@
"use client"
"use client";
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import * as React from "react";
import * as LabelPrimitive from "@radix-ui/react-label";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
);
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
@ -20,7 +20,7 @@ const Label = React.forwardRef<
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
));
Label.displayName = LabelPrimitive.Root.displayName;
export { Label }
export { Label };

View File

@ -1,15 +1,15 @@
"use client"
"use client";
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import * as React from "react";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const Popover = PopoverPrimitive.Root
const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverAnchor = PopoverPrimitive.Anchor
const PopoverAnchor = PopoverPrimitive.Anchor;
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
@ -22,12 +22,12 @@ const PopoverContent = React.forwardRef<
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border border-neutral-200 bg-white p-4 text-neutral-950 shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:border-neutral-800 dark:bg-neutral-950 dark:text-neutral-50",
className
className,
)}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };

View File

@ -1,9 +1,9 @@
"use client"
"use client";
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import * as React from "react";
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
@ -20,8 +20,8 @@ const ScrollArea = React.forwardRef<
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
));
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
@ -36,13 +36,13 @@ const ScrollBar = React.forwardRef<
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
className,
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-neutral-200 dark:bg-neutral-800" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
));
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
export { ScrollArea, ScrollBar }
export { ScrollArea, ScrollBar };

View File

@ -1,9 +1,9 @@
"use client"
"use client";
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import * as React from "react";
import * as SeparatorPrimitive from "@radix-ui/react-separator";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
@ -11,7 +11,7 @@ const Separator = React.forwardRef<
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
ref,
) => (
<SeparatorPrimitive.Root
ref={ref}
@ -20,12 +20,12 @@ const Separator = React.forwardRef<
className={cn(
"shrink-0 bg-neutral-200 dark:bg-neutral-800",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
className,
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
),
);
Separator.displayName = SeparatorPrimitive.Root.displayName;
export { Separator }
export { Separator };

View File

@ -1,9 +1,9 @@
"use client"
"use client";
import * as React from "react"
import * as SwitchPrimitives from "@radix-ui/react-switch"
import * as React from "react";
import * as SwitchPrimitives from "@radix-ui/react-switch";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
@ -12,18 +12,18 @@ const Switch = React.forwardRef<
<SwitchPrimitives.Root
className={cn(
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-neutral-950 focus-visible:ring-offset-2 focus-visible:ring-offset-white disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-neutral-900 data-[state=unchecked]:bg-neutral-200 dark:focus-visible:ring-neutral-300 dark:focus-visible:ring-offset-neutral-950 dark:data-[state=checked]:bg-neutral-50 dark:data-[state=unchecked]:bg-neutral-800",
className
className,
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
"pointer-events-none block h-4 w-4 rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0 dark:bg-neutral-950"
"pointer-events-none block h-4 w-4 rounded-full bg-white shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0 dark:bg-neutral-950",
)}
/>
</SwitchPrimitives.Root>
))
Switch.displayName = SwitchPrimitives.Root.displayName
));
Switch.displayName = SwitchPrimitives.Root.displayName;
export { Switch }
export { Switch };

View File

@ -1,6 +1,6 @@
import * as React from "react"
import * as React from "react";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const Table = React.forwardRef<
HTMLTableElement,
@ -13,16 +13,16 @@ const Table = React.forwardRef<
{...props}
/>
</div>
))
Table.displayName = "Table"
));
Table.displayName = "Table";
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
));
TableHeader.displayName = "TableHeader";
const TableBody = React.forwardRef<
HTMLTableSectionElement,
@ -33,8 +33,8 @@ const TableBody = React.forwardRef<
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
));
TableBody.displayName = "TableBody";
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
@ -44,12 +44,12 @@ const TableFooter = React.forwardRef<
ref={ref}
className={cn(
"border-t bg-neutral-100/50 font-medium [&>tr]:last:border-b-0 dark:bg-neutral-800/50",
className
className,
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
));
TableFooter.displayName = "TableFooter";
const TableRow = React.forwardRef<
HTMLTableRowElement,
@ -59,12 +59,12 @@ const TableRow = React.forwardRef<
ref={ref}
className={cn(
"border-b transition-colors hover:bg-neutral-100/50 data-[state=selected]:bg-neutral-100 dark:hover:bg-neutral-800/50 dark:data-[state=selected]:bg-neutral-800",
className
className,
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
));
TableRow.displayName = "TableRow";
const TableHead = React.forwardRef<
HTMLTableCellElement,
@ -74,12 +74,12 @@ const TableHead = React.forwardRef<
ref={ref}
className={cn(
"h-10 px-2 text-left align-middle font-medium text-neutral-500 [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px] dark:text-neutral-400",
className
className,
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
));
TableHead.displayName = "TableHead";
const TableCell = React.forwardRef<
HTMLTableCellElement,
@ -89,12 +89,12 @@ const TableCell = React.forwardRef<
ref={ref}
className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
className,
)}
{...props}
/>
))
TableCell.displayName = "TableCell"
));
TableCell.displayName = "TableCell";
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
@ -102,11 +102,14 @@ const TableCaption = React.forwardRef<
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-neutral-500 dark:text-neutral-400", className)}
className={cn(
"mt-4 text-sm text-neutral-500 dark:text-neutral-400",
className,
)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
));
TableCaption.displayName = "TableCaption";
export {
Table,
@ -117,4 +120,4 @@ export {
TableRow,
TableCell,
TableCaption,
}
};

View File

@ -1,6 +1,6 @@
import * as React from "react"
import * as React from "react";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
@ -11,14 +11,14 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
<textarea
className={cn(
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
className,
)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = "Textarea"
);
},
);
Textarea.displayName = "Textarea";
export { Textarea }
export { Textarea };

View File

@ -1,11 +1,11 @@
"use client"
"use client";
import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import * as React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
const TooltipProvider = TooltipPrimitive.Provider
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = ({ children, delayDuration = 10 }) => (
<TooltipPrimitive.Root delayDuration={delayDuration}>
@ -13,7 +13,7 @@ const Tooltip = ({ children, delayDuration = 10 }) => (
</TooltipPrimitive.Root>
);
const TooltipTrigger = TooltipPrimitive.Trigger
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
@ -24,11 +24,11 @@ const TooltipContent = React.forwardRef<
sideOffset={sideOffset}
className={cn(
"z-50 overflow-hidden rounded-md bg-neutral-900 px-3 py-1.5 text-xs text-neutral-50 animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 dark:bg-neutral-50 dark:text-neutral-900",
className
className,
)}
{...props}
/>
))
TooltipContent.displayName = TooltipPrimitive.Content.displayName
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };

View File

@ -4,7 +4,7 @@ const getServerUser = async () => {
const supabase = createServerClient();
if (!supabase) {
return { user: null, error: 'Failed to create Supabase client' };
return { user: null, error: "Failed to create Supabase client" };
}
try {

View File

@ -1,8 +1,8 @@
"use client";
import { useEffect, useState } from 'react';
import { User, Session } from '@supabase/supabase-js';
import { useSupabase } from '@/components/SupabaseProvider';
import { useEffect, useState } from "react";
import { User, Session } from "@supabase/supabase-js";
import { useSupabase } from "@/components/SupabaseProvider";
const useUser = () => {
const { supabase, isLoading: isSupabaseLoading } = useSupabase();
@ -19,12 +19,16 @@ const useUser = () => {
const fetchUser = async () => {
try {
setIsLoading(true);
const { data: { user } } = await supabase.auth.getUser();
const { data: { session } } = await supabase.auth.getSession();
const {
data: { user },
} = await supabase.auth.getUser();
const {
data: { session },
} = await supabase.auth.getSession();
setUser(user);
setSession(session);
} catch (e) {
setError('Failed to fetch user data');
setError("Failed to fetch user data");
} finally {
setIsLoading(false);
}
@ -32,7 +36,9 @@ const useUser = () => {
fetchUser();
const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
setUser(session?.user ?? null);
setIsLoading(false);

View File

@ -6,7 +6,7 @@ import {
GraphMeta,
GraphExecuteResponse,
NodeExecutionResult,
} from "./types"
} from "./types";
export default class AutoGPTServerAPI {
private baseUrl: string;
@ -15,7 +15,8 @@ export default class AutoGPTServerAPI {
private messageHandlers: { [key: string]: (data: any) => void } = {};
constructor(
baseUrl: string = process.env.AGPT_SERVER_URL || "http://localhost:8000/api"
baseUrl: string = process.env.AGPT_SERVER_URL ||
"http://localhost:8000/api",
) {
this.baseUrl = baseUrl;
this.wsUrl = `ws://${new URL(this.baseUrl).host}/ws`;
@ -26,11 +27,11 @@ export default class AutoGPTServerAPI {
}
async listGraphs(): Promise<GraphMeta[]> {
return this._get("/graphs")
return this._get("/graphs");
}
async listTemplates(): Promise<GraphMeta[]> {
return this._get("/templates")
return this._get("/templates");
}
async getGraph(id: string, version?: number): Promise<Graph> {
@ -52,22 +53,26 @@ export default class AutoGPTServerAPI {
}
async createGraph(graphCreateBody: GraphCreatable): Promise<Graph>;
async createGraph(fromTemplateID: string, templateVersion: number): Promise<Graph>;
async createGraph(
graphOrTemplateID: GraphCreatable | string, templateVersion?: number
fromTemplateID: string,
templateVersion: number,
): Promise<Graph>;
async createGraph(
graphOrTemplateID: GraphCreatable | string,
templateVersion?: number,
): Promise<Graph> {
let requestBody: GraphCreateRequestBody;
if (typeof(graphOrTemplateID) == "string") {
if (typeof graphOrTemplateID == "string") {
if (templateVersion == undefined) {
throw new Error("templateVersion not specified")
throw new Error("templateVersion not specified");
}
requestBody = {
template_id: graphOrTemplateID,
template_version: templateVersion,
}
};
} else {
requestBody = { graph: graphOrTemplateID }
requestBody = { graph: graphOrTemplateID };
}
return this._request("POST", "/graphs", requestBody);
@ -87,31 +92,40 @@ export default class AutoGPTServerAPI {
}
async setGraphActiveVersion(id: string, version: number): Promise<Graph> {
return this._request(
"PUT", `/graphs/${id}/versions/active`, { active_graph_version: version }
);
return this._request("PUT", `/graphs/${id}/versions/active`, {
active_graph_version: version,
});
}
async executeGraph(
id: string, inputData: { [key: string]: any } = {}
id: string,
inputData: { [key: string]: any } = {},
): Promise<GraphExecuteResponse> {
return this._request("POST", `/graphs/${id}/execute`, inputData);
}
async listGraphRunIDs(graphID: string, graphVersion?: number): Promise<string[]> {
const query = graphVersion !== undefined ? `?graph_version=${graphVersion}` : "";
async listGraphRunIDs(
graphID: string,
graphVersion?: number,
): Promise<string[]> {
const query =
graphVersion !== undefined ? `?graph_version=${graphVersion}` : "";
return this._get(`/graphs/${graphID}/executions` + query);
}
async getGraphExecutionInfo(graphID: string, runID: string): Promise<NodeExecutionResult[]> {
return (await this._get(`/graphs/${graphID}/executions/${runID}`))
.map((result: any) => ({
...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,
}));
async getGraphExecutionInfo(
graphID: string,
runID: string,
): Promise<NodeExecutionResult[]> {
return (await this._get(`/graphs/${graphID}/executions/${runID}`)).map(
(result: any) => ({
...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,
}),
);
}
private async _get(path: string) {
@ -129,19 +143,23 @@ export default class AutoGPTServerAPI {
const response = await fetch(
this.baseUrl + path,
method != "GET" ? {
method,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
} : undefined
method != "GET"
? {
method,
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
}
: undefined,
);
const response_data = await response.json();
if (!response.ok) {
console.warn(
`${method} ${path} returned non-OK response:`, response_data.detail, response
`${method} ${path} returned non-OK response:`,
response_data.detail,
response,
);
throw new Error(`HTTP error ${response.status}! ${response_data.detail}`);
}
@ -153,17 +171,17 @@ export default class AutoGPTServerAPI {
this.socket = new WebSocket(this.wsUrl);
this.socket.onopen = () => {
console.log('WebSocket connection established');
console.log("WebSocket connection established");
resolve();
};
this.socket.onclose = (event) => {
console.log('WebSocket connection closed', event);
console.log("WebSocket connection closed", event);
this.socket = null;
};
this.socket.onerror = (error) => {
console.error('WebSocket error:', error);
console.error("WebSocket error:", error);
reject(error);
};
@ -183,42 +201,48 @@ export default class AutoGPTServerAPI {
}
sendWebSocketMessage<M extends keyof WebsocketMessageTypeMap>(
method: M, data: WebsocketMessageTypeMap[M]
method: M,
data: WebsocketMessageTypeMap[M],
) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify({ method, data }));
} else {
console.error('WebSocket is not connected');
console.error("WebSocket is not connected");
}
}
onWebSocketMessage<M extends keyof WebsocketMessageTypeMap>(
method: M, handler: (data: WebsocketMessageTypeMap[M]) => void
method: M,
handler: (data: WebsocketMessageTypeMap[M]) => void,
) {
this.messageHandlers[method] = handler;
}
subscribeToExecution(graphId: string) {
this.sendWebSocketMessage('subscribe', { graph_id: graphId });
this.sendWebSocketMessage("subscribe", { graph_id: graphId });
}
runGraph(graphId: string, data: WebsocketMessageTypeMap["run_graph"]["data"] = {}) {
this.sendWebSocketMessage('run_graph', { graph_id: graphId, data });
runGraph(
graphId: string,
data: WebsocketMessageTypeMap["run_graph"]["data"] = {},
) {
this.sendWebSocketMessage("run_graph", { graph_id: graphId, data });
}
}
/* *** UTILITY TYPES *** */
type GraphCreateRequestBody = {
template_id: string;
template_version: number;
} | {
graph: GraphCreatable;
}
type GraphCreateRequestBody =
| {
template_id: string;
template_version: number;
}
| {
graph: GraphCreatable;
};
type WebsocketMessageTypeMap = {
subscribe: { graph_id: string; };
run_graph: { graph_id: string; data: { [key: string]: any }; };
subscribe: { graph_id: string };
run_graph: { graph_id: string; data: { [key: string]: any } };
execution_event: NodeExecutionResult;
}
};

View File

@ -12,7 +12,7 @@ export type BlockIORootSchema = {
properties: { [key: string]: BlockIOSchema };
required?: string[];
additionalProperties?: { type: string };
}
};
export type BlockIOSchema = {
title?: string;
@ -20,50 +20,59 @@ export type BlockIOSchema = {
placeholder?: string;
} & (BlockIOSimpleTypeSchema | BlockIOCombinedTypeSchema);
type BlockIOSimpleTypeSchema = {
type: "object";
properties: { [key: string]: BlockIOSchema };
required?: string[];
additionalProperties?: { type: string };
} | {
type: "array";
items?: BlockIOSimpleTypeSchema;
} | {
type: "string";
enum?: string[];
secret?: true;
default?: string;
} | {
type: "integer" | "number";
default?: number;
} | {
type: "boolean";
default?: boolean;
} | {
type: "null";
};
type BlockIOSimpleTypeSchema =
| {
type: "object";
properties: { [key: string]: BlockIOSchema };
required?: string[];
additionalProperties?: { type: string };
}
| {
type: "array";
items?: BlockIOSimpleTypeSchema;
}
| {
type: "string";
enum?: string[];
secret?: true;
default?: string;
}
| {
type: "integer" | "number";
default?: number;
}
| {
type: "boolean";
default?: boolean;
}
| {
type: "null";
};
// At the time of writing, combined schemas only occur on the first nested level in a
// block schema. It is typed this way to make the use of these objects less tedious.
type BlockIOCombinedTypeSchema = {
allOf: [BlockIOSimpleTypeSchema];
} | {
anyOf: BlockIOSimpleTypeSchema[];
default?: string | number | boolean | null;
} | {
oneOf: BlockIOSimpleTypeSchema[];
default?: string | number | boolean | null;
};
type BlockIOCombinedTypeSchema =
| {
allOf: [BlockIOSimpleTypeSchema];
}
| {
anyOf: BlockIOSimpleTypeSchema[];
default?: string | number | boolean | null;
}
| {
oneOf: BlockIOSimpleTypeSchema[];
default?: string | number | boolean | null;
};
/* Mirror of autogpt_server/data/graph.py:Node */
export type Node = {
id: string;
block_id: string;
input_default: { [key: string]: any };
input_nodes: Array<{ name: string, node_id: string }>;
output_nodes: Array<{ name: string, node_id: string }>;
input_nodes: Array<{ name: string; node_id: string }>;
output_nodes: Array<{ name: string; node_id: string }>;
metadata: {
position: { x: number; y: number; };
position: { x: number; y: number };
[key: string]: any;
};
};
@ -75,11 +84,11 @@ export type Link = {
sink_id: string;
source_name: string;
sink_name: string;
}
};
export type LinkCreatable = Omit<Link, "id"> & {
id?: string;
}
};
/* Mirror of autogpt_server/data/graph.py:GraphMeta */
export type GraphMeta = {
@ -89,7 +98,7 @@ export type GraphMeta = {
is_template: boolean;
name: string;
description: string;
}
};
/* Mirror of autogpt_server/data/graph.py:Graph */
export type Graph = GraphMeta & {
@ -105,16 +114,16 @@ export type GraphUpdateable = Omit<
is_active?: boolean;
is_template?: boolean;
links: Array<LinkCreatable>;
}
};
export type GraphCreatable = Omit<GraphUpdateable, "id"> & { id?: string }
export type GraphCreatable = Omit<GraphUpdateable, "id"> & { id?: string };
/* Derived from autogpt_server/executor/manager.py:ExecutionManager.add_execution */
export type GraphExecuteResponse = {
/** ID of the initiated run */
id: string;
/** List of node executions */
executions: Array<{ id: string, node_id: string }>;
executions: Array<{ id: string; node_id: string }>;
};
/* Mirror of autogpt_server/data/execution.py:ExecutionResult */
@ -124,7 +133,7 @@ export type NodeExecutionResult = {
graph_id: string;
graph_version: number;
node_id: string;
status: 'INCOMPLETE' | 'QUEUED' | 'RUNNING' | 'COMPLETED' | 'FAILED';
status: "INCOMPLETE" | "QUEUED" | "RUNNING" | "COMPLETED" | "FAILED";
input_data: { [key: string]: any };
output_data: { [key: string]: Array<any> };
add_time: Date;

View File

@ -4,17 +4,17 @@ import { Graph, Block, Node } from "./types";
export function safeCopyGraph(graph: Graph, block_defs: Block[]): Graph {
return {
...graph,
nodes: graph.nodes.map(node => {
const block = block_defs.find(b => b.id == node.block_id)!;
nodes: graph.nodes.map((node) => {
const block = block_defs.find((b) => b.id == node.block_id)!;
return {
...node,
input_default: Object.keys(node.input_default)
.filter(k => !block.inputSchema.properties[k].secret)
.reduce((obj: Node['input_default'], key) => {
.filter((k) => !block.inputSchema.properties[k].secret)
.reduce((obj: Node["input_default"], key) => {
obj[key] = node.input_default[key];
return obj;
}, {}),
}
};
}),
}
};
}

View File

@ -1,13 +1,12 @@
import { createBrowserClient } from '@supabase/ssr'
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) {
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
} catch (error) {
return null;
}
}

View File

@ -1,17 +1,18 @@
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
import { createServerClient } from "@supabase/ssr";
import { NextResponse, type NextRequest } from "next/server";
export async function updateSession(request: NextRequest) {
let supabaseResponse = NextResponse.next({
request,
})
});
const isAvailable = Boolean(
process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
process.env.NEXT_PUBLIC_SUPABASE_URL &&
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
);
if (!isAvailable) {
return supabaseResponse
return supabaseResponse;
}
try {
@ -21,20 +22,22 @@ export async function updateSession(request: NextRequest) {
{
cookies: {
getAll() {
return request.cookies.getAll()
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) => request.cookies.set(name, value))
cookiesToSet.forEach(({ name, value, options }) =>
request.cookies.set(name, value),
);
supabaseResponse = NextResponse.next({
request,
})
});
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
supabaseResponse.cookies.set(name, value, options),
);
},
},
}
)
},
);
// IMPORTANT: Avoid writing any logic between createServerClient and
// supabase.auth.getUser(). A simple mistake could make it very hard to debug
@ -42,16 +45,16 @@ export async function updateSession(request: NextRequest) {
const {
data: { user },
} = await supabase.auth.getUser()
} = await supabase.auth.getUser();
if (
!user &&
!request.nextUrl.pathname.startsWith('/login') &&
!request.nextUrl.pathname.startsWith('/auth')
!request.nextUrl.pathname.startsWith("/login") &&
!request.nextUrl.pathname.startsWith("/auth")
) {
// no user, potentially respond by redirecting the user to the login page
const url = request.nextUrl.clone()
url.pathname = '/login'
const url = request.nextUrl.clone();
url.pathname = "/login";
// return NextResponse.redirect(url)
}
@ -67,10 +70,9 @@ export async function updateSession(request: NextRequest) {
// return myNewResponse
// If this is not done, you may be causing the browser and server to go out
// of sync and terminate the user's session prematurely!
}
catch (error) {
console.error('Failed to run Supabase middleware', error)
} catch (error) {
console.error("Failed to run Supabase middleware", error);
}
return supabaseResponse
return supabaseResponse;
}

View File

@ -1,8 +1,11 @@
import { createServerClient as createClient, type CookieOptions } from '@supabase/ssr'
import { cookies } from 'next/headers'
import {
createServerClient as createClient,
type CookieOptions,
} from "@supabase/ssr";
import { cookies } from "next/headers";
export function createServerClient() {
const cookieStore = cookies()
const cookieStore = cookies();
try {
return createClient(
@ -11,13 +14,13 @@ export function createServerClient() {
{
cookies: {
getAll() {
return cookieStore.getAll()
return cookieStore.getAll();
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
cookieStore.set(name, value, options),
);
} catch {
// The `setAll` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
@ -25,10 +28,9 @@ export function createServerClient() {
}
},
},
}
)
}
catch (error) {
},
);
} catch (error) {
return null;
}
}

View File

@ -1,17 +1,18 @@
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
return twMerge(clsx(inputs));
}
/** Derived from https://stackoverflow.com/a/7616484 */
export function hashString(str: string): number {
let hash = 0, chr: number;
let hash = 0,
chr: number;
if (str.length === 0) return hash;
for (let i = 0; i < str.length; i++) {
chr = str.charCodeAt(i);
hash = ((hash << 5) - hash) + chr;
hash = (hash << 5) - hash + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
@ -19,84 +20,93 @@ export function hashString(str: string): number {
/** Derived from https://stackoverflow.com/a/32922084 */
export function deepEquals(x: any, y: any): boolean {
const ok = Object.keys, tx = typeof x, ty = typeof y;
return x && y && tx === ty && (
tx === 'object'
? (
ok(x).length === ok(y).length &&
ok(x).every(key => deepEquals(x[key], y[key]))
)
: (x === y)
const ok = Object.keys,
tx = typeof x,
ty = typeof y;
return (
x &&
y &&
tx === ty &&
(tx === "object"
? ok(x).length === ok(y).length &&
ok(x).every((key) => deepEquals(x[key], y[key]))
: x === y)
);
}
/** Get tailwind text color class from type name */
export function getTypeTextColor(type: string | null): string {
if (type === null) return 'bg-gray-500';
return {
string: 'text-green-500',
number: 'text-blue-500',
boolean: 'text-yellow-500',
object: 'text-purple-500',
array: 'text-indigo-500',
null: 'text-gray-500',
'': 'text-gray-500',
}[type] || 'text-gray-500';
if (type === null) return "bg-gray-500";
return (
{
string: "text-green-500",
number: "text-blue-500",
boolean: "text-yellow-500",
object: "text-purple-500",
array: "text-indigo-500",
null: "text-gray-500",
"": "text-gray-500",
}[type] || "text-gray-500"
);
}
/** Get tailwind bg color class from type name */
export function getTypeBgColor(type: string | null): string {
if (type === null) return 'bg-gray-500';
return {
string: 'bg-green-500',
number: 'bg-blue-500',
boolean: 'bg-yellow-500',
object: 'bg-purple-500',
array: 'bg-indigo-500',
null: 'bg-gray-500',
'': 'bg-gray-500',
}[type] || 'bg-gray-500';
if (type === null) return "bg-gray-500";
return (
{
string: "bg-green-500",
number: "bg-blue-500",
boolean: "bg-yellow-500",
object: "bg-purple-500",
array: "bg-indigo-500",
null: "bg-gray-500",
"": "bg-gray-500",
}[type] || "bg-gray-500"
);
}
export function getTypeColor(type: string | null): string {
if (type === null) return 'bg-gray-500';
return {
string: '#22c55e',
number: '#3b82f6',
boolean: '#eab308',
object: '#a855f7',
array: '#6366f1',
null: '#6b7280',
'': '#6b7280',
}[type] || '#6b7280';
if (type === null) return "bg-gray-500";
return (
{
string: "#22c55e",
number: "#3b82f6",
boolean: "#eab308",
object: "#a855f7",
array: "#6366f1",
null: "#6b7280",
"": "#6b7280",
}[type] || "#6b7280"
);
}
export function beautifyString(name: string): string {
// Regular expression to identify places to split, considering acronyms
const result = name
.replace(/([a-z])([A-Z])/g, '$1 $2') // Add space before capital letters
.replace(/([A-Z])([A-Z][a-z])/g, '$1 $2') // Add space between acronyms and next word
.replace(/_/g, ' ') // Replace underscores with spaces
.replace(/\b\w/g, char => char.toUpperCase()); // Capitalize the first letter of each word
.replace(/([a-z])([A-Z])/g, "$1 $2") // Add space before capital letters
.replace(/([A-Z])([A-Z][a-z])/g, "$1 $2") // Add space between acronyms and next word
.replace(/_/g, " ") // Replace underscores with spaces
.replace(/\b\w/g, (char) => char.toUpperCase()); // Capitalize the first letter of each word
return applyExceptions(result);
};
}
const exceptionMap: Record<string, string> = {
'Auto GPT': 'AutoGPT',
'Gpt': 'GPT',
'Creds': 'Credentials',
'Id': 'ID',
'Openai': 'OpenAI',
'Api': 'API',
'Url': 'URL',
'Http': 'HTTP',
'Json': 'JSON',
"Auto GPT": "AutoGPT",
Gpt: "GPT",
Creds: "Credentials",
Id: "ID",
Openai: "OpenAI",
Api: "API",
Url: "URL",
Http: "HTTP",
Json: "JSON",
};
const applyExceptions = (str: string): string => {
Object.keys(exceptionMap).forEach(key => {
const regex = new RegExp(`\\b${key}\\b`, 'g');
Object.keys(exceptionMap).forEach((key) => {
const regex = new RegExp(`\\b${key}\\b`, "g");
str = str.replace(regex, exceptionMap[key]);
});
return str;
@ -105,11 +115,11 @@ const applyExceptions = (str: string): string => {
export function exportAsJSONFile(obj: object, filename: string): void {
// Create downloadable blob
const jsonString = JSON.stringify(obj, null, 2);
const blob = new Blob([jsonString], { type: 'application/json' });
const blob = new Blob([jsonString], { type: "application/json" });
const url = URL.createObjectURL(blob);
// Trigger the browser to download the blob to a file
const link = document.createElement('a');
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
@ -126,7 +136,7 @@ export function setNestedProperty(obj: any, path: string, value: any) {
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!current[key] || typeof current[key] !== 'object') {
if (!current[key] || typeof current[key] !== "object") {
current[key] = {};
}
current = current[key];
@ -139,14 +149,20 @@ export function removeEmptyStringsAndNulls(obj: any): any {
if (Array.isArray(obj)) {
// If obj is an array, recursively remove empty strings and nulls from its elements
return obj
.map(item => removeEmptyStringsAndNulls(item))
.filter(item => item !== null && (typeof item !== 'string' || item.trim() !== ''));
} else if (typeof obj === 'object' && obj !== null) {
.map((item) => removeEmptyStringsAndNulls(item))
.filter(
(item) =>
item !== null && (typeof item !== "string" || item.trim() !== ""),
);
} else if (typeof obj === "object" && obj !== null) {
// If obj is an object, recursively remove empty strings and nulls from its properties
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
const value = obj[key];
if (value === null || (typeof value === 'string' && value.trim() === '')) {
if (
value === null ||
(typeof value === "string" && value.trim() === "")
) {
delete obj[key];
} else {
obj[key] = removeEmptyStringsAndNulls(value);

View File

@ -1,8 +1,8 @@
import { updateSession } from '@/lib/supabase/middleware'
import { type NextRequest } from 'next/server'
import { updateSession } from "@/lib/supabase/middleware";
import { type NextRequest } from "next/server";
export async function middleware(request: NextRequest) {
return await updateSession(request)
return await updateSession(request);
}
export const config = {
@ -14,6 +14,6 @@ export const config = {
* - favicon.ico (favicon file)
* Feel free to modify this pattern to include more paths.
*/
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
}
};

View File

@ -2,9 +2,7 @@ import type { Config } from "tailwindcss";
const config = {
darkMode: ["class"],
content: [
'./src/**/*.{ts,tsx}',
],
content: ["./src/**/*.{ts,tsx}"],
prefix: "",
theme: {
container: {
@ -16,64 +14,64 @@ const config = {
},
extend: {
fontFamily: {
sans: ['var(--font-geist-sans)'],
mono: ['var(--font-geist-mono)']
sans: ["var(--font-geist-sans)"],
mono: ["var(--font-geist-mono)"],
},
colors: {
border: 'hsl(var(--border))',
input: 'hsl(var(--input))',
ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))',
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))'
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
}
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
'accordion-down': {
from: { height: '0' },
to: { height: 'var(--radix-accordion-content-height)' }
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
'accordion-up': {
from: { height: 'var(--radix-accordion-content-height)' },
to: { height: '0' }
}
},
animation: {
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out'
}
}
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],
} satisfies Config;

View File

@ -1,7 +1,7 @@
module.exports = {
devServer: {
proxy: {
'/graphs': 'http://localhost:8000'
}
}
};
"/graphs": "http://localhost:8000",
},
},
};