Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>RookusAI - Dress Recoloring with CosXL</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
.gradient-bg { | |
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); | |
} | |
.upload-area { | |
border: 2px dashed #cbd5e0; | |
transition: all 0.3s ease; | |
} | |
.upload-area:hover { | |
border-color: #667eea; | |
} | |
.upload-area.active { | |
border-color: #667eea; | |
background-color: #ebf4ff; | |
} | |
.color-option { | |
width: 40px; | |
height: 40px; | |
border-radius: 50%; | |
cursor: pointer; | |
transition: transform 0.2s; | |
} | |
.color-option:hover { | |
transform: scale(1.1); | |
} | |
.color-option.selected { | |
border: 3px solid #4f46e5; | |
transform: scale(1.1); | |
} | |
.result-container { | |
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
} | |
.loading-spinner { | |
animation: spin 1s linear infinite; | |
} | |
@keyframes spin { | |
0% { transform: rotate(0deg); } | |
100% { transform: rotate(360deg); } | |
} | |
.history-item { | |
transition: all 0.2s ease; | |
} | |
.history-item:hover { | |
transform: translateY(-2px); | |
} | |
</style> | |
</head> | |
<body class="gradient-bg min-h-screen"> | |
<!-- Navigation --> | |
<nav class="bg-white shadow-sm"> | |
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | |
<div class="flex justify-between h-16"> | |
<div class="flex items-center"> | |
<div class="flex-shrink-0 flex items-center"> | |
<i class="fas fa-tshirt text-indigo-600 text-2xl mr-2"></i> | |
<span class="text-xl font-bold text-gray-900">RookusAI</span> | |
</div> | |
</div> | |
<div class="hidden sm:ml-6 sm:flex sm:items-center"> | |
<a href="#" class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-indigo-600">Home</a> | |
<a href="#" class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-indigo-600">Features</a> | |
<a href="#" class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-indigo-600">Pricing</a> | |
<a href="#" class="px-3 py-2 rounded-md text-sm font-medium text-gray-700 hover:text-indigo-600">About</a> | |
</div> | |
<div class="hidden sm:ml-6 sm:flex sm:items-center"> | |
<button class="px-4 py-2 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"> | |
Sign In | |
</button> | |
</div> | |
<div class="-mr-2 flex items-center sm:hidden"> | |
<button type="button" class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500" aria-controls="mobile-menu" aria-expanded="false"> | |
<span class="sr-only">Open main menu</span> | |
<i class="fas fa-bars"></i> | |
</button> | |
</div> | |
</div> | |
</div> | |
</nav> | |
<!-- Main Content --> | |
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> | |
<div class="text-center mb-12"> | |
<h1 class="text-4xl font-extrabold text-gray-900 sm:text-5xl sm:tracking-tight lg:text-6xl"> | |
AI-Powered Dress Recoloring | |
</h1> | |
<p class="mt-5 max-w-xl mx-auto text-xl text-gray-500"> | |
Transform your outfit with CosXL's advanced AI technology. Simply upload a photo and choose your desired color. | |
</p> | |
</div> | |
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> | |
<!-- Upload and Controls Section --> | |
<div class="bg-white rounded-xl shadow-md p-6"> | |
<div class="mb-6"> | |
<h2 class="text-xl font-semibold text-gray-800 mb-4">Upload Your Dress Photo</h2> | |
<div id="upload-area" class="upload-area rounded-lg p-8 text-center cursor-pointer"> | |
<input type="file" id="file-input" class="hidden" accept="image/*"> | |
<div id="upload-content"> | |
<i class="fas fa-cloud-upload-alt text-4xl text-gray-400 mb-3"></i> | |
<p class="text-gray-500">Drag & drop your photo here or click to browse</p> | |
<p class="text-sm text-gray-400 mt-2">Supports JPG, PNG (Max 5MB)</p> | |
</div> | |
<img id="preview-image" class="hidden max-w-full h-auto rounded-lg mx-auto" alt="Preview"> | |
</div> | |
</div> | |
<div class="mb-6"> | |
<h2 class="text-xl font-semibold text-gray-800 mb-4">Color Instructions</h2> | |
<div class="mb-4"> | |
<label for="color-prompt" class="block text-sm font-medium text-gray-700 mb-1">Describe the color change</label> | |
<input type="text" id="color-prompt" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" placeholder="e.g. Change the dress to emerald green"> | |
</div> | |
<div class="grid grid-cols-5 gap-4 mb-4"> | |
<div class="color-option bg-red-500 selected" data-prompt="change the dress to bright red" title="Red"></div> | |
<div class="color-option bg-blue-500" data-prompt="change the dress to deep blue" title="Blue"></div> | |
<div class="color-option bg-green-500" data-prompt="change the dress to emerald green" title="Green"></div> | |
<div class="color-option bg-yellow-400" data-prompt="change the dress to golden yellow" title="Yellow"></div> | |
<div class="color-option bg-purple-500" data-prompt="change the dress to royal purple" title="Purple"></div> | |
<div class="color-option bg-pink-500" data-prompt="change the dress to hot pink" title="Pink"></div> | |
<div class="color-option bg-indigo-500" data-prompt="change the dress to indigo" title="Indigo"></div> | |
<div class="color-option bg-teal-500" data-prompt="change the dress to teal" title="Teal"></div> | |
<div class="color-option bg-gray-500" data-prompt="change the dress to charcoal gray" title="Gray"></div> | |
<div class="color-option bg-black" data-prompt="change the dress to black" title="Black"></div> | |
</div> | |
</div> | |
<div class="mb-6"> | |
<h2 class="text-xl font-semibold text-gray-800 mb-4">Advanced Options</h2> | |
<div class="space-y-4"> | |
<div> | |
<label for="negative-prompt" class="block text-sm font-medium text-gray-700 mb-1">Negative Prompt</label> | |
<input type="text" id="negative-prompt" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" placeholder="Things you don't want in the result"> | |
</div> | |
<div> | |
<label for="guidance-scale" class="block text-sm font-medium text-gray-700 mb-1">Guidance Scale</label> | |
<input type="range" id="guidance-scale" min="1" max="20" value="7" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> | |
<div class="flex justify-between text-xs text-gray-500 mt-1"> | |
<span>1</span> | |
<span>7</span> | |
<span>20</span> | |
</div> | |
</div> | |
<div> | |
<label for="steps" class="block text-sm font-medium text-gray-700 mb-1">Steps</label> | |
<input type="range" id="steps" min="10" max="50" value="20" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"> | |
<div class="flex justify-between text-xs text-gray-500 mt-1"> | |
<span>10</span> | |
<span>20</span> | |
<span>50</span> | |
</div> | |
</div> | |
</div> | |
</div> | |
<button id="generate-btn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded-lg transition duration-200 flex items-center justify-center"> | |
<span>Recolor Dress</span> | |
<i id="generate-icon" class="fas fa-magic ml-2"></i> | |
<i id="loading-icon" class="fas fa-circle-notch loading-spinner ml-2 hidden"></i> | |
</button> | |
</div> | |
<!-- Results Section --> | |
<div class="bg-white rounded-xl shadow-md p-6"> | |
<h2 class="text-xl font-semibold text-gray-800 mb-4">Your Results</h2> | |
<div id="result-container" class="result-container bg-gray-50 rounded-lg p-4 hidden"> | |
<div class="flex justify-between mb-4"> | |
<h3 class="text-lg font-medium text-gray-700">Original</h3> | |
<h3 class="text-lg font-medium text-gray-700">Recolored</h3> | |
</div> | |
<div class="grid grid-cols-2 gap-4"> | |
<img id="original-image" class="w-full h-auto rounded-lg" alt="Original"> | |
<img id="result-image" class="w-full h-auto rounded-lg" alt="Result"> | |
</div> | |
<div class="mt-4 flex justify-between"> | |
<button id="download-btn" class="bg-indigo-100 hover:bg-indigo-200 text-indigo-700 font-medium py-2 px-4 rounded-lg transition duration-200"> | |
<i class="fas fa-download mr-2"></i> Download | |
</button> | |
<button id="save-btn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium py-2 px-4 rounded-lg transition duration-200"> | |
<i class="fas fa-save mr-2"></i> Save to Profile | |
</button> | |
<button id="share-btn" class="bg-gray-100 hover:bg-gray-200 text-gray-700 font-medium py-2 px-4 rounded-lg transition duration-200"> | |
<i class="fas fa-share-alt mr-2"></i> Share | |
</button> | |
</div> | |
</div> | |
<div id="empty-state" class="text-center py-12"> | |
<i class="fas fa-tshirt text-4xl text-gray-300 mb-4"></i> | |
<h3 class="text-lg font-medium text-gray-500">No results yet</h3> | |
<p class="text-gray-400 mt-1">Upload a photo and click "Recolor Dress" to see the magic!</p> | |
</div> | |
<div id="history-section" class="mt-8 hidden"> | |
<h3 class="text-lg font-medium text-gray-700 mb-3">Recent Recolors</h3> | |
<div class="grid grid-cols-3 gap-3" id="history-grid"> | |
<!-- History items will be added here dynamically --> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Features Section --> | |
<div class="mt-16"> | |
<h2 class="text-3xl font-bold text-center text-gray-900 mb-12">Why Choose RookusAI with CosXL?</h2> | |
<div class="grid grid-cols-1 md:grid-cols-3 gap-8"> | |
<div class="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition duration-200"> | |
<div class="text-indigo-600 mb-4"> | |
<i class="fas fa-bolt text-3xl"></i> | |
</div> | |
<h3 class="text-xl font-semibold mb-2">Advanced AI Technology</h3> | |
<p class="text-gray-500">Powered by CosXL's state-of-the-art image editing capabilities for realistic color transformations.</p> | |
</div> | |
<div class="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition duration-200"> | |
<div class="text-indigo-600 mb-4"> | |
<i class="fas fa-palette text-3xl"></i> | |
</div> | |
<h3 class="text-xl font-semibold mb-2">Precise Color Control</h3> | |
<p class="text-gray-500">Get exactly the color you want with natural-looking results that maintain fabric textures.</p> | |
</div> | |
<div class="bg-white p-6 rounded-xl shadow-sm hover:shadow-md transition duration-200"> | |
<div class="text-indigo-600 mb-4"> | |
<i class="fas fa-lock text-3xl"></i> | |
</div> | |
<h3 class="text-xl font-semibold mb-2">Secure Processing</h3> | |
<p class="text-gray-500">Your images are processed securely through CosXL's API with no permanent storage.</p> | |
</div> | |
</div> | |
</div> | |
</div> | |
<!-- Footer --> | |
<footer class="bg-white mt-16"> | |
<div class="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8"> | |
<div class="grid grid-cols-2 md:grid-cols-4 gap-8"> | |
<div> | |
<h3 class="text-sm font-semibold text-gray-400 tracking-wider uppercase">Product</h3> | |
<ul class="mt-4 space-y-2"> | |
<li><a href="#" class="text-base text-gray-500 hover:text-indigo-600">Features</a></li> | |
<li><a href="#" class="text-base text-gray-500 hover:text-indigo-600">Pricing</a></li> | |
<li><a href="#" class="text-base text-gray-500 hover:text-indigo-600">API</a></li> | |
</ul> | |
</div> | |
<div> | |
<h3 class="text-sm font-semibold text-gray-400 tracking-wider uppercase">Resources</h3> | |
<ul class="mt-4 space-y-2"> | |
<li><a href="#" class="text-base text-gray-500 hover:text-indigo-600">Documentation</a></li> | |
<li><a href="#" class="text-base text-gray-500 hover:text-indigo-600">Tutorials</a></li> | |
<li><a href="#" class="text-base text-gray-500 hover:text-indigo-600">Blog</a></li> | |
</ul> | |
</div> | |
<div> | |
<h3 class="text-sm font-semibold text-gray-400 tracking-wider uppercase">Company</h3> | |
<ul class="mt-4 space-y-2"> | |
<li><a href="#" class="text-base text-gray-500 hover:text-indigo-600">About</a></li> | |
<li><a href="#" class="text-base text-gray-500 hover:text-indigo-600">Careers</a></li> | |
<li><a href="#" class="text-base text-gray-500 hover:text-indigo-600">Contact</a></li> | |
</ul> | |
</div> | |
<div> | |
<h3 class="text-sm font-semibold text-gray-400 tracking-wider uppercase">Legal</h3> | |
<ul class="mt-4 space-y-2"> | |
<li><a href="#" class="text-base text-gray-500 hover:text-indigo-600">Privacy</a></li> | |
<li><a href="#" class="text-base text-gray-500 hover:text-indigo-600">Terms</a></li> | |
<li><a href="#" class="text-base text-gray-500 hover:text-indigo-600">License</a></li> | |
</ul> | |
</div> | |
</div> | |
<div class="mt-8 border-t border-gray-200 pt-8 flex flex-col md:flex-row justify-between items-center"> | |
<p class="text-gray-500 text-sm">© 2023 RookusAI. All rights reserved.</p> | |
<div class="flex space-x-6 mt-4 md:mt-0"> | |
<a href="#" class="text-gray-400 hover:text-indigo-600"> | |
<i class="fab fa-twitter"></i> | |
</a> | |
<a href="#" class="text-gray-400 hover:text-indigo-600"> | |
<i class="fab fa-instagram"></i> | |
</a> | |
<a href="#" class="text-gray-400 hover:text-indigo-600"> | |
<i class="fab fa-facebook"></i> | |
</a> | |
<a href="#" class="text-gray-400 hover:text-indigo-600"> | |
<i class="fab fa-github"></i> | |
</a> | |
</div> | |
</div> | |
</div> | |
</footer> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// DOM Elements | |
const uploadArea = document.getElementById('upload-area'); | |
const fileInput = document.getElementById('file-input'); | |
const uploadContent = document.getElementById('upload-content'); | |
const previewImage = document.getElementById('preview-image'); | |
const generateBtn = document.getElementById('generate-btn'); | |
const generateIcon = document.getElementById('generate-icon'); | |
const loadingIcon = document.getElementById('loading-icon'); | |
const resultContainer = document.getElementById('result-container'); | |
const emptyState = document.getElementById('empty-state'); | |
const originalImage = document.getElementById('original-image'); | |
const resultImage = document.getElementById('result-image'); | |
const colorOptions = document.querySelectorAll('.color-option'); | |
const colorPrompt = document.getElementById('color-prompt'); | |
const negativePrompt = document.getElementById('negative-prompt'); | |
const guidanceScale = document.getElementById('guidance-scale'); | |
const steps = document.getElementById('steps'); | |
const downloadBtn = document.getElementById('download-btn'); | |
const historySection = document.getElementById('history-section'); | |
const historyGrid = document.getElementById('history-grid'); | |
// API Endpoint | |
const API_URL = 'https://cors-anywhere.herokuapp.com/https://multimodalart-cosxl.hf.space/run/predict'; | |
// Current state | |
let uploadedImage = null; | |
let uploadedFile = null; | |
// Event Listeners | |
uploadArea.addEventListener('click', () => fileInput.click()); | |
fileInput.addEventListener('change', (e) => { | |
const file = e.target.files[0]; | |
if (file) { | |
if (file.size > 5 * 1024 * 1024) { | |
alert('File size exceeds 5MB limit'); | |
return; | |
} | |
uploadedFile = file; | |
const reader = new FileReader(); | |
reader.onload = (event) => { | |
uploadedImage = event.target.result; | |
previewImage.src = uploadedImage; | |
previewImage.classList.remove('hidden'); | |
uploadContent.classList.add('hidden'); | |
uploadArea.classList.add('active'); | |
}; | |
reader.readAsDataURL(file); | |
} | |
}); | |
uploadArea.addEventListener('dragover', (e) => { | |
e.preventDefault(); | |
uploadArea.classList.add('active'); | |
}); | |
uploadArea.addEventListener('dragleave', () => { | |
uploadArea.classList.remove('active'); | |
}); | |
uploadArea.addEventListener('drop', (e) => { | |
e.preventDefault(); | |
uploadArea.classList.remove('active'); | |
const file = e.dataTransfer.files[0]; | |
if (file && file.type.match('image.*')) { | |
if (file.size > 5 * 1024 * 1024) { | |
alert('File size exceeds 5MB limit'); | |
return; | |
} | |
uploadedFile = file; | |
const reader = new FileReader(); | |
reader.onload = (event) => { | |
uploadedImage = event.target.result; | |
previewImage.src = uploadedImage; | |
previewImage.classList.remove('hidden'); | |
uploadContent.classList.add('hidden'); | |
}; | |
reader.readAsDataURL(file); | |
} | |
}); | |
colorOptions.forEach(option => { | |
option.addEventListener('click', () => { | |
document.querySelector('.color-option.selected').classList.remove('selected'); | |
option.classList.add('selected'); | |
colorPrompt.value = option.dataset.prompt; | |
}); | |
}); | |
generateBtn.addEventListener('click', async () => { | |
if (!uploadedImage) { | |
alert('Please upload an image first'); | |
return; | |
} | |
if (!colorPrompt.value.trim()) { | |
alert('Please describe the color change you want'); | |
return; | |
} | |
// Show loading state | |
generateIcon.classList.add('hidden'); | |
loadingIcon.classList.remove('hidden'); | |
generateBtn.disabled = true; | |
try { | |
// Prepare form data according to Gradio's API requirements | |
const formData = new FormData(); | |
formData.append('data', JSON.stringify({ | |
'data': [ | |
await fileToBase64(uploadedFile), // Image | |
colorPrompt.value, // Prompt | |
negativePrompt.value, // Negative prompt | |
Number(guidanceScale.value), // Guidance scale | |
Number(steps.value) // Steps | |
], | |
'fn_index': 0 // Important for Gradio interface | |
})); | |
// API request with proper headers | |
const response = await fetch(API_URL, { | |
method: 'POST', | |
body: formData | |
}); | |
// Handle response errors | |
if (!response.ok) { | |
const errorText = await response.text(); | |
throw new Error(`API Error: ${response.status} - ${errorText}`); | |
} | |
const result = await response.json(); | |
// Handle Gradio response format | |
if (!result.data || !result.data[0]) { | |
throw new Error('Invalid API response format'); | |
} | |
// Display results | |
originalImage.src = URL.createObjectURL(uploadedFile); | |
resultImage.src = `data:image/png;base64,${result.data[0]}`; | |
emptyState.classList.add('hidden'); | |
resultContainer.classList.remove('hidden'); | |
// Add to history | |
addToHistory(URL.createObjectURL(uploadedFile), `data:image/png;base64,${result.data[0]}`); | |
historySection.classList.remove('hidden'); | |
} catch (error) { | |
console.error('API Error:', error); | |
alert(`Error processing image: ${error.message}`); | |
} finally { | |
generateIcon.classList.remove('hidden'); | |
loadingIcon.classList.add('hidden'); | |
generateBtn.disabled = false; | |
} | |
}); | |
// Helper function to convert file to base64 | |
async function fileToBase64(file) { | |
return new Promise((resolve, reject) => { | |
const reader = new FileReader(); | |
reader.readAsDataURL(file); | |
reader.onload = () => resolve(reader.result.split(',')[1]); | |
reader.onerror = error => reject(error); | |
}); | |
} | |
downloadBtn.addEventListener('click', () => { | |
if (resultImage.src) { | |
const link = document.createElement('a'); | |
link.href = resultImage.src; | |
link.download = 'rookusai-recolored-dress.png'; | |
document.body.appendChild(link); | |
link.click(); | |
document.body.removeChild(link); | |
} | |
}); | |
function addToHistory(originalUrl, resultUrl) { | |
const historyItem = document.createElement('div'); | |
historyItem.className = 'history-item bg-white p-2 rounded-lg shadow-sm cursor-pointer'; | |
historyItem.innerHTML = ` | |
<img src="${resultUrl}" class="w-full h-auto rounded" alt="History item"> | |
`; | |
historyItem.addEventListener('click', () => { | |
originalImage.src = originalUrl; | |
resultImage.src = resultUrl; | |
emptyState.classList.add('hidden'); | |
resultContainer.classList.remove('hidden'); | |
}); | |
historyGrid.insertBefore(historyItem, historyGrid.firstChild); | |
if (historyGrid.children.length > 6) { | |
historyGrid.removeChild(historyGrid.lastChild); | |
} | |
} | |
}); | |
</script> | |
</body> | |
</html> |