Spaces:
Sleeping
Sleeping
| document.addEventListener('DOMContentLoaded', function() { | |
| // Initialize AOS (Animate On Scroll) | |
| AOS.init({ | |
| duration: 800, | |
| easing: 'ease-in-out', | |
| once: false | |
| }); | |
| // Elements | |
| const loadingScreen = document.getElementById('loading-screen'); | |
| const appContainer = document.getElementById('app-container'); | |
| const themeToggle = document.getElementById('theme-toggle'); | |
| const menuToggle = document.getElementById('menu-toggle'); | |
| const sidebarClose = document.getElementById('sidebar-close'); | |
| const sidebar = document.getElementById('sidebar'); | |
| const generationForm = document.getElementById('generation-form'); | |
| const promptInput = document.getElementById('prompt'); | |
| const negativePromptInput = document.getElementById('negative-prompt'); | |
| const widthSelect = document.getElementById('width'); | |
| const heightSelect = document.getElementById('height'); | |
| const seedInput = document.getElementById('seed'); | |
| const randomSeedBtn = document.getElementById('random-seed'); | |
| const tilingCheckbox = document.getElementById('tiling'); | |
| const advancedSettingsBtn = document.getElementById('advanced-settings-btn'); | |
| const upgradePromptBtn = document.getElementById('upgrade-prompt-btn'); | |
| const imageDisplay = document.getElementById('image-display'); | |
| const historyGallery = document.getElementById('history-gallery'); | |
| const latestCreation = document.getElementById('latest-creation'); | |
| // Image Controls | |
| const downloadCurrentBtn = document.getElementById('download-current'); | |
| const shareCurrentBtn = document.getElementById('share-current'); | |
| const favoriteCurrentBtn = document.getElementById('favorite-current'); | |
| // Modals | |
| const advancedSettingsModal = document.getElementById('advanced-settings-modal'); | |
| const promptModal = document.getElementById('prompt-modal'); | |
| const imageViewerModal = document.getElementById('image-viewer-modal'); | |
| const shareModal = document.getElementById('share-modal'); | |
| // Advanced Settings Elements | |
| const subseedInput = document.getElementById('subseed'); | |
| const attentionInput = document.getElementById('attention'); | |
| const attentionValue = document.getElementById('attention-value'); | |
| const referenceImageInput = document.getElementById('reference-image'); | |
| const referencePreview = document.getElementById('reference-preview'); | |
| const referencePreviewImg = document.getElementById('reference-preview-img'); | |
| const removeReferenceBtn = document.getElementById('remove-reference'); | |
| const referenceTypeSelect = document.getElementById('reference-type'); | |
| const referenceStrengthInput = document.getElementById('reference-strength'); | |
| const referenceStrengthValue = document.getElementById('reference-strength-value'); | |
| const resetAdvancedSettingsBtn = document.getElementById('reset-advanced-settings'); | |
| const saveAdvancedSettingsBtn = document.getElementById('save-advanced-settings'); | |
| // Prompt Modal Elements | |
| const originalPromptInput = document.getElementById('original-prompt'); | |
| const enhancedPromptInput = document.getElementById('enhanced-prompt'); | |
| const cancelPromptEnhancementBtn = document.getElementById('cancel-prompt-enhancement'); | |
| const useEnhancedPromptBtn = document.getElementById('use-enhanced-prompt'); | |
| // Image Viewer Modal Elements | |
| const viewerImage = document.getElementById('viewer-image'); | |
| const viewerPrompt = document.getElementById('viewer-prompt'); | |
| const viewerNegativePrompt = document.getElementById('viewer-negative-prompt'); | |
| const viewerSize = document.getElementById('viewer-size'); | |
| const viewerSeed = document.getElementById('viewer-seed'); | |
| const viewerTimestamp = document.getElementById('viewer-timestamp'); | |
| const downloadImageBtn = document.getElementById('download-image'); | |
| const shareImageBtn = document.getElementById('share-image'); | |
| const regenerateImageBtn = document.getElementById('regenerate-image'); | |
| const editPromptImageBtn = document.getElementById('edit-prompt-image'); | |
| // Share Modal | |
| const sharePreviewImg = document.getElementById('share-preview-img'); | |
| const shareLinkInput = document.getElementById('share-link-input'); | |
| const copyLinkBtn = document.getElementById('copy-link'); | |
| const shareBtns = document.querySelectorAll('.share-btn'); | |
| // Global variables | |
| let currentImage = null; | |
| let referenceImageData = null; | |
| let isGenerating = false; | |
| const defaultAdvancedSettings = { | |
| subseed: 0, | |
| attention: 0.5, | |
| referenceImage: null, | |
| referenceType: 'style', | |
| referenceStrength: 0.5 | |
| }; | |
| let advancedSettings = {...defaultAdvancedSettings}; | |
| let favoriteImages = []; | |
| // Initialize | |
| initializeApp(); | |
| function initializeApp() { | |
| // Slower loading screen for a better experience | |
| setTimeout(() => { | |
| loadingScreen.style.opacity = '0'; | |
| setTimeout(() => { | |
| loadingScreen.style.visibility = 'hidden'; | |
| appContainer.classList.remove('hidden'); | |
| // Trigger AOS animations on initial load | |
| AOS.refresh(); | |
| }, 800); | |
| }, 3000); | |
| // Set random seed initially | |
| seedInput.value = Math.floor(Math.random() * 999999); | |
| // Load theme preference | |
| loadThemePreference(); | |
| // Load image history | |
| loadImageHistory(); | |
| // Load favorites | |
| loadFavorites(); | |
| // Event listeners | |
| setupEventListeners(); | |
| } | |
| function setupEventListeners() { | |
| // Theme toggle | |
| themeToggle.addEventListener('click', toggleTheme); | |
| // Mobile menu | |
| menuToggle.addEventListener('click', () => sidebar.classList.add('active')); | |
| sidebarClose.addEventListener('click', () => sidebar.classList.remove('active')); | |
| // Generate random seed | |
| randomSeedBtn.addEventListener('click', generateRandomSeed); | |
| // Advanced settings modal | |
| advancedSettingsBtn.addEventListener('click', openAdvancedSettingsModal); | |
| // Handle sliders in advanced settings | |
| attentionInput.addEventListener('input', () => { | |
| attentionValue.textContent = attentionInput.value; | |
| advancedSettings.attention = parseFloat(attentionInput.value); | |
| }); | |
| referenceStrengthInput.addEventListener('input', () => { | |
| referenceStrengthValue.textContent = referenceStrengthInput.value; | |
| advancedSettings.referenceStrength = parseFloat(referenceStrengthInput.value); | |
| }); | |
| // Reference image handling | |
| referenceImageInput.addEventListener('change', handleReferenceImageUpload); | |
| removeReferenceBtn.addEventListener('click', removeReferenceImage); | |
| referenceTypeSelect.addEventListener('change', () => { | |
| advancedSettings.referenceType = referenceTypeSelect.value; | |
| }); | |
| // Advanced settings buttons | |
| resetAdvancedSettingsBtn.addEventListener('click', resetAdvancedSettings); | |
| saveAdvancedSettingsBtn.addEventListener('click', () => closeModal(advancedSettingsModal)); | |
| // Upgrade prompt button | |
| upgradePromptBtn.addEventListener('click', handleUpgradePrompt); | |
| // Prompt modal buttons | |
| cancelPromptEnhancementBtn.addEventListener('click', () => closeModal(promptModal)); | |
| useEnhancedPromptBtn.addEventListener('click', applyEnhancedPrompt); | |
| // Image controls | |
| downloadCurrentBtn.addEventListener('click', downloadCurrentImage); | |
| shareCurrentBtn.addEventListener('click', openShareModal); | |
| favoriteCurrentBtn.addEventListener('click', toggleFavoriteCurrentImage); | |
| // Image viewer modal buttons | |
| downloadImageBtn.addEventListener('click', downloadCurrentImage); | |
| shareImageBtn.addEventListener('click', openShareModal); | |
| regenerateImageBtn.addEventListener('click', regenerateSimilarImage); | |
| editPromptImageBtn.addEventListener('click', editAndCreateNew); | |
| // Share modal buttons | |
| copyLinkBtn.addEventListener('click', copyShareLink); | |
| shareBtns.forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| shareToSocialMedia(this.className.split(' ')[1]); | |
| }); | |
| }); | |
| // Close modals | |
| document.querySelectorAll('.close-modal').forEach(btn => { | |
| btn.addEventListener('click', () => { | |
| closeAllModals(); | |
| }); | |
| }); | |
| // Form submission | |
| generationForm.addEventListener('submit', handleImageGeneration); | |
| // Escape key for modals | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key === 'Escape') { | |
| closeAllModals(); | |
| } | |
| }); | |
| // Disable context menu on images (for better UX) | |
| document.addEventListener('contextmenu', function(e) { | |
| if (e.target.tagName === 'IMG') { | |
| e.preventDefault(); | |
| return false; | |
| } | |
| }); | |
| } | |
| function closeAllModals() { | |
| closeModal(advancedSettingsModal); | |
| closeModal(promptModal); | |
| closeModal(imageViewerModal); | |
| closeModal(shareModal); | |
| } | |
| function loadThemePreference() { | |
| const isDarkMode = localStorage.getItem('darkMode') !== 'false'; // Default to dark | |
| setTheme(isDarkMode); | |
| } | |
| function toggleTheme() { | |
| const isDarkMode = !document.body.classList.contains('light-theme'); | |
| setTheme(!isDarkMode); | |
| localStorage.setItem('darkMode', !isDarkMode); | |
| } | |
| function setTheme(isDarkMode) { | |
| if (isDarkMode) { | |
| document.body.classList.remove('light-theme'); | |
| themeToggle.innerHTML = '<i class="fas fa-sun"></i>'; | |
| } else { | |
| document.body.classList.add('light-theme'); | |
| themeToggle.innerHTML = '<i class="fas fa-moon"></i>'; | |
| } | |
| } | |
| function generateRandomSeed() { | |
| const newSeed = Math.floor(Math.random() * 999999); | |
| seedInput.value = newSeed; | |
| // Add pulse animation to seed input | |
| seedInput.classList.add('pulse-effect'); | |
| setTimeout(() => { | |
| seedInput.classList.remove('pulse-effect'); | |
| }, 1000); | |
| showToast('Seed Updated', `Random seed generated: ${newSeed}`, 'info'); | |
| } | |
| function openAdvancedSettingsModal() { | |
| // Update modal with current settings | |
| subseedInput.value = advancedSettings.subseed; | |
| attentionInput.value = advancedSettings.attention; | |
| attentionValue.textContent = advancedSettings.attention; | |
| referenceTypeSelect.value = advancedSettings.referenceType; | |
| referenceStrengthInput.value = advancedSettings.referenceStrength; | |
| referenceStrengthValue.textContent = advancedSettings.referenceStrength; | |
| // Update reference image preview if exists | |
| if (referenceImageData) { | |
| referencePreviewImg.src = referenceImageData; | |
| referencePreview.classList.remove('hidden'); | |
| } else { | |
| referencePreview.classList.add('hidden'); | |
| } | |
| openModal(advancedSettingsModal); | |
| } | |
| function resetAdvancedSettings() { | |
| advancedSettings = {...defaultAdvancedSettings}; | |
| subseedInput.value = defaultAdvancedSettings.subseed; | |
| attentionInput.value = defaultAdvancedSettings.attention; | |
| attentionValue.textContent = defaultAdvancedSettings.attention; | |
| referenceTypeSelect.value = defaultAdvancedSettings.referenceType; | |
| referenceStrengthInput.value = defaultAdvancedSettings.referenceStrength; | |
| referenceStrengthValue.textContent = defaultAdvancedSettings.referenceStrength; | |
| removeReferenceImage(); | |
| showToast('Settings Reset', 'Advanced settings have been reset to defaults', 'info'); | |
| } | |
| function handleReferenceImageUpload(e) { | |
| const file = e.target.files[0]; | |
| if (file) { | |
| const reader = new FileReader(); | |
| reader.onload = function(event) { | |
| referenceImageData = event.target.result; | |
| referencePreviewImg.src = referenceImageData; | |
| referencePreview.classList.remove('hidden'); | |
| advancedSettings.referenceImage = referenceImageData; | |
| showToast('Reference Added', 'Reference image uploaded successfully', 'success'); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| } | |
| function removeReferenceImage() { | |
| referenceImageData = null; | |
| advancedSettings.referenceImage = null; | |
| referenceImageInput.value = ''; | |
| referencePreview.classList.add('hidden'); | |
| } | |
| async function handleUpgradePrompt() { | |
| const prompt = promptInput.value.trim(); | |
| if (!prompt) { | |
| showToast('Error', 'Please enter a prompt to enhance', 'error'); | |
| return; | |
| } | |
| showToast('Processing', 'Enhancing your prompt with AI...', 'info'); | |
| try { | |
| const response = await fetch('/api/upgrade-prompt', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ prompt }) | |
| }); | |
| const data = await response.json(); | |
| if (data.error) { | |
| throw new Error(data.error); | |
| } | |
| // Populate prompt modal | |
| originalPromptInput.value = prompt; | |
| enhancedPromptInput.value = data.upgradedPrompt || prompt; | |
| // Open modal | |
| openModal(promptModal); | |
| } catch (error) { | |
| console.error('Error upgrading prompt:', error); | |
| showToast('Error', error.message || 'Failed to enhance prompt. Please try again.', 'error'); | |
| } | |
| } | |
| function applyEnhancedPrompt() { | |
| const enhancedPrompt = enhancedPromptInput.value.trim(); | |
| if (enhancedPrompt) { | |
| promptInput.value = enhancedPrompt; | |
| showToast('Applied', 'Enhanced prompt applied successfully', 'success'); | |
| } | |
| closeModal(promptModal); | |
| } | |
| async function handleImageGeneration(e) { | |
| e.preventDefault(); | |
| if (isGenerating) { | |
| showToast('In Progress', 'An image is already being generated', 'warning'); | |
| return; | |
| } | |
| const prompt = promptInput.value.trim(); | |
| if (!prompt) { | |
| showToast('Error', 'Please enter a prompt', 'error'); | |
| return; | |
| } | |
| // Collect parameters | |
| const params = { | |
| prompt: prompt, | |
| seed: parseInt(seedInput.value) || Math.floor(Math.random() * 999999), | |
| subseed: parseInt(subseedInput.value) || 0, | |
| attention: parseFloat(attentionInput.value) || 0.5, | |
| width: parseInt(widthSelect.value) || 768, | |
| height: parseInt(heightSelect.value) || 768, | |
| tiling: tilingCheckbox.checked, | |
| negative_prompt: negativePromptInput.value.trim(), | |
| reference_image: referenceImageData, | |
| reference_image_type: referenceImageData ? referenceTypeSelect.value : null, | |
| reference_strength: referenceImageData ? parseFloat(referenceStrengthInput.value) : 0.5 | |
| }; | |
| isGenerating = true; | |
| showGeneratingUI(); | |
| try { | |
| // Generate the image | |
| const taskResponse = await fetch('/api/generate', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify(params) | |
| }); | |
| const taskData = await taskResponse.json(); | |
| if (taskData.error) { | |
| throw new Error(taskData.error); | |
| } | |
| const taskId = taskData.taskId; | |
| // Poll for completion | |
| await pollImageProgress(taskId, params); | |
| } catch (error) { | |
| console.error('Error generating image:', error); | |
| showToast('Error', error.message || 'Failed to generate image. Please try again.', 'error'); | |
| hideGeneratingUI(); | |
| isGenerating = false; | |
| } | |
| } | |
| async function pollImageProgress(taskId, originalParams) { | |
| // Start polling | |
| const maxAttempts = 30; // 60 seconds (2s per poll) | |
| let attempt = 0; | |
| const poll = async () => { | |
| try { | |
| const response = await fetch('/api/poll', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify({ taskId }) | |
| }); | |
| const data = await response.json(); | |
| if (data.error) { | |
| throw new Error(data.error); | |
| } | |
| if (data.final_image_url) { | |
| // Image is ready | |
| const imageUrl = data.final_image_url; | |
| // Create image object | |
| currentImage = { | |
| imageUrl: imageUrl, | |
| prompt: originalParams.prompt, | |
| negativePrompt: originalParams.negative_prompt, | |
| seed: originalParams.seed, | |
| width: originalParams.width, | |
| height: originalParams.height, | |
| timestamp: new Date().toISOString() | |
| }; | |
| // Display the image | |
| displayGeneratedImage(currentImage); | |
| // Save to history | |
| saveImageToHistory(currentImage); | |
| return; | |
| } | |
| if (data.status === 'done' && !data.final_image_url) { | |
| throw new Error('Image generation completed but no image was returned.'); | |
| } | |
| // Continue polling | |
| if (attempt < maxAttempts) { | |
| attempt++; | |
| setTimeout(poll, 2000); | |
| } else { | |
| throw new Error('Image generation timed out. Please try again.'); | |
| } | |
| } catch (error) { | |
| console.error('Error polling progress:', error); | |
| showToast('Error', error.message || 'Failed to generate image. Please try again.', 'error'); | |
| hideGeneratingUI(); | |
| isGenerating = false; | |
| } | |
| }; | |
| await poll(); | |
| } | |
| function showGeneratingUI() { | |
| // Show loading state in image display | |
| imageDisplay.innerHTML = ` | |
| <div class="loading-shimmer"></div> | |
| <div class="loading-indicator"> | |
| <div class="generation-status"> | |
| <div class="lds-ellipsis"><div></div><div></div><div></div><div></div></div> | |
| <p>Creating your masterpiece...</p> | |
| </div> | |
| </div> | |
| `; | |
| // Update generate button | |
| const generateBtn = document.getElementById('generate-btn'); | |
| generateBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Creating...'; | |
| generateBtn.disabled = true; | |
| // Disable sidebar controls | |
| advancedSettingsBtn.disabled = true; | |
| upgradePromptBtn.disabled = true; | |
| } | |
| function hideGeneratingUI() { | |
| // Enable form controls | |
| const generateBtn = document.getElementById('generate-btn'); | |
| generateBtn.innerHTML = '<i class="fas fa-bolt"></i> Create Image'; | |
| generateBtn.disabled = false; | |
| advancedSettingsBtn.disabled = false; | |
| upgradePromptBtn.disabled = false; | |
| // If no image was generated, restore placeholder | |
| if (!currentImage) { | |
| imageDisplay.innerHTML = ` | |
| <div class="placeholder-wrapper"> | |
| <div class="placeholder-content"> | |
| <i class="fas fa-image placeholder-icon pulse-slow"></i> | |
| <p>Your masterpiece will appear here</p> | |
| <span class="helper-tip">Use the form to create something amazing</span> | |
| </div> | |
| </div> | |
| `; | |
| } | |
| isGenerating = false; | |
| } | |
| function displayGeneratedImage(imageData) { | |
| // Update image display | |
| imageDisplay.innerHTML = ` | |
| <img src="${imageData.imageUrl}" alt="Generated Image" class="display-image" /> | |
| `; | |
| // Update latest creation | |
| updateLatestCreation(imageData); | |
| // Enable image controls | |
| downloadCurrentBtn.disabled = false; | |
| shareCurrentBtn.disabled = false; | |
| favoriteCurrentBtn.disabled = false; | |
| // Update favorite button state | |
| updateFavoriteButtonState(); | |
| hideGeneratingUI(); | |
| // Show success message | |
| showToast('Success', 'Your creation is ready!', 'success'); | |
| } | |
| function updateLatestCreation(imageData) { | |
| latestCreation.innerHTML = ` | |
| <img src="${imageData.imageUrl}" alt="Latest Creation" /> | |
| `; | |
| latestCreation.addEventListener('click', () => { | |
| openImageViewer(imageData); | |
| }); | |
| } | |
| function openImageViewer(imageData) { | |
| // Set current image | |
| currentImage = imageData; | |
| // Populate viewer details | |
| viewerImage.src = imageData.imageUrl; | |
| viewerPrompt.textContent = imageData.prompt; | |
| viewerNegativePrompt.textContent = imageData.negativePrompt || 'None'; | |
| viewerSize.textContent = `${imageData.width} × ${imageData.height}`; | |
| viewerSeed.textContent = imageData.seed; | |
| // Format date | |
| const date = new Date(imageData.timestamp); | |
| const formattedDate = date.toLocaleString(); | |
| viewerTimestamp.textContent = formattedDate; | |
| // Open modal | |
| openModal(imageViewerModal); | |
| } | |
| function regenerateSimilarImage(imageData) { | |
| // Close modal | |
| closeModal(imageViewerModal); | |
| // Fill form with same parameters | |
| promptInput.value = currentImage.prompt; | |
| negativePromptInput.value = currentImage.negativePrompt || ''; | |
| // Select closest dimensions in dropdowns | |
| selectClosestOption(widthSelect, currentImage.width); | |
| selectClosestOption(heightSelect, currentImage.height); | |
| // Use same seed | |
| seedInput.value = currentImage.seed; | |
| // Scroll to form | |
| sidebar.scrollIntoView({ behavior: 'smooth' }); | |
| // Show toast | |
| showToast('Ready to Regenerate', 'Parameters loaded from selected image', 'info'); | |
| } | |
| function editAndCreateNew() { | |
| // Close modal | |
| closeModal(imageViewerModal); | |
| // Fill form with same parameters but focus on prompt | |
| promptInput.value = currentImage.prompt; | |
| negativePromptInput.value = currentImage.negativePrompt || ''; | |
| // Select closest dimensions in dropdowns | |
| selectClosestOption(widthSelect, currentImage.width); | |
| selectClosestOption(heightSelect, currentImage.height); | |
| // Generate new seed for variation | |
| generateRandomSeed(); | |
| // Scroll to form and focus on prompt | |
| sidebar.scrollIntoView({ behavior: 'smooth' }); | |
| promptInput.focus(); | |
| // Show toast | |
| showToast('Ready to Edit', 'Edit the prompt to create a new variation', 'info'); | |
| } | |
| function selectClosestOption(selectElement, value) { | |
| let closestOption = null; | |
| let closestDiff = Infinity; | |
| // Find the closest option | |
| for (let i = 0; i < selectElement.options.length; i++) { | |
| const optionValue = parseInt(selectElement.options[i].value); | |
| const diff = Math.abs(optionValue - value); | |
| if (diff < closestDiff) { | |
| closestDiff = diff; | |
| closestOption = i; | |
| } | |
| } | |
| if (closestOption !== null) { | |
| selectElement.selectedIndex = closestOption; | |
| } | |
| } | |
| function downloadCurrentImage() { | |
| if (currentImage) { | |
| downloadImage(currentImage.imageUrl); | |
| } | |
| } | |
| function downloadImage(url) { | |
| // Create an anchor element | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| // Set the download attribute with a filename | |
| const filename = 'adarshkumar-creation-' + Date.now() + '.png'; | |
| a.download = filename; | |
| // Append to the body, click, and remove | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| showToast('Downloaded', 'Image saved to your device', 'success'); | |
| } | |
| function openShareModal() { | |
| if (!currentImage) return; | |
| // Set share preview image | |
| sharePreviewImg.src = currentImage.imageUrl; | |
| // Set share link | |
| shareLinkInput.value = currentImage.imageUrl; | |
| openModal(shareModal); | |
| } | |
| function copyShareLink() { | |
| shareLinkInput.select(); | |
| document.execCommand('copy'); | |
| showToast('Copied', 'Link copied to clipboard', 'success'); | |
| } | |
| function shareToSocialMedia(platform) { | |
| if (!currentImage) return; | |
| const url = encodeURIComponent(currentImage.imageUrl); | |
| const text = encodeURIComponent(`Check out this AI image I created: "${currentImage.prompt.substring(0, 50)}..."`); | |
| let shareUrl = ''; | |
| switch (platform) { | |
| case 'facebook': | |
| shareUrl = `https://www.facebook.com/sharer/sharer.php?u=${url}`; | |
| break; | |
| case 'twitter': | |
| shareUrl = `https://twitter.com/intent/tweet?text=${text}&url=${url}`; | |
| break; | |
| case 'pinterest': | |
| shareUrl = `https://pinterest.com/pin/create/button/?url=${url}&media=${url}&description=${text}`; | |
| break; | |
| case 'whatsapp': | |
| shareUrl = `https://api.whatsapp.com/send?text=${text} ${url}`; | |
| break; | |
| } | |
| if (shareUrl) { | |
| window.open(shareUrl, '_blank'); | |
| } | |
| } | |
| function toggleFavoriteCurrentImage() { | |
| if (!currentImage) return; | |
| const index = favoriteImages.findIndex(img => img.imageUrl === currentImage.imageUrl); | |
| if (index === -1) { | |
| // Add to favorites | |
| favoriteImages.push(currentImage); | |
| showToast('Added to Favorites', 'Image saved to your favorites', 'success'); | |
| } else { | |
| // Remove from favorites | |
| favoriteImages.splice(index, 1); | |
| showToast('Removed from Favorites', 'Image removed from your favorites', 'info'); | |
| } | |
| // Save favorites to localStorage | |
| localStorage.setItem('favoriteImages', JSON.stringify(favoriteImages)); | |
| // Update button state | |
| updateFavoriteButtonState(); | |
| } | |
| function updateFavoriteButtonState() { | |
| if (!currentImage) return; | |
| const isFavorite = favoriteImages.some(img => img.imageUrl === currentImage.imageUrl); | |
| if (isFavorite) { | |
| favoriteCurrentBtn.innerHTML = '<i class="fas fa-heart"></i> Favorited'; | |
| favoriteCurrentBtn.classList.add('favorited'); | |
| } else { | |
| favoriteCurrentBtn.innerHTML = '<i class="far fa-heart"></i> Favorite'; | |
| favoriteCurrentBtn.classList.remove('favorited'); | |
| } | |
| } | |
| function loadFavorites() { | |
| const storedFavorites = localStorage.getItem('favoriteImages'); | |
| if (storedFavorites) { | |
| try { | |
| favoriteImages = JSON.parse(storedFavorites); | |
| } catch (e) { | |
| favoriteImages = []; | |
| } | |
| } | |
| } | |
| async function saveImageToHistory(imageData) { | |
| try { | |
| // Save locally first for immediate feedback | |
| let history = getLocalHistory(); | |
| // Set an ID if none exists | |
| if (!imageData.id) { | |
| imageData.id = Date.now().toString(); | |
| } | |
| // Add to the beginning of the array | |
| history.unshift(imageData); | |
| // Keep only the last 20 images | |
| history = history.slice(0, 20); | |
| // Save to localStorage | |
| localStorage.setItem('imageHistory', JSON.stringify(history)); | |
| // Save to server | |
| await fetch('/api/save-history', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json' | |
| }, | |
| body: JSON.stringify(imageData) | |
| }); | |
| // Render gallery | |
| renderHistoryGallery(history); | |
| } catch (error) { | |
| console.error('Error saving to history:', error); | |
| } | |
| } | |
| async function loadImageHistory() { | |
| try { | |
| // Try to get from server first | |
| const response = await fetch('/api/get-history'); | |
| const data = await response.json(); | |
| if (data.images && Array.isArray(data.images) && data.images.length > 0) { | |
| // Save to localStorage for offline access | |
| localStorage.setItem('imageHistory', JSON.stringify(data.images)); | |
| renderHistoryGallery(data.images); | |
| } else { | |
| // Fall back to local storage | |
| const history = getLocalHistory(); | |
| renderHistoryGallery(history); | |
| } | |
| } catch (error) { | |
| console.error('Error loading history:', error); | |
| // Fall back to local storage | |
| const history = getLocalHistory(); | |
| renderHistoryGallery(history); | |
| } | |
| } | |
| function getLocalHistory() { | |
| const storageData = localStorage.getItem('imageHistory'); | |
| if (storageData) { | |
| try { | |
| return JSON.parse(storageData); | |
| } catch (e) { | |
| return []; | |
| } | |
| } | |
| return []; | |
| } | |
| function renderHistoryGallery(images) { | |
| if (!images || !Array.isArray(images) || images.length === 0) { | |
| showEmptyHistoryMessage(); | |
| return; | |
| } | |
| let galleryHTML = ''; | |
| images.forEach(image => { | |
| galleryHTML += ` | |
| <div class="history-item" data-image-id="${image.id || ''}"> | |
| <img src="${image.imageUrl}" alt="${image.prompt}" class="history-img"> | |
| <div class="history-overlay"> | |
| <div class="history-prompt">${image.prompt}</div> | |
| <div class="history-actions"> | |
| <button class="history-action-btn view-btn" title="View Details"> | |
| <i class="fas fa-eye"></i> | |
| </button> | |
| <button class="history-action-btn regenerate-btn" title="Regenerate Similar"> | |
| <i class="fas fa-sync-alt"></i> | |
| </button> | |
| <button class="history-action-btn download-btn" title="Download Image"> | |
| <i class="fas fa-download"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| `; | |
| }); | |
| historyGallery.innerHTML = galleryHTML; | |
| // Add click events to history items | |
| document.querySelectorAll('.history-item').forEach(item => { | |
| const imageId = item.getAttribute('data-image-id'); | |
| const image = images.find(img => img.id === imageId); | |
| if (!image) return; | |
| // View button click | |
| const viewBtn = item.querySelector('.view-btn'); | |
| viewBtn.addEventListener('click', (e) => { | |
| e.stopPropagation(); // Prevent triggering the parent click | |
| openImageViewer(image); | |
| }); | |
| // Regenerate button click | |
| const regenerateBtn = item.querySelector('.regenerate-btn'); | |
| regenerateBtn.addEventListener('click', (e) => { | |
| e.stopPropagation(); // Prevent triggering the parent click | |
| regenerateSimilarImage(image); | |
| }); | |
| // Download button click | |
| const downloadBtn = item.querySelector('.download-btn'); | |
| downloadBtn.addEventListener('click', (e) => { | |
| e.stopPropagation(); // Prevent triggering the parent click | |
| downloadImage(image.imageUrl); | |
| }); | |
| // Main image click (for viewing) | |
| item.addEventListener('click', () => { | |
| openImageViewer(image); | |
| }); | |
| }); | |
| } | |
| function showEmptyHistoryMessage() { | |
| historyGallery.innerHTML = ` | |
| <div class="empty-history-message"> | |
| <i class="fas fa-images"></i> | |
| <p>Your creations will be displayed here</p> | |
| <span class="helper-tip">Get started by generating your first image!</span> | |
| </div> | |
| `; | |
| } | |
| function openModal(modal) { | |
| modal.classList.add('active'); | |
| // Add animation to modal content for better UX | |
| const modalContent = modal.querySelector('.modal-content'); | |
| if (modalContent) { | |
| modalContent.style.animation = 'none'; | |
| setTimeout(() => { | |
| modalContent.style.animation = ''; | |
| }, 10); | |
| } | |
| } | |
| function closeModal(modal) { | |
| modal.classList.remove('active'); | |
| } | |
| function showToast(title, message, type = 'info') { | |
| const toastContainer = document.getElementById('toast-container'); | |
| // Create toast element | |
| const toast = document.createElement('div'); | |
| toast.className = `toast ${type}`; | |
| // Add toast content | |
| toast.innerHTML = ` | |
| <div class="toast-content"> | |
| <div class="toast-title">${title}</div> | |
| <div class="toast-message">${message}</div> | |
| </div> | |
| <button class="toast-close"><i class="fas fa-times"></i></button> | |
| <div class="toast-progress"></div> | |
| `; | |
| // Add to container | |
| toastContainer.appendChild(toast); | |
| // Add close functionality | |
| toast.querySelector('.toast-close').addEventListener('click', () => { | |
| toast.classList.add('exit'); | |
| setTimeout(() => { | |
| toast.remove(); | |
| }, 300); | |
| }); | |
| // Auto remove after 5 seconds | |
| setTimeout(() => { | |
| if (toast.parentNode) { | |
| toast.classList.add('exit'); | |
| setTimeout(() => { | |
| toast.remove(); | |
| }, 300); | |
| } | |
| }, 5000); | |
| } | |
| }); |