| <!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 - Free Hailuo Clone</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%); |
| min-height: 100vh; |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| padding: 20px; |
| } |
| |
| .container { |
| background: white; |
| border-radius: 20px; |
| box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); |
| padding: 40px; |
| max-width: 600px; |
| width: 100%; |
| } |
| |
| h1 { |
| color: #333; |
| margin-bottom: 10px; |
| font-size: 28px; |
| text-align: center; |
| } |
| |
| .subtitle { |
| color: #666; |
| text-align: center; |
| margin-bottom: 30px; |
| font-size: 14px; |
| } |
| |
| .input-group { |
| margin-bottom: 20px; |
| } |
| |
| label { |
| display: block; |
| color: #555; |
| margin-bottom: 8px; |
| font-weight: 500; |
| } |
| |
| #prompt { |
| width: 100%; |
| padding: 12px 16px; |
| border: 2px solid #e0e0e0; |
| border-radius: 10px; |
| font-size: 16px; |
| transition: border-color 0.3s; |
| font-family: inherit; |
| } |
| |
| #prompt:focus { |
| outline: none; |
| border-color: #667eea; |
| } |
| |
| .char-counter { |
| text-align: right; |
| font-size: 12px; |
| color: #999; |
| margin-top: 5px; |
| } |
| |
| button { |
| width: 100%; |
| padding: 14px; |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| color: white; |
| border: none; |
| border-radius: 10px; |
| font-size: 16px; |
| font-weight: 600; |
| cursor: pointer; |
| transition: transform 0.2s, box-shadow 0.2s; |
| } |
| |
| button:hover:not(:disabled) { |
| transform: translateY(-2px); |
| box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4); |
| } |
| |
| button:disabled { |
| opacity: 0.6; |
| cursor: not-allowed; |
| } |
| |
| .status { |
| margin-top: 20px; |
| padding: 12px; |
| border-radius: 8px; |
| text-align: center; |
| font-size: 14px; |
| display: none; |
| } |
| |
| .status.info { |
| background: #e3f2fd; |
| color: #1976d2; |
| display: block; |
| } |
| |
| .status.success { |
| background: #e8f5e9; |
| color: #388e3c; |
| display: block; |
| } |
| |
| .status.error { |
| background: #ffebee; |
| color: #d32f2f; |
| display: block; |
| } |
| |
| .loader { |
| border: 3px solid #f3f3f3; |
| border-top: 3px solid #667eea; |
| border-radius: 50%; |
| width: 30px; |
| height: 30px; |
| animation: spin 1s linear infinite; |
| margin: 20px auto; |
| display: none; |
| } |
| |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| |
| .video-container { |
| margin-top: 20px; |
| display: none; |
| } |
| |
| video { |
| width: 100%; |
| border-radius: 10px; |
| box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); |
| } |
| |
| .download-btn { |
| margin-top: 10px; |
| background: linear-gradient(135deg, #11998e 0%, #38ef7d 100%); |
| display: none; |
| } |
| |
| .example-prompts { |
| margin-top: 20px; |
| padding: 15px; |
| background: #f5f5f5; |
| border-radius: 10px; |
| } |
| |
| .example-prompts h3 { |
| font-size: 14px; |
| color: #666; |
| margin-bottom: 10px; |
| } |
| |
| .example-prompt { |
| display: inline-block; |
| padding: 6px 12px; |
| margin: 4px; |
| background: white; |
| border: 1px solid #ddd; |
| border-radius: 20px; |
| font-size: 12px; |
| cursor: pointer; |
| transition: all 0.2s; |
| } |
| |
| .example-prompt:hover { |
| background: #667eea; |
| color: white; |
| border-color: #667eea; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <h1>🎬 AI Video Generator</h1> |
| <p class="subtitle">Create amazing videos from text using AI</p> |
|
|
| <div class="input-group"> |
| <label for="prompt">Enter your prompt:</label> |
| <textarea |
| id="prompt" |
| rows="3" |
| placeholder="Describe the video you want to create (e.g., A golden retriever running through a field of flowers at sunset)" |
| maxlength="500" |
| ></textarea> |
| <div class="char-counter"> |
| <span id="char-count">0</span>/500 characters |
| </div> |
| </div> |
|
|
| <button id="generate-btn" onclick="generateVideo()"> |
| Generate Video |
| </button> |
|
|
| <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> |
| <button class="download-btn" id="download-btn" onclick="downloadVideo()"> |
| Download Video |
| </button> |
| </div> |
|
|
| <div class="example-prompts"> |
| <h3>💡 Try these examples:</h3> |
| <span class="example-prompt" onclick="setPrompt('A dog running in a park')">🐕 Dog in park</span> |
| <span class="example-prompt" onclick="setPrompt('Ocean waves crashing on a beach at sunset')">🌊 Ocean sunset</span> |
| <span class="example-prompt" onclick="setPrompt('A bird flying through clouds')">🦅 Bird flying</span> |
| <span class="example-prompt" onclick="setPrompt('City street with cars at night')">🌃 City night</span> |
| </div> |
| </div> |
|
|
| <script> |
| const promptInput = document.getElementById('prompt'); |
| const charCount = document.getElementById('char-count'); |
| const generateBtn = document.getElementById('generate-btn'); |
| const loader = document.getElementById('loader'); |
| const status = document.getElementById('status'); |
| const videoContainer = document.getElementById('video-container'); |
| const videoOutput = document.getElementById('video-output'); |
| const downloadBtn = document.getElementById('download-btn'); |
| |
| let currentVideoUrl = null; |
| |
| |
| promptInput.addEventListener('input', () => { |
| const length = promptInput.value.length; |
| charCount.textContent = length; |
| |
| if (length > 450) { |
| charCount.style.color = '#d32f2f'; |
| } else { |
| charCount.style.color = '#999'; |
| } |
| }); |
| |
| |
| promptInput.addEventListener('keydown', (e) => { |
| if (e.ctrlKey && e.key === 'Enter') { |
| generateVideo(); |
| } |
| }); |
| |
| function setPrompt(text) { |
| promptInput.value = text; |
| promptInput.dispatchEvent(new Event('input')); |
| } |
| |
| function showStatus(message, type) { |
| status.textContent = message; |
| status.className = 'status ' + type; |
| } |
| |
| function hideStatus() { |
| status.style.display = 'none'; |
| } |
| |
| async function generateVideo() { |
| const prompt = promptInput.value.trim(); |
| |
| |
| if (!prompt) { |
| showStatus('Please enter a prompt', 'error'); |
| return; |
| } |
| |
| if (prompt.length < 3) { |
| showStatus('Prompt must be at least 3 characters long', 'error'); |
| return; |
| } |
| |
| |
| generateBtn.disabled = true; |
| generateBtn.textContent = 'Generating...'; |
| loader.style.display = 'block'; |
| videoContainer.style.display = 'none'; |
| downloadBtn.style.display = 'none'; |
| showStatus('🎨 Creating your video... This may take 10-60 seconds', 'info'); |
| |
| try { |
| const response = await fetch('http://localhost:5000/generate-video', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ prompt }) |
| }); |
| |
| const data = await response.json(); |
| |
| if (!response.ok || data.error) { |
| throw new Error(data.error || 'Failed to generate video'); |
| } |
| |
| |
| currentVideoUrl = data.video_url; |
| videoOutput.src = currentVideoUrl; |
| videoContainer.style.display = 'block'; |
| downloadBtn.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'; |
| loader.style.display = 'none'; |
| } |
| } |
| |
| 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'); |
| } |
| } |
| |
| |
| window.addEventListener('load', async () => { |
| try { |
| const response = await fetch('http://localhost:5000/health'); |
| const data = await response.json(); |
| if (data.status === 'healthy' && data.client_initialized) { |
| console.log('✅ Server is healthy and ready'); |
| } else { |
| showStatus('⚠️ Server may not be fully initialized', 'error'); |
| } |
| } catch (error) { |
| showStatus('⚠️ Cannot connect to server. Make sure backend is running on port 5000', 'error'); |
| } |
| }); |
| </script> |
| </body> |
| </html> |