updated server side and added post image functionality
This commit is contained in:
parent
cedb9620b6
commit
5f03ef679f
15 changed files with 292 additions and 23 deletions
|
@ -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
|
9
ai_image_generator/client/src/api/index.js
Normal file
9
ai_image_generator/client/src/api/index.js
Normal 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)
|
|
@ -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'>
|
||||
<p className='flex gap-5'>{item?.prompt}</p>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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 generateImageFunction = () => {
|
||||
setImgLoading(true)
|
||||
const [error, setError] = useState("");
|
||||
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)
|
||||
await CreatePost(post).then((res) => {
|
||||
setPostLoading(false);
|
||||
route('/');
|
||||
|
||||
}).catch((error) => {
|
||||
setError(error?.response?.data?.message)
|
||||
setPostLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
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'>
|
||||
<p className='text-lg text-green-400 font-semibold'>Generate an image with your prompt</p>
|
||||
</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'/>
|
||||
<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'/>
|
||||
{
|
||||
error && <div className='text-red-500'>{error}</div>
|
||||
}
|
||||
</div>
|
||||
<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={() => 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={() => 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()} 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>
|
||||
)
|
||||
|
|
|
@ -1,32 +1,72 @@
|
|||
import React from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { IoSearchOutline } from "react-icons/io5";
|
||||
import Card from '../components/Card';
|
||||
import { GetPosts } from '../api';
|
||||
|
||||
const Home = () => {
|
||||
const item = {
|
||||
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",
|
||||
author: "Jtp",
|
||||
prompt: "This is prompt",
|
||||
const [posts, setPosts] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
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 (
|
||||
<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>
|
||||
{/*=== Search ===*/}
|
||||
<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'/>
|
||||
<button className='w-[5%] flex justify-center items-center duration-300 font-semibold'><IoSearchOutline/></button>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
<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'>
|
||||
<Card item={item}/>
|
||||
<Card item={item}/>
|
||||
<Card item={item}/>
|
||||
<Card item={item}/>
|
||||
<Card item={item}/>
|
||||
<Card item={item}/>
|
||||
{error && <div className='text-red-500'>{error}</div>}
|
||||
{loading ? (<>Loading...</>) : (
|
||||
<div className='grid gap-[10px] grid-cols-4 max-[999px]:grid-cols-3 max-[640px]:grid-cols-2'>
|
||||
{
|
||||
filterPost.length === 0 ? (<p>No post...</p>) : (
|
||||
<>
|
||||
{filterPost.slice().reverse().map((item, index) => (
|
||||
<Card key={index} item={item}/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
29
ai_image_generator/server/controllers/ImageGenerate.js
Normal file
29
ai_image_generator/server/controllers/ImageGenerate.js
Normal 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))
|
||||
}
|
||||
}
|
42
ai_image_generator/server/controllers/Post.js
Normal file
42
ai_image_generator/server/controllers/Post.js
Normal 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))
|
||||
|
||||
}
|
||||
}
|
6
ai_image_generator/server/error.js
Normal file
6
ai_image_generator/server/error.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
export const createError = (status, message) => {
|
||||
const err = new Error();
|
||||
err.status = status;
|
||||
err.message = message;
|
||||
return err;
|
||||
}
|
57
ai_image_generator/server/index.js
Normal file
57
ai_image_generator/server/index.js
Normal 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()
|
||||
|
19
ai_image_generator/server/models/Post.js
Normal file
19
ai_image_generator/server/models/Post.js
Normal 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;
|
1
ai_image_generator/server/package-lock.json
generated
1
ai_image_generator/server/package-lock.json
generated
|
@ -12,6 +12,7 @@
|
|||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"mongodb": "^6.5.0",
|
||||
"mongoose": "^8.3.3",
|
||||
"nodemon": "^3.1.0",
|
||||
"openai": "^4.40.0"
|
||||
|
|
|
@ -3,14 +3,16 @@
|
|||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"start": "nodemon index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"cloudinary": "^2.2.0",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"mongodb": "^6.5.0",
|
||||
"mongoose": "^8.3.3",
|
||||
"nodemon": "^3.1.0",
|
||||
"openai": "^4.40.0"
|
||||
|
|
7
ai_image_generator/server/routes/ImageGenerate.js
Normal file
7
ai_image_generator/server/routes/ImageGenerate.js
Normal 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;
|
8
ai_image_generator/server/routes/Posts.js
Normal file
8
ai_image_generator/server/routes/Posts.js
Normal 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;
|
Loading…
Add table
Reference in a new issue