updated server side and added post image functionality

This commit is contained in:
Juthatip McDevitt 2024-05-02 18:10:20 -05:00
parent cedb9620b6
commit 5f03ef679f
15 changed files with 292 additions and 23 deletions

View file

@ -0,0 +1,25 @@
# In this project we craete the application called "DELL-E Image Genartor" by using React
DELL-E Image Generator is the application that is used for creating images by describing what you want to see! After generating images, you can post and download your images.
This project uses OpenAI to generate images and cloudinary to store images.
## How to use this application on your local?
* You will need OpenAI API key
* You will need Cloudinary API Key
* You will also need to connect with MongoDB
* Create a `.env` file. This file is used to store the keys that access the API services and the database
```bash
touch .env # to create the .env file
echo 'MONGODB_URL=""' >> .env
echo 'CLOUD_NAME=""' >> .env
echo 'CLOUD_API_KEY=""' >> .env
echo 'CLOUD_SECRET=""' >> .env
echo 'OPENAI_API_KEY=""' >> .env
```
Fill in the information with your keys to complete the .env file.
## To start the project
* `npm start` on client side
* `npm start` on server side

View file

@ -0,0 +1,9 @@
import axios from "axios"
const API = axios.create({
baseURL: "http://localhost:8080/api/",
});
export const GetPosts = async () => await API.get("/post/");
export const CreatePost = async (data) => await API.post("/post/", data)
export const GenerateAIImage = async (data) => await API.post("/generateImage/", data)

View file

@ -18,7 +18,7 @@ const Card = ({item}) => {
<div className='flex flex-col justify-end absolute p-2 md:p-5 top-0 left-0 right-0 bottom-0 items-start gap-1 backdrop-blur-sm opacity-0 hover:opacity-100 duration-300'> <div className='flex flex-col justify-end absolute p-2 md:p-5 top-0 left-0 right-0 bottom-0 items-start gap-1 backdrop-blur-sm opacity-0 hover:opacity-100 duration-300'>
<p className='flex gap-5'>{item?.prompt}</p> <p className='flex gap-5'>{item?.prompt}</p>
<div className='w-full flex justify-between items-center'> <div className='w-full flex justify-between items-center'>
<p className='flex items-center justify-center gap-1'><span className='w-6 h-6 flex bg-gray-100 text-black rounded-full items-center justify-center'>{item?.author[0]}</span>{item.author}</p> <p className='flex items-center justify-center gap-1'><span className='w-6 h-6 flex bg-gray-100 text-black rounded-full items-center justify-center'>{item?.name[0]}</span>{item.name}</p>
<button onClick={() => FileSaver.saveAs(item?.photo, 'download.jpg')} className='md:text-2xl'><BiDownload/></button> <button onClick={() => FileSaver.saveAs(item?.photo, 'download.jpg')} className='md:text-2xl'><BiDownload/></button>
</div> </div>
</div> </div>

View file

@ -1,15 +1,36 @@
import React from 'react' import React, { useState } from 'react'
import { CreatePost, GenerateAIImage } from '../api';
import { useNavigate } from 'react-router-dom'
const GenerateForm = ({post, setPost, postLoading, setPostLoading, imgLoading, setImgLoading}) => { const GenerateForm = ({post, setPost, postLoading, setPostLoading, imgLoading, setImgLoading}) => {
const generateImageFunction = () => { const [error, setError] = useState("");
setImgLoading(true) const route = useNavigate()
const generateImageFunction = async () => {
setImgLoading(true);
await GenerateAIImage({prompt: post.prompt}).then((res) => {
setPost({...post, photo: `data: image/jpge;base64,${res?.data?.photo}`})
setImgLoading(false)
}).catch((error) => {
setError(error?.response?.data?.message)
setImgLoading(false)
console.log(error)
})
} }
const creatPostFunction = () => { const creatPostFunction = async () => {
setPostLoading(true) setPostLoading(true)
await CreatePost(post).then((res) => {
setPostLoading(false);
route('/');
}).catch((error) => {
setError(error?.response?.data?.message)
setPostLoading(false)
})
} }
return ( return (
<div className='flex-1 flex flex-col gap-5 p-2 justify-center border rounded-md'> <div className='flex-1 flex flex-col gap-5 p-2 justify-center'>
<div className='flex flex-col gap-1'> <div className='flex flex-col gap-1'>
<p className='text-lg text-green-400 font-semibold'>Generate an image with your prompt</p> <p className='text-lg text-green-400 font-semibold'>Generate an image with your prompt</p>
</div> </div>
@ -18,10 +39,13 @@ const GenerateForm = ({post, setPost, postLoading, setPostLoading, imgLoading, s
<input placeholder='Enter your name' value={post.name} onChange={(e) => setPost({...post, name: e.target.value})} className='bg-transparent border rounded-sm p-2 outline-none'/> <input placeholder='Enter your name' value={post.name} onChange={(e) => setPost({...post, name: e.target.value})} className='bg-transparent border rounded-sm p-2 outline-none'/>
<p className='-mb-4 uppercase text-xs'>Prompt</p> <p className='-mb-4 uppercase text-xs'>Prompt</p>
<textarea placeholder='Enter your prompt' value={post.prompt} onChange={(e) => setPost({...post, prompt: e.target.value})} className='min-h-[100px] bg-transparent border rounded-sm p-2 outline-none resize-none'/> <textarea placeholder='Enter your prompt' value={post.prompt} onChange={(e) => setPost({...post, prompt: e.target.value})} className='min-h-[100px] bg-transparent border rounded-sm p-2 outline-none resize-none'/>
{
error && <div className='text-red-500'>{error}</div>
}
</div> </div>
<div className='flex-1 flex gap-2 mt-5'> <div className='flex-1 flex gap-2 mt-5'>
<button onClick={() => generateImageFunction()} onLoad={() => imgLoading(true)} disabled={post.prompt === ""} className='w-full flex items-center justify-center rounded-md p-1 bg-[#FFC100] disabled:bg-opacity-70'>Generate</button> <button onClick={() => generateImageFunction()} onLoad={() => imgLoading()} disabled={post.prompt === ""} className='w-full h-[40px] flex items-center justify-center rounded-md p-1 bg-[#FFC100] disabled:bg-opacity-70'>Generate</button>
<button onClick={() => creatPostFunction()} onLoad={() => postLoading(true)} disabled={post.name === "" || post.prompt === "" || post.photo === ""} className='w-full flex items-center justify-center rounded-md p-1 bg-[#FF8A08] disabled:bg-opacity-70'>Post</button> <button onClick={() => creatPostFunction()} onLoad={() => postLoading()} disabled={post.name === "" || post.prompt === "" || post.photo === ""} className='w-full h-[40px] flex items-center justify-center rounded-md p-1 bg-[#FF8A08] disabled:bg-opacity-70'>Post</button>
</div> </div>
</div> </div>
) )

View file

@ -1,32 +1,72 @@
import React from 'react' import React, { useEffect, useState } from 'react'
import { IoSearchOutline } from "react-icons/io5"; import { IoSearchOutline } from "react-icons/io5";
import Card from '../components/Card'; import Card from '../components/Card';
import { GetPosts } from '../api';
const Home = () => { const Home = () => {
const item = { const [posts, setPosts] = useState([]);
photo: "https://images.unsplash.com/photo-1714547808442-e4199a7f8e09?q=80&w=1932&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", const [loading, setLoading] = useState(false);
author: "Jtp", const [error, setError] = useState("");
prompt: "This is prompt", const [search, setSearch] = useState("");
const [filterPost, setFilterPost] = useState([]);
const getPost = async () => {
setLoading(true)
await GetPosts().then((res) => {
setLoading(false)
setPosts(res?.data?.data);
setFilterPost(res?.data?.data);
}).catch((error) => {
setError(error?.response?.data?.message);
setLoading(false)
})
} }
useEffect(() => {
getPost()
}, [])
useEffect(() => {
if(!search){
setFilterPost(posts);
}
const searchFilterPost = posts.filter((post) => {
const promptMatch = post?.prompt?.toLowerCase().includes(search.toString().toLowerCase())
const authorMatch = post?.name?.toLowerCase().includes(search.toString().toLowerCase())
return promptMatch || authorMatch;
})
if(search){
setFilterPost(searchFilterPost)
}
}, [posts, search])
return ( return (
<div className='h-full overflow-y-scroll p-5 flex flex-col gap-5 items-center'> <div className='h-full overflow-y-scroll p-5 flex flex-col gap-5 items-center'>
<p className='flex px-2 text-2xl text-[#F6F1E9] text-center'>Explore our popular images using AI generator</p> <p className='flex px-2 text-2xl text-[#F6F1E9] text-center'>Explore our popular images using AI generator</p>
{/*=== Search ===*/} {/*=== Search ===*/}
<div className='w-[100%] sm:w-[50%] flex border rounded-sm overflow-hidden'> <div className='w-[100%] sm:w-[50%] flex border rounded-sm overflow-hidden'>
<input type="text" placeholder='What do you want to see?' className='w-[95%] border-none p-2 outline-none bg-black'/> <input value={search} onChange={(e) => setSearch(e.target.value)} type="text" placeholder='What do you want to see?' className='w-[95%] border-none p-2 outline-none bg-black'/>
<button className='w-[5%] flex justify-center items-center duration-300 font-semibold'><IoSearchOutline/></button> <button className='w-[5%] flex justify-center items-center duration-300 font-semibold'><IoSearchOutline/></button>
</div> </div>
<div className='max-w-[1500px] w-full flex justify-center py-5'> <div className='max-w-[1500px] w-full flex justify-center py-5'>
<div className='grid gap-2 grid-cols-4 max-[999px]:grid-cols-3 max-[640px]:grid-cols-2'> {error && <div className='text-red-500'>{error}</div>}
<Card item={item}/> {loading ? (<>Loading...</>) : (
<Card item={item}/> <div className='grid gap-[10px] grid-cols-4 max-[999px]:grid-cols-3 max-[640px]:grid-cols-2'>
<Card item={item}/> {
<Card item={item}/> filterPost.length === 0 ? (<p>No post...</p>) : (
<Card item={item}/> <>
<Card item={item}/> {filterPost.slice().reverse().map((item, index) => (
<Card key={index} item={item}/>
))}
</>
)}
</div> </div>
)}
</div> </div>
</div> </div>
) )

View file

@ -0,0 +1,29 @@
import * as dotenv from "dotenv"
import {createError} from "../error.js"
import OpenAI from "openai";
dotenv.config();
//Setup OpenAI
const openai = new OpenAI({apiKey: process.env.OPENAI_API_KEY});
export const generateImage = async(req, res, next) => {
try {
const {prompt} = req.body;
const response = await openai.images.generate(
{
model: "dall-e-3",
prompt,
n:1,
size:"1024x1024",
response_format: "b64_json",
}
);
const generatedImage = response.data[0].b64_json;
return res.status(200).json({photo: generatedImage})
//return res.status(200).json(response)
} catch (error) {
next(createError(error.status, error?.response?.data?.error?.message || error?.message))
}
}

View file

@ -0,0 +1,42 @@
import *as dotenv from "dotenv"
import { v2 as cloudinary} from "cloudinary"
import Post from "../models/Post.js";
import {createError} from "../error.js"
dotenv.config();
cloudinary.config({
cloud_name: process.env.CLOUD_NAME,
api_key: process.env.CLOUD_API_KEY,
api_secret: process.env.CLOUD_SECRET
});
export const getAllPosts = async(req, res, next) => {
try {
const posts = await Post.find({});
return res.status(200).json({success: true, data: posts})
} catch (error) {
next(createError(error.status, error?.response?.data?.error?.message || error?.message))
}
}
export const createPost = async (req, res, next) => {
try {
const {name, prompt, photo} = req.body;
const fixed_photo = photo.replace(/ /g, ""); //because the data that they send back have space so we use following command to remove it
const photoUrl = await cloudinary.uploader.upload(fixed_photo);
const newPost = await Post.create(
{
name,
prompt,
photo: photoUrl?.secure_url,
}
)
return res.status(201).json({success: true, data: newPost})
} catch (error) {
console.log(error)
//next(createError(error.status, error?.response?.data?.error?.message || error?.message))
}
}

View file

@ -0,0 +1,6 @@
export const createError = (status, message) => {
const err = new Error();
err.status = status;
err.message = message;
return err;
}

View file

@ -0,0 +1,57 @@
import cors from "cors"
import mongoose from "mongoose"
import *as dotenv from "dotenv"
import express from "express"
import PostRouter from "./routes/Posts.js"
import GenerateImageRouter from "./routes/ImageGenerate.js"
dotenv.config();
const app = express();
app.use(cors());
app.use(express.json({limit: "50mb"}));
app.use(express.urlencoded({extended: true}));
app.use((err, req, res, next) => {
const status = err.status || 500;
const message = err.message || "Error!!"
return res.status(status).json(
{
success: false,
status,
message
}
)
});
app.use("/api/post", PostRouter);
app.use("/api/generateImage", GenerateImageRouter)
app.get("/", async(req, res) => {
res.status(200).json({
message: "Test"
})
});
//connect with MongoDB
const connectDB = () => {
mongoose.set("strictQuery", true);
mongoose.connect(process.env.MONGODB_URL).then(() => console.log("MongoDB connected")).catch((err) => {
console.error("Failed to connect with MongoDB");
console.error(err);
})
}
//start server
const startServer = async () => {
try {
app.listen(8080, () => console.log("Server is running on port 8080"))
connectDB();
} catch (error) {
console.log(error)
}
};
startServer()

View file

@ -0,0 +1,19 @@
import mongoose from "mongoose"
const SchemaPost = new mongoose.Schema({
name: {
type: String,
required: true,
},
prompt: {
type: String,
required: true,
},
photo: {
type: String,
required: true,
}
});
const Post = mongoose.model("Post", SchemaPost);
export default Post;

View file

@ -12,6 +12,7 @@
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.19.2", "express": "^4.19.2",
"mongodb": "^6.5.0",
"mongoose": "^8.3.3", "mongoose": "^8.3.3",
"nodemon": "^3.1.0", "nodemon": "^3.1.0",
"openai": "^4.40.0" "openai": "^4.40.0"

View file

@ -3,14 +3,16 @@
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"type": "module",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "start": "nodemon index.js"
}, },
"dependencies": { "dependencies": {
"cloudinary": "^2.2.0", "cloudinary": "^2.2.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.19.2", "express": "^4.19.2",
"mongodb": "^6.5.0",
"mongoose": "^8.3.3", "mongoose": "^8.3.3",
"nodemon": "^3.1.0", "nodemon": "^3.1.0",
"openai": "^4.40.0" "openai": "^4.40.0"

View file

@ -0,0 +1,7 @@
import express from "express"
import { generateImage } from "../controllers/ImageGenerate.js";
const router = express.Router();
router.post("/", generateImage);
export default router;

View file

@ -0,0 +1,8 @@
import express from "express"
import { createPost, getAllPosts } from "../controllers/Post.js";
const router = express.Router();
router.get("/", getAllPosts);
router.post("/", createPost);
export default router;