finish project

This commit is contained in:
ErkiKadhafi 2025-01-17 09:41:09 +07:00
parent fb69bba05d
commit 3ed12c8db0
43 changed files with 6978 additions and 845 deletions

4456
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -10,17 +10,24 @@
},
"dependencies": {
"@hookform/resolvers": "^3.10.0",
"@privy-io/react-auth": "^2.0.3",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-navigation-menu": "^1.2.0",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-switch": "^1.1.0",
"@stepperize/react": "^4.1.3",
"@supabase/ssr": "^0.5.2",
"@tanstack/react-query": "^5.64.1",
"@tanstack/react-query-devtools": "^5.64.1",
"@types/canvas-confetti": "^1.9.0",
"axios": "^1.7.9",
"canvas-confetti": "^1.9.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"embla-carousel-react": "^8.1.7",

BIN
public/empty-data.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 KiB

View File

@ -1,11 +0,0 @@
interface MarketingLayoutProps {
children: React.ReactNode;
}
export default async function Layout({ children }: MarketingLayoutProps) {
return (
<main className="flex flex-col items-center justify-center h-screen">
{children}
</main>
);
}

View File

@ -1,76 +0,0 @@
import Link from "next/link";
import { Icons } from "@/components/icons";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export default function LoginForm() {
return (
<Card className="mx-auto max-w-sm">
<CardHeader>
<CardTitle className="text-2xl">Login</CardTitle>
<CardDescription>
Enter your email below to login to your account
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-4">
<Button variant="outline" className="w-full">
<Icons.google className="w-4 h-4 mr-2" />
Login with Google
</Button>
<Button variant="outline" className="w-full">
<Icons.github className="w-4 h-4 mr-2" />
Login with GitHub
</Button>
<div className="relative">
<div className="absolute inset-0 flex items-center">
<span className="w-full border-t" />
</div>
<div className="relative flex justify-center text-xs uppercase">
<span className="bg-background px-2 text-muted-foreground">
Or continue with
</span>
</div>
</div>
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
/>
</div>
<div className="grid gap-2">
<div className="flex items-center">
<Label htmlFor="password">Password</Label>
<Link href="#" className="ml-auto inline-block text-sm underline">
Forgot your password?
</Link>
</div>
<Input id="password" type="password" required />
</div>
<Button type="submit" className="w-full">
Login
</Button>
</div>
<div className="mt-4 text-center text-sm">
Don&apos;t have an account?{" "}
<Link href="/signup" className="underline">
Sign up
</Link>
</div>
</CardContent>
</Card>
);
}

View File

@ -1,64 +0,0 @@
import Link from "next/link";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
export default function LoginForm() {
return (
<Card className="mx-auto max-w-sm">
<CardHeader>
<CardTitle className="text-xl">Sign Up</CardTitle>
<CardDescription>
Enter your information to create an account
</CardDescription>
</CardHeader>
<CardContent>
<div className="grid gap-4">
<div className="grid grid-cols-2 gap-4">
<div className="grid gap-2">
<Label htmlFor="first-name">First name</Label>
<Input id="first-name" placeholder="Max" required />
</div>
<div className="grid gap-2">
<Label htmlFor="last-name">Last name</Label>
<Input id="last-name" placeholder="Robinson" required />
</div>
</div>
<div className="grid gap-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="m@example.com"
required
/>
</div>
<div className="grid gap-2">
<Label htmlFor="password">Password</Label>
<Input id="password" type="password" />
</div>
<Button type="submit" className="w-full">
Create an account
</Button>
<Button variant="outline" className="w-full">
Sign up with GitHub
</Button>
</div>
<div className="mt-4 text-center text-sm">
Already have an account?{" "}
<Link href="/login" className="underline">
Sign in
</Link>
</div>
</CardContent>
</Card>
);
}

View File

@ -0,0 +1,238 @@
"use client";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import axios from "axios";
import { Send } from "lucide-react";
import clsx from "clsx";
import PostgrestError from "@/lib/config";
import { formatDate } from "@/lib/utils";
import { createClient } from "@/utils/supabase/client";
import { Tables } from "@/utils/supabase/database.types";
import {
Form,
FormControl,
FormField,
FormItem,
FormMessage,
} from "@/components/ui/form";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import Spinner from "@/components/spinner";
type AskAgentCardProps = {
agentId: string;
disabled?: boolean;
};
export default function AskAgentCard({
agentId,
disabled = false,
}: AskAgentCardProps) {
const {
data: agentsData,
isLoading,
error,
} = useQuery({
retry: 0,
queryKey: ["agents", agentId],
queryFn: async ({ signal }) => {
const supabase = createClient();
const response = await supabase
.from("agents")
.select("*")
.eq("id", parseInt(agentId))
.limit(1)
.single();
if (response.error) throw new PostgrestError(response.error);
return response;
},
});
if (error) return <p className="text-center">{error.message}</p>;
if (isLoading || !agentsData || !agentsData.data)
return <Spinner className="mx-auto" />;
return <CardContent agent={agentsData.data} disabled={disabled} />;
}
const CardContent = ({
agent,
disabled,
}: {
agent: Tables<"agents">;
disabled: boolean;
}) => {
const {
id,
image_url,
name,
model_type,
description,
conversation,
last_conv,
} = agent;
const router = useRouter();
const formSchema = z.object({
question: z
.string({
required_error: "Question is required",
})
.min(2, {
message: "Minimum Question is 2 characters.",
}),
});
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
question: "",
},
mode: "onChange",
});
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async (values: z.infer<typeof formSchema>) => {
const supabase = createClient();
const { data: existingAgent, error: error1 } = await supabase
.from("agents")
.select("*")
.eq("id", id);
if (error1) throw new PostgrestError(error1);
const { error: error2 } = await supabase
.from("agents")
.update({
conversation: existingAgent ? existingAgent[0].conversation + 1 : 0,
last_conv: new Date().toISOString(),
})
.eq("id", id)
.select();
if (error2) throw new PostgrestError(error2);
const botResponseJson = await axios.get(
encodeURI(
`https://ai-endpoint-one.dev3vds1.link/deepinfra-ai/${name}/${model_type}/${description}/${values.question}`
)
);
const { data, error: error3 } = await supabase
.from("agent_responses")
.upsert({
agent_id: id,
question: values.question,
response: botResponseJson.data.response,
})
.select()
.limit(1)
.single();
if (error3) throw new PostgrestError(error3);
return data;
},
onSuccess: (data: Tables<"agent_responses">) => {
router.push(`/agents/${id}/responses/${data.id}`);
queryClient.invalidateQueries({
queryKey: ["agents", id],
});
},
});
const onSubmit = (values: z.infer<typeof formSchema>) => {
mutation.mutate(values);
};
return (
<>
<div className="flex flex-col-reverse sm:flex-row justify-between gap-3 mb-4">
<div className="mr-auto">
<div className="flex space-x-2 mb-2">
<p className="text-primary">
<time dateTime={"2024-11-10"} className="text-xs">
Last Active:{" "}
<span className="font-bold">
{last_conv ? formatDate(last_conv) : "-"}
</span>
</time>
</p>
<Badge className="" variant={"secondary"}>
{conversation} chats
</Badge>
</div>
<h1 className="text-primary font-bold text-xl mb-2">{name}</h1>
<p className="text-gray-500 text-sm">{description}</p>
</div>
<figure className="relative h-[300px] min-w-full sm:h-[150px] sm:min-w-[200px] rounded-xl overflow-hidden">
<Image
src={image_url}
fill={true}
alt="test"
className="object-cover"
/>
</figure>
</div>
<div className="flex gap-3">
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="w-full flex gap-3"
>
<FormField
control={form.control}
name="question"
render={({ field }) => (
<FormItem className="w-full">
<FormControl>
<Input
disabled={disabled}
placeholder="Ask our agent"
className={clsx(
"h-9",
form.formState.errors.question &&
"focus-visible:ring-destructive"
)}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button disabled={mutation.isPending || disabled} size={"sm"}>
{mutation.isPending ? (
<Spinner className="h-4 w-4" />
) : (
<Send className="h-4 w-4" />
)}
</Button>
</form>
</Form>
</div>
</>
);
};

View File

@ -0,0 +1,131 @@
"use client";
import Link from "next/link";
import Image from "next/image";
import { useQuery } from "@tanstack/react-query";
import { Copy, Play, Twitter } from "lucide-react";
import PostgrestError from "@/lib/config";
import { createClient } from "@/utils/supabase/client";
import { Tables } from "@/utils/supabase/database.types";
import { toast } from "sonner";
import { Separator } from "@/components/ui/separator";
import { Button, buttonVariants } from "@/components/ui/button";
import Spinner from "@/components/spinner";
type ResponseAgentCardProps = {
agentResponseId: string;
};
export default function ResponseAgentCard({
agentResponseId,
}: ResponseAgentCardProps) {
const {
data: responseData,
isLoading,
error,
} = useQuery({
retry: 0,
queryKey: ["agent-responses", agentResponseId],
queryFn: async ({ signal }) => {
const supabase = createClient();
const response = await supabase
.from("agent_responses")
.select(
`
*,
agents (
agent_id:id,
name,
image_url
)
`
)
.eq("id", agentResponseId)
.limit(1)
.single();
if (response.error) throw new PostgrestError(response.error);
return response;
},
});
if (error) return <p className="text-center">{error.message}</p>;
if (isLoading || !responseData || !responseData.data)
return <Spinner className="mx-auto" />;
return <CardContent agentResponse={responseData.data} />;
}
const CardContent = ({
agentResponse,
}: {
agentResponse: Tables<"agent_responses"> & {
agents: {
agent_id: number;
name: string;
image_url: string;
};
};
}) => {
const { id, agent_id, question, response, agents } = agentResponse;
return (
<>
<div className="flex items-center gap-3 mb-4">
<figure className="relative h-16 w-16 rounded-full overflow-hidden">
<Image
src={agents.image_url}
fill={true}
alt="test"
className="object-cover"
/>
</figure>
<div>
<h1 className="text-primary">{agents.name}</h1>
<p className="text-gray-400 text-sm">{id}</p>
</div>
</div>
<Separator className="mb-3" />
<h2 className="text-sm font-bold text-primary mb-2">Question: </h2>
<p className="text-gray-500 text-sm mb-3">{question}</p>
<h2 className="text-sm font-bold text-primary mb-2">Response: </h2>
<p className="text-gray-500 text-sm mb-3">{response}</p>
<Separator className="mb-4" />
<div className="grid grid-cols-3 gap-3">
<Link
href={encodeURI(`https://x.com/intent/post?text=${response}`)}
target="blank"
className={buttonVariants({ variant: "outline" })}
>
<Twitter className="h-4 w-4 mr-2" /> Share on Twitter
</Link>
<Link
href={`/agents/${agent_id}`}
className={buttonVariants({ variant: "default" })}
>
<Play className="h-4 w-4 mr-2" /> Ask Another
</Link>
<Button
variant={"outline"}
onClick={() => {
navigator.clipboard.writeText(window.location.href);
toast.info("Copied to clipboard!");
}}
>
<Copy className="h-4 w-4 mr-2" />
Copy Link
</Button>
</div>
</>
);
};

View File

@ -0,0 +1,19 @@
"use client";
import { motion } from "framer-motion";
import SparklesText from "@/components/ui/sparkles-text";
const ease = [0.16, 1, 0.3, 1];
export default function Title() {
return (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, ease }}
>
<SparklesText text="Ask a Question" className="mb-8 text-5xl" />
</motion.div>
);
}

