259 lines
8.3 KiB
TypeScript
259 lines
8.3 KiB
TypeScript
import React from "react";
|
|
|
|
import Image from "next/image";
|
|
|
|
import { z } from "zod";
|
|
import { useForm } from "react-hook-form";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
|
|
import { Pencil } 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";
|
|
|
|
type AgentCardProps = { data: Tables<"agents"> };
|
|
|
|
export default function AgentCard(props: AgentCardProps) {
|
|
const { id, image_url, name, description, model_type, created_at } =
|
|
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 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="bg-background flex flex-col rounded-lg p-4 border hover:shadow-sm transition-shadow duration-200">
|
|
<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" />
|
|
<Badge className="absolute bottom-3 left-3">{model_type}</Badge>
|
|
</figure>
|
|
<p className="mb-2">
|
|
<time dateTime={created_at} className="text-sm text-muted-foreground">
|
|
{formatDate(created_at)}
|
|
</time>
|
|
</p>
|
|
<h3 className="text-xl font-semibold mb-2 truncate">{name}</h3>
|
|
<p className="text-sm text-foreground line-clamp-2 mb-4">{description}</p>
|
|
<div className="mt-auto grid">
|
|
<Dialog
|
|
open={openDialogUpdate}
|
|
onOpenChange={(val) => {
|
|
// form.reset();
|
|
setOpenDialogUpdate(val);
|
|
}}
|
|
>
|
|
<DialogTrigger asChild>
|
|
<Button size={"sm"} variant={"outline"}>
|
|
<Pencil className="h-4 w-4 mr-2" />
|
|
<span>Try it</span>
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent className="w-[90%] 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>
|
|
<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" />
|
|
<Badge className="absolute bottom-3 left-3">{model_type}</Badge>
|
|
</figure>
|
|
<ul className="text-sm space-y-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>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
</li>
|
|
);
|
|
}
|