const socket = io(); var priorResultsLoadState = { page: 0, pages: 1, per_page: 10, total: 20, offset: 0, // number of items generated since last load loading: false, initialized: false, }; function loadPriorResults() { // Fix next page by offset let offsetPages = priorResultsLoadState.offset / priorResultsLoadState.per_page; priorResultsLoadState.page += offsetPages; priorResultsLoadState.pages += offsetPages; priorResultsLoadState.total += priorResultsLoadState.offset; priorResultsLoadState.offset = 0; if (priorResultsLoadState.loading) { return; } if (priorResultsLoadState.page >= priorResultsLoadState.pages) { return; // Nothing more to load } // Load priorResultsLoadState.loading = true; let url = new URL('/api/images', document.baseURI); url.searchParams.append( 'page', priorResultsLoadState.initialized ? priorResultsLoadState.page + 1 : priorResultsLoadState.page ); url.searchParams.append('per_page', priorResultsLoadState.per_page); fetch(url.href, { method: 'GET', headers: new Headers({ 'content-type': 'application/json' }), }) .then((response) => response.json()) .then((data) => { priorResultsLoadState.page = data.page; priorResultsLoadState.pages = data.pages; priorResultsLoadState.per_page = data.per_page; priorResultsLoadState.total = data.total; data.items.forEach(function (dreamId, index) { let src = 'api/images/' + dreamId; fetch('/api/images/' + dreamId + '/metadata', { method: 'GET', headers: new Headers({ 'content-type': 'application/json' }), }) .then((response) => response.json()) .then((metadata) => { let seed = metadata.seed || 0; // TODO: Parse old metadata appendOutput(src, seed, metadata, true); }); }); // Load until page is full if (!priorResultsLoadState.initialized) { if (document.body.scrollHeight <= window.innerHeight) { loadPriorResults(); } } }) .finally(() => { priorResultsLoadState.loading = false; priorResultsLoadState.initialized = true; }); } function resetForm() { var form = document.getElementById('generate-form'); form.querySelector('fieldset').removeAttribute('disabled'); } function initProgress(totalSteps, showProgressImages) { // TODO: Progress could theoretically come from multiple jobs at the same time (in the future) let progressSectionEle = document.querySelector('#progress-section'); progressSectionEle.style.display = 'initial'; let progressEle = document.querySelector('#progress-bar'); progressEle.setAttribute('max', totalSteps); let progressImageEle = document.querySelector('#progress-image'); progressImageEle.src = BLANK_IMAGE_URL; progressImageEle.style.display = showProgressImages ? 'initial' : 'none'; } function setProgress(step, totalSteps, src) { let progressEle = document.querySelector('#progress-bar'); progressEle.setAttribute('value', step); if (src) { let progressImageEle = document.querySelector('#progress-image'); progressImageEle.src = src; } } function resetProgress(hide = true) { if (hide) { let progressSectionEle = document.querySelector('#progress-section'); progressSectionEle.style.display = 'none'; } let progressEle = document.querySelector('#progress-bar'); progressEle.setAttribute('value', 0); } function toBase64(file) { return new Promise((resolve, reject) => { const r = new FileReader(); r.readAsDataURL(file); r.onload = () => resolve(r.result); r.onerror = (error) => reject(error); }); } function ondragdream(event) { let dream = event.target.dataset.dream; event.dataTransfer.setData('dream', dream); } function seedClick(event) { // Get element var image = event.target.closest('figure').querySelector('img'); var dream = JSON.parse(decodeURIComponent(image.dataset.dream)); let form = document.querySelector('#generate-form'); for (const [k, v] of new FormData(form)) { if (k == 'initimg') { continue; } let formElem = form.querySelector(`*[name=${k}]`); formElem.value = dream[k] !== undefined ? dream[k] : formElem.defaultValue; } document.querySelector('#seed').value = dream.seed; document.querySelector('#iterations').value = 1; // Reset to 1 iteration since we clicked a single image (not a full job) // NOTE: leaving this manual for the user for now - it was very confusing with this behavior // document.querySelector("#with_variations").value = variations || ''; // if (document.querySelector("#variation_amount").value <= 0) { // document.querySelector("#variation_amount").value = 0.2; // } saveFields(document.querySelector('#generate-form')); } function appendOutput(src, seed, config, toEnd = false) { let outputNode = document.createElement('figure'); let altText = seed.toString() + ' | ' + config.prompt; // img needs width and height for lazy loading to work // TODO: store the full config in a data attribute on the image? const figureContents = ` ${altText}
${seed}
`; outputNode.innerHTML = figureContents; if (toEnd) { document.querySelector('#results').append(outputNode); } else { document.querySelector('#results').prepend(outputNode); } document.querySelector('#no-results-message')?.remove(); } function saveFields(form) { for (const [k, v] of new FormData(form)) { if (typeof v !== 'object') { // Don't save 'file' type localStorage.setItem(k, v); } } } function loadFields(form) { for (const [k, v] of new FormData(form)) { const item = localStorage.getItem(k); if (item != null) { form.querySelector(`*[name=${k}]`).value = item; } } } function clearFields(form) { localStorage.clear(); let prompt = form.prompt.value; form.reset(); form.prompt.value = prompt; } const BLANK_IMAGE_URL = 'data:image/svg+xml,'; async function generateSubmit(form) { // Convert file data to base64 // TODO: Should probably uplaod files with formdata or something, and store them in the backend? let formData = Object.fromEntries(new FormData(form)); if (!formData.enable_generate && !formData.enable_init_image) { gen_label = document.querySelector('label[for=enable_generate]').innerHTML; initimg_label = document.querySelector( 'label[for=enable_init_image]' ).innerHTML; alert(`Error: one of "${gen_label}" or "${initimg_label}" must be set`); } formData.initimg_name = formData.initimg.name; formData.initimg = formData.initimg.name !== '' ? await toBase64(formData.initimg) : null; // Evaluate all checkboxes let checkboxes = form.querySelectorAll('input[type=checkbox]'); checkboxes.forEach(function (checkbox) { if (checkbox.checked) { formData[checkbox.name] = 'true'; } }); let strength = formData.strength; let totalSteps = formData.initimg ? Math.floor(strength * formData.steps) : formData.steps; let showProgressImages = formData.progress_images; // Set enabling flags // Initialize the progress bar initProgress(totalSteps, showProgressImages); // POST, use response to listen for events fetch(form.action, { method: form.method, headers: new Headers({ 'content-type': 'application/json' }), body: JSON.stringify(formData), }) .then((response) => response.json()) .then((data) => { var jobId = data.jobId; socket.emit('join_room', { room: jobId }); }); form.querySelector('fieldset').setAttribute('disabled', ''); } function fieldSetEnableChecked(event) { cb = event.target; fields = cb.closest('fieldset'); fields.disabled = !cb.checked; } // Socket listeners socket.on('job_started', (data) => {}); socket.on('dream_result', (data) => { var jobId = data.jobId; var dreamId = data.dreamId; var dreamRequest = data.dreamRequest; var src = 'api/images/' + dreamId; priorResultsLoadState.offset += 1; appendOutput(src, dreamRequest.seed, dreamRequest); resetProgress(false); }); socket.on('dream_progress', (data) => { // TODO: it'd be nice if we could get a seed reported here, but the generator would need to be updated var step = data.step; var totalSteps = data.totalSteps; var jobId = data.jobId; var dreamId = data.dreamId; var progressType = data.progressType; if (progressType === 'GENERATION') { var src = data.hasProgressImage ? 'api/intermediates/' + dreamId + '/' + step : null; setProgress(step, totalSteps, src); } else if (progressType === 'UPSCALING_STARTED') { // step and totalSteps are used for upscale count on this message document.getElementById('processing_cnt').textContent = step; document.getElementById('processing_total').textContent = totalSteps; document.getElementById('scaling-inprocess-message').style.display = 'block'; } else if (progressType == 'UPSCALING_DONE') { document.getElementById('scaling-inprocess-message').style.display = 'none'; } }); socket.on('job_canceled', (data) => { resetForm(); resetProgress(); }); socket.on('job_done', (data) => { jobId = data.jobId; socket.emit('leave_room', { room: jobId }); resetForm(); resetProgress(); }); window.onload = async () => { document.querySelector('#prompt').addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { const form = e.target.form; generateSubmit(form); } }); document.querySelector('#generate-form').addEventListener('submit', (e) => { e.preventDefault(); const form = e.target; generateSubmit(form); }); document.querySelector('#generate-form').addEventListener('change', (e) => { saveFields(e.target.form); }); document.querySelector('#reset-seed').addEventListener('click', (e) => { document.querySelector('#seed').value = 0; saveFields(e.target.form); }); document.querySelector('#reset-all').addEventListener('click', (e) => { clearFields(e.target.form); }); document.querySelector('#remove-image').addEventListener('click', (e) => { initimg.value = null; }); loadFields(document.querySelector('#generate-form')); document.querySelector('#cancel-button').addEventListener('click', () => { fetch('/api/cancel').catch((e) => { console.error(e); }); }); document.documentElement.addEventListener('keydown', (e) => { if (e.key === 'Escape') fetch('/api/cancel').catch((err) => { console.error(err); }); }); if (!config.gfpgan_model_exists) { document.querySelector('#gfpgan').style.display = 'none'; } window.addEventListener('scroll', () => { if (window.innerHeight + window.pageYOffset >= document.body.offsetHeight) { loadPriorResults(); } }); // Enable/disable forms by checkboxes document .querySelectorAll('legend > input[type=checkbox]') .forEach(function (cb) { cb.addEventListener('change', fieldSetEnableChecked); fieldSetEnableChecked({ target: cb }); }); // Load some of the previous results loadPriorResults(); // Image drop/upload WIP /* let drop = document.getElementById('dropper'); function ondrop(event) { let dreamData = event.dataTransfer.getData('dream'); if (dreamData) { var dream = JSON.parse(decodeURIComponent(dreamData)); alert(dream.dreamId); } }; function ondragenter(event) { event.preventDefault(); }; function ondragover(event) { event.preventDefault(); }; function ondragleave(event) { } drop.addEventListener('drop', ondrop); drop.addEventListener('dragenter', ondragenter); drop.addEventListener('dragover', ondragover); drop.addEventListener('dragleave', ondragleave); */ };