View File

@ -0,0 +1,34 @@
import { cn } from "@/lib/utils";
import AnimatedGridPattern from "@/components/ui/animated-grid-pattern";
import Title from "./_components/title";
import AskAgentCard from "./_components/ask-agent-card";
import CardBackground from "@/components/card-background";
export default function AskAgentPage({
params,
}: {
params: { agentId: string };
}) {
return (
<section className="relative flex min-h-[calc(100vh-64px)] w-full py-10 items-center justify-center overflow-hidden rounded-lg border bg-background">
<div className="layout flex flex-col items-center">
<Title />
<CardBackground>
<AskAgentCard agentId={params.agentId} />
</CardBackground>
</div>
<AnimatedGridPattern
numSquares={30}
maxOpacity={0.1}
duration={3}
repeatDelay={1}
className={cn(
"[mask-image:radial-gradient(500px_circle_at_center,white,transparent)]",
"inset-x-0 inset-y-[-30%] h-[200%] skew-y-12"
)}
/>
</section>
);
}

View File

@ -0,0 +1,38 @@
import { cn } from "@/lib/utils";
import AnimatedGridPattern from "@/components/ui/animated-grid-pattern";
import CardBackground from "@/components/card-background";
import Title from "../../_components/title";
import AskAgentCard from "../../_components/ask-agent-card";
import ResponseAgentCard from "../../_components/response-agent-card";
export default function AgentAsk({
params,
}: {
params: { agentId: string; responseId: string };
}) {
return (
<section className="relative flex min-h-[calc(100vh-64px)] w-full py-10 items-center justify-center overflow-hidden rounded-lg border bg-background">
<div className="layout flex flex-col items-center">
<Title />
<CardBackground>
<AskAgentCard agentId={params.agentId} disabled={true} />
</CardBackground>
<CardBackground>
<ResponseAgentCard agentResponseId={params.responseId} />
</CardBackground>
</div>
<AnimatedGridPattern
numSquares={30}
maxOpacity={0.1}
duration={3}
repeatDelay={1}
className={cn(
"[mask-image:radial-gradient(500px_circle_at_center,white,transparent)]",
"inset-x-0 inset-y-[-30%] h-[200%] skew-y-12"
)}
/>
</section>
);
}

View File

@ -0,0 +1,110 @@
import { z } from "zod";
import { UseFormReturn } from "react-hook-form";
import { Stepper } from "@stepperize/react";
import { cn } from "@/lib/utils";
import { createAgentSchema } from "../schemas/create-agent-schema";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import Spinner from "@/components/spinner";
type AgentFormProps = {
stepper: Stepper<
{
id: string;
title: string;
description: string;
}[]
>;
form: UseFormReturn<z.infer<typeof createAgentSchema>, any, undefined>;
onSubmit: (values: z.infer<typeof createAgentSchema>) => void;
isPending: boolean;
};
export default function AgentForm(props: AgentFormProps) {
const { stepper, form, onSubmit, isPending } = props;
const handleSubmit = (values: z.infer<typeof createAgentSchema>) => {
onSubmit(values);
};
const {
formState: { errors },
} = form;
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-3">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Agent Name</FormLabel>
<FormControl>
<Input
placeholder="astrobot"
className={cn(
errors.name && "focus-visible:ring-destructive"
)}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Textarea
rows={4}
placeholder="For answering daily questions."
className={cn(
errors.description && "focus-visible:ring-destructive"
)}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div>
<div className="flex justify-end gap-4">
<Button
variant="outline"
onClick={stepper.prev}
disabled={isPending}
>
{isPending && <Spinner className="h-4 w-4 mr-2" />}
Back
</Button>
<Button type="submit" disabled={isPending}>
{isPending && <Spinner className="h-4 w-4 mr-2" />}
Submit
</Button>
</div>
</div>
</form>
</Form>
);
}

View File

