vidcraft / src /pages /index.tsx
tsi-org's picture
Upload 89 files
19e25f3
import React, { useState, useEffect, FormEvent, ChangeEvent } from 'react';
interface InputData {
prompt: string;
width: number;
height: number;
steps: number;
fps: number;
seed?: number;
}
interface OutputData {
prediction: any;
timestamp: string;
prompt: string;
}
const TruncatedText = ({ text }: { text: string }) => {
const [isTruncated, setIsTruncated] = useState(true);
const toggleTruncated = () => {
setIsTruncated(!isTruncated);
};
if (text.length < 34) {
return <span>{text}</span>;
}
return (
<span>
{isTruncated ? `${text.substring(0, 24)}...` : text}
<button onClick={toggleTruncated}>
{isTruncated ? 'Read More' : 'Read Less'}
</button>
</span>
);
};
const IndexPage = () => {
const [inputData, setInputData] = useState<InputData>({
prompt: '',
width: 1024,
height: 576,
steps: 40,
fps: 15,
seed: undefined,
});
const [output, setOutput] = useState<OutputData[]>([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const savedOutput = localStorage.getItem('output');
if (savedOutput) {
setOutput(JSON.parse(savedOutput));
}
}, []);
const handleChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setInputData((prevData) => ({ ...prevData, [name]: value }));
};
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
setIsLoading(true);
try {
const response = await fetch('/api/replicate', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(inputData),
});
const result = await response.json();
if (response.ok) {
const timestamp = new Date().toISOString();
const newOutput = {
prediction: result.prediction,
timestamp,
prompt: inputData.prompt,
};
const updatedOutput = [...output, newOutput];
setOutput(updatedOutput);
localStorage.setItem('output', JSON.stringify(updatedOutput));
setInputData({
prompt: '',
width: 1024,
height: 576,
steps: 40,
fps: 15,
seed: undefined,
});
}
} catch (error) {
console.error("An error occurred:", error);
} finally {
setIsLoading(false);
}
};
return (
<div className="bg-gray-900 text-white p-4 container mx-auto" style={{ minHeight: '100vh' }}>
<h1 className="text-2xl mb-4">Video Generation App</h1>
<form onSubmit={handleSubmit} className="space-y-4">
<textarea
name="prompt"
value={inputData.prompt}
onChange={handleChange}
className="p-2 bg-gray-800 rounded border border-gray-600 w-full h-24"
placeholder="Enter video prompt..."
></textarea>
<div className="flex flex-wrap -mx-2">
<label className="flex flex-col items-center w-full md:w-1/5 px-2">
<span>Width</span>
<input type="number" name="width" value={inputData.width} onChange={handleChange} className="p-2 bg-gray-800 rounded border border-gray-600 w-full" />
</label>
<label className="flex flex-col items-center w-full md:w-1/5 px-2">
<span>Height</span>
<input type="number" name="height" value={inputData.height} onChange={handleChange} className="p-2 bg-gray-800 rounded border border-gray-600 w-full" />
</label>
<label className="flex flex-col items-center w-full md:w-1/5 px-2">
<span>Steps</span>
<input type="number" name="steps" value={inputData.steps} onChange={handleChange} className="p-2 bg-gray-800 rounded border border-gray-600 w-full" />
</label>
<label className="flex flex-col items-center w-full md:w-1/5 px-2">
<span>FPS</span>
<input type="number" name="fps" value={inputData.fps} onChange={handleChange} className="p-2 bg-gray-800 rounded border border-gray-600 w-full" />
</label>
<label className="flex flex-col items-center w-full md:w-1/5 px-2">
<span>Seed</span>
<input type="number" name="seed" value={inputData.seed} onChange={handleChange} className="p-2 bg-gray-800 rounded border border-gray-600 w-full" />
</label>
</div>
<button
type="submit"
className={`p-2 px-5 rounded ${isLoading ? 'bg-gray-600' : 'bg-green-600'} text-white`}
disabled={isLoading}
style={{ cursor: isLoading ? 'not-allowed' : 'pointer' }}
>
{isLoading ? 'Generating...' : 'Generate'}
</button>
</form>
<div className="grid lg:grid-cols-3 grid-cols-1 gap-4 mt-8">
{output.map((video, index) => (
<div key={index} className="space-y-2">
<h2 className="text-xl">Generated Video {index + 1}:</h2>
<div className="relative">
<video
controls
loop
width="100%"
onMouseOver={event => {
event.currentTarget.play();
event.currentTarget.setAttribute("controlsList", "nodownload");
}}
onMouseOut={event => {
event.currentTarget.pause();
event.currentTarget.removeAttribute("controlsList");
}}
>
<source src={video.prediction.output} type="video/mp4" />
</video>
{/* Download Button */}
<button
onClick={async () => {
try {
const response = await fetch(video.prediction.output);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'video.mp4';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(url);
} catch (error) {
console.error("Failed to download the video", error);
}
}}
className="absolute top-0 right-0 bg-green-600 text-white px-3 py-1 rounded"
>
Download
</button>
</div>
<p>Date: {new Date(video.timestamp).toLocaleDateString()} Time: {new Date(video.timestamp).toLocaleTimeString()}</p>
<TruncatedText text={video.prompt} />
</div>
))}
</div>
</div>
);
};
export default IndexPage;