-
+
+
+
+
+
+
+
✖
+
+
+
+ Postprocessing...1/3
-
-
+
+
+
+
+
+
+
diff --git a/static/dream_web/index.js b/static/dream_web/index.js
index ac68034920..5de690297d 100644
--- a/static/dream_web/index.js
+++ b/static/dream_web/index.js
@@ -1,3 +1,109 @@
+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();
@@ -7,17 +113,41 @@ function toBase64(file) {
});
}
-function appendOutput(src, seed, config) {
- let outputNode = document.createElement("figure");
-
- let variations = config.with_variations;
- if (config.variation_amount > 0) {
- variations = (variations ? variations + ',' : '') + seed + ':' + config.variation_amount;
+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;
}
- let baseseed = (config.with_variations || config.variation_amount > 0) ? config.seed : seed;
- let altText = baseseed + ' | ' + (variations ? variations + ' | ' : '') + config.prompt;
+
+ 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 = `
+ height="256"
+ draggable="true"
+ ondragstart="ondragdream(event, this)"
+ data-dream="${encodeURIComponent(JSON.stringify(config))}"
+ data-dreamId="${encodeURIComponent(config.dreamId)}">
-
${seed}
+
${seed}
`;
outputNode.innerHTML = figureContents;
- let figcaption = outputNode.querySelector('figcaption');
- // Reload image config
- figcaption.addEventListener('click', () => {
- let form = document.querySelector("#generate-form");
- for (const [k, v] of new FormData(form)) {
- if (k == 'initimg') { continue; }
- form.querySelector(`*[name=${k}]`).value = config[k];
- }
-
- document.querySelector("#seed").value = baseseed;
- 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"));
- });
-
- document.querySelector("#results").prepend(outputNode);
+ if (toEnd) {
+ document.querySelector("#results").append(outputNode);
+ } else {
+ document.querySelector("#results").prepend(outputNode);
+ }
+ document.querySelector("#no-results-message")?.remove();
}
function saveFields(form) {
@@ -79,93 +200,109 @@ function clearFields(form) {
const BLANK_IMAGE_URL = 'data:image/svg+xml,
';
async function generateSubmit(form) {
- const prompt = document.querySelector("#prompt").value;
-
// 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;
- let strength = formData.strength;
- let totalSteps = formData.initimg ? Math.floor(strength * formData.steps) : formData.steps;
-
- 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 = {}.hasOwnProperty.call(formData, 'progress_images') ? 'initial': 'none';
-
- // Post as JSON, using Fetch streaming to get results
- fetch(form.action, {
- method: form.method,
- body: JSON.stringify(formData),
- }).then(async (response) => {
- const reader = response.body.getReader();
-
- let noOutputs = true;
- while (true) {
- let {value, done} = await reader.read();
- value = new TextDecoder().decode(value);
- if (done) {
- progressSectionEle.style.display = 'none';
- break;
- }
-
- for (let event of value.split('\n').filter(e => e !== '')) {
- const data = JSON.parse(event);
-
- if (data.event === 'result') {
- noOutputs = false;
- appendOutput(data.url, data.seed, data.config);
- progressEle.setAttribute('value', 0);
- progressEle.setAttribute('max', totalSteps);
- } else if (data.event === 'upscaling-started') {
- document.getElementById("processing_cnt").textContent=data.processed_file_cnt;
- document.getElementById("scaling-inprocess-message").style.display = "block";
- } else if (data.event === 'upscaling-done') {
- document.getElementById("scaling-inprocess-message").style.display = "none";
- } else if (data.event === 'step') {
- progressEle.setAttribute('value', data.step);
- if (data.url) {
- progressImageEle.src = data.url;
- }
- } else if (data.event === 'canceled') {
- // avoid alerting as if this were an error case
- noOutputs = false;
- }
- }
- }
-
- // Re-enable form, remove no-results-message
- form.querySelector('fieldset').removeAttribute('disabled');
- document.querySelector("#prompt").value = prompt;
- document.querySelector('progress').setAttribute('value', '0');
-
- if (noOutputs) {
- alert("Error occurred while generating.");
- }
+ // 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 });
});
- // Disable form while generating
form.querySelector('fieldset').setAttribute('disabled','');
- document.querySelector("#prompt").value = `Generating: "${prompt}"`;
}
-async function fetchRunLog() {
- try {
- let response = await fetch('/run_log.json')
- const data = await response.json();
- for(let item of data.run_log) {
- appendOutput(item.url, item.seed, item);
- }
- } catch (e) {
- console.error(e);
- }
+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) {
@@ -183,7 +320,7 @@ window.onload = async () => {
saveFields(e.target.form);
});
document.querySelector("#reset-seed").addEventListener('click', (e) => {
- document.querySelector("#seed").value = -1;
+ document.querySelector("#seed").value = 0;
saveFields(e.target.form);
});
document.querySelector("#reset-all").addEventListener('click', (e) => {
@@ -195,13 +332,13 @@ window.onload = async () => {
loadFields(document.querySelector("#generate-form"));
document.querySelector('#cancel-button').addEventListener('click', () => {
- fetch('/cancel').catch(e => {
+ fetch('/api/cancel').catch(e => {
console.error(e);
});
});
document.documentElement.addEventListener('keydown', (e) => {
if (e.key === "Escape")
- fetch('/cancel').catch(err => {
+ fetch('/api/cancel').catch(err => {
console.error(err);
});
});
@@ -209,5 +346,51 @@ window.onload = async () => {
if (!config.gfpgan_model_exists) {
document.querySelector("#gfpgan").style.display = 'none';
}
- await fetchRunLog()
+
+ 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);
+ */
};