@ -0,0 +1,350 @@
"use client";
import * as React from "react";
import Link from "next/link";
import { z } from "zod";
import { useForm, UseFormReturn } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import axios from "axios";
import { Braces, Check, Code, MessageCircle } from "lucide-react";
import { toast } from "sonner";
import confetti from "canvas-confetti";
import { defineStepper, Stepper } from "@stepperize/react";
import { createAgentSchema } from "../schemas/create-agent-schema";
import { cn } from "@/lib/utils";
import { Tables } from "@/utils/supabase/database.types";
import { createClient } from "@/utils/supabase/client";
import { Button, buttonVariants } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import CardBackground from "@/components/card-background";
import AgentForm from "./agent-form";
const data = [
{
id: "agentType",
title: "Agent Type",
description: "Select how your Agent will interact",
},
{
id: "agentForm",
title: "Agent Form",
description: "Enter your agent informations",
},
{ id: "complete", title: "Complete", description: "Checkout complete" },
];
const { useStepper, steps, utils } = defineStepper(...data);
const triggerConfetti = () => {
const duration = 5 * 1000;
const animationEnd = Date.now() + duration;
const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 0 };
const randomInRange = (min: number, max: number) =>
Math.random() * (max - min) + min;
const interval = window.setInterval(() => {
const timeLeft = animationEnd - Date.now();
if (timeLeft <= 0) {
return clearInterval(interval);
}
const particleCount = 50 * (timeLeft / duration);
confetti({
...defaults,
particleCount,
origin: { x: randomInRange(0.1, 0.3), y: Math.random() - 0.2 },
});
confetti({
...defaults,
particleCount,
origin: { x: randomInRange(0.7, 0.9), y: Math.random() - 0.2 },
});
}, 250);
};
export default function FormStepper() {
const [agentId, setAgentId] = React.useState("");
const [agentName, setAgenName] = React.useState("");
const stepper = useStepper();
const currentIndex = utils.getIndex(stepper.current.id);
const form = useForm<z.infer<typeof createAgentSchema>>({
resolver: zodResolver(createAgentSchema),
defaultValues: {
userId: "",
name: "",
description: "",
modelType: "general",
},
mode: "onChange",
});
const queryClient = useQueryClient();
const createAgentMutation = useMutation({
mutationFn: async (values: z.infer<typeof createAgentSchema>) => {
const { modelType, userId, ...rest } = values;
const supabase = createClient();
const url = encodeURI(
`https://ai-endpoint-one.dev3vds1.link/ai-image/${values.description}`
);
const json = await axios.get(url);
const response = await supabase
.from("agents")
.insert({
...rest,
model_type: "general",
image_url: json.data.url,
user_id: userId,
conversation: 0,
last_conv: null,
})
.select()
.limit(1)
.single();
if (response.error) throw new Error(response.error.message);
return response.data;
},
onSuccess: (data: Tables<"agents">) => {
queryClient.invalidateQueries({
queryKey: ["agents"],
});
triggerConfetti();
stepper.next();
setAgentId(data.id.toString());
setAgenName(data.name);
stepper.next();
},
onError: (err) => {
toast.error(err.message);
},
});
const handleSubmit = (values: z.infer<typeof createAgentSchema>) => {
createAgentMutation.mutate(values);
};
return (
<CardBackground>
{/* form title */}
<div className="flex justify-between mb-6">
<div>
<h2 className="text-primary text-lg font-bold mb-1">Agent Form</h2>
<p className="text-sm text-muted-foreground">
{stepper.current.description}
</p>
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-muted-foreground">
Step {currentIndex + 1} of {steps.length}
</span>
<div />
</div>
</div>
{/* form nav */}
<nav aria-label="Checkout Steps" className="group mb-6">
<ol
className="flex items-center justify-between gap-2"
aria-orientation="horizontal"
>
{stepper.all.map((step, index, array) => (
<React.Fragment key={step.id}>
<li className="flex items-center gap-4 flex-shrink-0">
<Button
type="button"
role="tab"
variant={index <= currentIndex ? "default" : "outline"}
aria-current={
stepper.current.id === step.id ? "step" : undefined
}
aria-posinset={index + 1}
aria-setsize={steps.length}
aria-selected={stepper.current.id === step.id}
className="flex size-10 items-center justify-center rounded-full"
>
{index + 1}
</Button>
<span className="text-sm font-medium">{step.title}</span>
</li>
{index < array.length - 1 && (
<Separator
className={`flex-1 ${
index < currentIndex ? "bg-primary" : "bg-muted"
}`}
/>
)}
</React.Fragment>
))}
</ol>
</nav>
{/* form content */}
<div className="space-y-4">
{stepper.switch({
agentType: () => <AgentTypeComponent stepper={stepper} />,
agentForm: () => (
<AgentFormComponent
stepper={stepper}
form={form}
isPending={createAgentMutation.isPending}
onSubmit={handleSubmit}
/>
),
complete: () => (
<CompleteComponent agentId={agentId} agentName={agentName} />
),
})}
</div>
</CardBackground>
);
}
const AgentTypeComponent = ({
stepper,
}: {
stepper: Stepper<
{
id: string;
title: string;
description: string;
}[]
>;
}) => {
const agentTypes = [
{
icon: MessageCircle,
heading: "Chat",
description: "Access from our website",
},
{
icon: Code,
heading: "CLI",
description: "Access from terminal",
},
{
icon: Braces,
heading: "API",
description: "Access with API",
},
];
return (
<>
<div className="grid grid-cols-3 gap-4">
{agentTypes.map((agentType, idx) => (
<Button
disabled={idx !== 0}
key={idx}
variant={"outline"}
className={cn(
idx === 0 &&
"bg-accent-foreground hover:bg-accent-foreground border-primary",
"py-24 border-2"
)}
>
<div className="flex flex-col items-center space-y-3">
<agentType.icon className="h-9 w-9" />
<p className="text-xl font-bold">{agentType.heading}</p>
<p className="text-muted-foreground">{agentType.description}</p>
</div>
</Button>
))}
</div>
{/* form footer */}
<div>
<div className="flex justify-end gap-4">
<Button variant="outline" disabled={true}>
Back
</Button>
<Button onClick={stepper.next}>Next</Button>
</div>
</div>
</>
);
};
const AgentFormComponent = ({
stepper,
form,
onSubmit,
isPending,
}: {
stepper: Stepper<
{
id: string;
title: string;
description: string;
}[]
>;
form: UseFormReturn<z.infer<typeof createAgentSchema>, any, undefined>;
onSubmit: (values: z.infer<typeof createAgentSchema>) => void;
isPending: boolean;
}) => {
return (
<AgentForm
stepper={stepper}
form={form}
onSubmit={onSubmit}
isPending={isPending}
/>
);
};
const CompleteComponent = ({
agentId,
agentName,
}: {
agentId: string;
agentName: string;
}) => {
return (
<>
<div className="flex flex-col justify-center items-center py-4">
<div className="h-12 w-12 flex flex-col justify-center items-center rounded-full bg-green-300 ">
<Check className="h-6 w-6 text-green-800" />
</div>
<h3 className="text-lg py-4 font-bold">Agent succesfully created!</h3>
<p className="text-muted-foreground">
You can try to have a conversation with your agent.
</p>
<Link
href={`/agents/${agentId}`}
target="blank"
className="text-blue-500 hover:underline"
>
Chat with {agentName}
</Link>
</div>
{/* form footer */}
<div>
<div className="flex justify-end gap-4">
<Link href="/" className={buttonVariants({ variant: "outline" })}>
Back to home
</Link>
</div>
</div>
</>
);
};

View File

@ -0,0 +1,19 @@
"use client";
import { motion } from "framer-motion";
import SparklesText from "@/components/ui/sparkles-text";
const ease = [0.16, 1, 0.3, 1];
export default function Title() {
return (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.4, ease }}
>
<SparklesText text="Create your own agent" className="mb-8 text-5xl" />
</motion.div>
);
}

View File

@ -0,0 +1,27 @@
import { cn } from "@/lib/utils";
import AnimatedGridPattern from "@/components/ui/animated-grid-pattern";
import FormStepper from "./_components/form-stepper";
import Title from "./_components/title";
export default function CreateAgentPage() {
return (
<section className="relative flex min-h-[calc(100vh-64px)] w-full py-4 items-center justify-center overflow-hidden rounded-lg border bg-background">
<div className="layout flex flex-col h-full justify-center items-center gap-8">
<Title />
<FormStepper />
</div>
<AnimatedGridPattern
numSquares={30}
maxOpacity={0.1}
duration={3}
repeatDelay={1}
className={cn(
"[mask-image:radial-gradient(500px_circle_at_center,white,transparent)]",
"inset-x-0 inset-y-[-30%] h-[200%] skew-y-12"
)}
/>
</section>
);
}

View File

@ -0,0 +1,40 @@
import { z } from "zod";
export const createAgentSchema = z.object({
userId: z.string(),
name: z
.string({
required_error: "Name is required",
})
.min(2, {
message: "Minimum Name is 2 characters.",
})
.max(30, {
message: "Maximum Name is 30 characters.",
}),
description: z
.string({
required_error: "Description is required",
})
.min(2, {
message: "Minimum Description is 2 characters.",
})
.max(200, {
message: "Maximum Description is 200 characters.",
}),
modelType: z.enum([
"roleplay",
"programming",
"marketing",
"marketing_seo",
"technology",
"science",
"translation",
"legal",
"finance",
"health",
"trivia",
"academia",
"general",
]),
});

View File

