About a year ago, ChatGPT wowed the world with its ability to generate text content on demand. But generative AI goes beyond text — it can also edit and create images using text prompts.
In this guide, I will show you how to build a web application using cloud-based AI with features like generating photorealistic images, removing backgrounds and objects from images, and facial restoration.
In the process of building this application, you will learn how to leverage cloud-based AI models to create innovative web solutions. With these skills, you are set to build real-world, AI-driven applications and contribute to the evolving field of AI.
Replicate lets you run machine learning models with a cloud API without having to understand the intricacies of machine learning or managing your own infrastructure. Their tools start at $0.00010/sec, with a free trial plan.
Because Replicate is cloud-based and has a huge community, you can access over a thousand AI models that are dedicated to performing extraordinary things. Additionally, you don’t need to learn Python or R to build a model from scratch. Simply use your favorite language (JavaScript or TypeScript) to interact with these AI models.
The application we will build in this tutorial will make use of the following AI models:
If you’re ready to unlock the potential of AI in image generation and editing, let’s get started!
To follow this article, I recommend you have foundational experience with TypeScript, React, and HTML/CSS.
To begin, we will set up our project using Vite. To use Vite, first install Node on your machine if it’s not already, then set up a React project with this command:
npm create vite@latest
Vite will prompt you to provide extra information like the project name, framework, and programming language to complete the operation. If successful, you should have something similar to the terminal output shown below:
Now, cd into the project root directory and run npm install
. Afterwards, run npm run dev
to serve your React app on a local host.
Next, we will install external dependencies for Ant Design and Axios.
Ant Design (antd) is a React component library for building beautiful and modern user interfaces. It comes with a collection of prebuilt, enterprise-level UI components. To install Ant Design, use the command below:
npm install antd --save
Axios is a powerful HTTP client library for making network requests from your browser.
npm install axios
App
componentBy default, the App
component has some code we don’t need. For now, replace it with the code block below:
function App() { return <div>Hello world</div> } export default App
Additionally, delete the App.css
file from your source folder.
index.css
In the index.css
file, update the styling with the following:
body { margin: 0; padding: 0; } .layout { height: 100vh; } .site-layout .site-layout-background { background: #fff; }
We are replacing all the CSS styles that came with the application with ours so that we have full control over the application’s appearance. If you run the application again, you should see Hello world
positioned at the top left of the web page.
An API key is needed to access resources on Replicate’s server. To create one, sign up here.
The cloud AI models are identified by their unique IDs, which are constant. So, we will group them using an enum:
export enum ReplicateAIModels { TextToImage = "2b017d9b67edd2ee1401238df49d75da53c523f36e363881e057f5dc3ed3c5b2", BgRemover = "fb8af171cfa1616ddcf1242c093f9c46bcada5ad4cf6f2fbe8b81b330ec5c003", ObjRemover = "153b0087c2576ad30d8cbddb35275b387d1a6bf986bda5499948f843f6460faf", FaceRestorer = "297a243ce8643961d52f745f9b6c8c1bd96850a51c92be5f43628a0d3e08321a" }
We need a temporary image service to host images that will be fed into the cloud AI models. Create an account on ImgBB to get a key that we will use to communicate with their API.
Create a constant.ts
file with the following constants:
export const API_KEY = import.meta.env.VITE_REPLICATE_API_KEY export const IMG_BB_API_KEY = import.meta.env.VITE_IMG_BB_API_KEY export const BASE_URL = 'https://api.replicate.com/v1/' export const PROXY_SERVER_URL = "https://proxy-server-407001.ue.r.appspot.com/" export const IMGBB_URL = "https://api.imgbb.com/1/upload"
Because this project is hosted on GitHub, I am using the import.meta.env
object from Vite to read environment variables. For testing purposes, you can include your keys.
You might be wondering why we need both BASE_URL
and PROXY_URL
. In order to access resources on Replicate’s server from a web browser, we need to bypass the browser’s CORS policy with a proxy server, hence the PROXY_SERVER_URL
. Feel free to test without the proxy URL to see the error for yourself.
Within your source folder, create a ImageHostingService.ts
file with the following code:
export const uploadImageToImgBB = async (image: RcFile) => { try { const formData = new FormData(); formData.append('image', image); const response = await axios.post(IMGBB_URL, formData, { params: { expiration: 600, key: IMG_BB_API_KEY, }, headers: { 'Content-Type': 'multipart/form-data' }, }); return response.data\["data"\]["image"]["url"] } catch (error) { if (axios.isAxiosError(error)) { console.error('Request failed with status code:', error.response?.status); console.error('Response data:', error.response?.data); } throw "Error uploading image" } }
Within the uploadImageToImgBB
function block, we use Axios to make a multi-part request for uploading an image to the ImgBB server. Once the upload is successful, we extract the image URL from the response body. In the next section, we will see where this function is called.
import axios, {AxiosInstance} from 'axios'; import {ReplicateAIModels} from "./CloudAiModels.ts"; import {RcFile} from "antd/lib/upload"; import {uploadImageToImgBB} from "./ImageHostingService.ts"; import {delay} from "../common/Utils.ts"; import {PredictionResponse} from "./Response.ts"; import {API_KEY, BASE_URL, PROXY_SERVER_URL} from "./Constants.ts"; class ApiClient { private axiosInstance: AxiosInstance constructor(baseURL: string) { this.axiosInstance = axios.create({ baseURL, headers: { 'Content-Type': 'application/json', 'Accept': "application/json", "Authorization": `Token ${API_KEY}` }, }) this.setUpPredictionStatusPolling() } private setUpPredictionStatusPolling() { this.axiosInstance.interceptors.response.use(async (response) => { console.log("Interceptor", response) const predictionId = response.data["id"] const predictionUrl = `predictions/${predictionId}`; let pollingStatus = response.data["status"] while (pollingStatus === "starting" || pollingStatus === "processing") { await delay(3000) response = await this.axiosInstance.get(predictionUrl) pollingStatus = response.data["status"] console.log("Interceptor polling status: ", pollingStatus) } return response; }, (error) => Promise.reject(error) ) } public async generateImage(userPrompt: string): Promise<PredictionResponse> { const data = { "version": ReplicateAIModels.TextToImage, "input": { "prompt": userPrompt } }; return await this.createPrediction(data) } public async removeBackgroundFromImage(image: RcFile) { const url = await uploadImageToImgBB(image) const data = { "version": ReplicateAIModels.BgRemover, "input": { "image": url } }; return await this.createPrediction(data) } public async removeObjectFromImage(image: RcFile, objectToRemove: string) { const url = await uploadImageToImgBB(image) const data = { "version": ReplicateAIModels.ObjRemover, "input": { "image_path": url, 'objects_to_remove': objectToRemove } }; return await this.createPrediction(data) } public async restoreImage(image: RcFile) { const url = await uploadImageToImgBB(image) const data = { "version": ReplicateAIModels.FaceRestorer, "input": { "img": url, "scale": 1, } }; return await this.createPrediction(data) } async createPrediction(data: object): Promise<PredictionResponse> { const response = await this.axiosInstance.post<PredictionResponse>("/predictions", data) return response.data } } const apiClient = new ApiClient(PROXY_SERVER_URL + BASE_URL) export default apiClient
Pay attention to the createPrediction
and setUpPredictionStatusPolling
functions in the code block above, as they encapsulate the core business logic of the application.
The function createPrediction
submits an image processing request to Replicate’s server via the prediction endpoint. Because image processing takes time, the server returns a response immediately with a Job ID (Prediction ID), which we will use to query the Replicate server for the task status at a later time.
Within setUpPredictionStatusPolling
, we implement a polling logic that is common to all the image processing tasks we submit to Replicate’s server. Inside the response interceptor for the prediction endpoint, we query the server for the prediction status every three seconds.
If the status isn’t in the starting or processing phases, we return the response from the interceptor. By adopting this approach, we make sure there’s a single point of logic handling both the polling of prediction status and retrieving the task result.
Outside of the scope of the ApiClient
class, we created an instance of the same class and made it visible to other files within our source code. It is a single instance of the API client, which will be shared by all the components that will be created in later sections.
Having set up our core business logic, we are now going to create the user interfaces that will depend on them. As you already know, we will rely on React and UI components from Ant Design.
Imagine
componentWithin the Imagine
component, we are going to implement the user interface for generative photo creation. With only text prompts, we’ll be able to generate realistic images. Hang tight, we’ll achieve that in a few lines of code:
import {useState} from "react"; import {Flex, Image} from "antd"; import Search from "antd/lib/input/Search"; import apiClient from "../api/ReplicateApiClient.ts"; const Imagine = () => { const [imgSrc, setImgSrc] = useState("") const [loading, setLoading] = useState(false) function generateImage(query: string) { setLoading(true) apiClient.generateImage(query) .then((data) => { const imageUrl = data.output[0]; setImgSrc(imageUrl) setLoading(false) }) .catch(() => { setLoading(false) }) } return ( <Flex vertical={true} gap={10}> <Search style={{width: "500px"}} placeholder="Describe an image to generate" enterButton="Submit" disabled={loading} loading={loading} onSearch={(value) => { (value.trim().length > 5) && generateImage(value) }} /> {(imgSrc !== '') && ( <Image height="50%" width="50%" src={imgSrc} alt="Your Image Alt Text" /> )} </Flex> ) } export default Imagine;
The Imagine
function is composed of a Search
input component and an Image
component. Whenever imgSrc
changes state based on user text input, React updates the Image
component to reflect the latest image generated:
BackgroundRemove
componentThe BackgroundRemove
component encapsulates the logic for removing backgrounds from images. With one click, you can effortlessly remove distracting backgrounds from your photos without the hassle of an eraser tool. See the code block below for the implementation:
import {useState} from 'react'; import {RcFile} from "antd/lib/upload"; import {isString} from "antd/es/button"; import {UploadOutlined} from "@ant-design/icons"; import {Button, Flex, Image, Upload} from "antd"; import apiClient from "../api/ReplicateApiClient.ts"; const BackgroundRemover = () => { const [image, setImage] = useState<RcFile>() const [imageAsUrl, setImageAsUrl] = useState("") const [editedImgSrc, setEditedImgSrc] = useState("") const [loading, setLoading] = useState(false) function handleChange(file: RcFile) { setImage(file) setImageAsUrl(URL.createObjectURL(file)); } function removeImageBackground() { setLoading(true) apiClient.removeBackgroundFromImage(image as RcFile) .then((data) => { const imageUrl = data.output; if (isString(imageUrl)) setEditedImgSrc(imageUrl) setLoading(false) }) .catch(() => { setLoading(false) }); } return ( <Flex vertical={true} gap={10}> <Flex vertical={false} gap={10}> <Upload showUploadList={false} disabled={loading} beforeUpload={handleChange}> <Button disabled={loading} icon={<UploadOutlined/>} size={"middle"}>Import photo</Button> </Upload> {(imageAsUrl !== "") && <Button type="primary" disabled={loading} loading={loading} onClick={removeImageBackground} size={"middle"}> Remove Background </Button>} </Flex> <Flex vertical={false} gap={40}> {(imageAsUrl !== "") && ( <Image height="40%" width="40%" src={imageAsUrl} alt="Your Image Alt Text" /> )} {(editedImgSrc !== '') && ( <Image height="40%" width="40%" src={editedImgSrc} alt="Your Image Alt Text" /> )} </Flex> </Flex> ) } export default BackgroundRemover
Ant Design’s Upload
component responds to clicks by opening the file window for image selection. Within this flow, the selected file is passed to handleChange(file)
in order to set the image for preview.
Whenever removeImageBackground()
is triggered from an onClick()
button event, we invoke removeBackgroundFromImage(image)
on the apiClient
instance. The response from the method returns a promise with which we register a callback that will be triggered when the promise is fulfilled. Within this success callback, we extract imageUrl
and update the state of editedImgSrc
.
As usual, React updates the DOM to show the selected photo with its background removed:
ObjectRemover
componentUsing the generative removal tool from Replicate, you can effortlessly remove unwanted objects from your image with only simple prompts. An example use case might be to remove a car from an otherwise perfect property photo.
Within the ObjectRemover
component, we will build the interface and business logic that allows for object removal:
import {Button, Flex, Image, Upload} from "antd" import {UploadOutlined} from "@ant-design/icons" import {useState} from "react" import {RcFile} from "antd/lib/upload" import apiClient from "../api/ReplicateApiClient.ts" import Search from "antd/lib/input/Search" import {isString} from "antd/es/button" const ObjectRemover = () => { const [image, setImage] = useState<RcFile>() const [imageAsUrl, setImageAsUrl] = useState("") const [editedImgSrc, setEditedImgSrc] = useState("") const [loading, setLoading] = useState(false) function handleChange(file: RcFile) { setImage(file) setImageAsUrl(URL.createObjectURL(file)) } function removeObjectFromPhoto(objectDescription: string) { setLoading(true) apiClient.removeObjectFromImage(image as RcFile, objectDescription) .then((data) => { if (isString(data.output)) { setEditedImgSrc(data.output) } console.log("response", data) setLoading(false) }).catch((error) => { console.error("error", error) setLoading(false) }) } return ( <Flex vertical={true} gap={10}> <Flex vertical={false} gap={10}> <Upload showUploadList={false} disabled={loading} beforeUpload={handleChange}> <Button disabled={loading} icon={<UploadOutlined/>} size={"middle"}>Import photo</Button> </Upload> {(image !== null) && <Search style={{width: "20vw"}} placeholder="Objects(s) to remove" enterButton="Submit" disabled={loading} loading={loading} onSearch={(value) => { (value.trim().length > 0) && removeObjectFromPhoto(value) }} /> } </Flex> <Flex vertical={false} gap={40}> {(imageAsUrl !== "") && ( <Image height="40%" width="40%" src={imageAsUrl} alt="Your Image Alt Text" /> )} {(editedImgSrc !== '') && ( <Image height="40%" width="40%" src={editedImgSrc} alt="Your Image Alt Text" /> )} </Flex> </Flex> ) } export default ObjectRemover
The ObjectRemover
component is composed of an Upload
component for selecting an image and a Search
component for capturing user prompts. In response to the imported image and user prompt, apiClient.removeObjectFromImage(image, objectDescription)
is invoked with the appropriate arguments for requesting object removal.
The result from the operation will be the input image minus the objects from the user prompt:
Restaurer
componentRestoring details on blurry photos can be a daunting and time-consuming task, but with cloud AI models, we can perform photo restoration in a fraction of the time. The Restaurer
component contains logic to enable photo restoration in one click:
import {useState} from 'react'; import {Button, Flex, Image, Upload} from "antd"; import {UploadOutlined} from "@ant-design/icons"; import {RcFile} from "antd/lib/upload"; import apiClient from "../api/ReplicateApiClient.ts"; import {isString} from "antd/es/button"; const Restaurer = () => { const [image, setImage] = useState<RcFile>() const [imageAsUrl, setImageAsUrl] = useState("") const [editedImgSrc, setEditedImgSrc] = useState("") const [loading, setLoading] = useState(false) function handleChange(file: RcFile) { setImage(file) setImageAsUrl(URL.createObjectURL(file)); } function restoreImage() { setLoading(true) apiClient.restoreImage(image as RcFile) .then((data) => { const imageUrl = data.output; if (isString(imageUrl)) setEditedImgSrc(imageUrl) setLoading(false) }) .catch(() => { setLoading(false) }) } return ( <Flex vertical={true} gap={10}> <Flex vertical={false} gap={10}> <Upload showUploadList={false} disabled={loading} beforeUpload={handleChange}> <Button disabled={loading} icon={<UploadOutlined/>} size={"middle"}>Import photo</Button> </Upload> {(imageAsUrl !== "") && <Button type="primary" disabled={loading} loading={loading} onClick={restoreImage} size={"middle"}> Restore Photo </Button>} </Flex> <Flex vertical={false} gap={40}> {(imageAsUrl !== "") && ( <Image height="40%" width="40%" src={imageAsUrl} alt="Original photo" /> )} {(editedImgSrc !== '') && ( <Image height="40%" width="40%" src={editedImgSrc} alt="Edited Photo" /> )} </Flex> </Flex> ) } export default Restaurer
The code block is similar to that of the RemoveBackground
component, the only difference being the method called on the apiClient
instance. Successful execution of restoreImage
on apiClient
returns a new image with improvements to the originally imported photo. See the image below for a comparison:
App
componentThe final piece of work involves a sidebar navigation menu within the App
component. This setup enables a seamless switching between top-level components:
import {BlockOutlined, BoldOutlined, FileImageOutlined, RotateLeftOutlined} from '@ant-design/icons' import {Layout, Menu, MenuProps} from 'antd' import {useState} from "react" import Imagine from "./components/Imagine.tsx" import ObjectRemover from "./components/ObjectRemover.tsx" import BackgroundRemover from "./components/BackgroundRemover.tsx" import Restaurer from "./components/Restaurer.tsx" const {Sider, Content} = Layout function App() { const menuItems = [ { key: '1', icon: <FileImageOutlined/>, label: 'Imagine' }, { key: '2', icon: <BlockOutlined/>, label: 'Obj Remover', }, { key: '3', icon: <BoldOutlined/>, label: 'Bg Remover', }, { key: '4', icon: <RotateLeftOutlined/>, label: 'Restaurer', }, ] const sideBarComponents = [ <Imagine/>, <ObjectRemover/>, <BackgroundRemover/>, <Restaurer/> ] const [itemId, setItemId] = useState("1") const onClickHandler: MenuProps['onClick'] = (e) => { setItemId(e.key) } return ( <Layout className="layout"> <Sider trigger={null} collapsible width={200}> <div className="logo">Cloud-IMG</div> <Menu theme={"dark"} onClick={onClickHandler} mode="inline" defaultSelectedKeys={['1']} items={menuItems} /> </Sider> <Layout className="site-layout"> <Content className="site-layout-background" style={{ margin: '24px 16px', padding: 24, }} > {sideBarComponents[parseInt(itemId) - 1]} </Content> </Layout> </Layout> ) } export default App
The layout component defines a scaffold that hosts the Sider
(the sidebar) and Content
components. This simplifies web page setup for complex user interfaces.
On the sidebar, whenever a menu item is clicked, the state of the itemId
is set to the item’s key value. This change triggers React’s reconciliation process and updates the content component visually.
With the App
component fully set up, the web app should look like the image below:
Here’s a video recording of the final application, demonstrating the different cloud AI use cases we covered in the article:
Cloud-AI Image Manipulation and Generation, react.js demo
This react application interacts with AI models on replicate.
Feel free to explore the live demo of the application.
AI is here to stay, and cloud-based AI models have unlocked boundless possibilities for software applications. Through this article, we delved into the world of AI models, equipping you with the skills to build a remarkable photo editing application using React.
Beyond this, I want to challenge you to explore other cloud-based AI models on Replicate and discover interesting project ideas you can build.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.