Spaces:
Running
Running
| // JavaScript principal pour l'application FaceSwap Magic | |
| // DOM Elements | |
| const originalUpload = document.getElementById('original-upload'); | |
| const targetUpload = document.getElementById('target-upload'); | |
| const originalPreview = document.getElementById('original-preview'); | |
| const targetPreview = document.getElementById('target-preview'); | |
| const originalImage = document.getElementById('original-image'); | |
| const targetImage = document.getElementById('target-image'); | |
| const generateBtn = document.getElementById('generate-btn'); | |
| const resultSection = document.getElementById('result-section'); | |
| const resultImage = document.getElementById('result-image'); | |
| const displayOriginal = document.getElementById('display-original'); | |
| const downloadBtn = document.getElementById('download-btn'); | |
| const shareBtn = document.getElementById('share-btn'); | |
| const tryAgainBtn = document.getElementById('try-again-btn'); | |
| // Sample results for demo purposes (using placeholder API) | |
| const sampleResults = [ | |
| 'http://static.photos/people/640x360/201', | |
| 'http://static.photos/people/640x360/202', | |
| 'http://static.photos/people/640x360/203', | |
| 'http://static.photos/people/640x360/204', | |
| 'http://static.photos/people/640x360/205' | |
| ]; | |
| // State Management | |
| let currentState = { | |
| originalImage: null, | |
| targetImage: null, | |
| generatedResult: null, | |
| isProcessing: false | |
| }; | |
| // Initialize Application | |
| document.addEventListener('DOMContentLoaded', function() { | |
| console.log('FaceSwap Magic initialisé !'); | |
| // Set up event listeners | |
| setupEventListeners(); | |
| // Check for saved state in localStorage | |
| loadSavedState(); | |
| // Initialize tooltips | |
| initializeTooltips(); | |
| // Add animation to hero section | |
| animateHeroSection(); | |
| }); | |
| // Set up all event listeners | |
| function setupEventListeners() { | |
| // File upload handlers | |
| originalUpload.addEventListener('change', (e) => handleFileUpload(e, 'original')); | |
| targetUpload.addEventListener('change', (e) => handleFileUpload(e, 'target')); | |
| // Generate button click | |
| generateBtn.addEventListener('click', generateFaceSwap); | |
| // Result buttons | |
| downloadBtn.addEventListener('click', downloadResult); | |
| shareBtn.addEventListener('click', shareResult); | |
| tryAgainBtn.addEventListener('click', resetApplication); | |
| // Drag and drop functionality | |
| setupDragAndDrop(); | |
| // Range slider updates | |
| setupRangeSliders(); | |
| } | |
| // Handle file upload | |
| function handleFileUpload(event, type) { | |
| const file = event.target.files[0]; | |
| if (!file) return; | |
| if (!file.type.match('image.*')) { | |
| showToast('Veuillez télécharger un fichier image', 'error'); | |
| return; | |
| } | |
| if (file.size > 10 * 1024 * 1024) { // 10MB limit | |
| showToast('La taille du fichier doit être inférieure à 10Mo', 'error'); | |
| return; | |
| } | |
| const reader = new FileReader(); | |
| reader.onload = function(e) { | |
| const imageUrl = e.target.result; | |
| if (type === 'original') { | |
| currentState.originalImage = imageUrl; | |
| originalImage.src = imageUrl; | |
| originalPreview.classList.remove('hidden'); | |
| displayOriginal.src = imageUrl; | |
| } else { | |
| currentState.targetImage = imageUrl; | |
| targetImage.src = imageUrl; | |
| targetPreview.classList.remove('hidden'); | |
| } | |
| // Enable generate button if both images are uploaded | |
| if (currentState.originalImage && currentState.targetImage) { | |
| generateBtn.disabled = false; | |
| generateBtn.classList.remove('opacity-50', 'cursor-not-allowed'); | |
| } | |
| showToast(`${type === 'original' ? 'Originale' : 'Cible'} image téléchargée avec succès !`, 'success'); | |
| // Save to localStorage | |
| saveState(); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| // Setup drag and drop functionality | |
| function setupDragAndDrop() { | |
| const uploadAreas = document.querySelectorAll('.border-dashed'); | |
| uploadAreas.forEach(area => { | |
| area.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| area.classList.add('border-primary', 'bg-primary/5'); | |
| }); | |
| area.addEventListener('dragleave', () => { | |
| area.classList.remove('border-primary', 'bg-primary/5'); | |
| }); | |
| area.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| area.classList.remove('border-primary', 'bg-primary/5'); | |
| const file = e.dataTransfer.files[0]; | |
| if (file && file.type.match('image.*')) { | |
| // Simulate file input change | |
| const isOriginal = area.closest('.space-y-6').querySelector('h3').textContent.includes('Original'); | |
| const input = isOriginal ? originalUpload : targetUpload; | |
| // Create a new FileList (simulated) | |
| const dataTransfer = new DataTransfer(); | |
| dataTransfer.items.add(file); | |
| input.files = dataTransfer.files; | |
| // Trigger change event | |
| input.dispatchEvent(new Event('change')); | |
| } | |
| }); | |
| }); | |
| } | |
| // Setup range sliders | |
| function setupRangeSliders() { | |
| const sliders = document.querySelectorAll('input[type="range"]'); | |
| sliders.forEach(slider => { | |
| slider.addEventListener('input', function() { | |
| const value = this.value; | |
| const label = this.previousElementSibling; | |
| // Update label with percentage | |
| if (label && label.classList.contains('text-gray-700')) { | |
| const labelText = label.textContent.split(':')[0]; | |
| label.textContent = `${labelText}: ${value}%`; | |
| } | |
| }); | |
| }); | |
| } | |
| // Generate face swap (simulated) | |
| function generateFaceSwap() { | |
| if (!currentState.originalImage || !currentState.targetImage) { | |
| showToast('Veuillez télécharger les deux images d\'abord !', 'error'); | |
| return; | |
| } | |
| // Show loading state | |
| currentState.isProcessing = true; | |
| generateBtn.disabled = true; | |
| generateBtn.innerHTML = ` | |
| <div class="spinner"></div> | |
| Traitement en cours... | |
| `; | |
| // Simulate API call delay | |
| setTimeout(() => { | |
| // Get random sample result for demo | |
| const randomIndex = Math.floor(Math.random() * sampleResults.length); | |
| currentState.generatedResult = sampleResults[randomIndex]; | |
| resultImage.src = currentState.generatedResult; | |
| // Show result section | |
| resultSection.classList.remove('hidden'); | |
| resultSection.scrollIntoView({ behavior: 'smooth' }); | |
| // Reset button | |
| generateBtn.disabled = false; | |
| generateBtn.innerHTML = ` | |
| <i data-feather="zap"></i> | |
| Générer l'Échange | |
| `; | |
| feather.replace(); | |
| currentState.isProcessing = false; | |
| showToast('Échange de visage généré avec succès !', 'success'); | |
| // Save state | |
| saveState(); | |
| }, 3000); | |
| } | |
| // Download result | |
| function downloadResult() { | |
| if (!currentState.generatedResult) { | |
| showToast('Aucun résultat à télécharger !', 'error'); | |
| return; | |
| } | |
| // In a real app, this would download the actual generated image | |
| // For demo, we'll create a download link | |
| const link = document.createElement('a'); | |
| link.href = currentState.generatedResult; | |
| link.download = `faceswap-${Date.now()}.jpg`; | |
| document.body.appendChild(link); | |
| link.click(); | |
| document.body.removeChild(link); | |
| showToast('Téléchargement démarré !', 'success'); | |
| } | |
| // Share result | |
| function shareResult() { | |
| if (!currentState.generatedResult) { | |
| showToast('Aucun résultat à partager !', 'error'); | |
| return; | |
| } | |
| if (navigator.share) { | |
| navigator.share({ | |
| title: 'My AI FaceSwap Creation', | |
| text: 'Check out this awesome face swap I made with FaceSwap Magic!', | |
| url: window.location.href | |
| }) | |
| .then(() => showToast('Partagé avec succès !', 'success')) | |
| .catch(() => showToast('Partage annulé', 'info')); | |
| } else { | |
| // Fallback: copy to clipboard | |
| navigator.clipboard.writeText(window.location.href) | |
| .then(() => showToast('Lien copié dans le presse-papier !', 'success')) | |
| .catch(() => showToast('Échec de la copie du lien', 'error')); | |
| } | |
| } | |
| // Reset application | |
| function resetApplication() { | |
| // Reset file inputs | |
| originalUpload.value = ''; | |
| targetUpload.value = ''; | |
| // Hide previews | |
| originalPreview.classList.add('hidden'); | |
| targetPreview.classList.add('hidden'); | |
| resultSection.classList.add('hidden'); | |
| // Reset images | |
| originalImage.src = ''; | |
| targetImage.src = ''; | |
| resultImage.src = ''; | |
| // Reset state | |
| currentState = { | |
| originalImage: null, | |
| targetImage: null, | |
| generatedResult: null, | |
| isProcessing: false | |
| }; | |
| // Reset range sliders | |
| document.querySelectorAll('input[type="range"]').forEach(slider => { | |
| slider.value = slider.id.includes('alignment') ? 50 : | |
| slider.id.includes('skin') ? 75 : 60; | |
| slider.dispatchEvent(new Event('input')); | |
| }); | |
| // Scroll to upload section | |
| document.getElementById('upload-section').scrollIntoView({ behavior: 'smooth' }); | |
| showToast('Prêt pour un nouvel échange de visage !', 'info'); | |
| // Clear localStorage | |
| localStorage.removeItem('faceswapState'); | |
| } | |
| // Show toast notification | |
| function showToast(message, type = 'info') { | |
| // Remove existing toasts | |
| document.querySelectorAll('.toast').forEach(toast => toast.remove()); | |
| // Create toast element | |
| const toast = document.createElement('div'); | |
| toast.className = 'toast'; | |
| // Set icon based on type | |
| let icon = 'info'; | |
| let bgColor = 'bg-blue-100'; | |
| let borderColor = 'border-blue-500'; | |
| if (type === 'success') { | |
| icon = 'check-circle'; | |
| bgColor = 'bg-green-100'; | |
| borderColor = 'border-green-500'; | |
| } else if (type === 'error') { | |
| icon = 'alert-circle'; | |
| bgColor = 'bg-red-100'; | |
| borderColor = 'border-red-500'; | |
| } else if (type === 'warning') { | |
| icon = 'alert-triangle'; | |
| bgColor = 'bg-yellow-100'; | |
| borderColor = 'border-yellow-500'; | |
| } | |
| toast.innerHTML = ` | |
| <div class="flex items-center gap-3"> | |
| <i data-feather="${icon}" class="${type === 'success' ? 'text-green-600' : type === 'error' ? 'text-red-600' : type === 'warning' ? 'text-yellow-600' : 'text-blue-600'}"></i> | |
| <span>${message}</span> | |
| </div> | |
| `; | |
| // Add classes | |
| toast.classList.add(bgColor, borderColor, 'border-l-4'); | |
| // Add to DOM | |
| document.body.appendChild(toast); | |
| // Show toast | |
| setTimeout(() => toast.classList.add('show'), 10); | |
| // Update feather icons | |
| feather.replace(); | |
| // Remove toast after 5 seconds | |
| setTimeout(() => { | |
| toast.classList.remove('show'); | |
| setTimeout(() => toast.remove(), 300); | |
| }, 5000); | |
| } | |
| // Save state to localStorage | |
| function saveState() { | |
| const stateToSave = { | |
| originalImage: currentState.originalImage, | |
| targetImage: currentState.targetImage, | |
| generatedResult: currentState.generatedResult | |
| }; | |
| localStorage.setItem('faceswapState', JSON.stringify(stateToSave)); | |
| } | |
| // Load saved state from localStorage | |
| function loadSavedState() { | |
| try { | |
| const saved = localStorage.getItem('faceswapState'); | |
| if (saved) { | |
| const state = JSON.parse(saved); | |
| if (state.originalImage) { | |
| currentState.originalImage = state.originalImage; | |
| originalImage.src = state.originalImage; | |
| originalPreview.classList.remove('hidden'); | |
| displayOriginal.src = state.originalImage; | |
| } | |
| if (state.targetImage) { | |
| currentState.targetImage = state.targetImage; | |
| targetImage.src = state.targetImage; | |
| targetPreview.classList.remove('hidden'); | |
| } | |
| if (state.generatedResult) { | |
| currentState.generatedResult = state.generatedResult; | |
| resultImage.src = state.generatedResult; | |
| resultSection.classList.remove('hidden'); | |
| } | |
| if (currentState.originalImage && currentState.targetImage) { | |
| generateBtn.disabled = false; | |
| generateBtn.classList.remove('opacity-50', 'cursor-not-allowed'); | |
| } | |
| } | |
| } catch (error) { | |
| console.error('Error loading saved state:', error); | |
| localStorage.removeItem('faceswapState'); | |
| } | |
| } | |
| // Initialize tooltips | |
| function initializeTooltips() { | |
| // Add tooltips to range sliders | |
| const sliders = document.querySelectorAll('input[type="range"]'); | |
| sliders.forEach(slider => { | |
| slider.addEventListener('mouseenter', function() { | |
| const tooltip = document.createElement('div'); | |
| tooltip.className = 'absolute -top-8 bg-dark text-white px-2 py-1 rounded text-xs'; | |
| tooltip.textContent = `${this.value}%`; | |
| tooltip.style.left = `${(this.value / 100) * this.offsetWidth - 15}px`; | |
| this.parentElement.style.position = 'relative'; | |
| this.parentElement.appendChild(tooltip); | |
| this.addEventListener('mousemove', updateTooltip); | |
| this.addEventListener('mouseleave', () => tooltip.remove()); | |
| function updateTooltip() { | |
| tooltip.textContent = `${this.value}%`; | |
| tooltip.style.left = `${(this.value / 100) * this.offsetWidth - 15}px`; | |
| } | |
| }); | |
| }); | |
| } | |
| // Animate hero section elements | |
| function animateHeroSection() { | |
| const heroTitle = document.querySelector('h1'); | |
| const heroText = document.querySelector('section.mb-16 p'); | |
| const heroButtons = document.querySelector('section.mb-16 .flex'); | |
| if (heroTitle) heroTitle.classList.add('animate-fadeInUp'); | |
| if (heroText) { | |
| heroText.style.animationDelay = '0.2s'; | |
| heroText.classList.add('animate-fadeInUp'); | |
| } | |
| if (heroButtons) { | |
| heroButtons.style.animationDelay = '0.4s'; | |
| heroButtons.classList.add('animate-fadeInUp'); | |
| } | |
| } | |
| // API Integration (placeholder for real implementation) | |
| class FaceSwapAPI { | |
| static async generateFaceSwap(originalImage, targetImage, options = {}) { | |
| // In a real implementation, this would call your backend API | |
| // For demo, we return a mock response | |
| return new Promise((resolve) => { | |
| setTimeout(() => { | |
| resolve({ | |
| success: true, | |
| resultUrl: sampleResults[Math.floor(Math.random() * sampleResults.length)], | |
| processingTime: '2.8s', | |
| confidenceScore: Math.random() * 30 + 70 // 70-100% | |
| }); | |
| }, 3000); | |
| }); | |
| } | |
| static async getGalleryImages(page = 1, limit = 9) { | |
| // Mock gallery API call | |
| const images = []; | |
| for (let i = 0; i < limit; i++) { | |
| images.push({ | |
| id: i + 1, | |
| url: `http://static.photos/people/640x360/${200 + i}`, | |
| title: `FaceSwap Example ${i + 1}`, | |
| author: `User${Math.floor(Math.random() * 1000)}`, | |
| likes: Math.floor(Math.random() * 1000) | |
| }); | |
| } | |
| return new Promise((resolve) => { | |
| setTimeout(() => resolve(images), 500); | |
| }); | |
| } | |
| } | |
| // Export for potential module usage | |
| if (typeof module !== 'undefined' && module.exports) { | |
| module.exports = { FaceSwapAPI }; | |
| } |