| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>AI Image Generator</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> |
| .model-card { |
| transition: all 0.3s ease; |
| } |
| .model-card:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); |
| } |
| .tab-content { |
| display: none; |
| } |
| .tab-content.active { |
| display: block; |
| } |
| .tab-button.active { |
| border-bottom: 2px solid #8b5cf6; |
| color: #8b5cf6; |
| } |
| .gallery-container { |
| display: grid; |
| grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); |
| gap: 1rem; |
| } |
| .gallery-image { |
| max-height: 300px; |
| object-fit: contain; |
| border-radius: 0.5rem; |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); |
| } |
| .loading-spinner { |
| animation: spin 1s linear infinite; |
| } |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| .accordion-content { |
| max-height: 0; |
| overflow: hidden; |
| transition: max-height 0.3s ease-out; |
| } |
| .accordion-content.open { |
| max-height: 1000px; |
| } |
| .progress-bar { |
| height: 6px; |
| background-color: #e5e7eb; |
| border-radius: 3px; |
| overflow: hidden; |
| } |
| .progress-bar-fill { |
| height: 100%; |
| background: linear-gradient(90deg, #ec4899, #8b5cf6); |
| transition: width 0.3s ease; |
| } |
| .performance-meter { |
| background: linear-gradient(90deg, #ec4899, #8b5cf6); |
| -webkit-background-clip: text; |
| background-clip: text; |
| color: transparent; |
| font-weight: bold; |
| } |
| .model-tag { |
| font-size: 0.7rem; |
| padding: 0.15rem 0.4rem; |
| border-radius: 0.25rem; |
| margin-left: 0.5rem; |
| } |
| .cpu-tag { |
| background-color: #d1fae5; |
| color: #065f46; |
| } |
| .standard-tag { |
| background-color: #e0e7ff; |
| color: #3730a3; |
| } |
| .nsfw-tag { |
| background-color: #fee2e2; |
| color: #991b1b; |
| } |
| </style> |
| </head> |
| <body class="bg-gray-100 text-gray-800"> |
| <div class="container mx-auto px-4 py-8 max-w-6xl"> |
| |
| <header class="mb-8 text-center"> |
| <h1 class="text-4xl font-bold bg-gradient-to-r from-pink-500 to-purple-600 bg-clip-text text-transparent"> |
| AI Image Generator |
| </h1> |
| <p class="mt-2 text-gray-600">Generate images using various AI models</p> |
| <div class="mt-4 flex justify-center items-center space-x-4"> |
| <div class="text-center"> |
| <div class="text-sm text-gray-500">API Status</div> |
| <div id="api-status" class="performance-meter text-xl">Checking...</div> |
| </div> |
| <div class="text-center"> |
| <div class="text-sm text-gray-500">Server Load</div> |
| <div id="server-load" class="performance-meter text-xl">-</div> |
| </div> |
| </div> |
| </header> |
|
|
| |
| <div class="bg-white rounded-xl shadow-lg overflow-hidden"> |
| |
| <div class="flex border-b border-gray-200"> |
| <button class="tab-button px-4 py-3 font-medium active" data-tab="basic">Basic Settings</button> |
| <button class="tab-button px-4 py-3 font-medium" data-tab="advanced">Advanced Settings</button> |
| <button class="tab-button px-4 py-3 font-medium" data-tab="editor">Image Editor</button> |
| <button class="tab-button px-4 py-3 font-medium" data-tab="info">Information</button> |
| </div> |
|
|
| |
| <div class="p-6"> |
| |
| <div id="basic" class="tab-content active"> |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> |
| |
| <div class="md:col-span-2"> |
| <div class="mb-4"> |
| <label for="prompt" class="block text-sm font-medium text-gray-700 mb-1">Prompt</label> |
| <textarea id="prompt" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500" placeholder="Enter a prompt here"></textarea> |
| </div> |
| |
| <div class="mb-4"> |
| <label for="style-preset" class="block text-sm font-medium text-gray-700 mb-1">Style Preset</label> |
| <select id="style-preset" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"> |
| <option value="none">None</option> |
| <option value="digital-art">Digital Art</option> |
| <option value="photographic">Photographic</option> |
| <option value="anime">Anime</option> |
| <option value="fantasy-art">Fantasy Art</option> |
| <option value="3d-model">3D Model</option> |
| <option value="pixel-art">Pixel Art</option> |
| </select> |
| </div> |
|
|
| <div class="mb-4"> |
| <label for="batch-count" class="block text-sm font-medium text-gray-700 mb-1">Number of Images</label> |
| <select id="batch-count" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"> |
| <option value="1">1</option> |
| <option value="2">2</option> |
| <option value="3">3</option> |
| <option value="4" selected>4</option> |
| </select> |
| </div> |
| </div> |
| |
| |
| <div> |
| <div class="mb-4"> |
| <div class="flex justify-between items-center mb-2"> |
| <label class="block text-sm font-medium text-gray-700">Select Model</label> |
| <button id="model-accordion" class="text-sm text-purple-600 hover:text-purple-800"> |
| <i class="fas fa-chevron-down"></i> Show Models |
| </button> |
| </div> |
| |
| <div class="mb-3"> |
| <input type="text" id="model-search" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500" placeholder="Search models..."> |
| </div> |
| |
| <div id="model-list-container" class="h-96 overflow-y-auto border border-gray-200 rounded-md p-2 hidden"> |
| <div id="model-list" class="space-y-2"> |
| |
| </div> |
| </div> |
| |
| <div id="selected-model-display" class="p-3 bg-gray-50 rounded-md border border-gray-200"> |
| <p class="text-sm font-medium">Selected Model:</p> |
| <p id="selected-model" class="font-semibold text-purple-600">Loading models...</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="advanced" class="tab-content"> |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> |
| <div> |
| <div class="mb-4"> |
| <label for="negative-prompt" class="block text-sm font-medium text-gray-700 mb-1">Negative Prompt</label> |
| <textarea id="negative-prompt" rows="3" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500" placeholder="What should not be in the image">(deformed, distorted, disfigured), poorly drawn, bad anatomy, wrong anatomy, extra limb, missing limb, floating limbs, (mutated hands and fingers), disconnected limbs, mutation, mutated, ugly, disgusting, blurry, amputation, misspellings, typos</textarea> |
| </div> |
| |
| <div class="mb-4"> |
| <label for="sampling-method" class="block text-sm font-medium text-gray-700 mb-1">Sampling Method</label> |
| <select id="sampling-method" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"> |
| <option value="DPM++ 2M Karras" selected>DPM++ 2M Karras</option> |
| <option value="DPM++ SDE Karras">DPM++ SDE Karras</option> |
| <option value="Euler">Euler</option> |
| <option value="Euler a">Euler a</option> |
| </select> |
| </div> |
| </div> |
| |
| <div> |
| <div class="grid grid-cols-2 gap-4 mb-4"> |
| <div> |
| <label for="width" class="block text-sm font-medium text-gray-700 mb-1">Width</label> |
| <input type="range" id="width" min="128" max="1024" step="64" value="512" class="w-full"> |
| <div class="flex justify-between text-xs text-gray-500"> |
| <span>128</span> |
| <span id="width-value">512</span> |
| <span>1024</span> |
| </div> |
| </div> |
| |
| <div> |
| <label for="height" class="block text-sm font-medium text-gray-700 mb-1">Height</label> |
| <input type="range" id="height" min="128" max="1024" step="64" value="512" class="w-full"> |
| <div class="flex justify-between text-xs text-gray-500"> |
| <span>128</span> |
| <span id="height-value">512</span> |
| <span>1024</span> |
| </div> |
| </div> |
| </div> |
| |
| <div class="grid grid-cols-2 gap-4"> |
| <div> |
| <label for="steps" class="block text-sm font-medium text-gray-700 mb-1">Sampling Steps</label> |
| <input type="range" id="steps" min="10" max="50" value="20" class="w-full"> |
| <div class="flex justify-between text-xs text-gray-500"> |
| <span>10</span> |
| <span id="steps-value">20</span> |
| <span>50</span> |
| </div> |
| </div> |
| |
| <div> |
| <label for="cfg" class="block text-sm font-medium text-gray-700 mb-1">CFG Scale</label> |
| <input type="range" id="cfg" min="5" max="15" value="7" class="w-full"> |
| <div class="flex justify-between text-xs text-gray-500"> |
| <span>5</span> |
| <span id="cfg-value">7</span> |
| <span>15</span> |
| </div> |
| </div> |
| </div> |
| |
| <div class="grid grid-cols-2 gap-4 mt-4"> |
| <div> |
| <label for="seed" class="block text-sm font-medium text-gray-700 mb-1">Seed</label> |
| <input type="number" id="seed" min="-1" max="1000000000" value="-1" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"> |
| </div> |
| |
| <div> |
| <label for="quality" class="block text-sm font-medium text-gray-700 mb-1">Quality</label> |
| <select id="quality" class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-purple-500 focus:border-purple-500"> |
| <option value="low">Low (faster)</option> |
| <option value="medium" selected>Medium</option> |
| <option value="high">High (slower)</option> |
| </select> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div id="editor" class="tab-content"> |
| <div class="border-2 border-dashed border-gray-300 rounded-lg p-4 min-h-64 flex items-center justify-center"> |
| <p class="text-gray-500">Image editor will be displayed here after generation</p> |
| </div> |
| <div class="mt-4 flex space-x-2"> |
| <button class="px-4 py-2 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300">Crop</button> |
| <button class="px-4 py-2 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300">Adjust</button> |
| <button class="px-4 py-2 bg-gray-200 text-gray-800 rounded-md hover:bg-gray-300">Filters</button> |
| </div> |
| </div> |
|
|
| |
| <div id="info" class="tab-content"> |
| <div class="mb-6"> |
| <label class="block text-sm font-medium text-gray-700 mb-1">Sample prompt</label> |
| <div class="p-3 bg-gray-50 rounded-md border border-gray-200"> |
| <code>{prompt} | ultra detail, ultra elaboration, ultra quality, perfect.</code> |
| </div> |
| </div> |
| |
| <div class="mb-6"> |
| <button id="featured-models-accordion" class="flex items-center justify-between w-full py-2 text-left font-medium text-purple-600 hover:text-purple-800"> |
| <span>API Documentation</span> |
| <i class="fas fa-chevron-down"></i> |
| </button> |
| <div id="featured-models-content" class="accordion-content"> |
| <div class="mt-2 p-4 bg-gray-50 rounded-md"> |
| <h3 class="text-lg font-semibold mb-2">Available API Endpoints:</h3> |
| <ul class="list-disc pl-5 space-y-2 text-sm"> |
| <li><strong>POST /api/generate</strong> - Generate images</li> |
| <li><strong>GET /api/models</strong> - List available models</li> |
| <li><strong>GET /api/status</strong> - Check server status</li> |
| <li><strong>GET /api/history</strong> - Get generation history</li> |
| </ul> |
| |
| <h3 class="text-lg font-semibold mt-4 mb-2">Example Request:</h3> |
| <pre class="bg-gray-100 p-3 rounded text-xs overflow-x-auto"> |
| { |
| "prompt": "a beautiful sunset over mountains", |
| "model": "stable-diffusion-xl", |
| "negative_prompt": "blurry, low quality", |
| "width": 512, |
| "height": 512, |
| "steps": 20, |
| "batch_size": 1, |
| "seed": -1, |
| "cfg_scale": 7 |
| }</pre> |
| </div> |
| </div> |
| </div> |
| |
| <div> |
| <button id="advanced-settings-accordion" class="flex items-center justify-between w-full py-2 text-left font-medium text-purple-600 hover:text-purple-800"> |
| <span>System Information</span> |
| <i class="fas fa-chevron-down"></i> |
| </button> |
| <div id="advanced-settings-content" class="accordion-content"> |
| <div class="mt-2 p-4 bg-gray-50 rounded-md"> |
| <h3 class="text-lg font-semibold mb-2">Backend Information</h3> |
| <p class="mb-4 text-sm">This application uses a Python Flask backend with the following features:</p> |
| <ul class="list-disc pl-5 space-y-1 text-sm"> |
| <li>RESTful API endpoints</li> |
| <li>Model management system</li> |
| <li>Job queue for image generation</li> |
| <li>Rate limiting</li> |
| <li>Authentication (optional)</li> |
| </ul> |
| |
| <h3 class="text-lg font-semibold mt-4 mb-2">Frontend Information</h3> |
| <p class="mb-4 text-sm">The frontend is built with:</p> |
| <ul class="list-disc pl-5 space-y-1 text-sm"> |
| <li>HTML5, CSS3, JavaScript</li> |
| <li>TailwindCSS for styling</li> |
| <li>Fetch API for backend communication</li> |
| <li>Responsive design</li> |
| </ul> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <div class="px-6 pb-6"> |
| <div class="mb-4 hidden" id="generation-progress"> |
| <div class="flex justify-between text-sm mb-1"> |
| <span id="progress-status">Initializing model...</span> |
| <span id="progress-percent">0%</span> |
| </div> |
| <div class="progress-bar"> |
| <div id="progress-bar-fill" class="progress-bar-fill" style="width: 0%"></div> |
| </div> |
| <div class="text-xs text-gray-500 mt-1" id="time-remaining">Estimated time: calculating...</div> |
| </div> |
| |
| <button id="generate-btn" class="w-full py-3 px-4 bg-gradient-to-r from-pink-500 to-purple-600 text-white font-medium rounded-md hover:from-pink-600 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 flex items-center justify-center"> |
| <span id="generate-text">Generate Image</span> |
| <span id="loading-spinner" class="ml-2 hidden"> |
| <i class="fas fa-circle-notch loading-spinner"></i> |
| </span> |
| </button> |
| |
| <div id="error-message" class="mt-4 hidden p-3 bg-red-100 border border-red-400 text-red-700 rounded"></div> |
| |
| <div class="mt-6"> |
| <div class="flex justify-between items-center mb-2"> |
| <h3 class="text-lg font-medium text-gray-900">Output</h3> |
| <button id="download-all" class="hidden px-3 py-1 bg-gray-200 text-gray-800 rounded-md text-sm hover:bg-gray-300"> |
| <i class="fas fa-download mr-1"></i> Download All |
| </button> |
| </div> |
| <div id="gallery-container" class="border border-gray-200 rounded-md p-4 min-h-64"> |
| <div id="placeholder-text" class="flex items-center justify-center h-full text-gray-500"> |
| <p>Your generated images will appear here</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| |
| <script> |
| |
| |
| |
| |
| const API_BASE_URL = 'http://localhost:5000/api'; |
| |
| |
| async function checkApiStatus() { |
| try { |
| |
| |
| |
| |
| |
| const data = { |
| status: 'online', |
| load: Math.floor(Math.random() * 30) + 10, |
| version: '1.0.0' |
| }; |
| |
| document.getElementById('api-status').textContent = data.status; |
| document.getElementById('server-load').textContent = `${data.load}%`; |
| |
| |
| loadModels(); |
| } catch (error) { |
| document.getElementById('api-status').textContent = 'offline'; |
| document.getElementById('server-load').textContent = '-'; |
| showError('Could not connect to the API server'); |
| } |
| } |
| |
| |
| async function loadModels() { |
| try { |
| |
| |
| |
| |
| |
| const models = [ |
| { name: "3D Sketchfab", tags: ["standard"] }, |
| { name: "80s Cyberpunk", tags: ["standard"] }, |
| { name: "90s Anime Art", tags: ["standard"] }, |
| { name: "AbsoluteReality 1.8.1", tags: ["cpu"] }, |
| { name: "Analog", tags: ["standard"] }, |
| { name: "Analog Madness Realistic v7", tags: ["standard"] }, |
| { name: "Analog Redmond", tags: ["standard"] }, |
| { name: "Animagine 4.0", tags: ["cpu"] }, |
| { name: "Archfey Anime", tags: ["standard"] }, |
| { name: "Ascii Art", tags: ["cpu"] }, |
| { name: "Brain Melt Acid Art", tags: ["standard"] }, |
| { name: "Boreal", tags: ["standard"] }, |
| { name: "Caricature", tags: ["standard"] }, |
| { name: "Collage Flux", tags: ["standard"] }, |
| { name: "Coloring Book Flux", tags: ["cpu"] }, |
| { name: "Character Design", tags: ["standard"] }, |
| { name: "Chill Guy", tags: ["standard"] }, |
| { name: "Claude Art", tags: ["standard"] }, |
| { name: "Coloring Book Generator", tags: ["cpu"] }, |
| { name: "Cyborg Style XL", tags: ["standard"] }, |
| { name: "Disney", tags: ["standard"] }, |
| { name: "DreamPhotoGASM", tags: ["standard"] }, |
| { name: "Duchaiten Real3D NSFW XL", tags: ["nsfw"] }, |
| { name: "EBook Creative Cover", tags: ["standard"] }, |
| { name: "EpiCPhotoGasm", tags: ["standard"] }, |
| { name: "Fashion Hut Modeling LoRA", tags: ["standard"] }, |
| { name: "Filmgrain", tags: ["standard"] }, |
| { name: "FLUX.1 [Dev]", tags: ["cpu"] }, |
| { name: "FLUX.1 [Schnell]", tags: ["cpu"] }, |
| { name: "Flux Condensation", tags: ["cpu"] }, |
| { name: "Flux Handwriting", tags: ["cpu"] }, |
| { name: "Flux Realism LoRA", tags: ["cpu"] }, |
| { name: "Flux Super Realism LoRA", tags: ["cpu"] }, |
| { name: "Flux Uncensored", tags: ["nsfw"] }, |
| { name: "Flux Uncensored V2", tags: ["nsfw"] }, |
| { name: "Flux Game Assets V2", tags: ["cpu"] }, |
| { name: "Flux Icon Kit", tags: ["cpu"] }, |
| { name: "Flux Ghibsky Illustration", tags: ["cpu"] }, |
| { name: "Flux Animex V2", tags: ["cpu"] }, |
| { name: "Flux Animeo V1", tags: ["cpu"] }, |
| { name: "Flux AestheticAnime", tags: ["cpu"] }, |
| { name: "Flux SyntheticAnime", tags: ["cpu"] }, |
| { name: "Flux Stickers", tags: ["cpu"] }, |
| { name: "Flux Koda", tags: ["cpu"] }, |
| { name: "Flux Tarot v1", tags: ["cpu"] }, |
| { name: "Flux Tarot Cards", tags: ["cpu"] }, |
| { name: "Flux UltraRealism 2.0", tags: ["cpu"] }, |
| { name: "Flux Midjourney Anime", tags: ["cpu"] }, |
| { name: "Flux Miniature LoRA", tags: ["cpu"] }, |
| { name: "Flux Logo Design", tags: ["cpu"] }, |
| { name: "Flux Logo Design 2", tags: ["cpu"] }, |
| { name: "Flux Product Ad Backdrop", tags: ["cpu"] }, |
| { name: "Flux Outfit Generator", tags: ["cpu"] }, |
| { name: "Frosting Lane Flux", tags: ["cpu"] }, |
| { name: "Half Illustration", tags: ["standard"] }, |
| { name: "HiDream-I1-Full", tags: ["cpu"] }, |
| { name: "HiDream-I1-Dev", tags: ["cpu"] }, |
| { name: "HiDream-I1-Fast", tags: ["cpu"] }, |
| { name: "How2Draw", tags: ["standard"] }, |
| { name: "Huggieverse", tags: ["standard"] }, |
| { name: "Isometric 3D", tags: ["standard"] }, |
| { name: "Leonardo AI Style Illustration", tags: ["standard"] }, |
| { name: "Little Tinies", tags: ["standard"] }, |
| { name: "Lofi Cuties", tags: ["standard"] }, |
| { name: "Lustly Flux Uncensored v1", tags: ["nsfw"] }, |
| { name: "Maple Syrup", tags: ["standard"] }, |
| { name: "Meme XD", tags: ["standard"] }, |
| { name: "Midjourney", tags: ["standard"] }, |
| { name: "Midjourney Mix", tags: ["standard"] }, |
| { name: "Midjourney Mix 2", tags: ["standard"] }, |
| { name: "Movie Board", tags: ["standard"] }, |
| { name: "NSFWmodel", tags: ["nsfw"] }, |
| { name: "NSFW Master Flux", tags: ["nsfw"] }, |
| { name: "NSFW XL", tags: ["nsfw"] }, |
| { name: "OpenDalle v1.1", tags: ["cpu"] }, |
| { name: "Open Genmoji", tags: ["cpu"] }, |
| { name: "Pepe", tags: ["standard"] }, |
| { name: "Perfect Lewd Fantasy", tags: ["nsfw"] }, |
| { name: "Pixel Art Redmond", tags: ["standard"] }, |
| { name: "Pixel Art XL", tags: ["cpu"] }, |
| { name: "Pixel Art Sprites", tags: ["cpu"] }, |
| { name: "Pixel Background", tags: ["cpu"] }, |
| { name: "Product Design", tags: ["standard"] }, |
| { name: "Propaganda Poster", tags: ["standard"] }, |
| { name: "Purple Dreamy", tags: ["standard"] }, |
| { name: "Phantasma Anime", tags: ["standard"] }, |
| { name: "PS1 Style Flux", tags: ["cpu"] }, |
| { name: "Redmond SDXL", tags: ["standard"] }, |
| { name: "Retro Comic Flux", tags: ["cpu"] }, |
| { name: "SDXL HS Card Style", tags: ["standard"] }, |
| { name: "Sketch Smudge", tags: ["standard"] }, |
| { name: "Shou Xin", tags: ["standard"] }, |
| { name: "Softserve Anime", tags: ["standard"] }, |
| { name: "SoftPasty Flux", tags: ["cpu"] }, |
| { name: "Soviet Diffusion XL", tags: ["standard"] }, |
| { name: "Sketched Out Manga", tags: ["standard"] }, |
| { name: "Sketch Paint", tags: ["standard"] }, |
| { name: "SLDR FLUX NSFW v2 Studio", tags: ["nsfw"] }, |
| { name: "Selfie Photography", tags: ["standard"] }, |
| { name: "Stable Diffusion 2-1", tags: ["cpu"] }, |
| { name: "Stable Diffusion XL", tags: ["standard"] }, |
| { name: "Stable Diffusion 3 Medium", tags: ["cpu"] }, |
| { name: "Stable Diffusion 3.5 Large", tags: ["cpu"] }, |
| { name: "Stable Diffusion 3.5 Large Turbo", tags: ["cpu"] }, |
| { name: "YiffyMix", tags: ["nsfw"] } |
| ]; |
| |
| populateModelList(models); |
| document.getElementById('selected-model').textContent = models[0].name; |
| } catch (error) { |
| showError('Failed to load models'); |
| console.error('Error loading models:', error); |
| } |
| } |
| |
| |
| async function generateImages(prompt, model, batchCount) { |
| try { |
| |
| const width = parseInt(document.getElementById('width').value); |
| const height = parseInt(document.getElementById('height').value); |
| const steps = parseInt(document.getElementById('steps').value); |
| const seed = parseInt(document.getElementById('seed').value); |
| const cfgScale = parseInt(document.getElementById('cfg').value); |
| const negativePrompt = document.getElementById('negative-prompt').value; |
| const samplingMethod = document.getElementById('sampling-method').value; |
| const stylePreset = document.getElementById('style-preset').value; |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const data = { |
| job_id: `job_${Math.random().toString(36).substr(2, 9)}`, |
| estimated_time: batchCount * 10 |
| }; |
| |
| |
| await pollJobStatus(data.job_id, batchCount); |
| |
| } catch (error) { |
| throw error; |
| } |
| } |
| |
| |
| async function pollJobStatus(jobId, batchCount) { |
| let attempts = 0; |
| const maxAttempts = 30; |
| |
| while (attempts < maxAttempts) { |
| attempts++; |
| |
| try { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const progress = Math.min(100, Math.floor((attempts / maxAttempts) * 100)); |
| const data = { |
| status: progress < 100 ? 'processing' : 'completed', |
| progress: progress, |
| images: progress === 100 ? |
| Array(batchCount).fill().map((_, i) => ({ |
| url: `https://placehold.co/${document.getElementById('width').value}x${document.getElementById('height').value}/8b5cf6/FFFFFF?text=Generated+Image+${i+1}`, |
| prompt: document.getElementById('prompt').value |
| })) : null |
| }; |
| |
| |
| updateProgress(data.progress, `Generating images (${data.status})`); |
| |
| if (data.status === 'completed') { |
| |
| displayGeneratedImages(data.images); |
| return; |
| } |
| |
| |
| await new Promise(resolve => setTimeout(resolve, 2000)); |
| |
| } catch (error) { |
| throw error; |
| } |
| } |
| |
| throw new Error('Job did not complete in the expected time'); |
| } |
| |
| |
| function displayGeneratedImages(images) { |
| const galleryContainer = document.getElementById('gallery-container'); |
| |
| if (!images || images.length === 0) { |
| galleryContainer.innerHTML = '<div id="placeholder-text" class="flex items-center justify-center h-full text-gray-500"><p>No images were generated</p></div>'; |
| return; |
| } |
| |
| galleryContainer.innerHTML = '<div class="grid grid-cols-1 sm:grid-cols-2 gap-4" id="gallery-grid"></div>'; |
| const galleryGrid = document.getElementById('gallery-grid'); |
| |
| images.forEach((image, index) => { |
| const imageContainer = document.createElement('div'); |
| imageContainer.className = 'relative'; |
| imageContainer.innerHTML = ` |
| <img src="${image.url}" alt="Generated image: ${image.prompt}" class="gallery-image w-full h-64 object-cover rounded-md"> |
| <div class="absolute bottom-2 right-2"> |
| <button class="download-btn px-2 py-1 bg-white bg-opacity-80 rounded text-xs hover:bg-opacity-100" data-url="${image.url}"> |
| <i class="fas fa-download"></i> |
| </button> |
| </div> |
| `; |
| |
| |
| imageContainer.querySelector('.download-btn').addEventListener('click', (e) => { |
| e.stopPropagation(); |
| downloadImage(e.target.closest('button').getAttribute('data-url'), `generated-image-${index + 1}.jpg`); |
| }); |
| |
| galleryGrid.appendChild(imageContainer); |
| }); |
| |
| |
| if (images.length > 1) { |
| document.getElementById('download-all').classList.remove('hidden'); |
| document.getElementById('download-all').addEventListener('click', () => { |
| images.forEach((image, index) => { |
| downloadImage(image.url, `generated-image-${index + 1}.jpg`); |
| }); |
| }); |
| } |
| } |
| |
| |
| 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); |
| } |
| |
| |
| function updateProgress(percent, status) { |
| const progressBar = document.getElementById('progress-bar-fill'); |
| const progressPercent = document.getElementById('progress-percent'); |
| const progressStatus = document.getElementById('progress-status'); |
| |
| progressBar.style.width = `${percent}%`; |
| progressPercent.textContent = `${percent}%`; |
| progressStatus.textContent = status; |
| |
| |
| if (percent < 100) { |
| const estimatedTime = Math.round((100 - percent) * 0.3); |
| document.getElementById('time-remaining').textContent = `Estimated time: ${estimatedTime} seconds`; |
| } else { |
| document.getElementById('time-remaining').textContent = 'Completed'; |
| } |
| } |
| |
| |
| function showError(message) { |
| const errorElement = document.getElementById('error-message'); |
| errorElement.textContent = message; |
| errorElement.classList.remove('hidden'); |
| } |
| |
| |
| function clearGallery() { |
| const galleryContainer = document.getElementById('gallery-container'); |
| galleryContainer.innerHTML = '<div id="placeholder-text" class="flex items-center justify-center h-full text-gray-500"><p>Your generated images will appear here</p></div>'; |
| document.getElementById('download-all').classList.add('hidden'); |
| } |
| |
| |
| function populateModelList(models) { |
| const modelListContainer = document.getElementById('model-list'); |
| modelListContainer.innerHTML = ''; |
| |
| models.forEach(model => { |
| const modelCard = document.createElement('div'); |
| |
| |
| let bgColor = 'bg-gray-50'; |
| let borderColor = 'border-gray-200'; |
| |
| if (model.tags.includes('cpu')) { |
| bgColor = 'bg-green-50'; |
| borderColor = 'border-green-200'; |
| } else if (model.tags.includes('nsfw')) { |
| bgColor = 'bg-red-50'; |
| borderColor = 'border-red-200'; |
| } |
| |
| modelCard.className = `model-card p-3 rounded-md cursor-pointer ${bgColor} border ${borderColor}`; |
| |
| |
| let tagsHtml = ''; |
| if (model.tags.includes('cpu')) { |
| tagsHtml += '<span class="model-tag cpu-tag">CPU Optimized</span>'; |
| } |
| if (model.tags.includes('nsfw')) { |
| tagsHtml += '<span class="model-tag nsfw-tag">NSFW</span>'; |
| } |
| if (!model.tags.includes('cpu') && !model.tags.includes('nsfw')) { |
| tagsHtml += '<span class="model-tag standard-tag">Standard</span>'; |
| } |
| |
| modelCard.innerHTML = ` |
| <div class="flex items-center justify-between"> |
| <span class="font-medium">${model.name}</span> |
| <div class="flex"> |
| ${tagsHtml} |
| </div> |
| </div> |
| `; |
| |
| modelCard.addEventListener('click', () => { |
| document.getElementById('selected-model').textContent = model.name; |
| document.getElementById('model-list-container').classList.add('hidden'); |
| }); |
| |
| modelListContainer.appendChild(modelCard); |
| }); |
| } |
| |
| |
| document.querySelectorAll('.tab-button').forEach(button => { |
| button.addEventListener('click', () => { |
| |
| document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active')); |
| document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); |
| |
| |
| button.classList.add('active'); |
| const tabId = button.getAttribute('data-tab'); |
| document.getElementById(tabId).classList.add('active'); |
| }); |
| }); |
| |
| |
| function setupAccordion(buttonId, contentId) { |
| const button = document.getElementById(buttonId); |
| const content = document.getElementById(contentId); |
| |
| button.addEventListener('click', () => { |
| content.classList.toggle('open'); |
| const icon = button.querySelector('i'); |
| icon.classList.toggle('fa-chevron-down'); |
| icon.classList.toggle('fa-chevron-up'); |
| }); |
| } |
| |
| |
| setupAccordion('model-accordion', 'model-list-container'); |
| setupAccordion('featured-models-accordion', 'featured-models-content'); |
| setupAccordion('advanced-settings-accordion', 'advanced-settings-content'); |
| |
| |
| function updateSliderValue(sliderId, displayId) { |
| const slider = document.getElementById(sliderId); |
| const display = document.getElementById(displayId); |
| display.textContent = slider.value; |
| |
| slider.addEventListener('input', () => { |
| display.textContent = slider.value; |
| }); |
| } |
| |
| |
| updateSliderValue('width', 'width-value'); |
| updateSliderValue('height', 'height-value'); |
| updateSliderValue('steps', 'steps-value'); |
| updateSliderValue('cfg', 'cfg-value'); |
| |
| |
| document.getElementById('generate-btn').addEventListener('click', async () => { |
| const prompt = document.getElementById('prompt').value.trim(); |
| const selectedModel = document.getElementById('selected-model').textContent; |
| const batchCount = parseInt(document.getElementById('batch-count').value); |
| |
| if (!prompt) { |
| showError('Please enter a prompt'); |
| return; |
| } |
| |
| |
| const generateBtn = document.getElementById('generate-btn'); |
| const generateText = document.getElementById('generate-text'); |
| const loadingSpinner = document.getElementById('loading-spinner'); |
| const progressContainer = document.getElementById('generation-progress'); |
| |
| generateText.textContent = 'Generating...'; |
| loadingSpinner.classList.remove('hidden'); |
| generateBtn.disabled = true; |
| progressContainer.classList.remove('hidden'); |
| |
| |
| document.getElementById('error-message').classList.add('hidden'); |
| |
| try { |
| |
| clearGallery(); |
| |
| |
| updateProgress(0, 'Initializing model...'); |
| |
| |
| await generateImages(prompt, selectedModel, batchCount); |
| |
| } catch (error) { |
| showError(error.message); |
| } finally { |
| |
| generateText.textContent = 'Generate Image'; |
| loadingSpinner.classList.add('hidden'); |
| generateBtn.disabled = false; |
| } |
| }); |
| |
| |
| document.getElementById('model-search').addEventListener('input', (e) => { |
| const searchTerm = e.target.value.toLowerCase(); |
| const filteredModels = allModels.filter(model => |
| model.name.toLowerCase().includes(searchTerm) |
| ); |
| populateModelList(filteredModels); |
| }); |
| |
| |
| document.addEventListener('DOMContentLoaded', checkApiStatus); |
| </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=SIMCEEHALO/less-steep-learning-curve" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |