MovieMinter v2 — Part 3: The User Interface
In this article series, I will be using LivePeer, Replicate and Replit to create a React-based Decentralized Generative AI Movie NFT Minter. In this article, I will create a user interface for my application and then demonstrate a fully running server with a Replicate API call.
I designed a very simple UI for my project using Google Slides. With ChatGPT4, it is possible to submit an image of a front-end and to request that the CSS and client code be written.
Designing the front end with ChatGPT
I gave this image to ChatGPT and asked it to design a React application using the following prompt:
ChatGPT returned with the following (there were several rounds of iteration but the basics go like this):
import React, { useState } from "react";
import "./App.css";
export default function App() {
const [apiKeys, setApiKeys] = useState({ replicateKey: "", livePeerKey: "" });
const [prompt, setPrompt] = useState("");
const [loading, setLoading] = useState(false);
const [videoSrc, setVideoSrc] = useState("");
function handleInputChange(e) {
const { name, value } = e.target;
setApiKeys((prevState) => ({ ...prevState, [name]: value }));
}
function handlePromptChange(e) {
setPrompt(e.target.value);
}
async function handleGenerateClick() {
setLoading(true);
try {
const response = await fetch("http://localhost:5001/generate-video", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
prompt: prompt,
apiKey: apiKeys.replicateKey, // Send the API key here
}),
});
const data = await response.json();
setVideoSrc(data.output); // Assuming the output is the URL to the video
} catch (error) {
console.error("Failed to generate video:", error);
}
setLoading(false);
}
async function handleStoreOnLivePeerClick() {
// Implement storing the video on LivePeer
}
async function handleMintAsNFTClick() {
// Implement minting the video as an NFT
}
return (
<div className="app">
<header className="header">
<h1>LivePeer Generative AI Movie Builder</h1>
<button
onClick={() => {
/* Connect Wallet Function */
}}
>
Connect Wallet
</button>
</header>
<div className="content">
<div className="form">
<input
name="replicateKey"
placeholder="Replicate API Key"
value={apiKeys.replicateKey}
onChange={handleInputChange}
/>
<input
name="livePeerKey"
placeholder="LivePeer API Key"
value={apiKeys.livePeerKey}
onChange={handleInputChange}
/>
<textarea
placeholder="Prompt"
value={prompt}
onChange={handlePromptChange}
/>
<button onClick={handleGenerateClick}>Generate</button>
</div>
<div className="video-container">
{loading ? (
<p>Loading...</p>
) : (
videoSrc && <video src={videoSrc} controls />
)}
</div>
</div>
<footer className="footer">
<button onClick={handleStoreOnLivePeerClick}>Store on LivePeer</button>
<button onClick={handleMintAsNFTClick}>Mint as NFT</button>
</footer>
</div>
);
}
My CSS is:
/* App.css */
/* Apply a reset on margins and paddings */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* Style the whole app */
.app {
font-family: Arial, sans-serif;
display: flex;
flex-direction: column;
height: 100vh;
}
/* Style the header */
.header {
background-color: #333;
color: white;
padding: 10px 20px;
display: flex;
justify-content: space-between;
align-items: center;
}
.header h1 {
margin: 0;
}
/* Style the main content area */
.content {
display: flex;
flex: 1;
padding: 20px;
}
/* Style the form area */
.form {
flex-basis: 300px;
padding-right: 20px;
}
/* Add space between form items */
.form input, .form textarea, .form button {
margin-bottom: 10px;
width: 100%;
}
/* Style the video container */
.video-container {
flex: 1;
background-color: #f90; /* Placeholder color */
display: flex;
justify-content: center;
align-items: center;
}
.video-container video {
width: 100%;
height: auto;
}
/* Style the footer */
.footer {
background-color: #333;
padding: 10px 20px;
display: flex;
justify-content: space-between;
}
.footer button {
background-color: #f90;
border: none;
padding: 10px;
margin: 0 5px;
cursor: pointer;
}
/* Add hover effect to buttons */
.footer button:hover {
opacity: 0.9;
}
/* Additional styles for the connect button */
.header button {
background-color: #f90;
color: #fff;
border: none;
padding: 10px;
cursor: pointer;
}
.header button:hover {
opacity: 0.9;
}
Testing the application
The problem with this approach of having a client and a server in the same application is that you can’t run it directly in Replit. My solution was to simply push the Replit to git, then clone it to my local machine for testing.
On my PC:
git clone https://github.com/rexsaurus/movie-minter-v2
Then, open two different terminal windows. In the first window, run the server:
node api/replicate.js
In the second window, run the client:
npm run dev
Once the client is running, you get an IP address to view the client running locally:
Mine looked like this:
Next, test the Generative AI endpoint (requires that you have entered a credit card into Replicate in order to work).
Copy / paste your API key into the box for your deployment, enter a prompt and click “Generate.”
If the API call is successful, there will be a pause and you will see, under your deployments -> predictions area that a generative AI prediction has started.
Next, we will build out the rest of the application.