@ -1,47 +1,16 @@
import React from "react";
import Link from "next/link";
import Image from "next/image";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Play } from "lucide-react";
import { formatDate } from "@/lib/utils";
import { Tables } from "@/utils/supabase/database.types";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Badge } from "@/components/ui/badge";
import Spinner from "@/components/spinner";
import { Textarea } from "@/components/ui/textarea";
import clsx from "clsx";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import axios, { AxiosError } from "axios";
import { toast } from "sonner";
import { createClient } from "@/utils/supabase/client";
import { buttonVariants } from "@/components/ui/button";
type AgentCardProps = { data: Tables<"agents"> };
@ -57,106 +26,6 @@ export default function AgentCard(props: AgentCardProps) {
last_conv,
} = props.data;
const [openDialogUpdate, setOpenDialogUpdate] = React.useState(false);
const formSchema = z.object({
question: z
.string({
required_error: "Question is required",
})
.min(2, {
message: "Minimum Question is 2 characters.",
}),
});
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
question: "",
},
mode: "onChange",
});
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: async (values: z.infer<typeof formSchema>) => {
const supabase = createClient();
const { data: existingAgent } = await supabase
.from("agents")
.select("*")
.eq("id", id);
const { data, error } = await supabase
.from("agents")
.update({
conversation: existingAgent ? existingAgent[0].conversation + 1 : 0,
last_conv: new Date().toISOString(),
})
.eq("id", id)
.select();
const url = encodeURI(
`https://ai-endpoint-one.dev3vds1.link/deepinfra-ai/${name}/${model_type}/${description}/${values.question}`
);
const json = await axios.get(url);
return json.data;
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["agents"],
});
},
onError: (error) => {
if (error instanceof AxiosError) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
const errResponse = error.response.data;
console.log({ errResponse });
// if (errResponse.errors && Array.isArray(errResponse.errors)) {
// errResponse.errors.forEach(
// (inputErr: { field: string; message: string }) => {
// toast.error(`Error field : ${inputErr.field}`, {
// description: inputErr.message,
// });
// }
// );
// }
// if (errResponse.code) {
// toast.error(errResponse?.code, {
// description: errResponse?.message,
// });
// }
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
toast.error("No response from the server", {
description: "Failed to fetch the data, server returns null.",
});
} else {
// Something happened in setting up the request that triggered an Error
toast.error("Failed to set up the request", {
description: "There is something wrong when setting up the request",
});
}
}
// toast.error(err.message);
},
});
const onSubmit = (values: z.infer<typeof formSchema>) => {
mutation.mutate(values);
};
const {
formState: { errors },
} = form;
return (
<li className="flex flex-col rounded-lg p-4 border border-accent-foreground hover:shadow-sm transition-shadow duration-200">
<figure className="overflow-hidden relative rounded-lg object-cover border mb-3 w-full h-[200px]">
@ -184,124 +53,14 @@ export default function AgentCard(props: AgentCardProps) {
<h3 className="text-xl font-semibold mb-2 truncate">{name}</h3>
<p className="text-sm text-foreground line-clamp-3 mb-4">{description}</p>
<div className="mt-auto grid">
<Dialog
open={openDialogUpdate}
onOpenChange={(val) => {
form.reset();
mutation.reset();
setOpenDialogUpdate(val);
}}
<Link
href={`/agents/${id}`}
target="blank"
className={buttonVariants({ variant: "outline" })}
>
<DialogTrigger asChild>
<Button size={"sm"} variant={"outline"}>
<Play className="h-4 w-4 mr-2" />
<span>Try it</span>
</Button>
</DialogTrigger>
<DialogContent className="w-[90%] max-h-[90vh] max-w-xl rounded-md overflow-y-scroll">
<DialogHeader>
<DialogTitle>Try Agent</DialogTitle>
<DialogDescription>
Try to ask a question to our agent
</DialogDescription>
</DialogHeader>
<div>
<figure className="overflow-hidden relative rounded-lg object-cover border mb-3 w-full h-[200px]">
<Image
className="w-full object-cover"
src={image_url ? image_url : "/placeholder.png"}
fill={true}
alt={name}
/>
<div className="h-full w-full absolute top-0 left-0" />
</figure>
<div className="flex items-center justify-between mb-3">
<Badge className="mb-2" variant={"secondary"}>
{conversation} chats
</Badge>
<p className="mb-2 text-primary">
<time dateTime={created_at} className="text-xs">
Last Active:{" "}
<span className="font-bold">
{last_conv ? formatDate(last_conv) : "-"}
</span>
</time>
</p>
</div>
<ul className="text-sm space-y-3 mb-3">
<li>
<h3 className="font-bold">Name:</h3>
<p className="text-gray-500">{name}</p>
</li>
<li>
<h3 className="font-bold">Description:</h3>
<p className="text-gray-500">{description}</p>
</li>
</ul>
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-3"
>
<FormField
control={form.control}
name="question"
render={({ field }) => (
<FormItem>
<FormLabel className="font-bold">Question:</FormLabel>
<FormControl>
<Textarea
rows={4}
placeholder="For answering daily questions."
className={clsx(
errors.question &&
"focus-visible:ring-destructive"
)}
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{mutation.isSuccess && (
<>
<h3 className="text-sm font-bold">Answer:</h3>
<blockquote className="border-l-2 pl-6 italic">
{mutation.data.response}
</blockquote>
</>
)}
<DialogFooter className="mt-4 flex justify-end space-x-2">
<DialogClose asChild>
<Button
type="button"
variant="outline"
disabled={mutation.isPending}
size="sm"
>
{mutation.isPending && (
<Spinner className="mr-2 h-4 w-4 " />
)}
Cancel
</Button>
</DialogClose>
<Button
type="submit"
disabled={mutation.isPending}
size="sm"
>
{mutation.isPending && (
<Spinner className="mr-2 h-4 w-4 " />
)}
Submit
</Button>
</DialogFooter>
</form>
</Form>
</div>
</DialogContent>
</Dialog>
<Play className="h-4 w-4 mr-2" />
<span>Try it</span>
</Link>
</div>
</li>
);

View File

@ -1,25 +1,29 @@
"use client";
import Image from "next/image";
import Link from "next/link";
import { parseAsInteger, useQueryState } from "nuqs";
import { useQuery } from "@tanstack/react-query";
import { ChevronLeft, ChevronRight, Search } from "lucide-react";
import { BadgePlus, ChevronLeft, ChevronRight, Search } from "lucide-react";
import PostgrestError from "@/lib/config";
import { cn } from "@/lib/utils";
import { createClient } from "@/utils/supabase/client";
import { Input } from "@/components/ui/input";
import { Button, buttonVariants } from "@/components/ui/button";
import {
Pagination,
PaginationContent,
PaginationItem,
} from "@/components/ui/pagination";
import { createClient } from "@/utils/supabase/client";
import { Button } from "@/components/ui/button";
import AgentCardSkeleton from "./agent-card-skeleton";
import AgentCard from "./agent-card";
import { ChatFloatingButton } from "@/components/chat-floating-button";
import AgentCardSkeleton from "./agent-card-skeleton";
const DATA_DISPLAY = 6;
@ -61,85 +65,106 @@ export function AgentList() {
paginationFilter * DATA_DISPLAY + DATA_DISPLAY - 1
);
console.log(response);
if (response.error) throw new PostgrestError(response.error);
return response;
},
});
return (
<>
<section className="group pt-8 pb-10 min-h-[50vh] ">
<div className="layout">
<h1 className="mb-4 text-3xl font-bold text-foreground sm:text-4xl">
Agents
</h1>
<div className="mb-8 flex flex-col sm:flex-row justify-between">
<div className="relative">
<Input
aria-label="search"
id="search"
type="search"
placeholder="Search..."
className="min-w-64 pl-7"
value={searchFilter}
onChange={(e) => {
setPaginationFilter(0);
e.target.value === ""
? setSearchFilter(null)
: setSearchFilter(e.target.value);
}}
/>
<Search className="absolute w-4 h-4 left-2 top-1/2 -translate-y-1/2 text-gray-300" />
</div>
</div>
<ul className="grid gap-8 mb-10 sm:grid-cols-2 lg:grid-cols-3">
{isLoading &&
[...Array(6)].map((_, idx) => (
<li key={idx}>
<AgentCardSkeleton />
</li>
))}
{agentsData &&
agentsData.data?.map((agent) => (
<AgentCard key={agent.name} data={agent} />
))}
</ul>
{agentsData ? (
<Pagination>
<PaginationContent>
<PaginationItem>
<Button
variant={"outline"}
size={"icon"}
disabled={paginationFilter === 0}
onClick={() => setPaginationFilter((old) => old - 1)}
>
<ChevronLeft className="h-4 w-4" />
</Button>
</PaginationItem>
<p className="text-sm font-semibold text-gray-400">
Page {paginationFilter + 1} of{" "}
{agentsData.count
? Math.ceil(agentsData.count / DATA_DISPLAY)
: 1}
</p>
<PaginationItem>
<Button
variant={"outline"}
size={"icon"}
disabled={
paginationFilter + 1 ===
Math.ceil(agentsData.count! / DATA_DISPLAY)
}
onClick={() => setPaginationFilter((old) => old + 1)}
>
<ChevronRight className="h-4 w-4" />
</Button>
</PaginationItem>
</PaginationContent>
</Pagination>
) : null}
<div className="mb-8 flex flex-col sm:flex-row justify-between">
<div className="relative">
<Input
aria-label="search"
id="search"
type="search"
placeholder="Search..."
className="min-w-64 pl-7"
value={searchFilter}
onChange={(e) => {
setPaginationFilter(0);
e.target.value === ""
? setSearchFilter(null)
: setSearchFilter(e.target.value);
}}
/>
<Search className="absolute w-4 h-4 left-2 top-1/2 -translate-y-1/2 text-gray-300" />
</div>
</section>
<Link
href={`${process.env.NEXT_PUBLIC_DASHBOARD_URL!}/agents/create`}
className={cn(
buttonVariants({ variant: "default" }),
"w-full sm:w-auto text-background flex gap-2"
)}
>
<BadgePlus className="h-6 w-6" />
Create new Agent
</Link>
</div>
{agentsData && agentsData.data.length === 0 && (
<div className="flex flex-col justify-center items-center ">
<figure className="relative w-full sm:w-1/2 h-72 sm:h-96 mb-6">
<Image
className="object-cover"
src="/empty-data.jpg"
fill={true}
alt="Empty data"
/>
</figure>
<h1 className="text-xl font-bold">No data found</h1>
</div>
)}
<ul className="grid gap-8 mb-10 sm:grid-cols-2 lg:grid-cols-3">
{isLoading &&
[...Array(6)].map((_, idx) => (
<li key={idx}>
<AgentCardSkeleton />
</li>
))}
{agentsData &&
agentsData.data?.map((agent) => (
<AgentCard key={agent.name} data={agent} />
))}
</ul>
{agentsData ? (
<Pagination>
<PaginationContent>
<PaginationItem>
<Button
variant={"outline"}
size={"icon"}
disabled={paginationFilter === 0}
onClick={() => setPaginationFilter((old) => old - 1)}
>
<ChevronLeft className="h-4 w-4" />
</Button>
</PaginationItem>
<p className="text-sm font-semibold text-gray-400">
Page {paginationFilter + 1} of{" "}
{agentsData.count
? Math.ceil(agentsData.count / DATA_DISPLAY)
: 1}
</p>
<PaginationItem>
<Button
variant={"outline"}
size={"icon"}
disabled={
agentsData.count === 0
? true
: paginationFilter + 1 ===
Math.ceil(agentsData.count! / DATA_DISPLAY)
}
onClick={() => setPaginationFilter((old) => old + 1)}
>
<ChevronRight className="h-4 w-4" />
</Button>
</PaginationItem>
</PaginationContent>
</Pagination>
) : null}
</>
);
}

View File

@ -3,8 +3,15 @@ import { AgentList } from "./_components/agent-list";
export default function Explore() {
return (
<Suspense>
<AgentList />
</Suspense>
<section className="group pt-8 pb-10 min-h-[50vh] ">
<div className="layout">
<h1 className="mb-4 text-3xl font-bold text-foreground sm:text-4xl">
Agents
</h1>
<Suspense>
<AgentList />
</Suspense>
</div>
</section>
);
}

View File

