我在这个下一个的应用程序,我使用upload.io上传和replicate.com的人工智能模型,我可以上传1张图片,但我不能上传多张。有人看到这里的问题了吗?
import { AnimatePresence, motion } from "framer-motion";
import { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
import { useEffect, useState } from "react";
import { UploadDropzone } from "react-uploader";
import { Uploader } from "uploader";
import { CompareSlider } from "../components/CompareSlider";
import Footer from "../components/Footer";
import Header from "../components/Header";
import LoadingDots from "../components/LoadingDots";
import ResizablePanel from "../components/ResizablePanel";
import Toggle from "../components/Toggle";
import appendNewToName from "../utils/appendNewToName";
import downloadPhoto from "../utils/downloadPhoto";
import DropDown from "../components/DropDown";
import { roomType, rooms, themeType, themes } from "../utils/dropdownTypes";
import { GenerateResponseData } from "./api/generate";
import { useSession, signIn } from "next-auth/react";
import useSWR from "swr";
import { Rings } from "react-loader-spinner";
import Link from "next/link";
import { useRouter } from "next/router";
import { Toaster, toast } from "react-hot-toast";
// Configuration for the uploader
const uploader = Uploader({
apiKey: !!process.env.NEXT_PUBLIC_UPLOAD_API_KEY
? process.env.NEXT_PUBLIC_UPLOAD_API_KEY
: "free",
});
const Home: NextPage = () => {
const [originalPhoto, setOriginalPhoto] = useState<string | null>(null);
const [restoredImage, setRestoredImage] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const [restoredLoaded, setRestoredLoaded] = useState<boolean>(false);
const [sideBySide, setSideBySide] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const [photoName, setPhotoName] = useState<string | null>(null);
const [theme, setTheme] = useState<themeType>("Modern");
const [room, setRoom] = useState<roomType>("Living Room");
const fetcher = (url: string) => fetch(url).then((res) => res.json());
const { data, mutate } = useSWR("/api/remaining", fetcher);
const { data: session, status } = useSession();
const options = {
multi: true,
maxFileCount: 1,
mimeTypes: ["image/jpeg", "image/png", "image/jpg"],
editor: { images: { crop: false } },
// tags: [data?.remainingGenerations > 3 ? "paid" : "free"],
styles: {
colors: {
primary: "#2563EB", // Primary buttons & links
error: "#d23f4d", // Error messages
shade100: "#5A5A5A", // Standard text
shade200: "#fffe", // Secondary button text
shade300: "#fffd", // Secondary button text (hover)
shade400: "#5A5A5A", // Welcome text
shade500: "#fff9", // Modal close button
shade600: "#5A5A5A", // Border
shade700: "#fff2", // Progress indicator background
shade800: "#fff1", // File item background
shade900: "#ffff", // Various (draggable crop buttons, etc.)
},
},
onValidate: async (file: File): Promise<undefined | string> => {
return data.remainingGenerations === 0
? `No more credits left. Buy more above.`
: undefined;
},
};
const UploadDropZone = () => (
<UploadDropzone
uploader={uploader}
options={options}
onUpdate={(file) => {
if (file.length !== 0) {
setPhotoName(file[0].originalFile.originalFileName);
setOriginalPhoto(file[0].fileUrl.replace("raw", "thumbnail"));
generatePhoto(file[0].fileUrl.replace("raw", "thumbnail"));
}
}}
width="670px"
height="250px"
/>
);
async function generatePhoto(fileUrl: string) {
await new Promise((resolve) => setTimeout(resolve, 200));
setLoading(true);
const res = await fetch("/api/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ imageUrl: fileUrl, theme, room }),
});
let response = (await res.json()) as GenerateResponseData;
if (res.status !== 200) {
setError(response as any);
} else {
mutate();
const rooms =
(JSON.parse(localStorage.getItem("rooms") || "[]") as string[]) || [];
rooms.push(response.id);
localStorage.setItem("rooms", JSON.stringify(rooms));
setRestoredImage(response.generated);
}
setTimeout(() => {
setLoading(false);
}, 1300);
}
const router = useRouter();
useEffect(() => {
if (router.query.success === "true") {
toast.success("Payment successful!");
}
}, [router.query.success]);
return (<div className="bg-white">
<div className="flex max-w-6xl mx-auto flex-col items-center justify-center py-2 min-h-screen">
<Head>
<title>ProPhotoshoot.io</title>
</Head>
<Header
photo={session?.user?.image || undefined}
email={session?.user?.email || undefined}
/>
<main className="flex flex-1 w-full flex-col items-center justify-center text-center px-4 mt-4 sm:mb-0 mb-8">
{status === "authenticated" ? (
<Link
href="/buy-credits"
className="border border-black rounded-2xl py-2 px-4 text-black text-sm my-6 duration-300 ease-in-out hover:text-black hover:scale-105 transition"
>
Pricing is now available.{" "}
<span className="font-semibold text-blue-600 ">Click here</span> to
buy credits!
</Link>
) : (
<a
href="https://twitter.com/nutlope/status/1635674124738523139?cxt=HHwWhsCz1ei8irMtAAAA"
target="_blank"
rel="noopener noreferrer"
className="border border-black rounded-2xl py-2 px-4 text-black text-sm my-6 duration-300 ease-in-out hover:text-gray-300 transition"
>
Over{" "}
<span className="font-semibold text-blue-600">1 million users</span>{" "}
have used ProPhotoshoot.io so far
</a>
)}
<h1 className="mx-auto max-w-4xl font-display text-4xl font-bold tracking-normal text-black sm:text-6xl mb-5">
Generate your <span className="text-blue-600">A.I</span> photoshoot
</h1>
{status === "authenticated" && data && !restoredImage && (
<p className="text-black">
You have{" "}
<span className="font-semibold text-blue-600">
{data.remainingGenerations}{" "}
{data?.remainingGenerations > 1 ? "credits" : "credit"}
</span>{" "}
left.{" "}
{data.remainingGenerations < 2 && (
<span>
Buy more credits{" "}
<Link
href="/buy-credits"
className="font-semibold text-gray-300 underline underline-offset-2 hover:text-gray-200 transition"
>
here
</Link>
.
</span>
)}
</p>
)}
<ResizablePanel>
<AnimatePresence mode="wait">
<motion.div className="flex justify-between items-center w-full flex-col mt-4">
{restoredImage && (
<div>
Here's your remodeled <b>{room.toLowerCase()}</b> in the{" "}
<b>{theme.toLowerCase()}</b> theme!{" "}
</div>
)}
<div
className={`${restoredLoaded ? "visible mt-6 -ml-8" : "invisible"
}`}
>
<Toggle
className={`${restoredLoaded ? "visible mb-6" : "invisible"}`}
sideBySide={sideBySide}
setSideBySide={(newVal) => setSideBySide(newVal)}
/>
</div>
{restoredLoaded && sideBySide && (
<CompareSlider
original={originalPhoto!}
restored={restoredImage!}
/>
)}
{status === "loading" ? (
<div className="max-w-[670px] h-[250px] flex justify-center items-center">
<Rings
height="100"
width="100"
color="white"
radius="6"
wrapperStyle={{}}
wrapperClass=""
visible={true}
ariaLabel="rings-loading"
/>
</div>
) : status === "authenticated" && !originalPhoto ? (
<>
<div className="space-y-4 w-full max-w-sm">
<div className="flex mt-3 items-center space-x-3">
<Image
src="/number-1-white.svg"
width={30}
height={30}
alt="1 icon"
/>
<p className="text-left font-medium text-black">
Choose your outfit.
</p>
</div>
<DropDown
theme={theme}
// @ts-ignore
setTheme={(newTheme) => setTheme(newTheme)}
themes={themes}
/>
</div>
<div className="space-y-4 w-full max-w-sm">
<div className="flex mt-10 items-center space-x-3">
<Image
src="/number-2-white.svg"
width={30}
height={30}
alt="1 icon"
/>
<p className="text-left font-medium text-black">
Choose your background.
</p>
</div>
<DropDown
theme={room}
// @ts-ignore
setTheme={(newRoom) => setRoom(newRoom)}
themes={rooms}
/>
</div>
<div className="mt-4 w-full max-w-sm">
<div className="flex mt-6 w-96 items-center space-x-3">
<Image
src="/number-3-white.svg"
width={30}
height={30}
alt="1 icon"
/>
<p className="text-left font-medium">
Upload a picture of yourself.
</p>
</div>
</div>
<UploadDropZone />
</>
) : (
!originalPhoto && (
<div className="h-[250px] flex flex-col items-center space-y-6 max-w-[670px] -mt-8">
<div className="max-w-xl text-black">
Sign in below with Google to create a free account and
redesign your room today. You will get 3 generations for
free.
</div>
<button
onClick={() => signIn("google")}
className="bg-gray-200 text-black font-semibold py-3 px-6 rounded-2xl flex items-center space-x-2"
>
<Image
src="/google.png"
width={20}
height={20}
alt="google's logo"
/>
<span>Sign in with Google</span>
</button>
</div>
)
)}
{originalPhoto && !restoredImage && (
<Image
alt="original photo"
src={originalPhoto}
className="rounded-2xl h-96"
width={475}
height={475}
/>
)}
{restoredImage && originalPhoto && !sideBySide && (
<div className="flex sm:space-x-4 sm:flex-row flex-col">
<div>
<h2 className="mb-1 font-medium text-lg">Original Room</h2>
<Image
alt="original photo"
src={originalPhoto}
className="rounded-2xl relative w-full h-96"
width={475}
height={475}
/>
</div>
<div className="sm:mt-0 mt-8">
<h2 className="mb-1 font-medium text-lg">Generated Room</h2>
<a href={restoredImage} target="_blank" rel="noreferrer">
<Image
alt="restored photo"
src={restoredImage}
className="rounded-2xl relative sm:mt-0 mt-2 cursor-zoom-in w-full h-96"
width={475}
height={475}
onLoadingComplete={() => setRestoredLoaded(true)}
/>
</a>
</div>
</div>
)}
{loading && (
<button
disabled
className="bg-blue-500 rounded-full text-white font-medium px-4 pt-2 pb-3 mt-8 w-40"
>
<span className="pt-4">
<LoadingDots color="white" style="large" />
</span>
</button>
)}
{error && (
<div
className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded-xl mt-8 max-w-[575px]"
role="alert"
>
<div className="bg-red-500 text-white font-bold rounded-t px-4 py-2">
Please try again later.
</div>
<div className="border border-t-0 border-red-400 rounded-b bg-red-100 px-4 py-3 text-red-700">
{error}
</div>
</div>
)}
<div className="flex space-x-2 justify-center">
{originalPhoto && !loading && !error && (
<button
onClick={() => {
setOriginalPhoto(null);
setRestoredImage(null);
setRestoredLoaded(false);
setError(null);
}}
className="bg-blue-500 rounded-full text-white font-medium px-4 py-2 mt-8 hover:bg-blue-500/80 transition"
>
Generate New Room
</button>
)}
{restoredLoaded && (
<button
onClick={() => {
downloadPhoto(
restoredImage!,
appendNewToName(photoName!)
);
}}
className="bg-white rounded-full text-black border font-medium px-4 py-2 mt-8 hover:bg-gray-100 transition"
>
Download Generated Room
</button>
)}
</div>
</motion.div>
</AnimatePresence>
</ResizablePanel>
<Toaster position="top-center" reverseOrder={false} />
</main>
<Footer />
</div></div>
);
};
export default Home;
我在'const options'中指定了multi:true,这应该使它工作,但它没有,这是代码。我也改变了maxFileCount,但这也不起作用。
1条答案
按热度按时间gudnpqoy1#
您的示例中有
maxFileCount: 1
--您提到您已经更改了它,但只是突出显示它,因为这将防止多个文件。此外,如果在
UploadDropzone
组件渲染后更新options
对象 *,则需要通过添加<UploadDropzone key={someNewValue}
来强制重新渲染(这是由于react-uploader
的限制,即它当前不会根据 prop 更改进行更新)。