diff --git a/ai-minter/.env.example b/ai-minter/.env.example new file mode 100644 index 00000000..24ad6f22 --- /dev/null +++ b/ai-minter/.env.example @@ -0,0 +1 @@ +REPLICATE_API_TOKEN=r8_cMNzo59ZjcNKRp0YYOFoEXwF1d0eAAM1hqaqb \ No newline at end of file diff --git a/ai-minter/src/app/api/predictions/[[...id]]/route.ts b/ai-minter/src/app/api/predictions/[[...id]]/route.ts new file mode 100644 index 00000000..ee0751e7 --- /dev/null +++ b/ai-minter/src/app/api/predictions/[[...id]]/route.ts @@ -0,0 +1,2 @@ +import { handlers } from "@/server/replicate"; +export const { GET, POST } = handlers; diff --git a/ai-minter/src/app/globals.css b/ai-minter/src/app/globals.css index 11979362..c3167ed5 100644 --- a/ai-minter/src/app/globals.css +++ b/ai-minter/src/app/globals.css @@ -79,3 +79,72 @@ background: rgb(144,40,47); background: radial-gradient(circle, rgba(144,40,47,0.3) 0%, rgba(144,40,47,0.6) 0%, rgba(16,18,35,0.3730085784313726) 55%); } + + + +.lds-ellipsis { + display: inline-block; + position: relative; + width: 80px; + height: 80px; +} + +.lds-ellipsis div { + position: absolute; + top: 33px; + width: 13px; + height: 13px; + border-radius: 50%; + background: #ff2424; + animation-timing-function: cubic-bezier(0, 1, 1, 0); +} + +.lds-ellipsis div:nth-child(1) { + left: 8px; + animation: lds-ellipsis1 0.6s infinite; +} + +.lds-ellipsis div:nth-child(2) { + left: 8px; + animation: lds-ellipsis2 0.6s infinite; +} + +.lds-ellipsis div:nth-child(3) { + left: 32px; + animation: lds-ellipsis2 0.6s infinite; +} + +.lds-ellipsis div:nth-child(4) { + left: 56px; + animation: lds-ellipsis3 0.6s infinite; +} + +@keyframes lds-ellipsis1 { + 0% { + transform: scale(0); + } + + 100% { + transform: scale(1); + } +} + +@keyframes lds-ellipsis3 { + 0% { + transform: scale(1); + } + + 100% { + transform: scale(0); + } +} + +@keyframes lds-ellipsis2 { + 0% { + transform: translate(0, 0); + } + + 100% { + transform: translate(24px, 0); + } +} \ No newline at end of file diff --git a/ai-minter/src/components/Minter.tsx b/ai-minter/src/components/Minter.tsx index 0483e2f2..01b18ee1 100644 --- a/ai-minter/src/components/Minter.tsx +++ b/ai-minter/src/components/Minter.tsx @@ -23,26 +23,37 @@ import useMintImage from "@/hooks/useMint"; import { getImageData } from "@/hooks/utils"; import { useState } from "react"; +const Spinner = () => { + return ( +
+
+
+
+
+
+ ); +}; + export default function Minter() { const { form, onSubmit, preview, setPreview } = useMintImage(); - const [prediction, setPrediction] = useState(null); + const [promptResult, setPrediction] = useState(null); const [error, setError] = useState(null); - const sleep = (ms:number) => new Promise((r) => setTimeout(r, ms)); + const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); const handlePrompt = async (e: any) => { e.preventDefault(); - const promptValue = form.getValues("description"); - console.log(promptValue, 'promptValue') + const promptValue = form.getValues("description"); const response = await fetch("/api/predictions", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ - input: {prompt: promptValue}, - version: "50f96fcd1980e7dcaba18e757acbac05e7f2ad4fbcb4a75f86a13c4086df26d0" + input: { prompt: promptValue }, + version: + "50f96fcd1980e7dcaba18e757acbac05e7f2ad4fbcb4a75f86a13c4086df26d0", }), }); let prediction = await response.json(); @@ -51,9 +62,15 @@ export default function Minter() { return; } - console.log(prediction, 'prediction') + console.log(prediction, "prediction"); setPrediction(prediction); + if( prediction.status === "succeeded") { + form.setValue('media', prediction?.output[24]) + + console.log(form.getValues("media"), 'form.getValues("description")') + } + while ( prediction.status !== "succeeded" && prediction.status !== "failed" @@ -65,12 +82,26 @@ export default function Minter() { setError(prediction.detail); return; } - console.log({prediction}) + console.log({ prediction }); setPrediction(prediction); } }; - return ( + const handleSubmit = async () => { + + form.setValue('media', promptResult?.output[24]) + + await onSubmit(form.getValues()) + console.log(form.getValues()) + + } + + console.log( + promptResult?.status !== null && + promptResult?.status == "succeeded", promptResult?.output + ) + + return (
@@ -98,51 +129,40 @@ export default function Minter() { - )} /> -
- {preview && ( - Selected Preview - )} - ( - - - { - const { files, displayUrl } = getImageData(event); - setPreview(displayUrl); - onChange(files); - }} - className="file:bg-black file:text-white file:border file:border-solid file:border-grey-700 file:rounded-md" - /> - - - + + {promptResult?.status !== undefined && + promptResult?.status !== "succeeded" && ( +
+ +

+ {promptResult?.status} +

+
+ )} + + {promptResult?.status !== null && + promptResult?.status == "succeeded" && ( + )} - /> - - + + {" "} +
diff --git a/ai-minter/src/hooks/formSchema.ts b/ai-minter/src/hooks/formSchema.ts index 8feadd66..7ec2d689 100644 --- a/ai-minter/src/hooks/formSchema.ts +++ b/ai-minter/src/hooks/formSchema.ts @@ -9,15 +9,9 @@ const formSchema = z.object({ description: z.string().min(2, { message: "description must be at least 2 characters.", }), - media: z - .custom() - .transform((file) => file.length > 0 && file.item(0)) - .refine((file) => !file || (!!file && file.size <= 10 * 1024 * 1024), { - message: "The media must be a maximum of 10MB.", - }) - .refine((file) => !file || (!!file && file.type?.startsWith("image")), { - message: "Only images are allowed to be sent.", - }), + media: z.string().min(2, { + message: "description must be at least 2 characters.", + }) }); diff --git a/ai-minter/src/hooks/useMint.ts b/ai-minter/src/hooks/useMint.ts index 7b56d7ab..65ec8191 100644 --- a/ai-minter/src/hooks/useMint.ts +++ b/ai-minter/src/hooks/useMint.ts @@ -9,9 +9,9 @@ import { TITLE, DESCRIPTION } from "@/constants"; import { zodResolver } from "@hookform/resolvers/zod"; import { mint, execute } from "@mintbase-js/sdk"; +import { uploadReference } from "@mintbase-js/storage" import { formSchema } from "./formSchema"; import { MintbaseWalletSetup } from "@/config/setup"; -import { uploadReference } from "@mintbase-js/storage"; const useMintImage = () => { const { selector, activeAccountId } = useMbWallet(); @@ -26,31 +26,44 @@ const useMintImage = () => { } }; + const getImageAsBlob = async (url: string): Promise => { + try { + // Fetch the image + const response = await fetch(url); + + if (!response.ok) { + throw new Error(`Failed to fetch image. Status code: ${response.status}`); + } + + // Convert the image to a Blob + const imageBlob = await response.blob(); + + console.log(url, imageBlob, 'blob') + // Create a File object from the Blob + const imageFile = new File([imageBlob], 'image', { type: imageBlob.type }); + + return imageFile; + } catch (error: any) { + console.error(`Error: ${error?.message}`); + return null; + } +}; + const onSubmit = async (data: { [x: string]: any }) => { + console.log(data, 'data') + const wallet = await getWallet(); - const uploadRef = await handleUpload(data.media, data); - await handleMint(uploadRef, activeAccountId as string, wallet); + const image = await getImageAsBlob(data?.media) + + console.log(image, 'image') + const reference = await uploadReference(image) + await handleMint(reference, activeAccountId as string, wallet); }; const form = useForm>({ resolver: zodResolver(formSchema) }); - async function handleUpload( - file: File, - data: { [x: string]: any } - ): Promise { - const metadata = { - title: data[TITLE], - description: data[DESCRIPTION], - media: file, - }; - - const referenceJson = await uploadReference(metadata); - console.log("Successfully uploaded with reference:", referenceJson.id); - return referenceJson.id; - } - async function handleMint( reference: string, activeAccountId: string, @@ -60,7 +73,7 @@ const useMintImage = () => { const mintCall = mint({ noMedia: true, metadata: { - reference: reference, + reference:reference }, contractAddress: MintbaseWalletSetup.contractAddress, ownerId: activeAccountId, diff --git a/ai-minter/src/providers/replicate.tsx b/ai-minter/src/providers/replicate.tsx index 512c10cb..22600d2f 100644 --- a/ai-minter/src/providers/replicate.tsx +++ b/ai-minter/src/providers/replicate.tsx @@ -41,7 +41,7 @@ export const ReplicateProvider: React.FC = ({ const [prediction, setPrediction] = useState(null); const [error, setError] = useState(null); - const handleSubmit = async (e) => { + const handleSubmit = async (e: any) => { e.preventDefault(); const response = await fetch("/api/predictions", { method: "POST",