@ -1,135 +0,0 @@
import Author from "@/components/blog-author";
import CtaSection from "@/components/sections/cta";
import { getPost } from "@/lib/blog";
import { siteConfig } from "@/lib/config";
import { formatDate } from "@/lib/utils";
import type { Metadata } from "next";
import Image from "next/image";
import { notFound } from "next/navigation";
import { Suspense } from "react";
export async function generateMetadata({
params,
}: {
params: {
slug: string;
};
}): Promise<Metadata | undefined> {
let post = await getPost(params.slug);
let {
title,
publishedAt: publishedTime,
summary: description,
image,
} = post.metadata;
return {
title,
description,
openGraph: {
title,
description,
type: "article",
publishedTime,
url: `${siteConfig.url}/blog/${post.slug}`,
images: [
{
url: image,
},
],
},
twitter: {
card: "summary_large_image",
title,
description,
images: [image],
},
};
}
export default async function Blog({
params,
}: {
params: {
slug: string;
};
}) {
let post = await getPost(params.slug);
if (!post) {
notFound();
}
return (
<section id="blog">
<script
type="application/ld+json"
suppressHydrationWarning
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: post.metadata.title,
datePublished: post.metadata.publishedAt,
dateModified: post.metadata.publishedAt,
description: post.metadata.summary,
image: post.metadata.image
? `${siteConfig.url}${post.metadata.image}`
: `${siteConfig.url}/blog/${post.slug}/opengraph-image`,
url: `${siteConfig.url}/blog/${post.slug}`,
author: {
"@type": "Person",
name: siteConfig.name,
},
}),
}}
/>
<div className="mx-auto w-full max-w-[800px] px-4 sm:px-6 lg:px-8 space-y-4 my-12">
<Suspense
fallback={
<div className="mb-8 w-full h-64 bg-gray-200 animate-pulse rounded-lg"></div>
}
>
{post.metadata.image && (
<div className="mb-8">
<Image
width={1920}
height={1080}
src={post.metadata.image}
alt={post.metadata.title}
className="w-full h-auto rounded-lg border shadow-md"
/>
</div>
)}
</Suspense>
<div className="flex flex-col">
<h1 className="title font-medium text-3xl tracking-tighter">
{post.metadata.title}
</h1>
</div>
<div className="flex justify-between items-center text-sm">
<Suspense fallback={<p className="h-5" />}>
<div className="flex items-center space-x-2">
<time
dateTime={post.metadata.publishedAt}
className="text-sm text-gray-500"
>
{formatDate(post.metadata.publishedAt)}
</time>
</div>
</Suspense>
</div>
<div className="flex items-center space-x-2">
<Author
twitterUsername={post.metadata.author}
name={post.metadata.author}
image={"/author.jpg"}
/>
</div>
<article
className="prose dark:prose-invert mx-auto max-w-full"
dangerouslySetInnerHTML={{ __html: post.source }}
></article>
</div>
<CtaSection />
</section>
);
}

View File

@ -1,16 +0,0 @@
import Footer from "@/components/sections/footer";
import Header from "@/components/sections/header";
interface MarketingLayoutProps {
children: React.ReactNode;
}
export default async function Layout({ children }: MarketingLayoutProps) {
return (
<>
<Header />
<main>{children}</main>
<Footer />
</>
);
}

View File

@ -1,39 +0,0 @@
import BlogCard from "@/components/blog-card";
import { getBlogPosts } from "@/lib/blog";
import { siteConfig } from "@/lib/config";
import { constructMetadata } from "@/lib/utils";
export const metadata = constructMetadata({
title: "Blog",
description: `Latest news and updates from ${siteConfig.name}.`,
});
export default async function Blog() {
const allPosts = await getBlogPosts();
const articles = await Promise.all(
allPosts.sort((a, b) => b.publishedAt.localeCompare(a.publishedAt))
);
return (
<>
<div className="mx-auto w-full max-w-screen-xl px-2.5 lg:px-20 mt-24">
<div className="text-center py-16">
<h1 className="text-3xl font-bold text-foreground sm:text-4xl">
Articles
</h1>
<p className="mt-4 text-xl text-muted-foreground">
Latest news and updates from {siteConfig.name}
</p>
</div>
</div>
<div className="min-h-[50vh] bg-white/50 shadow-[inset_10px_-50px_94px_0_rgb(199,199,199,0.2)] backdrop-blur-lg">
<div className="mx-auto grid w-full max-w-screen-xl grid-cols-1 gap-8 px-2.5 py-10 lg:px-20 lg:grid-cols-3">
{articles.map((data, idx) => (
<BlogCard key={data.slug} data={data} priority={idx <= 1} />
))}
</div>
</div>
</>
);
}

View File

@ -13,6 +13,8 @@ import { cn, constructMetadata } from "@/lib/utils";
import ReactQueryProvider from "@/providers/react-query-provider";
import { Toaster } from "@/components/ui/sonner";
import MyPrivyProvider from "@/providers/privy-provider";
export const metadata: Metadata = constructMetadata({});
export const viewport: Viewport = {
@ -40,18 +42,20 @@ export default function RootLayout({
)}
>
<ReactQueryProvider>
<NuqsAdapter>
<ThemeProvider
attribute="class"
defaultTheme="light"
enableSystem={false}
>
{children}
{/* <ThemeToggle /> */}
{/* <TailwindIndicator /> */}
<Toaster richColors />
</ThemeProvider>
</NuqsAdapter>
<MyPrivyProvider>
<NuqsAdapter>
<ThemeProvider
attribute="class"
defaultTheme="light"
enableSystem={false}
>
{children}
{/* <ThemeToggle /> */}
{/* <TailwindIndicator /> */}
<Toaster richColors />
</ThemeProvider>
</NuqsAdapter>
</MyPrivyProvider>
</ReactQueryProvider>
</body>
</html>

View File

@ -0,0 +1,31 @@
"use client";
import React from "react";
import { motion } from "framer-motion";
import ShineBorder from "@/components/ui/shine-border";
const ease = [0.16, 1, 0.3, 1];
export default function CardBackground({
children,
}: {
children: React.ReactNode;
}) {
return (
<motion.div
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2, duration: 0.4, ease }}
className="w-full"
>
<ShineBorder
className="mb-8 z-20 relative p-6 mx-auto w-full max-w-2xl flex flex-col overflow-hidden rounded-lg border bg-background md:shadow-xl"
color={["#f8e5da", "#FE8FB5", "#812f20"]}
>
{children}
</ShineBorder>
</motion.div>
);
}

View File

