Spaces:
Running
Running
File size: 15,965 Bytes
5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 e9f7374 5a638e3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 |
// 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 };
} |