Spaces:
Runtime error
Runtime error
| <html> | |
| <head> | |
| <title>FlipSketch - GIF Generator</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| @keyframes shimmer { | |
| 0% { transform: translateX(-100%); } | |
| 100% { transform: translateX(100%); } | |
| } | |
| .loading-bar { | |
| display: none; | |
| position: relative; | |
| overflow: hidden; | |
| background: #f3f4f6; | |
| height: 4px; | |
| width: 100%; | |
| } | |
| .loading-bar::after { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 50%; | |
| height: 100%; | |
| background: linear-gradient(90deg, transparent, #4f46e5, transparent); | |
| animation: shimmer 1.5s infinite; | |
| } | |
| .loading-bar.active { | |
| display: block; | |
| } | |
| .results { | |
| display: none; | |
| } | |
| .results.active { | |
| display: block; | |
| } | |
| .preview-container { | |
| max-width: 300px; | |
| margin: 10px 0; | |
| } | |
| .preview-image { | |
| max-width: 100%; | |
| height: auto; | |
| border-radius: 0.5rem; | |
| } | |
| /* Updated styles to display multiple GIFs in a grid */ | |
| .results-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); | |
| grid-gap: 20px; | |
| justify-items: center; | |
| } | |
| .results-grid img { | |
| width: 100%; | |
| height: auto; | |
| border-radius: 0.5rem; | |
| } | |
| .examples-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); /* Increased minmax to 150px */ | |
| grid-gap: 15px; /* Increased gap between images */ | |
| margin-top: 10px; | |
| } | |
| .example-item { | |
| cursor: pointer; | |
| border: 2px solid transparent; | |
| border-radius: 0.25rem; | |
| overflow: hidden; | |
| } | |
| .example-item.selected { | |
| border-color: #4f46e5; /* Indigo border when selected */ | |
| } | |
| .example-item img { | |
| width: 100%; | |
| height: auto; | |
| display: block; | |
| } | |
| .example-item img { | |
| width: 100%; | |
| height: auto; | |
| display: block; | |
| } | |
| .tab-buttons { | |
| display: flex; | |
| border-bottom: 1px solid #e5e7eb; | |
| margin-bottom: 1rem; | |
| } | |
| .tab-button { | |
| flex: 1; | |
| padding: 0.75rem 1rem; | |
| text-align: center; | |
| cursor: pointer; | |
| border: 1px solid transparent; | |
| border-bottom: none; | |
| background-color: #f9fafb; | |
| font-weight: 500; | |
| color: #6b7280; | |
| } | |
| .tab-button.active { | |
| background-color: #ffffff; | |
| border-top-left-radius: 0.5rem; | |
| border-top-right-radius: 0.5rem; | |
| border-color: #e5e7eb; | |
| color: #4f46e5; | |
| border-bottom: 1px solid #ffffff; | |
| } | |
| .tab-content { | |
| display: none; | |
| } | |
| .tab-content.active { | |
| display: block; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <h1 class="text-4xl font-bold text-center mb-8 text-indigo-600">FlipSketch</h1> | |
| <div class="max-w-2xl mx-auto bg-white rounded-lg shadow-lg p-6"> | |
| <form id="generatorForm" class="space-y-6"> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Enter your prompt</label> | |
| <input type="text" name="prompt" required | |
| class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-indigo-500 focus:border-indigo-500"> | |
| </div> | |
| <div> | |
| <div class="tab-buttons"> | |
| <div class="tab-button active" data-tab="examplesTab">Sketch</div> | |
| <div class="tab-button" data-tab="uploadTab">Upload</div> | |
| </div> | |
| <div id="examplesTab" class="tab-content active"> | |
| <div id="exampleSketches" class="examples-grid"> | |
| </div> | |
| </div> | |
| <div id="uploadTab" class="tab-content"> | |
| <input type="file" id="imageInput" name="image" accept="image/*" | |
| class="w-full px-4 py-2 border border-gray-300 rounded-md"> | |
| <div id="imagePreview" class="preview-container"></div> | |
| </div> | |
| </div> | |
| <!-- Seeds input remains unchanged --> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Seeds (1-10)</label> | |
| <input type="number" name="seeds" min="1" max="10" value="5" | |
| class="w-full px-4 py-2 border border-gray-300 rounded-md"> | |
| </div> | |
| <div> | |
| <label class="block text-sm font-medium text-gray-700 mb-2">Motion (0.0 - 1.0) [Reduce when the animation changes the input sketch]</label> | |
| <input type="range" name="lambda" min="0" max="1" step="0.05" value="0.5" class="w-full"> | |
| <div class="text-sm text-gray-600 mt-1">Value (1-Lambda) : <span id="lambdaValue">0.5</span></div> | |
| </div> | |
| <button type="submit" | |
| class="w-full bg-indigo-600 text-white py-2 px-4 rounded-md hover:bg-indigo-700 transition duration-200"> | |
| Generate GIF | |
| </button> | |
| </form> | |
| <div id="loadingBar" class="loading-bar mt-6"></div> | |
| <div id="progressText" class="text-center mt-4 text-gray-600 hidden">Generating your GIF... | |
| <span id="progressPercent">0%</span> | |
| </div> | |
| <div class="results mt-8"> | |
| <h2 class="text-2xl font-semibold mb-4 text-center">Generated GIFs</h2> | |
| <div class="results-grid" id="resultsGrid"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Lambda Slider Update | |
| const lambdaSlider = document.querySelector('input[name="lambda"]'); | |
| const lambdaValue = document.getElementById('lambdaValue'); | |
| lambdaSlider.addEventListener('input', () => { | |
| lambdaValue.textContent = lambdaSlider.value; | |
| }); | |
| const examplePrompts = { | |
| 'sketch1.png': 'The camel walks slowly', | |
| 'sketch2.png': 'The wine in the wine glass sways from side to side', | |
| 'sketch3.png': 'The squirrel is eating a nut', | |
| 'sketch4.png': 'The surfer surfs on the waves', | |
| 'sketch5.png': 'A galloping horse', | |
| 'sketch6.png': 'The cat walks forward', | |
| 'sketch7.png': 'The eagle flies in the sky', | |
| 'sketch8.png': 'The flower is blooming slowly', | |
| 'sketch9.png': 'The reindeer looks around', | |
| 'sketch10.png': 'The cloud floats in the sky', | |
| 'sketch11.png': 'The jazz saxophonist performs on stage with a rhythmic sway, his upper body sways subtly to the rhythm of the music.', | |
| 'sketch12.png': 'The biker rides on the road', | |
| }; | |
| const tabButtons = document.querySelectorAll('.tab-button'); | |
| const tabContents = document.querySelectorAll('.tab-content'); | |
| let selectedExample = null; | |
| tabButtons.forEach(button => { | |
| button.addEventListener('click', () => { | |
| // Remove active class from all buttons | |
| tabButtons.forEach(btn => btn.classList.remove('active')); | |
| // Hide all tab contents | |
| tabContents.forEach(content => content.classList.remove('active')); | |
| // Add active class to clicked button | |
| button.classList.add('active'); | |
| // Show corresponding tab content | |
| const tabId = button.getAttribute('data-tab'); | |
| document.getElementById(tabId).classList.add('active'); | |
| // Reset inputs when switching tabs | |
| if (tabId === 'uploadTab') { | |
| // Clear selected example | |
| selectedExample = null; | |
| const exampleItems = document.querySelectorAll('.example-item'); | |
| exampleItems.forEach(item => item.classList.remove('selected')); | |
| // Enable image input | |
| document.getElementById('imageInput').disabled = false; | |
| // Clear the prompt input field | |
| const promptInput = document.querySelector('input[name="prompt"]'); | |
| promptInput.value = ''; | |
| } else if (tabId === 'examplesTab') { | |
| // Clear uploaded image | |
| const imageInput = document.getElementById('imageInput'); | |
| imageInput.value = ''; | |
| document.getElementById('imagePreview').innerHTML = ''; | |
| // Disable image input | |
| imageInput.disabled = true; | |
| // Clear the prompt input field | |
| const promptInput = document.querySelector('input[name="prompt"]'); | |
| promptInput.value = ''; | |
| } | |
| }); | |
| }); | |
| // Load example sketches when the page loads | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const exampleSketches = document.getElementById('exampleSketches'); | |
| const numExamples = 12; // Number of example sketches | |
| for (let i = 1; i <= numExamples; i++) { | |
| const filename = `sketch${i}.png`; | |
| const prompt = examplePrompts[filename] || ''; // Get the default prompt, or empty string if not defined | |
| const exampleItem = document.createElement('div'); | |
| exampleItem.className = 'example-item'; | |
| exampleItem.dataset.prompt = prompt; // Store the prompt as a data attribute | |
| const img = document.createElement('img'); | |
| img.src = `static/examples/${filename}`; // Adjust the path to your examples | |
| img.alt = `Sketch ${i}`; | |
| img.dataset.filename = filename; // Store the filename | |
| exampleItem.appendChild(img); | |
| exampleSketches.appendChild(exampleItem); | |
| } | |
| }); | |
| // Handle selection of an example sketch | |
| document.getElementById('exampleSketches').addEventListener('click', (e) => { | |
| if (e.target.tagName === 'IMG') { | |
| const clickedItem = e.target.parentElement; | |
| const isSelected = clickedItem.classList.contains('selected'); | |
| // Deselect all items | |
| const exampleItems = document.querySelectorAll('.example-item'); | |
| exampleItems.forEach(item => item.classList.remove('selected')); | |
| if (!isSelected) { | |
| // Select the clicked item | |
| clickedItem.classList.add('selected'); | |
| selectedExample = e.target.dataset.filename; | |
| // Retrieve and set the default prompt | |
| const defaultPrompt = clickedItem.dataset.prompt || ''; | |
| const promptInput = document.querySelector('input[name="prompt"]'); | |
| promptInput.value = defaultPrompt; | |
| } else { | |
| // Deselecting the item | |
| selectedExample = null; | |
| const promptInput = document.querySelector('input[name="prompt"]'); | |
| promptInput.value = ''; | |
| } | |
| } | |
| }); | |
| // Image upload preview (optional) | |
| document.getElementById('imageInput').addEventListener('change', (e) => { | |
| const previewContainer = document.getElementById('imagePreview'); | |
| previewContainer.innerHTML = ''; | |
| if (e.target.files.length > 0) { | |
| const file = e.target.files[0]; | |
| const img = document.createElement('img'); | |
| img.className = 'preview-image'; | |
| img.src = URL.createObjectURL(file); | |
| previewContainer.appendChild(img); | |
| } | |
| }); | |
| document.getElementById('generatorForm').addEventListener('submit', async (e) => { | |
| e.preventDefault(); | |
| const form = e.target; | |
| const formData = new FormData(form); | |
| // Determine which tab is active | |
| const activeTab = document.querySelector('.tab-content.active').id; | |
| if (activeTab === 'uploadTab') { | |
| // Ensure a file is uploaded | |
| const imageInput = document.getElementById('imageInput'); | |
| if (!imageInput.files.length) { | |
| alert('Please upload an image before submitting!'); | |
| return; // Stop form submission | |
| } | |
| // No need to append selected_example | |
| } else if (activeTab === 'examplesTab') { | |
| // Ensure an example is selected | |
| if (!selectedExample) { | |
| alert('Please select an example sketch before submitting!'); | |
| return; // Stop form submission | |
| } | |
| // Append selected_example to form data | |
| formData.append('selected_example', selectedExample); | |
| } | |
| // Show Loading Bar | |
| const loadingBar = document.getElementById('loadingBar'); | |
| const progressText = document.getElementById('progressText'); | |
| const progressPercent = document.getElementById('progressPercent'); | |
| loadingBar.classList.add('active'); | |
| progressText.classList.remove('hidden'); | |
| document.querySelector('.results').classList.remove('active'); | |
| // Simulate Loading Progress | |
| let progress = 0; | |
| const totalDuration = 65000; // 30 seconds | |
| const updateInterval = 100; // Update every 100ms | |
| const increment = 100 / (totalDuration / updateInterval); | |
| const progressInterval = setInterval(() => { | |
| progress += increment; | |
| if (progress >= 100) { | |
| progress = 100; | |
| clearInterval(progressInterval); | |
| } | |
| progressPercent.textContent = `${Math.floor(progress)}%`; | |
| }, updateInterval); | |
| try { | |
| const response = await fetch('/generate', { | |
| method: 'POST', | |
| body: formData, | |
| }); | |
| const data = await response.json(); | |
| if (response.ok) { | |
| // Complete the Progress Bar | |
| progress = 100; | |
| progressPercent.textContent = '100%'; | |
| const resultsGrid = document.getElementById('resultsGrid'); | |
| resultsGrid.innerHTML = ''; | |
| // Display all GIFs returned by the server | |
| data.gifs.forEach((gifUrl) => { | |
| const gifElement = document.createElement('div'); | |
| gifElement.className = 'gif-container'; | |
| gifElement.innerHTML = ` | |
| <img src="${gifUrl}" alt="Generated GIF"> | |
| `; | |
| resultsGrid.appendChild(gifElement); | |
| }); | |
| document.querySelector('.results').classList.add('active'); | |
| } else { | |
| alert(data.error || 'An error occurred'); | |
| } | |
| } catch (error) { | |
| console.error('Error:', error); | |
| alert('An error occurred while generating the GIFs'); | |
| } finally { | |
| clearInterval(progressInterval); | |
| setTimeout(() => { | |
| loadingBar.classList.remove('active'); | |
| progressText.classList.add('hidden'); | |
| }, 500); | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html>` |