@ -1,5 +1,5 @@
import { Icons } from "@/components/icons";
import { buttonVariants } from "@/components/ui/button";
import { Button, buttonVariants } from "@/components/ui/button";
import {
Drawer,
DrawerContent,
@ -9,10 +9,15 @@ import {
} from "@/components/ui/drawer";
import { siteConfig } from "@/lib/config";
import { cn } from "@/lib/utils";
import { LoginModalOptions } from "@privy-io/react-auth";
import Link from "next/link";
import { IoMenuSharp } from "react-icons/io5";
export default function drawerDemo() {
export default function drawerDemo({
login,
}: {
login: (options?: LoginModalOptions | React.MouseEvent<any, any>) => void;
}) {
return (
<Drawer>
<DrawerTrigger>
@ -47,22 +52,12 @@ export default function drawerDemo() {
</nav>
</DrawerHeader>
<DrawerFooter>
<Link
href="/login"
className={buttonVariants({ variant: "outline" })}
<Button
onClick={login}
className={buttonVariants({ variant: "default" })}
>
Login
</Link>
<Link
href="/signup"
className={cn(
buttonVariants({ variant: "default" }),
"w-full sm:w-auto text-background flex gap-2"
)}
>
<Icons.logo className="h-6 w-6" />
Get Started for Free
</Link>
Connect Wallet
</Button>
</DrawerFooter>
</DrawerContent>
</Drawer>

View File

@ -17,7 +17,7 @@ export default function CtaSection() {
>
<div className="flex flex-col w-full sm:flex-row items-center justify-center space-y-4 sm:space-y-0 sm:space-x-4 pt-4">
<Link
href={`${process.env.NEXT_PUBLIC_DASHBOARD_URL}/login`}
href={`${process.env.NEXT_PUBLIC_DASHBOARD_URL}/explore`}
className={cn(
buttonVariants({ variant: "default" }),
"w-full sm:w-auto text-background flex gap-2"

View File

@ -1,19 +1,52 @@
"use client";
import Drawer from "@/components/drawer";
import { Icons } from "@/components/icons";
import Menu from "@/components/menu";
import { buttonVariants } from "@/components/ui/button";
import { siteConfig } from "@/lib/config";
import { cn } from "@/lib/utils";
import Image from "next/image";
import React from "react";
import Link from "next/link";
import { useEffect, useState } from "react";
import Image from "next/image";
import { useRouter } from "next/navigation";
import { ChevronsUpDown, LogOut } from "lucide-react";
import { useLogin, usePrivy } from "@privy-io/react-auth";
import { cn, getInitials } from "@/lib/utils";
import { siteConfig } from "@/lib/config";
import { toast } from "sonner";
import Drawer from "@/components/drawer";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Button, buttonVariants } from "@/components/ui/button";
import Spinner from "../spinner";
export default function Header() {
const [addBorder, setAddBorder] = useState(false);
const [addBorder, setAddBorder] = React.useState(false);
useEffect(() => {
const { ready, authenticated, logout, user } = usePrivy();
const router = useRouter();
const { login } = useLogin({
onComplete: ({ user, isNewUser, wasAlreadyAuthenticated, loginMethod }) => {
if (!wasAlreadyAuthenticated) toast.success("Login success");
router.replace("/");
},
onError: (error) => {
console.log(error);
toast.error(error);
},
});
React.useEffect(() => {
const handleScroll = () => {
if (window.scrollY > 20) {
setAddBorder(true);
@ -31,7 +64,7 @@ export default function Header() {
return (
<header
className={" sticky top-0 z-50 py-2 bg-background/60 backdrop-blur"}
className={"h-16 sticky top-0 z-50 py-2 bg-background/60 backdrop-blur"}
>
<div className="flex justify-between items-center container">
<Link
@ -46,35 +79,151 @@ export default function Header() {
alt="logo"
className="rounded-md shadow"
/>
{/* <Icons.logoCrystal className="w-auto h-[28px]" /> */}
<span className="font-bold text-xl">{siteConfig.name}</span>
</Link>
<div className="hidden lg:block">
<div className="flex items-center ">
{/* <nav className="mr-10">
<Menu />
</nav> */}
<div className="gap-2 flex">
<Link href="/" className={buttonVariants({ variant: "default" })}>
Connect Wallet
</Link>
{/* <Link
href="/signup"
className={cn(
buttonVariants({ variant: "default" }),
"w-full sm:w-auto text-background flex gap-2"
)}
>
<Icons.logo className="h-6 w-6" />
Get Started for Free
</Link> */}
{!ready ? (
<Spinner />
) : (
<>
{authenticated && user ? (
<DropdownMenu>
<DropdownMenuTrigger className="max-w-48 flex items-center gap-2 border p-2 rounded-md">
<Avatar className="h-8 w-8 rounded-lg">
<AvatarFallback className="rounded-lg">
{user.email ? getInitials(user.email.address) : "U"}
</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{user.email && user.email.address}
{user.wallet && user.wallet.address}
</span>
<span className="truncate text-xs">{user.id}</span>
</div>
<ChevronsUpDown className="ml-auto size-4" />
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
side={"bottom"}
align="end"
sideOffset={4}
>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar className="h-8 w-8 rounded-lg">
<AvatarFallback className="rounded-lg">
{user.email
? getInitials(user.email.address)
: "U"}
</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{user.email?.address}
</span>
<span className="truncate text-xs">
{user.id}
</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => {
logout();
toast.success("You have been signed out");
}}
asChild
>
<Button
variant={"ghost"}
className="w-full text-primary "
>
<LogOut />
Log out
</Button>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : (
<Button
onClick={login}
className={buttonVariants({ variant: "default" })}
>
Connect Wallet
</Button>
)}
</>
)}
</div>
</div>
</div>
<div className="mt-2 cursor-pointer block lg:hidden">
<Drawer />
<div className="ml-2 mt-2 cursor-pointer block lg:hidden">
{authenticated && user ? (
<DropdownMenu>
<DropdownMenuTrigger className="max-w-48 flex items-center gap-2 border p-2 rounded-md">
<Avatar className="hidden sm:block h-8 w-8 rounded-lg">
<AvatarFallback className="rounded-lg">
{user.email ? getInitials(user.email.address) : "U"}
</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{user.email && user.email.address}
{user.wallet && user.wallet.address}
</span>
<span className="truncate text-xs">{user.id}</span>
</div>
<ChevronsUpDown className="ml-auto size-4" />
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
side={"bottom"}
align="end"
sideOffset={4}
>
<DropdownMenuLabel className="p-0 font-normal">
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<Avatar className="h-8 w-8 rounded-lg">
<AvatarFallback className="rounded-lg">
{user.email ? getInitials(user.email.address) : "U"}
</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-semibold">
{user.email?.address}
</span>
<span className="truncate text-xs">{user.id}</span>
</div>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={() => {
logout();
toast.success("You have been signed out");
}}
asChild
>
<Button variant={"ghost"} className="w-full text-primary ">
<LogOut />
Log out
</Button>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
) : (
<Button
onClick={login}
className={buttonVariants({ variant: "default" })}
>
Connect Wallet
</Button>
)}
</div>
</div>
<hr

View File

@ -104,7 +104,7 @@ function HeroCTA() {
transition={{ delay: 0.8, duration: 0.8, ease }}
>
<Link
href={`${process.env.NEXT_PUBLIC_DASHBOARD_URL!}`}
href={`${process.env.NEXT_PUBLIC_DASHBOARD_URL!}/agents/create`}
className={cn(
buttonVariants({ variant: "default" }),
"w-full sm:w-auto text-background flex gap-2"

View File

@ -0,0 +1,47 @@
import BlurFade from "@/components/magicui/blur-fade";
import Section from "@/components/section";
import { Card, CardContent } from "@/components/ui/card";
import { Brain, Shield, Zap } from "lucide-react";
const problems = [
{
title: "Dynamic Goal Formation",
description:
"Unlike traditional AI systems that rely on predefined objectives, our agents can formulate and prioritize their own goals based on their understanding of the broader context and desired outcomes.",
icon: Brain,
},
{
title: "Contextual Understanding",
description:
"Our agents don't operate in a vacuum. They grasp the nuances of their environment, understanding not just what's happening, but why it matters and how it affects their objectives.",
icon: Zap,
},
{
title: "Ethical Framework Integration",
description:
"We've built robust ethical considerations directly into our agents' decision-making processes, ensuring they operate not just effectively, but responsibly and in alignment with human values.",
icon: Shield,
},
];
export default function Statistic() {
return (
<Section title="Be Different" subtitle="Technology That Sets Us Apart">
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mt-12">
{problems.map((problem, index) => (
<BlurFade key={index} delay={0.2 + index * 0.2} inView>
<Card className="bg-background border-none shadow-none">
<CardContent className="p-6 space-y-4">
<div className="w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center">
<problem.icon className="w-6 h-6 text-primary" />
</div>
<h3 className="text-xl font-semibold">{problem.title}</h3>
<p className="text-muted-foreground">{problem.description}</p>
</CardContent>
</Card>
</BlurFade>
))}
</div>
</Section>
);
}

View File

@ -0,0 +1,148 @@
"use client";
import { motion } from "motion/react";
import { useEffect, useId, useRef, useState } from "react";
import { cn } from "@/lib/utils";
interface AnimatedGridPatternProps {
width?: number;
height?: number;
x?: number;
y?: number;
strokeDasharray?: any;
numSquares?: number;
className?: string;
maxOpacity?: number;
duration?: number;
repeatDelay?: number;
}
export default function AnimatedGridPattern({
width = 40,
height = 40,
x = -1,
y = -1,
strokeDasharray = 0,
numSquares = 50,
className,
maxOpacity = 0.5,
duration = 4,
repeatDelay = 0.5,
...props
}: AnimatedGridPatternProps) {
const id = useId();
const containerRef = useRef(null);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const [squares, setSquares] = useState(() => generateSquares(numSquares));
function getPos() {
return [
Math.floor((Math.random() * dimensions.width) / width),
Math.floor((Math.random() * dimensions.height) / height),
];
}
// Adjust the generateSquares function to return objects with an id, x, and y
function generateSquares(count: number) {
return Array.from({ length: count }, (_, i) => ({
id: i,
pos: getPos(),
}));
}
// Function to update a single square's position
const updateSquarePosition = (id: number) => {
setSquares((currentSquares) =>
currentSquares.map((sq) =>
sq.id === id
? {
...sq,
pos: getPos(),
}
: sq,
),
);
};
// Update squares to animate in
useEffect(() => {
if (dimensions.width && dimensions.height) {
setSquares(generateSquares(numSquares));
}
}, [dimensions, numSquares]);
// Resize observer to update container dimensions
useEffect(() => {
const resizeObserver = new ResizeObserver((entries) => {
for (let entry of entries) {
setDimensions({
width: entry.contentRect.width,
height: entry.contentRect.height,
});
}
});
if (containerRef.current) {
resizeObserver.observe(containerRef.current);
}
return () => {
if (containerRef.current) {
resizeObserver.unobserve(containerRef.current);
}
};
}, [containerRef]);
return (
<svg
ref={containerRef}
aria-hidden="true"
className={cn(
"pointer-events-none absolute inset-0 h-full w-full fill-red-primary/30 stroke-primary/30",
className,
)}
{...props}
>
<defs>
<pattern
id={id}
width={width}
height={height}
patternUnits="userSpaceOnUse"
x={x}
y={y}
>
<path
d={`M.5 ${height}V.5H${width}`}
fill="none"
strokeDasharray={strokeDasharray}
/>
</pattern>
</defs>
<rect width="100%" height="100%" fill={`url(#${id})`} />
<svg x={x} y={y} className="overflow-visible">
{squares.map(({ pos: [x, y], id }, index) => (
<motion.rect
initial={{ opacity: 0 }}
animate={{ opacity: maxOpacity }}
transition={{
duration,
repeat: 1,
delay: index * 0.1,
repeatType: "reverse",
}}
onAnimationComplete={() => updateSquarePosition(id)}
key={`${x}-${y}-${index}`}
width={width - 1}
height={height - 1}
x={x * width + 1}
y={y * height + 1}
fill="currentColor"
strokeWidth="0"
/>
))}
</svg>
</svg>
);
}

View File

@ -0,0 +1,50 @@
"use client"
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "@/lib/utils"
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }

View File

@ -0,0 +1,149 @@
import type { ReactNode } from "react";
import React, {
createContext,
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useMemo,
useRef,
} from "react";
import type {
GlobalOptions as ConfettiGlobalOptions,
CreateTypes as ConfettiInstance,
Options as ConfettiOptions,
} from "canvas-confetti";
import confetti from "canvas-confetti";
import { Button, ButtonProps } from "@/components/ui/button";
type Api = {
fire: (options?: ConfettiOptions) => void;
};
type Props = React.ComponentPropsWithRef<"canvas"> & {
options?: ConfettiOptions;
globalOptions?: ConfettiGlobalOptions;
manualstart?: boolean;
children?: ReactNode;
};
export type ConfettiRef = Api | null;
const ConfettiContext = createContext<Api>({} as Api);
// Define component first
const ConfettiComponent = forwardRef<ConfettiRef, Props>((props, ref) => {
const {
options,
globalOptions = { resize: true, useWorker: true },
manualstart = false,
children,
...rest
} = props;
const instanceRef = useRef<ConfettiInstance | null>(null);
const canvasRef = useCallback(
(node: HTMLCanvasElement) => {
if (node !== null) {
if (instanceRef.current) return;
instanceRef.current = confetti.create(node, {
...globalOptions,
resize: true,
});
} else {
if (instanceRef.current) {
instanceRef.current.reset();
instanceRef.current = null;
}
}
},
[globalOptions],
);
const fire = useCallback(
async (opts = {}) => {
try {
await instanceRef.current?.({ ...options, ...opts });
} catch (error) {
console.error("Confetti error:", error);
}
},
[options],
);
const api = useMemo(
() => ({
fire,
}),
[fire],
);
useImperativeHandle(ref, () => api, [api]);
useEffect(() => {
if (!manualstart) {
(async () => {
try {
await fire();
} catch (error) {
console.error("Confetti effect error:", error);
}
})();
}
}, [manualstart, fire]);
return (
<ConfettiContext.Provider value={api}>
<canvas ref={canvasRef} {...rest} />
{children}
</ConfettiContext.Provider>
);
});
// Set display name immediately
ConfettiComponent.displayName = "Confetti";
// Export as Confetti
export const Confetti = ConfettiComponent;
interface ConfettiButtonProps extends ButtonProps {
options?: ConfettiOptions &
ConfettiGlobalOptions & { canvas?: HTMLCanvasElement };
children?: React.ReactNode;
}
const ConfettiButtonComponent = ({
options,
children,
...props
}: ConfettiButtonProps) => {
const handleClick = async (event: React.MouseEvent<HTMLButtonElement>) => {
try {
const rect = event.currentTarget.getBoundingClientRect();
const x = rect.left + rect.width / 2;
const y = rect.top + rect.height / 2;
await confetti({
...options,
origin: {
x: x / window.innerWidth,
y: y / window.innerHeight,
},
});
} catch (error) {
console.error("Confetti button error:", error);
}
};
return (
<Button onClick={handleClick} {...props}>
{children}
</Button>
);
};
ConfettiButtonComponent.displayName = "ConfettiButton";
export const ConfettiButton = ConfettiButtonComponent;
export default Confetti;

View File

@ -0,0 +1,200 @@
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground 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",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md 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
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}

View File

@ -0,0 +1,43 @@
"use client";
import { useEffect, useState } from "react";
import { cn } from "@/lib/utils";
interface MeteorsProps extends React.HTMLAttributes<HTMLSpanElement> {
number?: number;
}
export const Meteors = ({ number = 20, ...props }: MeteorsProps) => {
const [meteorStyles, setMeteorStyles] = useState<Array<React.CSSProperties>>(
[],
);
useEffect(() => {
const styles = [...new Array(number)].map(() => ({
top: -5,
left: Math.floor(Math.random() * window.innerWidth) + "px",
animationDelay: Math.random() * 1 + 0.2 + "s",
animationDuration: Math.floor(Math.random() * 8 + 2) + "s",
}));
setMeteorStyles(styles);
}, [number]);
return (
<>
{[...meteorStyles].map((style, idx) => (
// Meteor Head
<span
key={idx}
className={cn(
"pointer-events-none absolute left-1/2 top-1/2 size-0.5 rotate-[215deg] animate-meteor rounded-full bg-slate-500 shadow-[0_0_0_1px_#ffffff10]",
)}
style={style}
{...props}
>
{/* Meteor Tail */}
<div className="pointer-events-none absolute top-1/2 -z-10 h-px w-[50px] -translate-y-1/2 bg-gradient-to-r from-slate-500 to-transparent" />
</span>
))}
</>
);
};

View File

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

View File

@ -0,0 +1,61 @@
"use client";
import { cn } from "@/lib/utils";
type TColorProp = string | string[];
interface ShineBorderProps {
borderRadius?: number;
borderWidth?: number;
duration?: number;
color?: TColorProp;
className?: string;
children: React.ReactNode;
}
/**
* @name Shine Border
* @description It is an animated background border effect component with easy to use and configurable props.
* @param borderRadius defines the radius of the border.
* @param borderWidth defines the width of the border.
* @param duration defines the animation duration to be applied on the shining border
* @param color a string or string array to define border color.
* @param className defines the class name to be applied to the component
* @param children contains react node elements.
*/
export default function ShineBorder({
borderRadius = 8,
borderWidth = 1,
duration = 14,
color = "#000000",
className,
children,
}: ShineBorderProps) {
return (
<div
style={
{
"--border-radius": `${borderRadius}px`,
} as React.CSSProperties
}
className={cn(
"relative min-h-[60px] w-fit min-w-[300px] rounded-[--border-radius] bg-white p-3 text-black dark:bg-black dark:text-white",
className,
)}
>
<div
style={
{
"--border-width": `${borderWidth}px`,
"--border-radius": `${borderRadius}px`,
"--duration": `${duration}s`,
"--mask-linear-gradient": `linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)`,
"--background-radial-gradient": `radial-gradient(transparent,transparent, ${color instanceof Array ? color.join(",") : color},transparent,transparent)`,
} as React.CSSProperties
}
className={`pointer-events-none before:bg-shine-size before:absolute before:inset-0 before:size-full before:rounded-[--border-radius] before:p-[--border-width] before:will-change-[background-position] before:content-[""] before:![-webkit-mask-composite:xor] before:![mask-composite:exclude] before:[background-image:--background-radial-gradient] before:[background-size:300%_300%] before:[mask:--mask-linear-gradient] motion-safe:before:animate-shine`}
></div>
{children}
</div>
);
}

View File

@ -0,0 +1,152 @@
"use client";
import { CSSProperties, ReactElement, useEffect, useState } from "react";
import { motion } from "motion/react";
import { cn } from "@/lib/utils";
interface Sparkle {
id: string;
x: string;
y: string;
color: string;
delay: number;
scale: number;
lifespan: number;
}
interface SparklesTextProps {
/**
* @default <div />
* @type ReactElement
* @description
* The component to be rendered as the text
* */
as?: ReactElement;
/**
* @default ""
* @type string
* @description
* The className of the text
*/
className?: string;
/**
* @required
* @type string
* @description
* The text to be displayed
* */
text: string;
/**
* @default 10
* @type number
* @description
* The count of sparkles
* */
sparklesCount?: number;
/**
* @default "{first: '#9E7AFF', second: '#FE8BBB'}"
* @type string
* @description
* The colors of the sparkles
* */
colors?: {
first: string;
second: string;
};
}
const SparklesText: React.FC<SparklesTextProps> = ({
text,
colors = { first: "#9E7AFF", second: "#FE8BBB" },
className,
sparklesCount = 10,
...props
}) => {
const [sparkles, setSparkles] = useState<Sparkle[]>([]);
useEffect(() => {
const generateStar = (): Sparkle => {
const starX = `${Math.random() * 100}%`;
const starY = `${Math.random() * 100}%`;
const color = Math.random() > 0.5 ? colors.first : colors.second;
const delay = Math.random() * 2;
const scale = Math.random() * 1 + 0.3;
const lifespan = Math.random() * 10 + 5;
const id = `${starX}-${starY}-${Date.now()}`;
return { id, x: starX, y: starY, color, delay, scale, lifespan };
};
const initializeStars = () => {
const newSparkles = Array.from({ length: sparklesCount }, generateStar);
setSparkles(newSparkles);
};
const updateStars = () => {
setSparkles((currentSparkles) =>
currentSparkles.map((star) => {
if (star.lifespan <= 0) {
return generateStar();
} else {
return { ...star, lifespan: star.lifespan - 0.1 };
}
}),
);
};
initializeStars();
const interval = setInterval(updateStars, 100);
return () => clearInterval(interval);
}, [colors.first, colors.second, sparklesCount]);
return (
<div
className={cn("text-6xl font-bold", className)}
{...props}
style={
{
"--sparkles-first-color": `${colors.first}`,
"--sparkles-second-color": `${colors.second}`,
} as CSSProperties
}
>
<span className="relative inline-block">
{sparkles.map((sparkle) => (
<Sparkle key={sparkle.id} {...sparkle} />
))}
<strong>{text}</strong>
</span>
</div>
);
};
const Sparkle: React.FC<Sparkle> = ({ id, x, y, color, delay, scale }) => {
return (
<motion.svg
key={id}
className="pointer-events-none absolute z-20"
initial={{ opacity: 0, left: x, top: y }}
animate={{
opacity: [0, 1, 0],
scale: [0, scale, 0],
rotate: [75, 120, 150],
}}
transition={{ duration: 0.8, repeat: Infinity, delay }}
width="21"
height="21"
viewBox="0 0 21 21"
>
<path
d="M9.82531 0.843845C10.0553 0.215178 10.9446 0.215178 11.1746 0.843845L11.8618 2.72026C12.4006 4.19229 12.3916 6.39157 13.5 7.5C14.6084 8.60843 16.8077 8.59935 18.2797 9.13822L20.1561 9.82534C20.7858 10.0553 20.7858 10.9447 20.1561 11.1747L18.2797 11.8618C16.8077 12.4007 14.6084 12.3916 13.5 13.5C12.3916 14.6084 12.4006 16.8077 11.8618 18.2798L11.1746 20.1562C10.9446 20.7858 10.0553 20.7858 9.82531 20.1562L9.13819 18.2798C8.59932 16.8077 8.60843 14.6084 7.5 13.5C6.39157 12.3916 4.19225 12.4007 2.72023 11.8618L0.843814 11.1747C0.215148 10.9447 0.215148 10.0553 0.843814 9.82534L2.72023 9.13822C4.19225 8.59935 6.39157 8.60843 7.5 7.5C8.60843 6.39157 8.59932 4.19229 9.13819 2.72026L9.82531 0.843845Z"
fill={color}
/>
</motion.svg>
);
};
export default SparklesText;

View File

@ -249,3 +249,22 @@ export const siteConfig = {
};
export type SiteConfig = typeof siteConfig;
export default class PostgrestError extends Error {
details: string;
hint: string;
code: string;
constructor(context: {
message: string;
details: string;
hint: string;
code: string;
}) {
super(context.message);
this.name = "PostgrestError";
this.details = context.details;
this.hint = context.hint;
this.code = context.code;
}
}

View File

@ -87,3 +87,8 @@ export function formatDate(date: string) {
return `${yearsAgo} year${yearsAgo > 1 ? "s" : ""} ago`;
}
}
export const getInitials = (string: string) =>
string
.split(/\s/)
.reduce((response, word) => (response += word.slice(0, 1)), "");

View File

@ -0,0 +1,49 @@
"use client";
import { PrivyProvider } from "@privy-io/react-auth";
import { toSolanaWalletConnectors } from "@privy-io/react-auth/solana";
const solanaConnectors = toSolanaWalletConnectors();
export default function MyPrivyProvider({
children,
}: {
children: React.ReactNode;
}) {
return (
<PrivyProvider
appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID!}
config={{
// Customize Privy's appearance in your app
appearance: {
accentColor: "#812f20",
theme: "light",
showWalletLoginFirst: false,
walletChainType: "ethereum-and-solana",
walletList: ["detected_wallets", "phantom"],
logo: "https://supabasekong-mco40gw4sc0gs4ks40w4c4g4.dev3vds1.link/storage/v1/object/public/agent-thumbnails/Beactio%20Banner.png",
},
loginMethods: ["email", "wallet", "discord"],
fundingMethodConfig: {
moonpay: {
useSandbox: true,
},
},
// Create embedded wallets for users who don't have a wallet
embeddedWallets: {
createOnLogin: "all-users",
showWalletUIs: true,
},
mfa: {
noPromptOnMfaRequired: false,
},
externalWallets: {
solana: { connectors: solanaConnectors },
},
}}
>
{children}
</PrivyProvider>
);
}

View File

@ -1,23 +1,63 @@
"use client";
// import { handleError } from "@/lib/utils";
import PostgrestError from "@/lib/config";
import {
MutationCache,
QueryCache,
QueryClient,
QueryClientProvider,
} from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { AxiosError } from "axios";
import { toast } from "sonner";
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error) => {
// handleError(error);
if (error instanceof PostgrestError) {
toast.error(error.code, { description: error.message });
}
},
}),
mutationCache: new MutationCache({
onError: (error) => {
// handleError(error);
if (error instanceof AxiosError) {
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
const errResponse = error.response.data;
console.log({ errResponse });
// if (errResponse.errors && Array.isArray(errResponse.errors)) {
// errResponse.errors.forEach(
// (inputErr: { field: string; message: string }) => {
// toast.error(`Error field : ${inputErr.field}`, {
// description: inputErr.message,
// });
// }
// );
// }
// if (errResponse.code) {
// toast.error(errResponse?.code, {
// description: errResponse?.message,
// });
// }
} else if (error.request) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
toast.error("No response from the server", {
description: "Failed to fetch the data, server returns null.",
});
} else {
// Something happened in setting up the request that triggered an Error
toast.error("Failed to set up the request", {
description: "There is something wrong when setting up the request",
});
}
}
},
}),
});

View File

@ -59,13 +59,44 @@ export type Database = {
};
public: {
Tables: {
agent_responses: {
Row: {
agent_id: number;
created_at: string;
id: string;
question: string;
response: string;
};
Insert: {
agent_id: number;
created_at?: string;
id?: string;
question: string;
response: string;
};
Update: {
agent_id?: number;
created_at?: string;
id?: string;
question?: string;
response?: string;
};
Relationships: [
{
foreignKeyName: "agent_responses_agent_id_fkey";
columns: ["agent_id"];
referencedRelation: "agents";
referencedColumns: ["id"];
}
];
};
agents: {
Row: {
conversation: number;
created_at: string;
description: string;
id: number;
image_url: string | null;
image_url: string;
last_conv: string | null;
model_type: Database["public"]["Enums"]["modelType"];
name: string;
@ -76,7 +107,7 @@ export type Database = {
created_at?: string;
description: string;
id?: number;
image_url?: string | null;
image_url?: string;
last_conv?: string | null;
model_type: Database["public"]["Enums"]["modelType"];
name: string;
@ -87,7 +118,7 @@ export type Database = {
created_at?: string;
description?: string;
id?: number;
image_url?: string | null;
image_url?: string;
last_conv?: string | null;
model_type?: Database["public"]["Enums"]["modelType"];
name?: string;

View File

@ -10,94 +10,136 @@ const config = {
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
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))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
animation: {
marquee: "marquee var(--duration) linear infinite",
"marquee-vertical": "marquee-vertical var(--duration) linear infinite",
"border-beam": "border-beam calc(var(--duration)*1s) infinite linear",
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
ripple: "ripple var(--duration,2s) ease calc(var(--i, 0)*.2s) infinite",
},
keyframes: {
marquee: {
from: { transform: "translateX(0)" },
to: { transform: "translateX(calc(-100% - var(--gap)))" },
},
"marquee-vertical": {
from: { transform: "translateY(0)" },
to: { transform: "translateY(calc(-100% - var(--gap)))" },
},
"border-beam": {
"100%": {
"offset-distance": "100%",
},
},
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
ripple: {
"0%, 100%": {
transform: "translate(-50%, -50%) scale(1)",
},
"50%": {
transform: "translate(-50%, -50%) scale(0.9)",
},
},
},
},
container: {
center: true,
padding: '2rem',
screens: {
'2xl': '1400px'
}
},
extend: {
colors: {
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))'
},
secondary: {
DEFAULT: 'hsl(var(--secondary))',
foreground: 'hsl(var(--secondary-foreground))'
},
destructive: {
DEFAULT: 'hsl(var(--destructive))',
foreground: 'hsl(var(--destructive-foreground))'
},
muted: {
DEFAULT: 'hsl(var(--muted))',
foreground: 'hsl(var(--muted-foreground))'
},
accent: {
DEFAULT: 'hsl(var(--accent))',
foreground: 'hsl(var(--accent-foreground))'
},
popover: {
DEFAULT: 'hsl(var(--popover))',
foreground: 'hsl(var(--popover-foreground))'
},
card: {
DEFAULT: 'hsl(var(--card))',
foreground: 'hsl(var(--card-foreground))'
}
},
borderRadius: {
lg: 'var(--radius)',
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
animation: {
marquee: 'marquee var(--duration) linear infinite',
'marquee-vertical': 'marquee-vertical var(--duration) linear infinite',
'border-beam': 'border-beam calc(var(--duration)*1s) infinite linear',
'accordion-down': 'accordion-down 0.2s ease-out',
'accordion-up': 'accordion-up 0.2s ease-out',
ripple: 'ripple var(--duration,2s) ease calc(var(--i, 0)*.2s) infinite',
meteor: 'meteor 5s linear infinite',
shine: 'shine var(--duration) infinite linear'
},
keyframes: {
marquee: {
from: {
transform: 'translateX(0)'
},
to: {
transform: 'translateX(calc(-100% - var(--gap)))'
}
},
'marquee-vertical': {
from: {
transform: 'translateY(0)'
},
to: {
transform: 'translateY(calc(-100% - var(--gap)))'
}
},
'border-beam': {
'100%': {
'offset-distance': '100%'
}
},
'accordion-down': {
from: {
height: '0'
},
to: {
height: 'var(--radix-accordion-content-height)'
}
},
'accordion-up': {
from: {
height: 'var(--radix-accordion-content-height)'
},
to: {
height: '0'
}
},
ripple: {
'0%, 100%': {
transform: 'translate(-50%, -50%) scale(1)'
},
'50%': {
transform: 'translate(-50%, -50%) scale(0.9)'
}
},
meteor: {
'0%': {
transform: 'rotate(215deg) translateX(0)',
opacity: '1'
},
'70%': {
opacity: '1'
},
'100%': {
transform: 'rotate(215deg) translateX(-500px)',
opacity: '0'
}
},
shine: {
'0%': {
'background-position': '0% 0%'
},
'50%': {
'background-position': '100% 100%'
},
to: {
'background-position': '0% 0%'
}
}
}
}
},
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
} satisfies Config;