Spaces:
Running on Zero
Running on Zero
| () => { | |
| function init() { | |
| if (window.__fireRedInitDone) return; | |
| const galleryGrid = document.getElementById('image-gallery-grid'); | |
| const dropZone = document.getElementById('gallery-drop-zone'); | |
| const uploadPrompt = document.getElementById('upload-prompt'); | |
| const uploadClick = document.getElementById('upload-click-area'); | |
| const fileInput = document.getElementById('custom-file-input'); | |
| const btnUpload = document.getElementById('tb-upload'); | |
| const btnRemove = document.getElementById('tb-remove'); | |
| const btnClear = document.getElementById('tb-clear'); | |
| const promptInput = document.getElementById('custom-prompt-input'); | |
| const runBtnEl = document.getElementById('custom-run-btn'); | |
| const imgCountTb = document.getElementById('tb-image-count'); | |
| const imgCountSb = document.getElementById('sb-image-count'); | |
| if (!galleryGrid || !fileInput || !dropZone) { | |
| setTimeout(init, 250); | |
| return; | |
| } | |
| window.__fireRedInitDone = true; | |
| let images = []; | |
| window.__uploadedImages = images; | |
| let selectedIdx = -1; | |
| let toastTimer = null; | |
| function showToast(message, type) { | |
| let toast = document.getElementById('app-toast'); | |
| if (!toast) { | |
| toast = document.createElement('div'); | |
| toast.id = 'app-toast'; | |
| toast.className = 'toast-notification'; | |
| toast.innerHTML = '<span class="toast-icon"></span><span class="toast-text"></span>'; | |
| document.body.appendChild(toast); | |
| } | |
| const icon = toast.querySelector('.toast-icon'); | |
| const text = toast.querySelector('.toast-text'); | |
| toast.className = 'toast-notification ' + (type || 'error'); | |
| if (type === 'warning') icon.textContent = '⚠'; | |
| else if (type === 'info') icon.textContent = 'ℹ'; | |
| else icon.textContent = '✗'; | |
| text.textContent = message; | |
| if (toastTimer) clearTimeout(toastTimer); | |
| void toast.offsetWidth; | |
| toast.classList.add('visible'); | |
| toastTimer = setTimeout(() => toast.classList.remove('visible'), 3500); | |
| } | |
| window.__showToast = showToast; | |
| function flashPromptError() { | |
| if (!promptInput) return; | |
| promptInput.classList.add('error-flash'); | |
| promptInput.focus(); | |
| setTimeout(() => promptInput.classList.remove('error-flash'), 800); | |
| } | |
| function setGradioValue(containerId, value) { | |
| const container = document.getElementById(containerId); | |
| if (!container) return; | |
| container.querySelectorAll('input, textarea').forEach(el => { | |
| if (el.type === 'file' || el.type === 'range' || el.type === 'checkbox') return; | |
| const proto = el.tagName === 'TEXTAREA' ? HTMLTextAreaElement.prototype : HTMLInputElement.prototype; | |
| const ns = Object.getOwnPropertyDescriptor(proto, 'value'); | |
| if (ns && ns.set) { | |
| ns.set.call(el, value); | |
| el.dispatchEvent(new Event('input', {bubbles:true, composed:true})); | |
| el.dispatchEvent(new Event('change', {bubbles:true, composed:true})); | |
| } | |
| }); | |
| } | |
| window.__setGradioValue = setGradioValue; | |
| function syncImagesToGradio() { | |
| window.__uploadedImages = images; | |
| const b64Array = images.map(img => img.b64); | |
| setGradioValue('hidden-images-b64', JSON.stringify(b64Array)); | |
| updateCounts(); | |
| } | |
| function syncPromptToGradio() { | |
| if (promptInput) setGradioValue('prompt-gradio-input', promptInput.value); | |
| } | |
| function updateCounts() { | |
| const n = images.length; | |
| const txt = n > 0 ? n + ' image' + (n > 1 ? 's' : '') : 'No images'; | |
| if (imgCountTb) imgCountTb.textContent = txt; | |
| if (imgCountSb) imgCountSb.textContent = n > 0 ? txt + ' uploaded' : 'No images uploaded'; | |
| } | |
| function addImage(b64, name) { | |
| images.push({id: Date.now() + Math.random(), b64: b64, name: name}); | |
| renderGallery(); | |
| syncImagesToGradio(); | |
| } | |
| window.__addImage = addImage; | |
| function removeImage(idx) { | |
| images.splice(idx, 1); | |
| if (selectedIdx === idx) selectedIdx = -1; | |
| else if (selectedIdx > idx) selectedIdx--; | |
| renderGallery(); | |
| syncImagesToGradio(); | |
| } | |
| function clearAll() { | |
| images = []; | |
| window.__uploadedImages = images; | |
| selectedIdx = -1; | |
| renderGallery(); | |
| syncImagesToGradio(); | |
| } | |
| window.__clearAll = clearAll; | |
| function selectImage(idx) { | |
| selectedIdx = (selectedIdx === idx) ? -1 : idx; | |
| renderGallery(); | |
| } | |
| function renderGallery() { | |
| if (images.length === 0) { | |
| galleryGrid.innerHTML = ''; | |
| galleryGrid.style.display = 'none'; | |
| if (uploadPrompt) uploadPrompt.style.display = ''; | |
| return; | |
| } | |
| if (uploadPrompt) uploadPrompt.style.display = 'none'; | |
| galleryGrid.style.display = 'grid'; | |
| let html = ''; | |
| images.forEach((img, i) => { | |
| const sel = i === selectedIdx ? ' selected' : ''; | |
| html += '<div class="gallery-thumb' + sel + '" data-idx="' + i + '">' | |
| + '<img src="' + img.b64 + '" alt="' + (img.name||'image') + '">' | |
| + '<span class="thumb-badge">#' + (i+1) + '</span>' | |
| + '<button class="thumb-remove" data-remove="' + i + '">✕</button>' | |
| + '</div>'; | |
| }); | |
| html += '<div class="gallery-add-card" id="gallery-add-card">' | |
| + '<span class="add-icon">+</span>' | |
| + '<span class="add-text">Add</span>' | |
| + '</div>'; | |
| galleryGrid.innerHTML = html; | |
| galleryGrid.querySelectorAll('.gallery-thumb').forEach(thumb => { | |
| thumb.addEventListener('click', (e) => { | |
| if (e.target.closest('.thumb-remove')) return; | |
| selectImage(parseInt(thumb.dataset.idx)); | |
| }); | |
| }); | |
| galleryGrid.querySelectorAll('.thumb-remove').forEach(btn => { | |
| btn.addEventListener('click', (e) => { | |
| e.stopPropagation(); | |
| removeImage(parseInt(btn.dataset.remove)); | |
| }); | |
| }); | |
| const addCard = document.getElementById('gallery-add-card'); | |
| if (addCard) addCard.addEventListener('click', () => fileInput.click()); | |
| } | |
| function processFiles(files) { | |
| Array.from(files).forEach(file => { | |
| if (!file.type.startsWith('image/')) return; | |
| const reader = new FileReader(); | |
| reader.onload = (e) => addImage(e.target.result, file.name); | |
| reader.readAsDataURL(file); | |
| }); | |
| } | |
| fileInput.addEventListener('change', (e) => { processFiles(e.target.files); e.target.value = ''; }); | |
| if (uploadClick) uploadClick.addEventListener('click', () => fileInput.click()); | |
| if (btnUpload) btnUpload.addEventListener('click', () => fileInput.click()); | |
| if (btnRemove) btnRemove.addEventListener('click', () => { | |
| if (selectedIdx >= 0 && selectedIdx < images.length) removeImage(selectedIdx); | |
| }); | |
| if (btnClear) btnClear.addEventListener('click', clearAll); | |
| dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('drag-over'); }); | |
| dropZone.addEventListener('dragleave', (e) => { e.preventDefault(); dropZone.classList.remove('drag-over'); }); | |
| dropZone.addEventListener('drop', (e) => { | |
| e.preventDefault(); dropZone.classList.remove('drag-over'); | |
| if (e.dataTransfer.files.length) processFiles(e.dataTransfer.files); | |
| }); | |
| if (promptInput) promptInput.addEventListener('input', (e) => { if (!e.isComposing) syncPromptToGradio(); }); | |
| window.__setPrompt = function(text) { | |
| if (promptInput) { promptInput.value = text; syncPromptToGradio(); } | |
| }; | |
| document.querySelectorAll('.example-card[data-idx]').forEach(card => { | |
| card.addEventListener('click', () => { | |
| const idx = card.getAttribute('data-idx'); | |
| document.querySelectorAll('.example-card.loading').forEach(c => c.classList.remove('loading')); | |
| card.classList.add('loading'); | |
| showToast('Loading example...', 'info'); | |
| setGradioValue('example-result-data', ''); | |
| setGradioValue('example-idx-input', idx); | |
| setTimeout(() => { | |
| const btn = document.getElementById('example-load-btn'); | |
| if (btn) { | |
| const b = btn.querySelector('button'); | |
| if (b) b.click(); else btn.click(); | |
| } | |
| }, 150); | |
| setTimeout(() => card.classList.remove('loading'), 12000); | |
| }); | |
| }); | |
| function syncSlider(customId, gradioId) { | |
| const slider = document.getElementById(customId); | |
| const valSpan = document.getElementById(customId + '-val'); | |
| if (!slider) return; | |
| slider.addEventListener('input', () => { | |
| if (valSpan) valSpan.textContent = slider.value; | |
| const container = document.getElementById(gradioId); | |
| if (!container) return; | |
| container.querySelectorAll('input[type="range"],input[type="number"]').forEach(el => { | |
| const ns = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); | |
| if (ns && ns.set) { | |
| ns.set.call(el, slider.value); | |
| el.dispatchEvent(new Event('input', {bubbles:true, composed:true})); | |
| el.dispatchEvent(new Event('change', {bubbles:true, composed:true})); | |
| } | |
| }); | |
| }); | |
| } | |
| syncSlider('custom-seed', 'gradio-seed'); | |
| syncSlider('custom-guidance', 'gradio-guidance'); | |
| syncSlider('custom-steps', 'gradio-steps'); | |
| syncSlider('custom-gpu-duration', 'gradio-gpu-duration'); | |
| const randCheck = document.getElementById('custom-randomize'); | |
| if (randCheck) { | |
| randCheck.addEventListener('change', () => { | |
| const container = document.getElementById('gradio-randomize'); | |
| if (!container) return; | |
| const cb = container.querySelector('input[type="checkbox"]'); | |
| if (cb && cb.checked !== randCheck.checked) cb.click(); | |
| }); | |
| } | |
| function showLoader() { | |
| const l = document.getElementById('output-loader'); | |
| if (l) l.classList.add('active'); | |
| const sb = document.querySelector('.sb-fixed'); | |
| if (sb) sb.textContent = 'Processing...'; | |
| } | |
| function hideLoader() { | |
| const l = document.getElementById('output-loader'); | |
| if (l) l.classList.remove('active'); | |
| const sb = document.querySelector('.sb-fixed'); | |
| if (sb) sb.textContent = 'Done'; | |
| } | |
| window.__showLoader = showLoader; | |
| window.__hideLoader = hideLoader; | |
| function validateBeforeRun() { | |
| const promptVal = promptInput ? promptInput.value.trim() : ''; | |
| const hasImages = images.length > 0; | |
| if (!hasImages && !promptVal) { showToast('Please upload an image and enter a prompt', 'error'); flashPromptError(); return false; } | |
| if (!hasImages) { showToast('Please upload at least one image', 'error'); return false; } | |
| if (!promptVal) { showToast('Please enter an edit prompt', 'warning'); flashPromptError(); return false; } | |
| return true; | |
| } | |
| window.__clickGradioRunBtn = function() { | |
| if (!validateBeforeRun()) return; | |
| syncPromptToGradio(); syncImagesToGradio(); showLoader(); | |
| setTimeout(() => { | |
| const gradioBtn = document.getElementById('gradio-run-btn'); | |
| if (!gradioBtn) return; | |
| const btn = gradioBtn.querySelector('button'); | |
| if (btn) btn.click(); else gradioBtn.click(); | |
| }, 200); | |
| }; | |
| if (runBtnEl) runBtnEl.addEventListener('click', () => window.__clickGradioRunBtn()); | |
| renderGallery(); | |
| updateCounts(); | |
| } | |
| init(); | |
| } | |