ZeroPointMonkey-bot
patch: replace app.py with 2509 fork's Server() surface + 66 NSFW LoRAs, retarget Qwen-Image-Edit-2511 + Rapid-AIO-V19
3212582 | <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Qwen-Image-Edit + Lora stack</title> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
| <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;700&family=Plus+Jakarta+Sans:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <script src="https://unpkg.com/lucide@latest"></script> | |
| <style> | |
| :root { | |
| --accent: #6366f1; | |
| --accent-glow: rgba(99, 102, 241, 0.4); | |
| --bg: #030712; | |
| --surface: #111827; | |
| --surface-hover: #1f2937; | |
| --border: #374151; | |
| --text: #f9fafb; | |
| --text-muted: #9ca3af; | |
| --glass: rgba(17, 24, 39, 0.7); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| font-family: 'Plus Jakarta Sans', sans-serif; | |
| } | |
| body { | |
| background-color: var(--bg); | |
| color: var(--text); | |
| min-height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| background-image: | |
| radial-gradient(circle at 0% 0%, rgba(99, 102, 241, 0.08) 0%, transparent 40%), | |
| radial-gradient(circle at 100% 100%, rgba(139, 92, 246, 0.08) 0%, transparent 40%); | |
| } | |
| /* --- Header --- */ | |
| header { | |
| padding: 1.5rem 2rem; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| backdrop-filter: blur(10px); | |
| position: sticky; | |
| top: 0; | |
| z-index: 50; | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .logo { | |
| font-family: 'Outfit', sans-serif; | |
| font-size: 1.25rem; | |
| font-weight: 700; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| letter-spacing: -0.02em; | |
| } | |
| .logo i { | |
| color: var(--accent); | |
| filter: drop-shadow(0 0 8px var(--accent-glow)); | |
| } | |
| /* --- Main Content --- */ | |
| main { | |
| flex: 1; | |
| max-width: 1000px; | |
| width: 100%; | |
| margin: 0 auto; | |
| padding: 3rem 1.5rem; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 2rem; | |
| } | |
| .hero-section { | |
| text-align: center; | |
| margin-bottom: 1rem; | |
| } | |
| .hero-section h1 { | |
| font-family: 'Outfit', sans-serif; | |
| font-size: 3rem; | |
| font-weight: 700; | |
| margin-bottom: 0.75rem; | |
| background: linear-gradient(to right, #fff, #94a3b8); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .hero-section p { | |
| color: var(--text-muted); | |
| font-size: 1.1rem; | |
| } | |
| /* --- Editor Area --- */ | |
| .editor-card { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 24px; | |
| overflow: hidden; | |
| box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5); | |
| transition: transform 0.3s ease; | |
| } | |
| .drop-zone { | |
| aspect-ratio: 16 / 9; | |
| width: 100%; | |
| max-height: 600px; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| position: relative; | |
| cursor: pointer; | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| background: rgba(0, 0, 0, 0.2); | |
| border-bottom: 1px solid var(--border); | |
| } | |
| .drop-zone:hover { | |
| background: rgba(99, 102, 241, 0.03); | |
| } | |
| .drop-zone.drag-over { | |
| border: 2px dashed var(--accent); | |
| background: rgba(99, 102, 241, 0.05); | |
| } | |
| .upload-ui { | |
| text-align: center; | |
| pointer-events: none; | |
| } | |
| .upload-ui i { | |
| width: 48px; | |
| height: 48px; | |
| color: var(--accent); | |
| margin-bottom: 1rem; | |
| } | |
| .upload-ui p { | |
| font-weight: 500; | |
| margin-bottom: 0.25rem; | |
| } | |
| .upload-ui span { | |
| font-size: 0.875rem; | |
| color: var(--text-muted); | |
| } | |
| #main-preview { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: contain; | |
| display: none; | |
| } | |
| /* --- Input Bar --- */ | |
| .input-area { | |
| padding: 1.5rem; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1rem; | |
| } | |
| .prompt-container { | |
| position: relative; | |
| display: flex; | |
| align-items: center; | |
| } | |
| textarea { | |
| width: 100%; | |
| background: var(--bg); | |
| border: 1px solid var(--border); | |
| border-radius: 16px; | |
| padding: 1.25rem 4rem 1.25rem 1.25rem; | |
| color: white; | |
| font-size: 1rem; | |
| resize: none; | |
| outline: none; | |
| transition: all 0.2s; | |
| line-height: 1.5; | |
| } | |
| textarea:focus { | |
| border-color: var(--accent); | |
| box-shadow: 0 0 0 4px var(--accent-glow); | |
| } | |
| .send-btn { | |
| position: absolute; | |
| right: 0.75rem; | |
| width: 2.75rem; | |
| height: 2.75rem; | |
| background: var(--accent); | |
| border: none; | |
| border-radius: 12px; | |
| color: white; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.2s; | |
| } | |
| .send-btn:hover:not(:disabled) { | |
| transform: scale(1.05); | |
| background: #4f46e5; | |
| } | |
| .send-btn:disabled { | |
| background: var(--border); | |
| opacity: 0.5; | |
| cursor: not-allowed; | |
| } | |
| /* --- Tool Bar --- */ | |
| .toolbar { | |
| display: flex; | |
| justify-content: center; | |
| gap: 0.75rem; | |
| } | |
| .tool-btn { | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| padding: 0.625rem 1.25rem; | |
| border-radius: 100px; | |
| color: var(--text); | |
| font-size: 0.875rem; | |
| font-weight: 500; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| } | |
| .tool-btn:hover { | |
| border-color: var(--text-muted); | |
| background: var(--surface-hover); | |
| } | |
| .tool-btn.active { | |
| border-color: var(--accent); | |
| background: rgba(99, 102, 241, 0.1); | |
| color: var(--accent); | |
| } | |
| /* --- Panel (LoRA/Settings) --- */ | |
| .panel { | |
| display: none; | |
| background: var(--surface); | |
| border: 1px solid var(--border); | |
| border-radius: 20px; | |
| padding: 1.5rem; | |
| margin-top: 1rem; | |
| animation: slideUp 0.3s ease-out; | |
| } | |
| @keyframes slideUp { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .grid-settings { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
| gap: 1.5rem; | |
| } | |
| .setting-item label { | |
| display: block; | |
| font-size: 0.8rem; | |
| font-weight: 600; | |
| color: var(--text-muted); | |
| text-transform: uppercase; | |
| margin-bottom: 0.5rem; | |
| letter-spacing: 0.05em; | |
| } | |
| .slider-row { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| } | |
| input[type="range"] { | |
| flex: 1; | |
| height: 4px; | |
| background: var(--border); | |
| appearance: none; | |
| outline: none; | |
| border-radius: 2px; | |
| } | |
| input[type="range"]::-webkit-slider-thumb { | |
| appearance: none; | |
| width: 16px; | |
| height: 16px; | |
| background: var(--accent); | |
| border-radius: 50%; | |
| cursor: pointer; | |
| box-shadow: 0 0 10px var(--accent-glow); | |
| } | |
| .range-val { | |
| font-size: 0.875rem; | |
| font-weight: 600; | |
| min-width: 2rem; | |
| text-align: right; | |
| } | |
| /* --- LoRA Stack --- */ | |
| .lora-stack { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 1rem; | |
| } | |
| .lora-row { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| background: rgba(0,0,0,0.2); | |
| padding: 0.75rem; | |
| border-radius: 12px; | |
| } | |
| select { | |
| background: var(--bg); | |
| border: 1px solid var(--border); | |
| color: white; | |
| padding: 0.5rem; | |
| border-radius: 8px; | |
| flex: 1; | |
| outline: none; | |
| } | |
| /* --- Status --- */ | |
| .status-overlay { | |
| position: absolute; | |
| inset: 0; | |
| background: rgba(3, 7, 18, 0.8); | |
| backdrop-filter: blur(8px); | |
| display: none; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 100; | |
| } | |
| .spinner { | |
| width: 40px; | |
| height: 40px; | |
| border: 3px solid rgba(99, 102, 241, 0.1); | |
| border-top-color: var(--accent); | |
| border-radius: 50%; | |
| animation: spin 0.8s linear infinite; | |
| margin-bottom: 1rem; | |
| } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| .toast { | |
| position: fixed; | |
| bottom: 2rem; | |
| left: 50%; | |
| transform: translateX(-50%) translateY(200%); | |
| background: var(--accent); | |
| color: white; | |
| padding: 0.75rem 1.5rem; | |
| border-radius: 100px; | |
| font-weight: 500; | |
| box-shadow: 0 10px 25px rgba(0,0,0,0.3); | |
| transition: all 0.3s cubic-bezier(0.18, 0.89, 0.32, 1.28); | |
| z-index: 1000; | |
| } | |
| .toast.show { transform: translateX(-50%) translateY(0); } | |
| /* --- Actions --- */ | |
| .actions-bar { | |
| display: none; | |
| justify-content: center; | |
| gap: 1rem; | |
| margin-top: 1rem; | |
| } | |
| .btn-ghost { | |
| background: transparent; | |
| border: 1px solid var(--border); | |
| color: var(--text); | |
| padding: 0.5rem 1rem; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| transition: all 0.2s; | |
| } | |
| .btn-ghost:hover { | |
| background: var(--surface-hover); | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <header> | |
| <div class="logo"> | |
| <i data-lucide="sparkle"></i> | |
| Qwen-Image-Edit | |
| </div> | |
| </header> | |
| <main> | |
| <section class="hero-section"> | |
| <h1>Qwen-Image-Edit + Lora stack</h1> | |
| <p>Base: Qwen-Image-Edit-2509 + Qwen-Image-Edit-Rapid-AIO-V4 Add optional extra LoRAs below — selecting one auto-fills its trigger words.</p> | |
| </section> | |
| <div class="editor-card"> | |
| <div class="drop-zone" id="drop-zone"> | |
| <div id="upload-ui" class="upload-ui"> | |
| <i data-lucide="image"></i> | |
| <p>Drop your image here</p> | |
| <span>or click to browse from device</span> | |
| </div> | |
| <img id="main-preview" alt="Preview"> | |
| <div class="status-overlay" id="status-overlay"> | |
| <div class="spinner"></div> | |
| <p id="status-text">Processing vision...</p> | |
| </div> | |
| </div> | |
| <div class="input-area"> | |
| <div class="prompt-container"> | |
| <textarea id="prompt" rows="1" placeholder="Describe the change... (e.g., 'make the sky a neon purple sunset')"></textarea> | |
| <button class="send-btn" id="run-btn" disabled> | |
| <i data-lucide="arrow-up"></i> | |
| </button> | |
| </div> | |
| <div class="toolbar"> | |
| <button class="tool-btn" id="lora-toggle"> | |
| <i data-lucide="layers"></i> LoRA Stack | |
| </button> | |
| <button class="tool-btn" id="settings-toggle"> | |
| <i data-lucide="settings"></i> Settings | |
| </button> | |
| <button class="tool-btn" id="clear-btn"> | |
| <i data-lucide="trash-2"></i> Reset | |
| </button> | |
| </div> | |
| <!-- LoRA Panel --> | |
| <div class="panel" id="lora-panel"> | |
| <h3 style="font-size: 0.9rem; margin-bottom: 1rem; color: var(--text-muted);">Stack Extra LoRAs</h3> | |
| <div class="lora-stack" id="lora-list"> | |
| <!-- Dynamic LoRAs --> | |
| </div> | |
| </div> | |
| <!-- Settings Panel --> | |
| <div class="panel" id="settings-panel"> | |
| <div class="grid-settings"> | |
| <div class="setting-item"> | |
| <label>Steps</label> | |
| <div class="slider-row"> | |
| <input type="range" id="steps" min="1" max="30" value="4"> | |
| <span class="range-val" id="steps-val">4</span> | |
| </div> | |
| </div> | |
| <div class="setting-item"> | |
| <label>CFG Scale</label> | |
| <div class="slider-row"> | |
| <input type="range" id="cfg" min="1" max="5" step="0.1" value="1.0"> | |
| <span class="range-val" id="cfg-val">1.0</span> | |
| </div> | |
| </div> | |
| <div class="setting-item"> | |
| <label>Seed Control</label> | |
| <div style="display: flex; gap: 0.5rem;"> | |
| <input type="text" id="seed" value="0" disabled style="background: var(--bg); border: 1px solid var(--border); color: white; padding: 0.5rem; border-radius: 8px; width: 100px;"> | |
| <label style="display: flex; align-items: center; gap: 0.5rem; text-transform: none; font-size: 0.75rem; margin: 0; cursor: pointer;"> | |
| <input type="checkbox" id="randomize-seed" checked> Auto | |
| </label> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="actions-bar" id="actions-bar"> | |
| <button class="btn-ghost" id="download-btn"> | |
| <i data-lucide="download"></i> Download | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <input type="file" id="file-input" accept="image/*" style="display: none;"> | |
| <div id="toast" class="toast">Action completed</div> | |
| <script type="module"> | |
| import { Client, handle_file } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"; | |
| let client; | |
| let selectedFile = null; | |
| let loraOptions = []; | |
| // UI Binding | |
| const dropZone = document.getElementById('drop-zone'); | |
| const fileInput = document.getElementById('file-input'); | |
| const uploadUI = document.getElementById('upload-ui'); | |
| const mainPreview = document.getElementById('main-preview'); | |
| const runBtn = document.getElementById('run-btn'); | |
| const loraList = document.getElementById('lora-list'); | |
| const statusOverlay = document.getElementById('status-overlay'); | |
| const statusText = document.getElementById('status-text'); | |
| const toast = document.getElementById('toast'); | |
| const promptInput = document.getElementById('prompt'); | |
| const loraToggle = document.getElementById('lora-toggle'); | |
| const settingsToggle = document.getElementById('settings-toggle'); | |
| const loraPanel = document.getElementById('lora-panel'); | |
| const settingsPanel = document.getElementById('settings-panel'); | |
| const actionsBar = document.getElementById('actions-bar'); | |
| const downloadBtn = document.getElementById('download-btn'); | |
| const clearBtn = document.getElementById('clear-btn'); | |
| lucide.createIcons(); | |
| async function init() { | |
| try { | |
| client = await Client.connect(window.location.origin); | |
| const resp = await fetch('/loras'); | |
| loraOptions = await resp.json(); | |
| renderLoras(3); | |
| } catch (err) { | |
| showToast("Backend connection failed"); | |
| } | |
| } | |
| function renderLoras(count) { | |
| loraList.innerHTML = ''; | |
| for (let i = 0; i < count; i++) { | |
| const div = document.createElement('div'); | |
| div.className = 'lora-row'; | |
| div.innerHTML = ` | |
| <select class="lora-name"> | |
| <option value="None">Select LoRA</option> | |
| ${loraOptions.map(o => `<option value="${o}">${o}</option>`).join('')} | |
| </select> | |
| <input type="range" class="lora-strength" min="0" max="1.5" step="0.05" value="0.75" style="width: 100px;"> | |
| <span class="range-val">0.75</span> | |
| `; | |
| const select = div.querySelector('.lora-name'); | |
| select.onchange = (e) => { | |
| if (e.target.value !== 'None') { | |
| const val = e.target.value; | |
| if (!promptInput.value.includes(val)) { | |
| promptInput.value += (promptInput.value ? ', ' : '') + val; | |
| promptInput.dispatchEvent(new Event('input')); | |
| } | |
| } | |
| }; | |
| loraList.appendChild(div); | |
| } | |
| document.querySelectorAll('.lora-strength').forEach(s => { | |
| s.oninput = (e) => e.target.nextElementSibling.textContent = e.target.value; | |
| }); | |
| } | |
| // Interaction Logic | |
| dropZone.onclick = () => fileInput.click(); | |
| fileInput.onchange = (e) => handleFiles(e.target.files); | |
| dropZone.ondragover = (e) => { e.preventDefault(); dropZone.classList.add('drag-over'); }; | |
| dropZone.ondragleave = () => dropZone.classList.remove('drag-over'); | |
| dropZone.ondrop = (e) => { e.preventDefault(); dropZone.classList.remove('drag-over'); handleFiles(e.dataTransfer.files); }; | |
| function handleFiles(files) { | |
| if (!files[0]) return; | |
| selectedFile = files[0]; | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| mainPreview.src = e.target.result; | |
| mainPreview.style.display = 'block'; | |
| uploadUI.style.display = 'none'; | |
| runBtn.disabled = false; | |
| actionsBar.style.display = 'none'; | |
| }; | |
| reader.readAsDataURL(selectedFile); | |
| } | |
| // Toggles | |
| loraToggle.onclick = () => togglePanel(loraPanel, loraToggle); | |
| settingsToggle.onclick = () => togglePanel(settingsPanel, settingsToggle); | |
| function togglePanel(panel, btn) { | |
| const isOpen = panel.style.display === 'block'; | |
| panel.style.display = isOpen ? 'none' : 'block'; | |
| btn.classList.toggle('active', !isOpen); | |
| } | |
| promptInput.oninput = () => { | |
| promptInput.style.height = 'auto'; | |
| promptInput.style.height = promptInput.scrollHeight + 'px'; | |
| }; | |
| // Params | |
| document.getElementById('steps').oninput = (e) => document.getElementById('steps-val').textContent = e.target.value; | |
| document.getElementById('cfg').oninput = (e) => document.getElementById('cfg-val').textContent = e.target.value; | |
| document.getElementById('randomize-seed').onchange = (e) => document.getElementById('seed').disabled = e.target.checked; | |
| // Run | |
| runBtn.onclick = async () => { | |
| if (!selectedFile) return; | |
| setLoading(true); | |
| const loras = []; | |
| document.querySelectorAll('.lora-row').forEach(r => { | |
| const name = r.querySelector('.lora-name').value; | |
| const strength = parseFloat(r.querySelector('.lora-strength').value); | |
| if (name !== 'None') loras.push({ name, strength }); | |
| }); | |
| try { | |
| const result = await client.predict("/edit_image", { | |
| input_image: handle_file(selectedFile), | |
| prompt: promptInput.value, | |
| seed: parseInt(document.getElementById('seed').value), | |
| randomize_seed: document.getElementById('randomize-seed').checked, | |
| guidance_scale: parseFloat(document.getElementById('cfg').value), | |
| steps: parseInt(document.getElementById('steps').value), | |
| loras: loras | |
| }); | |
| const url = result.data[0].url; | |
| mainPreview.src = url; | |
| actionsBar.style.display = 'flex'; | |
| downloadBtn.onclick = () => { | |
| const a = document.createElement('a'); | |
| a.href = url; a.download = 'qwen_edit.png'; a.click(); | |
| }; | |
| showToast("Magic complete!", "success"); | |
| } catch (err) { | |
| showToast(err.message || "Something went wrong"); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| clearBtn.onclick = () => location.reload(); | |
| function setLoading(isLoading) { | |
| statusOverlay.style.display = isLoading ? 'flex' : 'none'; | |
| runBtn.disabled = isLoading; | |
| statusText.textContent = "Synthesizing your vision..."; | |
| } | |
| function showToast(msg, type = "info") { | |
| toast.textContent = msg; | |
| toast.style.background = type === "success" ? "#10b981" : "#6366f1"; | |
| toast.classList.add('show'); | |
| setTimeout(() => toast.classList.remove('show'), 3000); | |
| } | |
| init(); | |
| </script> | |
| </body> | |
| </html> | |