Skip to content

Commit 926fc81

Browse files
authored
feat: add img2img feature for new model (#34)
1 parent 22d0b96 commit 926fc81

File tree

17 files changed

+669
-203
lines changed

17 files changed

+669
-203
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
"openai": "^3.1.0",
4343
"plaiceholder": "^2.5.0",
4444
"react": "18.2.0",
45+
"react-advanced-cropper": "^0.17.0",
4546
"react-dom": "18.2.0",
4647
"react-dropzone": "^14.2.3",
4748
"react-icons": "^4.7.1",

src/components/dashboard/Uploader.tsx

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { resizeImage } from "@/core/utils/upload";
1+
import { createPreviewMedia, resizeImage } from "@/core/utils/upload";
22
import {
33
Box,
44
Button,
@@ -30,11 +30,12 @@ import { CheckedListItem } from "../home/Pricing";
3030
import UploadErrorMessages from "./UploadErrorMessages";
3131

3232
type TUploadState = "not_uploaded" | "uploading" | "uploaded";
33+
export type FilePreview = (File | Blob) & { preview: string };
3334

3435
const MAX_FILES = 25;
3536

3637
const Uploader = ({ handleOnAdd }: { handleOnAdd: () => void }) => {
37-
const [files, setFiles] = useState<(File & { preview: string })[]>([]);
38+
const [files, setFiles] = useState<FilePreview[]>([]);
3839
const [uploadState, setUploadState] = useState<TUploadState>("not_uploaded");
3940
const [errorMessages, setErrorMessages] = useState<string[]>([]);
4041
const [urls, setUrls] = useState<string[]>([]);
@@ -74,11 +75,7 @@ const Uploader = ({ handleOnAdd }: { handleOnAdd: () => void }) => {
7475
setErrorMessages([]);
7576
setFiles([
7677
...files,
77-
...acceptedFiles.map((file) =>
78-
Object.assign(file, {
79-
preview: URL.createObjectURL(file),
80-
})
81-
),
78+
...acceptedFiles.map((file) => createPreviewMedia(file)),
8279
]);
8380
}
8481
},

src/components/layout/AuthForm.tsx

+35-12
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,56 @@
1+
import { getEmailProvider } from "@/core/utils/mail";
12
import {
23
Box,
34
Button,
45
FormControl,
56
FormLabel,
7+
Heading,
8+
Icon,
69
Input,
10+
Link,
711
Stack,
812
Text,
913
} from "@chakra-ui/react";
1014
import { signIn } from "next-auth/react";
11-
import { useRouter } from "next/router";
1215
import { useState } from "react";
1316
import { FaPaperPlane } from "react-icons/fa";
17+
import { MdCheckCircleOutline } from "react-icons/md";
1418
import { useMutation } from "react-query";
1519

1620
export default function AuthForm() {
1721
const [email, setEmail] = useState("");
18-
const router = useRouter();
19-
20-
const { mutate: login, isLoading } = useMutation(
21-
"login",
22-
() =>
23-
signIn("email", { email, redirect: false, callbackUrl: "/dashboard" }),
24-
{
25-
onSuccess: () => {
26-
router.push("/login?verifyRequest=1");
27-
},
28-
}
22+
const {
23+
mutate: login,
24+
isLoading,
25+
isSuccess,
26+
} = useMutation("login", () =>
27+
signIn("email", { email, redirect: false, callbackUrl: "/dashboard" })
2928
);
3029

30+
if (isSuccess) {
31+
const { name, url } = getEmailProvider(email);
32+
33+
return (
34+
<Box mx={{ base: 4, md: 0 }} textAlign="center">
35+
<Heading>
36+
Check your email <Icon mb="-4px" as={MdCheckCircleOutline} />
37+
</Heading>
38+
<Text maxWidth="30rem" mt={3} fontSize="2xl">
39+
A <b>sign in link</b> has been sent to your email address.{" "}
40+
{name && url && (
41+
<>
42+
Check{" "}
43+
<Link textDecoration="underline" isExternal href={url}>
44+
your {name} inbox
45+
</Link>
46+
.
47+
</>
48+
)}
49+
</Text>
50+
</Box>
51+
);
52+
}
53+
3154
return (
3255
<Stack spacing={4} width="100%" mx="auto" maxW="md" py={12} px={6}>
3356
<Stack textAlign="center" align="center" spacing={0}>

src/components/projects/PomptWizardPopover.tsx

-112
This file was deleted.

src/components/projects/ProjectCard.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const ProjectCard = ({
7474
<Badge colorScheme="teal">{project.credits} shots left</Badge>
7575
)}
7676
</Text>
77-
<Text textTransform="capitalize" fontSize="sm">
77+
<Text textTransform="capitalize" fontSize="sm" color="beige.500">
7878
{formatRelative(new Date(project.createdAt), new Date())}
7979
</Text>
8080
</Box>
+164
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import { createPreviewMedia, resizeImage } from "@/core/utils/upload";
2+
import useProjectContext from "@/hooks/use-project-context";
3+
import {
4+
Button,
5+
ButtonGroup,
6+
Center,
7+
Icon,
8+
Modal,
9+
ModalBody,
10+
ModalCloseButton,
11+
ModalContent,
12+
ModalFooter,
13+
ModalHeader,
14+
ModalOverlay,
15+
Text,
16+
useDisclosure,
17+
} from "@chakra-ui/react";
18+
import { useS3Upload } from "next-s3-upload";
19+
import { useRef, useState } from "react";
20+
import {
21+
FixedCropper,
22+
FixedCropperRef,
23+
ImageRestriction,
24+
} from "react-advanced-cropper";
25+
import { useDropzone } from "react-dropzone";
26+
import {
27+
BsCloud,
28+
BsCloudArrowDown,
29+
BsCloudArrowUp,
30+
BsImage,
31+
} from "react-icons/bs";
32+
import { FilePreview } from "../dashboard/Uploader";
33+
34+
import "react-advanced-cropper/dist/style.css";
35+
import "react-advanced-cropper/dist/themes/compact.css";
36+
37+
const PromptImage = () => {
38+
const [isLoading, setLoading] = useState(false);
39+
const { isOpen, onOpen, onClose } = useDisclosure({
40+
onClose: () => {
41+
setImagePreview(undefined);
42+
},
43+
});
44+
45+
const { setPromptImageUrl } = useProjectContext();
46+
const cropperRef = useRef<FixedCropperRef>(null);
47+
48+
const [imagePreview, setImagePreview] = useState<FilePreview>();
49+
50+
const { uploadToS3 } = useS3Upload();
51+
52+
const { getRootProps, getInputProps } = useDropzone({
53+
accept: {
54+
"image/png": [".png"],
55+
"image/jpeg": [".jpeg", ".jpg"],
56+
},
57+
maxSize: 10000000,
58+
multiple: false,
59+
onDrop: (acceptedFiles) => {
60+
if (acceptedFiles?.[0]) {
61+
setImagePreview(createPreviewMedia(acceptedFiles[0]));
62+
}
63+
},
64+
});
65+
66+
const handleSubmit = async () => {
67+
if (!cropperRef.current) {
68+
return;
69+
}
70+
71+
setLoading(true);
72+
const canvas = cropperRef.current.getCanvas({
73+
height: 512,
74+
width: 512,
75+
})!;
76+
77+
canvas.toBlob(async (blob) => {
78+
const croppedImage = createPreviewMedia(blob!);
79+
const file = await resizeImage(croppedImage as File);
80+
81+
const { url } = await uploadToS3(file);
82+
setLoading(false);
83+
84+
setPromptImageUrl(url);
85+
onClose();
86+
}, "image/jpeg");
87+
};
88+
89+
return (
90+
<>
91+
<Button
92+
rightIcon={<BsImage />}
93+
variant="outline"
94+
size="sm"
95+
onClick={onOpen}
96+
>
97+
Use image as model
98+
</Button>
99+
100+
<Modal isOpen={isOpen} onClose={onClose}>
101+
<ModalOverlay backdropFilter="auto" backdropBlur="4px" />
102+
<ModalContent
103+
as="form"
104+
onSubmit={async (e) => {
105+
e.preventDefault();
106+
e.stopPropagation();
107+
108+
handleSubmit();
109+
}}
110+
>
111+
<ModalHeader>Use image as model</ModalHeader>
112+
<ModalCloseButton />
113+
<ModalBody>
114+
{imagePreview ? (
115+
<FixedCropper
116+
ref={cropperRef}
117+
src={imagePreview && imagePreview.preview}
118+
stencilSize={{ width: 512, height: 512 }}
119+
imageRestriction={ImageRestriction.stencil}
120+
/>
121+
) : (
122+
<Center
123+
_hover={{
124+
bg: "whiteAlpha.800",
125+
}}
126+
fontSize="sm"
127+
transition="all 0.2s"
128+
backgroundColor="whiteAlpha.500"
129+
cursor="pointer"
130+
borderRadius="xl"
131+
border="1px dashed gray"
132+
p={10}
133+
flexDirection="column"
134+
{...getRootProps({ className: "dropzone" })}
135+
>
136+
<input {...getInputProps()} />
137+
<Icon as={BsCloudArrowUp} fontSize="3xl" />
138+
<Text>Upload reference image</Text>
139+
</Center>
140+
)}
141+
</ModalBody>
142+
143+
<ModalFooter>
144+
<ButtonGroup>
145+
<Button onClick={onClose} variant="ghost">
146+
Cancel
147+
</Button>
148+
<Button
149+
disabled={!imagePreview}
150+
isLoading={isLoading}
151+
variant="brand"
152+
type="submit"
153+
>
154+
Use image
155+
</Button>
156+
</ButtonGroup>
157+
</ModalFooter>
158+
</ModalContent>
159+
</Modal>
160+
</>
161+
);
162+
};
163+
164+
export default PromptImage;

0 commit comments

Comments
 (0)