parent
6fa7d22c91
commit
c7fdfa0f77
|
@ -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
|
||||
|
|
|
@ -14,4 +14,4 @@
|
|||
"components": "@/components",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}, []);
|
||||
|
||||
|
|
|
@ -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`);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
export default function ErrorPage() {
|
||||
return <p>Sorry, something went wrong</p>
|
||||
return <p>Sorry, something went wrong</p>;
|
||||
}
|
||||
|
|
|
@ -8,8 +8,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 0 0% 100%;
|
||||
|
@ -67,7 +65,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
@ -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]"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
>
|
||||
×
|
||||
</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>
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}, {}),
|
||||
}
|
||||
};
|
||||
}),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)$).*)",
|
||||
],
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module.exports = {
|
||||
devServer: {
|
||||
proxy: {
|
||||
'/graphs': 'http://localhost:8000'
|
||||
}
|
||||
}
|
||||
};
|
||||
"/graphs": "http://localhost:8000",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue