videoAI / index_enhanced.html
sravya's picture
Upload 33 files
54ed165 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Video Generator Pro - Hailuo-Inspired</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
background-attachment: fixed;
min-height: 100vh;
padding: 20px;
animation: gradientShift 15s ease infinite;
}
@keyframes gradientShift {
0%, 100% { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
50% { background: linear-gradient(135deg, #764ba2 0%, #667eea 100%); }
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 40px 40px;
text-align: center;
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
animation: headerPulse 10s ease-in-out infinite;
}
@keyframes headerPulse {
0%, 100% { transform: translate(0, 0); }
50% { transform: translate(10%, 10%); }
}
.header h1 {
font-size: 36px;
margin-bottom: 10px;
position: relative;
z-index: 1;
text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
}
.header p {
font-size: 18px;
opacity: 0.95;
position: relative;
z-index: 1;
}
.main-content {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 30px;
padding: 40px;
}
.left-panel, .right-panel {
display: flex;
flex-direction: column;
gap: 20px;
}
.section {
background: #f8f9fa;
padding: 20px;
border-radius: 12px;
}
.section h3 {
color: #333;
margin-bottom: 15px;
font-size: 18px;
display: flex;
align-items: center;
gap: 8px;
}
label {
display: block;
color: #555;
margin-bottom: 8px;
font-weight: 500;
font-size: 14px;
}
textarea, select, input[type="file"] {
width: 100%;
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 8px;
font-size: 14px;
font-family: inherit;
transition: border-color 0.3s;
}
textarea {
resize: vertical;
min-height: 100px;
}
textarea:focus, select:focus {
outline: none;
border-color: #667eea;
}
.char-counter {
text-align: right;
font-size: 12px;
color: #999;
margin-top: 5px;
}
.options-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.option-group {
display: flex;
flex-direction: column;
gap: 8px;
}
button {
padding: 14px 24px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 10px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
button::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
}
button:hover::before {
width: 300px;
height: 300px;
}
button:hover:not(:disabled) {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(102, 126, 234, 0.5);
}
button:active:not(:disabled) {
transform: translateY(0);
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.generate-btn {
width: 100%;
padding: 16px;
font-size: 18px;
}
.tabs {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.tab {
flex: 1;
padding: 10px;
background: white;
border: 2px solid #e0e0e0;
border-radius: 8px;
cursor: pointer;
text-align: center;
transition: all 0.3s;
font-weight: 500;
}
.tab.active {
background: #667eea;
color: white;
border-color: #667eea;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.loader {
border: 3px solid #f3f3f3;
border-top: 3px solid #667eea;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 20px auto;
display: none;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.status {
padding: 12px;
border-radius: 8px;
text-align: center;
font-size: 14px;
display: none;
margin-top: 15px;
}
.status.info {
background: #e3f2fd;
color: #1976d2;
display: block;
}
.status.success {
background: #e8f5e9;
color: #388e3c;
display: block;
}
.status.error {
background: #ffebee;
color: #d32f2f;
display: block;
}
.video-container {
background: #000;
border-radius: 12px;
overflow: hidden;
display: none;
}
video {
width: 100%;
display: block;
}
.video-info {
background: #f8f9fa;
padding: 15px;
margin-top: 10px;
border-radius: 8px;
font-size: 13px;
color: #666;
}
.video-actions {
display: flex;
gap: 10px;
margin-top: 10px;
}
.video-actions button {
flex: 1;
background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%);
}
.example-prompts {
display: flex;
flex-direction: column;
gap: 10px;
}
.prompt-category {
margin-bottom: 10px;
}
.prompt-category h4 {
font-size: 13px;
color: #666;
margin-bottom: 8px;
}
.example-prompt {
display: inline-block;
padding: 8px 16px;
margin: 6px 4px;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
border: 2px solid transparent;
border-radius: 25px;
font-size: 13px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.example-prompt:hover {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-color: #667eea;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
}
.example-prompt:active {
transform: translateY(0);
}
.image-upload-area {
border: 2px dashed #ddd;
border-radius: 8px;
padding: 20px;
text-align: center;
cursor: pointer;
transition: all 0.3s;
}
.image-upload-area:hover {
border-color: #667eea;
background: #f8f9fa;
}
.image-upload-area.has-image {
border-style: solid;
border-color: #667eea;
}
.preview-image {
max-width: 100%;
max-height: 200px;
border-radius: 8px;
margin-top: 10px;
}
@media (max-width: 768px) {
.main-content {
grid-template-columns: 1fr;
}
.options-grid {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎬 AI Video Generator Pro</h1>
<p>Create stunning videos with multiple AI models and Hailuo-inspired features</p>
</div>
<div class="main-content">
<!-- Left Panel: Input & Options -->
<div class="left-panel">
<!-- Generation Mode -->
<div class="section">
<h3>🎯 Generation Mode</h3>
<div class="tabs">
<div class="tab active" onclick="switchMode('text')">📝 Text to Video</div>
<div class="tab" onclick="switchMode('image')">🖼️ Image to Video</div>
</div>
<!-- Text to Video Tab -->
<div id="text-mode" class="tab-content active">
<label for="prompt">Enter your prompt:</label>
<textarea
id="prompt"
rows="4"
placeholder="Describe the video you want to create..."
maxlength="1000"
></textarea>
<div class="char-counter">
<span id="char-count">0</span>/1000 characters
</div>
</div>
<!-- Image to Video Tab -->
<div id="image-mode" class="tab-content">
<label>Upload Image:</label>
<div class="image-upload-area" id="upload-area" onclick="document.getElementById('image-input').click()">
<p>📤 Click to upload or drag & drop</p>
<p style="font-size: 12px; color: #999; margin-top: 5px;">Supports JPG, PNG</p>
<img id="preview-image" class="preview-image" style="display: none;">
</div>
<input type="file" id="image-input" accept="image/*" style="display: none;" onchange="handleImageUpload(event)">
<label for="image-prompt" style="margin-top: 15px;">Animation prompt (optional):</label>
<textarea
id="image-prompt"
rows="2"
placeholder="Describe how you want the image to animate..."
></textarea>
</div>
</div>
<!-- Model Selection -->
<div class="section">
<h3>🤖 AI Model</h3>
<select id="model-select" onchange="updateModelInfo()">
<option value="cogvideox-5b">🔥 CogVideoX-5B - Best Quality (6s, 720p)</option>
<option value="cogvideox-2b">⚡ CogVideoX-2B - Faster Version</option>
<option value="hunyuan-video">🌟 HunyuanVideo - SOTA by Tencent</option>
<option value="stable-video-diffusion">🖼️ Stable Video Diffusion - Image Animation</option>
<option value="demo">💡 Demo Mode - Test UI (Instant)</option>
</select>
<div id="model-info" style="margin-top: 10px; font-size: 13px; color: #666;"></div>
</div>
<!-- Advanced Options (Hailuo-inspired) -->
<div class="section">
<h3>🎥 Advanced Options</h3>
<div class="options-grid">
<div class="option-group">
<label for="camera-select">Camera Movement:</label>
<select id="camera-select">
<option value="">Static (No movement)</option>
</select>
</div>
<div class="option-group">
<label for="effect-select">Visual Effects:</label>
<select id="effect-select">
<option value="">None</option>
</select>
</div>
<div class="option-group">
<label for="style-select">Video Style:</label>
<select id="style-select">
<option value="">Default</option>
</select>
</div>
</div>
</div>
<!-- Generate Button -->
<button class="generate-btn" onclick="generateVideo()">
🎬 Generate Video
</button>
</div>
<!-- Right Panel: Output & Examples -->
<div class="right-panel">
<!-- Video Output -->
<div class="section">
<h3>🎞️ Generated Video</h3>
<div class="loader" id="loader"></div>
<div class="status" id="status"></div>
<div class="video-container" id="video-container">
<video id="video-output" controls></video>
</div>
<div id="video-info" class="video-info" style="display: none;"></div>
<div class="video-actions" id="video-actions" style="display: none;">
<button onclick="downloadVideo()">📥 Download</button>
<button onclick="shareVideo()">🔗 Share</button>
</div>
</div>
<!-- Example Prompts -->
<div class="section">
<h3>💡 Example Prompts</h3>
<div class="example-prompts" id="example-prompts">
<!-- Will be populated by JavaScript -->
</div>
</div>
</div>
</div>
</div>
<script>
// Global state
let currentVideoUrl = null;
let currentMode = 'text';
let uploadedImage = null;
let modelsData = null;
// Initialize app
window.addEventListener('load', async () => {
await loadModelsData();
checkServerHealth();
setupEventListeners();
});
function setupEventListeners() {
const promptInput = document.getElementById('prompt');
const charCount = document.getElementById('char-count');
promptInput.addEventListener('input', () => {
const length = promptInput.value.length;
charCount.textContent = length;
charCount.style.color = length > 900 ? '#d32f2f' : '#999';
});
promptInput.addEventListener('keydown', (e) => {
if (e.ctrlKey && e.key === 'Enter') {
generateVideo();
}
});
}
async function loadModelsData() {
try {
const response = await fetch('/api/models');
modelsData = await response.json();
populateCameraMovements();
populateVisualEffects();
populateVideoStyles();
populateExamplePrompts();
updateModelInfo();
} catch (error) {
console.error('Failed to load models data:', error);
// Use fallback data if backend is not available
useFallbackData();
}
}
function useFallbackData() {
// Fallback example prompts
const fallbackPrompts = {
"Nature": [
"A majestic waterfall cascading down mossy rocks in a lush rainforest",
"Ocean waves crashing on a rocky shore at sunset with seagulls flying",
"A field of sunflowers swaying in the breeze under a blue sky"
],
"Animals": [
"A golden retriever running through a field of flowers at sunset",
"A majestic eagle soaring through clouds above mountain peaks",
"A playful dolphin jumping out of crystal clear ocean water"
],
"Urban": [
"City street with cars and pedestrians at night, neon lights reflecting on wet pavement",
"Time-lapse of clouds moving over modern skyscrapers in downtown",
"A busy coffee shop with people working on laptops, warm lighting"
],
"Fantasy": [
"A magical portal opening in an ancient forest with glowing particles",
"A dragon flying over a medieval castle at dawn",
"Floating islands in the sky connected by glowing bridges"
]
};
const container = document.getElementById('example-prompts');
Object.entries(fallbackPrompts).forEach(([category, prompts]) => {
const categoryDiv = document.createElement('div');
categoryDiv.className = 'prompt-category';
const title = document.createElement('h4');
title.textContent = category;
categoryDiv.appendChild(title);
prompts.forEach(prompt => {
const span = document.createElement('span');
span.className = 'example-prompt';
span.textContent = prompt.substring(0, 50) + (prompt.length > 50 ? '...' : '');
span.title = prompt;
span.onclick = () => setPrompt(prompt);
categoryDiv.appendChild(span);
});
container.appendChild(categoryDiv);
});
}
function populateCameraMovements() {
const select = document.getElementById('camera-select');
modelsData.camera_movements.forEach(movement => {
const option = document.createElement('option');
option.value = movement.tag;
option.textContent = `${movement.name} - ${movement.description}`;
select.appendChild(option);
});
}
function populateVisualEffects() {
const select = document.getElementById('effect-select');
modelsData.visual_effects.forEach(effect => {
const option = document.createElement('option');
option.value = effect.tag;
option.textContent = `${effect.name} - ${effect.description}`;
select.appendChild(option);
});
}
function populateVideoStyles() {
const select = document.getElementById('style-select');
modelsData.video_styles.forEach(style => {
const option = document.createElement('option');
option.value = style.tag;
option.textContent = `${style.name} - ${style.description}`;
select.appendChild(option);
});
}
function populateExamplePrompts() {
const container = document.getElementById('example-prompts');
Object.entries(modelsData.example_prompts).forEach(([category, prompts]) => {
const categoryDiv = document.createElement('div');
categoryDiv.className = 'prompt-category';
const title = document.createElement('h4');
title.textContent = category;
categoryDiv.appendChild(title);
prompts.forEach(prompt => {
const span = document.createElement('span');
span.className = 'example-prompt';
span.textContent = prompt.substring(0, 50) + (prompt.length > 50 ? '...' : '');
span.title = prompt;
span.onclick = () => setPrompt(prompt);
categoryDiv.appendChild(span);
});
container.appendChild(categoryDiv);
});
}
function updateModelInfo() {
const modelId = document.getElementById('model-select').value;
const model = modelsData.models[modelId];
const infoDiv = document.getElementById('model-info');
infoDiv.textContent = `${model.description} | Type: ${model.type}`;
}
function switchMode(mode) {
currentMode = mode;
// Update tabs
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
event.target.classList.add('active');
// Update content
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
document.getElementById(`${mode}-mode`).classList.add('active');
// Update model selection for image mode
if (mode === 'image') {
const modelSelect = document.getElementById('model-select');
modelSelect.value = 'stable-video-diffusion';
updateModelInfo();
}
}
function handleImageUpload(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
uploadedImage = e.target.result;
const preview = document.getElementById('preview-image');
preview.src = uploadedImage;
preview.style.display = 'block';
document.getElementById('upload-area').classList.add('has-image');
};
reader.readAsDataURL(file);
}
function setPrompt(text) {
document.getElementById('prompt').value = text;
document.getElementById('prompt').dispatchEvent(new Event('input'));
}
function showStatus(message, type) {
const status = document.getElementById('status');
status.textContent = message;
status.className = 'status ' + type;
}
async function generateVideo() {
if (currentMode === 'text') {
await generateTextToVideo();
} else {
await generateImageToVideo();
}
}
async function generateTextToVideo() {
const prompt = document.getElementById('prompt').value.trim();
const model = document.getElementById('model-select').value;
const cameraMovement = document.getElementById('camera-select').value;
const visualEffect = document.getElementById('effect-select').value;
const style = document.getElementById('style-select').value;
if (!prompt) {
showStatus('Please enter a prompt', 'error');
return;
}
if (prompt.length < 3) {
showStatus('Prompt must be at least 3 characters long', 'error');
return;
}
// UI updates
const generateBtn = document.querySelector('.generate-btn');
generateBtn.disabled = true;
generateBtn.textContent = '🎬 Generating...';
document.getElementById('loader').style.display = 'block';
document.getElementById('video-container').style.display = 'none';
document.getElementById('video-actions').style.display = 'none';
document.getElementById('video-info').style.display = 'none';
showStatus('🎨 Creating your video... This may take 30-120 seconds depending on the model', 'info');
try {
const response = await fetch('/api/generate-video', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt,
// Map enhanced UI model ids to Replicate API models
model: model === 'cogvideox-5b' || model === 'cogvideox-2b' ? 'cogvideox' : (model === 'demo' ? 'demo' : 'hailuo'),
camera_movement: cameraMovement,
visual_effect: visualEffect,
style: style
})
});
const data = await response.json();
if (!response.ok || data.error) {
throw new Error(data.error || 'Failed to generate video');
}
// Success
currentVideoUrl = data.video_url;
const videoOutput = document.getElementById('video-output');
videoOutput.src = currentVideoUrl;
document.getElementById('video-container').style.display = 'block';
document.getElementById('video-actions').style.display = 'flex';
// Show video info
const videoInfo = document.getElementById('video-info');
videoInfo.innerHTML = `
<strong>Model:</strong> ${data.model_name}<br>
<strong>Prompt:</strong> ${data.prompt}<br>
${data.enhanced_prompt !== data.prompt ? `<strong>Enhanced:</strong> ${data.enhanced_prompt}<br>` : ''}
<strong>Generated:</strong> ${new Date(data.timestamp).toLocaleString()}
`;
videoInfo.style.display = 'block';
showStatus('✅ Video generated successfully!', 'success');
videoOutput.play().catch(() => {});
} catch (error) {
console.error('Error:', error);
showStatus('❌ Error: ' + error.message, 'error');
} finally {
generateBtn.disabled = false;
generateBtn.textContent = '🎬 Generate Video';
document.getElementById('loader').style.display = 'none';
}
}
async function generateImageToVideo() {
// Image-to-video not supported in the Vercel serverless API yet
showStatus('⚠️ Image-to-Video is not available in this hosted version. Use Local mode for this feature.', 'error');
}
async function downloadVideo() {
if (!currentVideoUrl) return;
try {
showStatus('📥 Preparing download...', 'info');
const response = await fetch(currentVideoUrl);
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `ai-video-${Date.now()}.mp4`;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
showStatus('✅ Download started!', 'success');
} catch (error) {
console.error('Download error:', error);
showStatus('❌ Failed to download video', 'error');
}
}
function shareVideo() {
if (!currentVideoUrl) return;
if (navigator.share) {
navigator.share({
title: 'AI Generated Video',
text: 'Check out this AI-generated video!',
url: currentVideoUrl
}).catch(err => console.log('Error sharing:', err));
} else {
// Fallback: copy to clipboard
navigator.clipboard.writeText(currentVideoUrl).then(() => {
showStatus('🔗 Video URL copied to clipboard!', 'success');
});
}
}
async function checkServerHealth() {
try {
const response = await fetch('/api/health');
const data = await response.json();
if (data.status === 'healthy') {
console.log('✅ API is healthy');
}
} catch (error) {
showStatus('⚠️ Cannot connect to API. If deploying on Vercel, ensure functions are enabled.', 'error');
}
}
</script>
</body>
</html>