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); */ };