vidcraft / src /pages /animatediff.tsx
tsi-org's picture
Upload 89 files
19e25f3
import React, { useState, useEffect, FormEvent, ChangeEvent } from 'react';
interface InputData {
prompt: string;
negativePrompt: string; // New field
base_model: string;
steps: number;
guidance_scale: number;
frames: number;
width: number;
height: number;
seed: number;
zoom_in_motion_strength: number;
zoom_out_motion_strength: number;
pan_left_motion_strength: number;
pan_right_motion_strength: number;
pan_up_motion_strength: number;
pan_down_motion_strength: number;
rolling_clockwise_motion_strength: number;
rolling_anticlockwise_motion_strength: number;
output_format: string;
}
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: '',
negativePrompt: '',
base_model: 'realisticVisionV20_v20',
steps: 25,
guidance_scale: 7.5,
frames: 16,
width: 768,
height: 512,
seed: -1,
zoom_in_motion_strength: 0,
zoom_out_motion_strength: 0,
pan_left_motion_strength: 0,
pan_right_motion_strength: 0.75,
pan_up_motion_strength: 0,
pan_down_motion_strength: 0,
rolling_clockwise_motion_strength: 0,
rolling_anticlockwise_motion_strength: 0,
output_format: 'mp4'
});
const [output, setOutput] = useState<OutputData[]>([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const savedOutput = localStorage.getItem('output');
if (savedOutput) {
setOutput(JSON.parse(savedOutput));
}
}, []);
useEffect(() => {
output.forEach(video => {
console.log('Video URL:', video.prediction.output);
});
}, [output]);
const handleChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value } = e.target;
setInputData((prevData) => ({ ...prevData, [name]: value }));
};
const handleSubmit = async (e: FormEvent) => {
e.preventDefault();
setIsLoading(true);
// Automatically convert string numbers to actual numbers
const payload = Object.fromEntries(
Object.entries(inputData).map(([key, value]) => {
return [key, isNaN(Number(value)) ? value : Number(value)];
})
);
try {
const response = await fetch('/api/animatediff', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload), // use payload here instead of 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: '',
negativePrompt: '',
base_model: 'realisticVisionV20_v20',
steps: 25,
guidance_scale: 7.5,
frames: 16,
width: 768,
height: 512,
seed: -1,
zoom_in_motion_strength: 0,
zoom_out_motion_strength: 0,
pan_left_motion_strength: 0,
pan_right_motion_strength: 0.75,
pan_up_motion_strength: 0,
pan_down_motion_strength: 0,
rolling_clockwise_motion_strength: 0,
rolling_anticlockwise_motion_strength: 0,
output_format: 'mp4'
});
}
} 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">AnimateDiff Generation</h1>
<form onSubmit={handleSubmit} className="space-y-4">
{/* Video Settings */}
<div className="bg-gray-800 p-4 rounded space-y-2">
<h2 className="text-lg font-bold">Video Settings</h2>
<div className="flex space-x-4"> {/* Added flex container */}
<textarea
name="prompt"
value={inputData.prompt}
onChange={handleChange}
className="p-2 bg-gray-700 rounded border border-gray-600 w-1/2 h-24"
placeholder="Enter video prompt..."
></textarea>
<textarea
name="negativePrompt"
value={inputData.negativePrompt}
onChange={handleChange}
className="p-2 bg-gray-700 rounded border border-gray-600 w-1/2 h-24"
placeholder="Enter negative prompt..."
></textarea>
</div></div>
{/* Dimensions */}
<div className="bg-gray-800 p-4 rounded space-y-2">
<label className="block text-lg">Base Settings</label>
<div className="flex flex-wrap space-x-4">
<div className="w-1/5">
<label>Base Model:</label>
<select
name="base_model"
value={inputData.base_model}
onChange={handleChange}
className="p-3 bg-gray-800 rounded border border-gray-600 text-lg w-full">
{/* Options */}
<option value="realisticVisionV20_v20">realisticVisionV20_v20</option>
<option value="lyriel_v16">lyriel_v16</option>
<option value="majicmixRealistic_v5Preview">majicmixRealistic_v5Preview</option>
<option value="rcnzCartoon3d_v10">rcnzCartoon3d_v10</option>
<option value="toonyou_beta3">toonyou_beta3</option>
</select>
</div>
{/* Numeric Inputs */}
{['steps', 'guidance_scale', 'frames', 'width', 'height', 'seed'].map((key) => (
<div className="w-1/5" key={key}>
<label>{key.replace('_', ' ').charAt(0).toUpperCase() + key.slice(1)}:</label>
<input
type="number"
name={key}
value={inputData[key as keyof InputData]}
onChange={handleChange}
className="p-2 bg-gray-800 rounded border border-gray-600 text-lg w-full"
/>
</div>
))}
</div>
</div>
{/* Motion Settings */}
<div className="bg-gray-800 p-4 rounded space-y-2">
<h2 className="text-lg font-bold">Motion Settings</h2>
<div className="flex space-x-4">
<label>Zoom In: <input type="range" min="0" max="1" step="0.01" name="zoom_in_motion_strength" value={inputData.zoom_in_motion_strength} onChange={handleChange} /><span>{inputData.zoom_in_motion_strength}</span></label>
<label>Zoom Out: <input type="range" min="0" max="1" step="0.01" name="zoom_out_motion_strength" value={inputData.zoom_out_motion_strength} onChange={handleChange} /><span>{inputData.zoom_out_motion_strength}</span></label>
</div>
<label>Pan Left: <input type="range" min="0" max="1" step="0.01" name="pan_left_motion_strength" value={inputData.pan_left_motion_strength} onChange={handleChange} /><span>{inputData.pan_left_motion_strength}</span></label>
<label>Pan Right: <input type="range" min="0" max="1" step="0.01" name="pan_right_motion_strength" value={inputData.pan_right_motion_strength} onChange={handleChange} /><span>{inputData.pan_right_motion_strength}</span></label>
<label>Pan Up: <input type="range" min="0" max="1" step="0.01" name="pan_up_motion_strength" value={inputData.pan_up_motion_strength} onChange={handleChange} /><span>{inputData.pan_up_motion_strength}</span></label>
<label>Pan Down: <input type="range" min="0" max="1" step="0.01" name="pan_down_motion_strength" value={inputData.pan_down_motion_strength} onChange={handleChange} /><span>{inputData.pan_down_motion_strength}</span></label>
</div>
{/* Output Settings */}
<div className="bg-gray-800 p-4 rounded space-y-2">
<h2 className="text-lg font-bold">Output Settings</h2>
<div className="flex space-x-4">
<input type="radio" id="mp4" name="output_format" value="mp4" checked={inputData.output_format === 'mp4'} onChange={handleChange} />
<label htmlFor="mp4">MP4</label>
<input type="radio" id="gif" name="output_format" value="gif" checked={inputData.output_format === 'gif'} onChange={handleChange} />
<label htmlFor="gif">GIF</label>
</div>
</div>
{/* Submit Button */}
<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;