Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AI Image Generator | Real-Time</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, #1e1e2e 0%, #2d1e2e 100%); | |
| } | |
| .prompt-textarea { | |
| min-height: 120px; | |
| resize: none; | |
| } | |
| .image-container { | |
| aspect-ratio: 1/1; | |
| background-color: #2a2a3a; | |
| } | |
| .slider-thumb::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: #8b5cf6; | |
| cursor: pointer; | |
| } | |
| .fade-in { | |
| animation: fadeIn 0.5s; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| .blur-overlay { | |
| backdrop-filter: blur(10px); | |
| background-color: rgba(0, 0, 0, 0.5); | |
| } | |
| .history-item { | |
| transition: transform 0.2s; | |
| } | |
| .history-item:hover { | |
| transform: scale(1.05); | |
| } | |
| /* Optimized animations */ | |
| @media (prefers-reduced-motion: reduce) { | |
| .fade-in, .history-item { | |
| animation: none ; | |
| transition: none ; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="gradient-bg text-gray-100 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <!-- Header --> | |
| <header class="flex justify-between items-center mb-8"> | |
| <div> | |
| <h1 class="text-3xl font-bold bg-gradient-to-r from-purple-500 to-pink-500 bg-clip-text text-transparent">AI Image Generator</h1> | |
| <p class="text-gray-400">Real-time image generation as you type</p> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <div class="flex items-center"> | |
| <span class="mr-2">SFW</span> | |
| <label class="relative inline-flex items-center cursor-pointer"> | |
| <input type="checkbox" id="nsfw-toggle" class="sr-only peer"> | |
| <div class="w-12 h-6 bg-gray-600 rounded-full peer peer-checked:bg-red-500 transition-colors"></div> | |
| <div class="absolute left-1 top-0.5 bg-white w-5 h-5 rounded-full transition-transform peer-checked:translate-x-6"></div> | |
| </label> | |
| <span class="ml-2">NSFW</span> | |
| </div> | |
| <button id="settings-btn" class="p-2 rounded-full hover:bg-gray-700 transition" aria-label="Settings"> | |
| <i class="fas fa-cog text-xl"></i> | |
| </button> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <div class="grid grid-cols-1 lg:grid-cols-2 gap-8"> | |
| <!-- Prompt Section --> | |
| <div class="space-y-6"> | |
| <div> | |
| <label for="prompt" class="block text-sm font-medium mb-2">Prompt</label> | |
| <textarea id="prompt" class="w-full prompt-textarea p-4 rounded-lg bg-gray-800 border border-gray-700 focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none transition" placeholder="Describe what you want to generate..." aria-label="Image description prompt"></textarea> | |
| </div> | |
| <div> | |
| <label for="negative-prompt" class="block text-sm font-medium mb-2">Negative Prompt</label> | |
| <textarea id="negative-prompt" class="w-full prompt-textarea p-4 rounded-lg bg-gray-800 border border-gray-700 focus:border-purple-500 focus:ring-1 focus:ring-purple-500 outline-none transition" placeholder="What you don't want to see..." aria-label="Negative image description"></textarea> | |
| </div> | |
| <!-- Advanced Options (Collapsible) --> | |
| <div class="bg-gray-800 rounded-lg overflow-hidden"> | |
| <button id="advanced-toggle" class="flex justify-between items-center p-4 cursor-pointer w-full text-left" aria-expanded="false" aria-controls="advanced-options"> | |
| <h3 class="font-medium">Advanced Options</h3> | |
| <i id="advanced-arrow" class="fas fa-chevron-down transition-transform"></i> | |
| </button> | |
| <div id="advanced-options" class="px-4 pb-4 hidden space-y-4"> | |
| <div> | |
| <label for="model-select" class="block text-sm font-medium mb-2">Model</label> | |
| <select id="model-select" class="w-full p-2 rounded bg-gray-700 border border-gray-600 outline-none"> | |
| <option value="stabilityai/stable-diffusion-xl-base-1.0">Stable Diffusion XL</option> | |
| <option value="runwayml/stable-diffusion-v1-5">Stable Diffusion 1.5</option> | |
| <option value="hakurei/waifu-diffusion">Waifu Diffusion</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="sampler-select" class="block text-sm font-medium mb-2">Sampler</label> | |
| <select id="sampler-select" class="w-full p-2 rounded bg-gray-700 border border-gray-600 outline-none"> | |
| <option value="DPMSolverMultistepScheduler">DPM++ 2M Karras</option> | |
| <option value="EulerAncestralDiscreteScheduler">Euler a</option> | |
| <option value="DDIMScheduler">DDIM</option> | |
| <option value="LMSDiscreteScheduler">LMS</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="steps-slider" class="block text-sm font-medium mb-2">Steps <span id="steps-value" class="text-gray-400">20</span></label> | |
| <input id="steps-slider" type="range" min="10" max="50" value="20" class="w-full slider-thumb" aria-labelledby="steps-value"> | |
| </div> | |
| <div> | |
| <label for="cfg-slider" class="block text-sm font-medium mb-2">CFG Scale <span id="cfg-value" class="text-gray-400">7</span></label> | |
| <input id="cfg-slider" type="range" min="1" max="20" value="7" class="w-full slider-thumb" aria-labelledby="cfg-value"> | |
| </div> | |
| <div> | |
| <label for="seed-input" class="block text-sm font-medium mb-2">Seed</label> | |
| <div class="flex space-x-2"> | |
| <input id="seed-input" type="number" class="flex-1 p-2 rounded bg-gray-700 border border-gray-600 outline-none" placeholder="Random" aria-label="Seed value"> | |
| <button id="randomize-seed" class="px-4 py-2 bg-gray-700 rounded hover:bg-gray-600 transition">Randomize</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex space-x-4"> | |
| <button id="generate-btn" class="flex-1 py-3 px-6 bg-gradient-to-r from-purple-600 to-pink-600 rounded-lg font-medium hover:opacity-90 transition flex items-center justify-center"> | |
| <i class="fas fa-bolt mr-2"></i> Generate | |
| </button> | |
| <button id="random-prompt" class="py-3 px-6 bg-gray-700 rounded-lg font-medium hover:bg-gray-600 transition" aria-label="Random prompt"> | |
| <i class="fas fa-random"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Image Output --> | |
| <div class="space-y-6"> | |
| <div class="image-container rounded-lg overflow-hidden relative"> | |
| <div id="image-placeholder" class="w-full h-full flex flex-col items-center justify-center text-gray-500"> | |
| <i class="fas fa-image fa-3x mb-4"></i> | |
| <p>Your generated image will appear here</p> | |
| </div> | |
| <img id="generated-image" class="w-full h-full hidden object-cover" alt="Generated image" loading="lazy"> | |
| <div id="progress-overlay" class="absolute inset-0 blur-overlay hidden flex-col items-center justify-center"> | |
| <div class="w-3/4 bg-gray-700 rounded-full h-2 mb-4"> | |
| <div id="progress-bar" class="bg-gradient-to-r from-purple-500 to-pink-500 h-2 rounded-full" style="width: 0%"></div> | |
| </div> | |
| <p id="progress-text" class="text-sm">Initializing model...</p> | |
| </div> | |
| </div> | |
| <!-- Image Controls --> | |
| <div class="flex space-x-2"> | |
| <button id="download-btn" class="flex-1 py-2 px-4 bg-gray-700 rounded-lg hover:bg-gray-600 transition flex items-center justify-center"> | |
| <i class="fas fa-download mr-2"></i> Download | |
| </button> | |
| <button id="retry-btn" class="flex-1 py-2 px-4 bg-gray-700 rounded-lg hover:bg-gray-600 transition flex items-center justify-center"> | |
| <i class="fas fa-undo mr-2"></i> Retry | |
| </button> | |
| <button id="enhance-btn" class="flex-1 py-2 px-4 bg-gray-700 rounded-lg hover:bg-gray-600 transition flex items-center justify-center"> | |
| <i class="fas fa-magic mr-2"></i> Enhance | |
| </button> | |
| <button id="upscale-btn" class="flex-1 py-2 px-4 bg-gray-700 rounded-lg hover:bg-gray-600 transition flex items-center justify-center"> | |
| <i class="fas fa-expand mr-2"></i> Upscale | |
| </button> | |
| </div> | |
| <!-- Generation History --> | |
| <div class="bg-gray-800 rounded-lg p-4"> | |
| <h3 class="font-medium mb-3">Generation History</h3> | |
| <div id="history-grid" class="grid grid-cols-4 gap-2"> | |
| <!-- History items will be added here dynamically --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Settings Modal --> | |
| <div id="settings-modal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50"> | |
| <div class="bg-gray-800 rounded-lg w-full max-w-md max-h-[90vh] overflow-y-auto"> | |
| <div class="p-4 border-b border-gray-700 flex justify-between items-center"> | |
| <h3 class="text-lg font-medium">Settings</h3> | |
| <button id="close-settings" class="p-1 rounded-full hover:bg-gray-700 transition" aria-label="Close settings"> | |
| <i class="fas fa-times"></i> | |
| </button> | |
| </div> | |
| <div class="p-4 space-y-4"> | |
| <div> | |
| <label for="update-delay" class="block text-sm font-medium mb-2">Real-time Update Delay (ms)</label> | |
| <input id="update-delay" type="number" value="1000" min="500" max="5000" step="100" class="w-full p-2 rounded bg-gray-700 border border-gray-600 outline-none"> | |
| </div> | |
| <div> | |
| <label for="resolution-select" class="block text-sm font-medium mb-2">Image Resolution</label> | |
| <select id="resolution-select" class="w-full p-2 rounded bg-gray-700 border border-gray-600 outline-none"> | |
| <option value="512x512">512x512</option> | |
| <option value="768x768">768x768</option> | |
| <option value="512x768">512x768</option> | |
| <option value="768x512">768x512</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="nsfw-filter" class="block text-sm font-medium mb-2">NSFW Filter Strength</label> | |
| <input id="nsfw-filter" type="range" min="0" max="100" value="50" class="w-full slider-thumb"> | |
| </div> | |
| <div class="pt-2"> | |
| <label class="inline-flex items-center"> | |
| <input id="save-history" type="checkbox" class="rounded bg-gray-700 border-gray-600 text-purple-500 focus:ring-purple-500" checked> | |
| <span class="ml-2">Save generation history</span> | |
| </label> | |
| </div> | |
| <div class="pt-2"> | |
| <label class="inline-flex items-center"> | |
| <input id="show-progress" type="checkbox" class="rounded bg-gray-700 border-gray-600 text-purple-500 focus:ring-purple-500" checked> | |
| <span class="ml-2">Show generation progress</span> | |
| </label> | |
| </div> | |
| </div> | |
| <div class="p-4 border-t border-gray-700 flex justify-end space-x-2"> | |
| <button id="cancel-settings" class="px-4 py-2 bg-gray-700 rounded-lg hover:bg-gray-600 transition">Cancel</button> | |
| <button id="save-settings" class="px-4 py-2 bg-gradient-to-r from-purple-600 to-pink-600 rounded-lg hover:opacity-90 transition">Save</button> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Configuration - Using const for immutable values | |
| const config = { | |
| nsfwEnabled: false, | |
| updateDelay: 1000, | |
| resolution: '512x512', | |
| history: [], | |
| currentGeneration: null | |
| }; | |
| // Cache DOM elements | |
| const elements = { | |
| prompt: document.getElementById('prompt'), | |
| negativePrompt: document.getElementById('negative-prompt'), | |
| generateBtn: document.getElementById('generate-btn'), | |
| imagePlaceholder: document.getElementById('image-placeholder'), | |
| generatedImage: document.getElementById('generated-image'), | |
| progressOverlay: document.getElementById('progress-overlay'), | |
| progressBar: document.getElementById('progress-bar'), | |
| progressText: document.getElementById('progress-text'), | |
| historyGrid: document.getElementById('history-grid'), | |
| downloadBtn: document.getElementById('download-btn'), | |
| nsfwToggle: document.getElementById('nsfw-toggle'), | |
| advancedToggle: document.getElementById('advanced-toggle'), | |
| advancedOptions: document.getElementById('advanced-options'), | |
| advancedArrow: document.getElementById('advanced-arrow'), | |
| randomPromptBtn: document.getElementById('random-prompt'), | |
| randomizeSeedBtn: document.getElementById('randomize-seed'), | |
| retryBtn: document.getElementById('retry-btn'), | |
| enhanceBtn: document.getElementById('enhance-btn'), | |
| upscaleBtn: document.getElementById('upscale-btn'), | |
| settingsBtn: document.getElementById('settings-btn'), | |
| closeSettingsBtn: document.getElementById('close-settings'), | |
| cancelSettingsBtn: document.getElementById('cancel-settings'), | |
| saveSettingsBtn: document.getElementById('save-settings'), | |
| settingsModal: document.getElementById('settings-modal') | |
| }; | |
| // Initialize with throttled functions | |
| let lastGenerationTime = 0; | |
| let typingTimer; | |
| let progressInterval; | |
| // Debounce function for better performance | |
| function debounce(func, wait) { | |
| let timeout; | |
| return function() { | |
| const context = this; | |
| const args = arguments; | |
| clearTimeout(timeout); | |
| timeout = setTimeout(() => func.apply(context, args), wait); | |
| }; | |
| } | |
| // Throttle function for rate limiting | |
| function throttle(func, limit) { | |
| let inThrottle; | |
| return function() { | |
| const args = arguments; | |
| const context = this; | |
| if (!inThrottle) { | |
| func.apply(context, args); | |
| inThrottle = true; | |
| setTimeout(() => inThrottle = false, limit); | |
| } | |
| }; | |
| } | |
| // Initialize the application | |
| function init() { | |
| loadSettings(); | |
| setupEventListeners(); | |
| renderHistory(); | |
| } | |
| // Load settings from localStorage | |
| function loadSettings() { | |
| if (localStorage.getItem('updateDelay')) { | |
| config.updateDelay = parseInt(localStorage.getItem('updateDelay')); | |
| } | |
| if (localStorage.getItem('resolution')) { | |
| config.resolution = localStorage.getItem('resolution'); | |
| } | |
| if (localStorage.getItem('nsfwEnabled')) { | |
| config.nsfwEnabled = localStorage.getItem('nsfwEnabled') === 'true'; | |
| elements.nsfwToggle.checked = config.nsfwEnabled; | |
| } | |
| if (localStorage.getItem('history')) { | |
| try { | |
| config.history = JSON.parse(localStorage.getItem('history')) || []; | |
| } catch (e) { | |
| console.error("Error parsing history:", e); | |
| config.history = []; | |
| } | |
| } | |
| } | |
| // Set up event listeners | |
| function setupEventListeners() { | |
| // NSFW toggle | |
| elements.nsfwToggle.addEventListener('change', function() { | |
| config.nsfwEnabled = this.checked; | |
| localStorage.setItem('nsfwEnabled', this.checked); | |
| }); | |
| // Advanced options toggle | |
| elements.advancedToggle.addEventListener('click', toggleAdvancedOptions); | |
| // Prompt typing with debounce | |
| elements.prompt.addEventListener('input', debounce(() => { | |
| if (elements.prompt.value) { | |
| generateImage(); | |
| } | |
| }, config.updateDelay)); | |
| // Generate button | |
| elements.generateBtn.addEventListener('click', throttle(generateImage, 1000)); | |
| // Random prompt button | |
| elements.randomPromptBtn.addEventListener('click', randomizePrompt); | |
| // Randomize seed button | |
| elements.randomizeSeedBtn.addEventListener('click', randomizeSeed); | |
| // Image action buttons | |
| elements.retryBtn.addEventListener('click', regenerateImage); | |
| elements.enhanceBtn.addEventListener('click', enhanceImage); | |
| elements.upscaleBtn.addEventListener('click', upscaleImage); | |
| // Settings modal buttons | |
| elements.settingsBtn.addEventListener('click', openSettings); | |
| elements.closeSettingsBtn.addEventListener('click', closeSettings); | |
| elements.cancelSettingsBtn.addEventListener('click', closeSettings); | |
| elements.saveSettingsBtn.addEventListener('click', saveSettings); | |
| // Slider inputs | |
| elements.stepsSlider = document.getElementById('steps-slider'); | |
| elements.cfgSlider = document.getElementById('cfg-slider'); | |
| elements.stepsSlider.addEventListener('input', () => { | |
| document.getElementById('steps-value').textContent = elements.stepsSlider.value; | |
| }); | |
| elements.cfgSlider.addEventListener('input', () => { | |
| document.getElementById('cfg-value').textContent = elements.cfgSlider.value; | |
| }); | |
| // Download button | |
| elements.downloadBtn.addEventListener('click', () => { | |
| if (config.currentGeneration?.imageUrl) { | |
| downloadImage(config.currentGeneration.imageUrl, `ai-image-${Date.now()}.png`); | |
| } | |
| }); | |
| } | |
| // UI Toggles | |
| function toggleAdvancedOptions() { | |
| const isExpanded = elements.advancedOptions.classList.toggle('hidden'); | |
| elements.advancedArrow.classList.toggle('rotate-180'); | |
| elements.advancedToggle.setAttribute('aria-expanded', !isExpanded); | |
| } | |
| // Settings Modal | |
| function openSettings() { | |
| document.getElementById('update-delay').value = config.updateDelay; | |
| document.getElementById('resolution-select').value = config.resolution; | |
| document.getElementById('save-history').checked = localStorage.getItem('saveHistory') !== 'false'; | |
| document.getElementById('show-progress').checked = localStorage.getItem('showProgress') !== 'false'; | |
| elements.settingsModal.classList.remove('hidden'); | |
| } | |
| function closeSettings() { | |
| elements.settingsModal.classList.add('hidden'); | |
| } | |
| function saveSettings() { | |
| config.updateDelay = parseInt(document.getElementById('update-delay').value); | |
| config.resolution = document.getElementById('resolution-select').value; | |
| localStorage.setItem('updateDelay', config.updateDelay); | |
| localStorage.setItem('resolution', config.resolution); | |
| localStorage.setItem('saveHistory', document.getElementById('save-history').checked); | |
| localStorage.setItem('showProgress', document.getElementById('show-progress').checked); | |
| closeSettings(); | |
| } | |
| // Randomization functions | |
| function randomizeSeed() { | |
| const seed = Math.floor(Math.random() * 1000000); | |
| document.getElementById('seed-input').value = seed; | |
| } | |
| function randomizePrompt() { | |
| const prompts = [ | |
| "beautiful anime girl with long pink hair, detailed eyes, fantasy setting, intricate outfit, highly detailed, digital painting, artstation, concept art, smooth, sharp focus, illustration", | |
| "cyberpunk cityscape at night, neon lights, rain-soaked streets, futuristic vehicles, 4k detailed, cinematic lighting, ultra realistic", | |
| "fantasy warrior with glowing sword, armor with intricate designs, standing on cliff overlooking valley, dramatic lighting, digital painting", | |
| "cute chibi character with oversized head, big expressive eyes, simple outfit, pastel colors, kawaii style, soft shading" | |
| ]; | |
| const negativePrompts = [ | |
| "low quality, bad anatomy, extra limbs, poorly drawn face, disfigured, blurry, boring, ugly", | |
| "deformed, distorted, disfigured, poorly drawn, bad anatomy, wrong anatomy, extra limb, missing limb, floating limbs, disconnected limbs", | |
| "text, watermark, signature, logo, cropped, out of frame, worst quality, low quality, jpeg artifacts, blurry", | |
| "mutated hands, mutated fingers, deformed, bad anatomy, disfigured, poorly drawn face, mutation, extra limb, ugly" | |
| ]; | |
| elements.prompt.value = prompts[Math.floor(Math.random() * prompts.length)]; | |
| elements.negativePrompt.value = negativePrompts[Math.floor(Math.random() * negativePrompts.length)]; | |
| randomizeSeed(); | |
| } | |
| // Image Generation | |
| async function generateImage() { | |
| const prompt = elements.prompt.value; | |
| if (!prompt) return; | |
| // Don't generate too frequently | |
| const now = Date.now(); | |
| if (now - lastGenerationTime < config.updateDelay) { | |
| return; | |
| } | |
| lastGenerationTime = now; | |
| // Clear any existing progress interval | |
| if (progressInterval) { | |
| clearInterval(progressInterval); | |
| } | |
| // Show loading state | |
| elements.imagePlaceholder.classList.add('hidden'); | |
| elements.generatedImage.classList.add('hidden'); | |
| elements.progressOverlay.classList.remove('hidden'); | |
| elements.progressBar.style.width = '0%'; | |
| elements.progressText.textContent = "Initializing model..."; | |
| // Get generation parameters | |
| const model = document.getElementById('model-select').value; | |
| const sampler = document.getElementById('sampler-select').value; | |
| const steps = parseInt(document.getElementById('steps-slider').value); | |
| const cfgScale = parseFloat(document.getElementById('cfg-slider').value); | |
| let seed = document.getElementById('seed-input').value; | |
| seed = seed ? parseInt(seed) : Math.floor(Math.random() * 1000000); | |
| // Get resolution | |
| const [width, height] = config.resolution.split('x').map(Number); | |
| try { | |
| // Simulate progress | |
| let progress = 0; | |
| progressInterval = setInterval(() => { | |
| progress += Math.random() * 10; | |
| if (progress > 100) progress = 100; | |
| elements.progressBar.style.width = `${progress}%`; | |
| if (progress < 30) { | |
| elements.progressText.textContent = "Loading model weights..."; | |
| } else if (progress < 60) { | |
| elements.progressText.textContent = "Processing prompt..."; | |
| } else if (progress < 90) { | |
| elements.progressText.textContent = "Generating image..."; | |
| } else { | |
| elements.progressText.textContent = "Applying final touches..."; | |
| } | |
| if (progress === 100) { | |
| clearInterval(progressInterval); | |
| } | |
| }, 200); | |
| // Get a placeholder image based on the prompt and NSFW setting | |
| const imageUrl = await getPlaceholderImage(prompt, config.nsfwEnabled); | |
| // Display the generated image | |
| elements.generatedImage.src = imageUrl; | |
| elements.generatedImage.onload = () => { | |
| clearInterval(progressInterval); | |
| elements.progressOverlay.classList.add('hidden'); | |
| elements.generatedImage.classList.remove('hidden'); | |
| elements.generatedImage.classList.add('fade-in'); | |
| // Add to history if enabled | |
| if (localStorage.getItem('saveHistory') !== 'false') { | |
| addToHistory(imageUrl, prompt); | |
| } | |
| }; | |
| config.currentGeneration = { | |
| prompt: prompt, | |
| negativePrompt: elements.negativePrompt.value, | |
| model: model, | |
| sampler: sampler, | |
| steps: steps, | |
| cfgScale: cfgScale, | |
| seed: seed, | |
| width: width, | |
| height: height, | |
| imageUrl: imageUrl, | |
| timestamp: Date.now() | |
| }; | |
| } catch (error) { | |
| console.error("Generation failed:", error); | |
| elements.progressText.textContent = "Generation failed. Please try again."; | |
| setTimeout(() => { | |
| elements.progressOverlay.classList.add('hidden'); | |
| elements.imagePlaceholder.classList.remove('hidden'); | |
| }, 2000); | |
| } | |
| } | |
| // Get placeholder image based on prompt | |
| async function getPlaceholderImage(prompt, isNSFW) { | |
| // Create a simple hash from the prompt to determine which image to show | |
| let hash = 0; | |
| for (let i = 0; i < prompt.length; i++) { | |
| hash = (hash << 5) - hash + prompt.charCodeAt(i); | |
| hash |= 0; // Convert to 32bit integer | |
| } | |
| // Use absolute value and modulo to get a consistent index | |
| const index = Math.abs(hash) % 6; | |
| // SFW images | |
| const sfwImages = [ | |
| 'https://images.unsplash.com/photo-1637858868799-7f26a0640eb6?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=880&q=80', | |
| 'https://images.unsplash.com/photo-1633332755192-727a05c4013d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=880&q=80', | |
| 'https://images.unsplash.com/photo-1638727290811-78757cf43199?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=880&q=80', | |
| 'https://images.unsplash.com/photo-1637858866850-7f5a515375d8?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=880&q=80', | |
| 'https://images.unsplash.com/photo-1637858868092-d5675b013278?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=880&q=80', | |
| 'https://images.unsplash.com/photo-1637858868799-7f26a0640eb6?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=880&q=80' | |
| ]; | |
| // NSFW images (using artistic nudes as placeholders) | |
| const nsfwImages = [ | |
| 'https://images.unsplash.com/photo-1600334129128-685c5582fd35?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=880&q=80', | |
| 'https://images.unsplash.com/photo-1600618528240-fb9fc964b853?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=880&q=80', | |
| 'https://images.unsplash.com/photo-1600618528240-fb9fc964b853?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=880&q=80', | |
| 'https://images.unsplash.com/photo-1600618528240-fb9fc964b853?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=880&q=80', | |
| 'https://images.unsplash.com/photo-1600618528240-fb9fc964b853?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=880&q=80', | |
| 'https://images.unsplash.com/photo-1600618528240-fb9fc964b853?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=880&q=80' | |
| ]; | |
| // Return after a delay to simulate API call | |
| return new Promise(resolve => { | |
| setTimeout(() => { | |
| resolve(isNSFW ? nsfwImages[index] : sfwImages[index]); | |
| }, 1500); | |
| }); | |
| } | |
| function regenerateImage() { | |
| if (config.currentGeneration) { | |
| elements.prompt.value = config.currentGeneration.prompt; | |
| elements.negativePrompt.value = config.currentGeneration.negativePrompt; | |
| document.getElementById('model-select').value = config.currentGeneration.model; | |
| document.getElementById('sampler-select').value = config.currentGeneration.sampler; | |
| document.getElementById('steps-slider').value = config.currentGeneration.steps; | |
| document.getElementById('cfg-slider').value = config.currentGeneration.cfgScale; | |
| document.getElementById('seed-input').value = config.currentGeneration.seed; | |
| generateImage(); | |
| } | |
| } | |
| function enhanceImage() { | |
| if (config.currentGeneration) { | |
| elements.prompt.value = "highly detailed, 8k, professional photography, " + config.currentGeneration.prompt; | |
| generateImage(); | |
| } | |
| } | |
| function upscaleImage() { | |
| if (config.currentGeneration) { | |
| alert("In a real implementation, this would upscale the image to higher resolution"); | |
| } | |
| } | |
| // History Management | |
| function addToHistory(imageUrl, prompt) { | |
| const historyItem = { | |
| imageUrl: imageUrl, | |
| prompt: prompt, | |
| timestamp: Date.now() | |
| }; | |
| config.history.unshift(historyItem); | |
| if (config.history.length > 8) { | |
| config.history.pop(); | |
| } | |
| localStorage.setItem('history', JSON.stringify(config.history)); | |
| renderHistory(); | |
| } | |
| function renderHistory() { | |
| elements.historyGrid.innerHTML = ''; | |
| config.history.forEach((item, index) => { | |
| const historyItem = document.createElement('div'); | |
| historyItem.className = 'history-item rounded overflow-hidden cursor-pointer relative'; | |
| historyItem.innerHTML = ` | |
| <img src="${item.imageUrl}" alt="History image ${index}" class="w-full h-full object-cover aspect-square" loading="lazy"> | |
| <div class="absolute inset-0 bg-black bg-opacity-50 opacity-0 hover:opacity-100 transition-opacity flex items-center justify-center p-2 text-xs"> | |
| ${item.prompt.substring(0, 30)}${item.prompt.length > 30 ? '...' : ''} | |
| </div> | |
| `; | |
| historyItem.addEventListener('click', () => { | |
| elements.generatedImage.src = item.imageUrl; | |
| elements.imagePlaceholder.classList.add('hidden'); | |
| elements.generatedImage.classList.remove('hidden'); | |
| }); | |
| elements.historyGrid.appendChild(historyItem); | |
| }); | |
| } | |
| // Download image | |
| function downloadImage(url, filename) { | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = filename; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| } | |
| // Initialize the app when DOM is loaded | |
| document.addEventListener('DOMContentLoaded', init); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Donmill/real-time-magic" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |