const SOUND_ENABLED_KEY = "soundEnabled"
const SAVE_TO_DISK_KEY = "saveToDisk"
const USE_CPU_KEY = "useCPU"
const USE_FULL_PRECISION_KEY = "useFullPrecision"
const USE_TURBO_MODE_KEY = "useTurboMode"
const DISK_PATH_KEY = "diskPath"
const ADVANCED_PANEL_OPEN_KEY = "advancedPanelOpen"
const MODIFIERS_PANEL_OPEN_KEY = "modifiersPanelOpen"
const USE_FACE_CORRECTION_KEY = "useFaceCorrection"
const USE_UPSCALING_KEY = "useUpscaling"
const SHOW_ONLY_FILTERED_IMAGE_KEY = "showOnlyFilteredImage"
const STREAM_IMAGE_PROGRESS_KEY = "streamImageProgress"
const HEALTH_PING_INTERVAL = 5 // seconds
const MAX_INIT_IMAGE_DIMENSION = 768
const IMAGE_REGEX = new RegExp('data:image/[A-Za-z]+;base64')
let sessionId = new Date().getTime()
let promptField = document.querySelector('#prompt')
let numOutputsTotalField = document.querySelector('#num_outputs_total')
let numOutputsParallelField = document.querySelector('#num_outputs_parallel')
let numInferenceStepsField = document.querySelector('#num_inference_steps')
let guidanceScaleSlider = document.querySelector('#guidance_scale_slider')
let guidanceScaleField = document.querySelector('#guidance_scale')
let randomSeedField = document.querySelector("#random_seed")
let seedField = document.querySelector('#seed')
let widthField = document.querySelector('#width')
let heightField = document.querySelector('#height')
let initImageSelector = document.querySelector("#init_image")
let initImagePreview = document.querySelector("#init_image_preview")
let maskImageSelector = document.querySelector("#mask")
let maskImagePreview = document.querySelector("#mask_preview")
let turboField = document.querySelector('#turbo')
let useCPUField = document.querySelector('#use_cpu')
let useFullPrecisionField = document.querySelector('#use_full_precision')
let saveToDiskField = document.querySelector('#save_to_disk')
let diskPathField = document.querySelector('#diskPath')
// let allowNSFWField = document.querySelector("#allow_nsfw")
let useBetaChannelField = document.querySelector("#use_beta_channel")
let promptStrengthSlider = document.querySelector('#prompt_strength_slider')
let promptStrengthField = document.querySelector('#prompt_strength')
let samplerField = document.querySelector('#sampler')
let samplerSelectionContainer = document.querySelector("#samplerSelection")
let useFaceCorrectionField = document.querySelector("#use_face_correction")
let useUpscalingField = document.querySelector("#use_upscale")
let upscaleModelField = document.querySelector("#upscale_model")
let showOnlyFilteredImageField = document.querySelector("#show_only_filtered_image")
let updateBranchLabel = document.querySelector("#updateBranchLabel")
let streamImageProgressField = document.querySelector("#stream_image_progress")
let makeImageBtn = document.querySelector('#makeImage')
let stopImageBtn = document.querySelector('#stopImage')
let imagesContainer = document.querySelector('#current-images')
let initImagePreviewContainer = document.querySelector('#init_image_preview_container')
let initImageClearBtn = document.querySelector('.init_image_clear')
let promptStrengthContainer = document.querySelector('#prompt_strength_container')
// let maskSetting = document.querySelector('#editor-inputs-mask_setting')
// let maskImagePreviewContainer = document.querySelector('#mask_preview_container')
// let maskImageClearBtn = document.querySelector('#mask_clear')
let maskSetting = document.querySelector('#enable_mask')
let editorModifierEntries = document.querySelector('#editor-modifiers-entries')
let editorModifierTagsList = document.querySelector('#editor-inputs-tags-list')
let editorTagsContainer = document.querySelector('#editor-inputs-tags-container')
let previewImageField = document.querySelector('#preview-image')
previewImageField.onchange = () => changePreviewImages(previewImageField.value);
let modifierCardSizeSlider = document.querySelector('#modifier-card-size-slider')
modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value);
let previewPrompt = document.querySelector('#preview-prompt')
let showConfigToggle = document.querySelector('#configToggleBtn')
// let configBox = document.querySelector('#config')
let outputMsg = document.querySelector('#outputMsg')
let progressBar = document.querySelector("#progressBar")
let soundToggle = document.querySelector('#sound_toggle')
let serverStatusColor = document.querySelector('#server-status-color')
let serverStatusMsg = document.querySelector('#server-status-msg')
let advancedPanelHandle = document.querySelector("#editor-settings .collapsible")
let modifiersPanelHandle = document.querySelector("#editor-modifiers .collapsible")
let inpaintingEditorContainer = document.querySelector('#inpaintingEditor')
let inpaintingEditor = new DrawingBoard.Board('inpaintingEditor', {
color: "#ffffff",
background: false,
size: 30,
webStorage: false,
controls: [{'DrawingMode': {'filler': false}}, 'Size', 'Navigation']
})
let inpaintingEditorCanvasBackground = document.querySelector('.drawing-board-canvas-wrapper')
document.querySelector('.drawing-board-control-navigation-back').innerHTML = ''
document.querySelector('.drawing-board-control-navigation-forward').innerHTML = ''
let maskResetButton = document.querySelector('.drawing-board-control-navigation-reset')
maskResetButton.innerHTML = 'Clear'
maskResetButton.style.fontWeight = 'normal'
maskResetButton.style.fontSize = '10pt'
let serverStatus = 'offline'
let activeTags = []
let modifiers = []
let lastPromptUsed = ''
let taskStopped = true
let batchesDone = 0
const modifierThumbnailPath = 'media/modifier-thumbnails';
const activeCardClass = 'modifier-card-active';
function getLocalStorageItem(key, fallback) {
let item = localStorage.getItem(key)
if (item === null) {
return fallback
}
return item
}
function getLocalStorageBoolItem(key, fallback) {
let item = localStorage.getItem(key)
if (item === null) {
return fallback
}
return (item === 'true' ? true : false)
}
function handleBoolSettingChange(key) {
return function(e) {
localStorage.setItem(key, e.target.checked.toString())
}
}
function handleStringSettingChange(key) {
return function(e) {
localStorage.setItem(key, e.target.value.toString())
}
}
function isSoundEnabled() {
return getLocalStorageBoolItem(SOUND_ENABLED_KEY, true)
}
function isFaceCorrectionEnabled() {
return getLocalStorageBoolItem(USE_FACE_CORRECTION_KEY, false)
}
function isUpscalingEnabled() {
return getLocalStorageBoolItem(USE_UPSCALING_KEY, false)
}
function isShowOnlyFilteredImageEnabled() {
return getLocalStorageBoolItem(SHOW_ONLY_FILTERED_IMAGE_KEY, true)
}
function isSaveToDiskEnabled() {
return getLocalStorageBoolItem(SAVE_TO_DISK_KEY, false)
}
function isUseCPUEnabled() {
return getLocalStorageBoolItem(USE_CPU_KEY, false)
}
function isUseFullPrecisionEnabled() {
return getLocalStorageBoolItem(USE_FULL_PRECISION_KEY, false)
}
function isUseTurboModeEnabled() {
return getLocalStorageBoolItem(USE_TURBO_MODE_KEY, true)
}
function getSavedDiskPath() {
return getLocalStorageItem(DISK_PATH_KEY, '')
}
function isAdvancedPanelOpenEnabled() {
return getLocalStorageBoolItem(ADVANCED_PANEL_OPEN_KEY, false)
}
function isModifiersPanelOpenEnabled() {
return getLocalStorageBoolItem(MODIFIERS_PANEL_OPEN_KEY, false)
}
function isStreamImageProgressEnabled() {
return getLocalStorageBoolItem(STREAM_IMAGE_PROGRESS_KEY, false)
}
function setStatus(statusType, msg, msgType) {
if (statusType !== 'server') {
return;
}
if (msgType == 'error') {
// msg = '' + msg + ''
serverStatusColor.style.color = 'red'
serverStatusMsg.style.color = 'red'
serverStatusMsg.innerText = 'Stable Diffusion has stopped'
} else if (msgType == 'success') {
// msg = '' + msg + ''
serverStatusColor.style.color = 'green'
serverStatusMsg.style.color = 'green'
serverStatusMsg.innerText = 'Stable Diffusion is ready'
serverStatus = 'online'
}
}
function logMsg(msg, level) {
if (level === 'error') {
outputMsg.innerHTML = 'Error: ' + msg + ''
} else if (level === 'warn') {
outputMsg.innerHTML = 'Warning: ' + msg + ''
} else {
outputMsg.innerText = msg
}
console.log(level, msg)
}
function logError(msg, res) {
logMsg(msg, 'error')
console.log('request error', res)
setStatus('request', 'error', 'error')
}
function playSound() {
const audio = new Audio('/media/ding.mp3')
audio.volume = 0.2
audio.play()
}
async function healthCheck() {
try {
let res = await fetch('/ping')
res = await res.json()
if (res[0] == 'OK') {
setStatus('server', 'online', 'success')
} else {
setStatus('server', 'offline', 'error')
}
} catch (e) {
setStatus('server', 'offline', 'error')
}
}
function showImages(req, res, livePreview) {
res.output.forEach((result, index) => {
if(typeof res != 'object') return;
const imageData = result?.data || result?.path,
imageSeed = req.seed + index,
imageWidth = req.width,
imageHeight = req.height,
imageIdentifier = 'IMG_' + (imageSeed + '').replace(/\d/g, c => 'SUOMIPERKL'[c]);
if (!imageData) {
// res contained no data for the image, stop execution
setStatus('request', 'invalid image', 'error');
return;
}
let imageItemElem = document.querySelector('#' + imageIdentifier);
if(!imageItemElem) {
imageItemElem = document.createElement('div');
imageItemElem.className = 'imgItem';
imageItemElem.id = imageIdentifier;
imageItemElem.innerHTML = `
`;
const useAsInputBtn = imageItemElem.querySelector('.imgUseBtn');
const saveImageBtn = imageItemElem.querySelector('.imgSaveBtn');
useAsInputBtn.addEventListener('click', () => {
initImageSelector.value = null;
initImagePreview.src = imageData;
initImagePreviewContainer.style.display = 'block';
inpaintingEditorContainer.style.display = 'none';
promptStrengthContainer.style.display = 'block';
maskSetting.checked = false;
// maskSetting.style.display = 'block';
randomSeedField.checked = false;
seedField.value = imageSeed;
seedField.disabled = false;
});
saveImageBtn.addEventListener('click', () => {
const imgDownload = document.createElement('a');
imgDownload.download = createFileName(imageSeed);
imgDownload.href = imageData;
imgDownload.click();
});
imagesContainer.appendChild(imageItemElem);
}
const imageElem = imageItemElem.querySelector('img');
const imageSeedLabel = imageItemElem.querySelector('.imgSeedLabel');
imageElem.src = livePreview
? imageData + '?t=' + new Date().getTime()
: imageData;
imageElem.width = parseInt(imageWidth);
imageElem.height = parseInt(imageHeight);
imageSeedLabel.innerText = livePreview
? '(Live Preview)'
: 'Seed: ' + imageSeed;
});
}
// makes a single image. don't call this directly, use makeImage() instead
async function doMakeImage(reqBody, batchCount) {
if (taskStopped) {
return
}
let res = ''
let seed = reqBody['seed']
let numOutputs = parseInt(reqBody['num_outputs'])
try {
res = await fetch('/image', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(reqBody)
})
let reader = res.body.getReader()
let textDecoder = new TextDecoder()
let finalJSON = ''
let prevTime = -1
while (true) {
try {
let t = new Date().getTime()
const {value, done} = await reader.read()
if (done) {
break
}
let timeTaken = (prevTime === -1 ? -1 : t - prevTime)
let jsonStr = textDecoder.decode(value)
try {
let stepUpdate = JSON.parse(jsonStr)
if (stepUpdate.step === undefined) {
finalJSON += jsonStr
} else {
let batchSize = stepUpdate.total_steps
let overallStepCount = stepUpdate.step + batchesDone * batchSize
let totalSteps = batchCount * batchSize
let percent = 100 * (overallStepCount / totalSteps)
percent = (percent > 100 ? 100 : percent)
percent = percent.toFixed(0)
stepsRemaining = totalSteps - overallStepCount
stepsRemaining = (stepsRemaining < 0 ? 0 : stepsRemaining)
timeRemaining = (timeTaken === -1 ? '' : stepsRemaining * timeTaken) // ms
outputMsg.innerHTML = `Batch ${batchesDone+1} of ${batchCount}`
progressBar.innerHTML = `Generating image(s): ${percent}%`
if (timeTaken !== -1) {
progressBar.innerHTML += ` Time remaining (approx): ${millisecondsToStr(timeRemaining)}`
}
progressBar.style.display = 'block'
if (stepUpdate.output !== undefined) {
showImages(reqBody, stepUpdate, true);
}
}
} catch (e) {
finalJSON += jsonStr
}
prevTime = t
} catch (e) {
logError('Stable Diffusion had an error. Please check the logs in the command-line window.', res)
res = undefined
throw e
}
}
if (res.status != 200) {
if (serverStatus === 'online') {
logError('Stable Diffusion had an error: ' + await res.text(), res)
} else {
logError("Stable Diffusion is still starting up, please wait. If this goes on beyond a few minutes, Stable Diffusion has probably crashed. Please check the error message in the command-line window.", res)
}
res = undefined
progressBar.style.display = 'none'
} else {
if (finalJSON !== undefined && finalJSON.indexOf('}{') !== -1) {
// hack for a middleman buffering all the streaming updates, and unleashing them
// on the poor browser in one shot.
// this results in having to parse JSON like {"step": 1}{"step": 2}...{"status": "succeeded"..}
// which is obviously invalid.
// So we need to just extract the last {} section, starting from "status" to the end of the response
let lastChunkIdx = finalJSON.lastIndexOf('}{')
if (lastChunkIdx !== -1) {
let remaining = finalJSON.substring(lastChunkIdx)
finalJSON = remaining.substring(1)
}
}
res = JSON.parse(finalJSON)
progressBar.style.display = 'none'
if (res.status !== 'succeeded') {
let msg = ''
if (res.detail !== undefined) {
msg = res.detail
if (msg.toLowerCase().includes('out of memory')) {
msg += `
Suggestions:
1. If you have set an initial image, please try reducing its dimension to ${MAX_INIT_IMAGE_DIMENSION}x${MAX_INIT_IMAGE_DIMENSION} or smaller.
2. Try disabling the 'Turbo mode' under 'Advanced Settings'.
3. Try generating a smaller image. `
}
} else {
msg = res
}
logError(msg, res)
res = undefined
}
}
} catch (e) {
console.log('request error', e)
logError('Stable Diffusion had an error. Please check the logs in the command-line window.
' + e + '
' + e.stack + '
', res)
setStatus('request', 'error', 'error')
progressBar.style.display = 'none'
res = undefined
}
if (!res) return false;
lastPromptUsed = reqBody['prompt'];
showImages(reqBody, res, false);
return true;
}
function validateInput() {
let width = parseInt(widthField.value)
let height = parseInt(heightField.value)
if (IMAGE_REGEX.test(initImagePreview.src)) {
if (initImagePreview.naturalWidth > MAX_INIT_IMAGE_DIMENSION || initImagePreview.naturalHeight > MAX_INIT_IMAGE_DIMENSION) {
return {'isValid': false, 'warning': `The dimensions of your initial image are very large, and can cause 'Out of Memory' errors! Please ensure that its dimensions are equal (or smaller) than the desired output image.
Your initial image size is ${initImagePreview.naturalWidth}x${initImagePreview.naturalHeight} pixels. Please try to keep it smaller than ${MAX_INIT_IMAGE_DIMENSION}x${MAX_INIT_IMAGE_DIMENSION}.`}
}
}
return {'isValid': true}
}
async function makeImage() {
if (serverStatus !== 'online') {
logError('The server is still starting up..')
return
}
let validation = validateInput()
if (validation['isValid']) {
outputMsg.innerHTML = 'Starting..'
} else {
if (validation['error']) {
logError(validation['error'])
return
} else if (validation['warning']) {
logMsg(validation['warning'], 'warn')
}
}
setStatus('request', 'fetching..')
makeImageBtn.innerHTML = 'Processing..'
makeImageBtn.disabled = true
makeImageBtn.style.display = 'none'
stopImageBtn.style.display = 'block'
taskStopped = false
batchesDone = 0
let seed = (randomSeedField.checked ? Math.floor(Math.random() * 10000000) : parseInt(seedField.value))
let numOutputsTotal = parseInt(numOutputsTotalField.value)
let numOutputsParallel = parseInt(numOutputsParallelField.value)
let batchCount = Math.ceil(numOutputsTotal / numOutputsParallel)
let batchSize = numOutputsParallel
let streamImageProgress = (numOutputsTotal > 50 ? false : streamImageProgressField.checked)
let prompt = promptField.value
if (activeTags.length > 0) {
let promptTags = activeTags.map(x => x.name).join(", ");
prompt += ", " + promptTags;
}
previewPrompt.innerText = prompt
let reqBody = {
session_id: sessionId,
prompt: prompt,
num_outputs: batchSize,
num_inference_steps: numInferenceStepsField.value,
guidance_scale: guidanceScaleField.value,
width: widthField.value,
height: heightField.value,
// allow_nsfw: allowNSFWField.checked,
turbo: turboField.checked,
use_cpu: useCPUField.checked,
use_full_precision: useFullPrecisionField.checked,
stream_progress_updates: true,
stream_image_progress: streamImageProgress,
show_only_filtered_image: showOnlyFilteredImageField.checked
}
if (IMAGE_REGEX.test(initImagePreview.src)) {
reqBody['init_image'] = initImagePreview.src
reqBody['prompt_strength'] = promptStrengthField.value
// if (IMAGE_REGEX.test(maskImagePreview.src)) {
// reqBody['mask'] = maskImagePreview.src
// }
if (maskSetting.checked) {
reqBody['mask'] = inpaintingEditor.getImg()
}
reqBody['sampler'] = 'ddim'
} else {
reqBody['sampler'] = samplerField.value
}
if (saveToDiskField.checked && diskPathField.value.trim() !== '') {
reqBody['save_to_disk_path'] = diskPathField.value.trim()
}
if (useFaceCorrectionField.checked) {
reqBody['use_face_correction'] = 'GFPGANv1.3'
}
if (useUpscalingField.checked) {
reqBody['use_upscale'] = upscaleModelField.value
}
let time = new Date().getTime()
imagesContainer.innerHTML = ''
let successCount = 0
for (let i = 0; i < batchCount; i++) {
reqBody['seed'] = seed + (i * batchSize)
let success = await doMakeImage(reqBody, batchCount)
batchesDone++
if (success) {
outputMsg.innerText = 'Processed batch ' + (i+1) + '/' + batchCount
successCount++
}
}
makeImageBtn.innerText = 'Make Image'
makeImageBtn.disabled = false
makeImageBtn.style.display = 'block'
stopImageBtn.style.display = 'none'
if (isSoundEnabled()) {
playSound()
}
time = new Date().getTime() - time
time /= 1000
if (successCount === batchCount) {
outputMsg.innerText = 'Processed ' + numOutputsTotal + ' images in ' + time + ' seconds'
setStatus('request', 'done', 'success')
}
if (randomSeedField.checked) {
seedField.value = seed
}
}
// create a file name with embedded prompt and metadata
// for easier cateloging and comparison
function createFileName(seed) {
// Most important information is the prompt
let underscoreName = lastPromptUsed.replace(/[^a-zA-Z0-9]/g, '_')
underscoreName = underscoreName.substring(0, 100)
const steps = numInferenceStepsField.value
const guidance = guidanceScaleField.value
// name and the top level metadata
let fileName = `${underscoreName}_Seed-${seed}_Steps-${steps}_Guidance-${guidance}`
// add the tags
// let tags = [];
// let tagString = '';
// document.querySelectorAll(modifyTagsSelector).forEach(function(tag) {
// tags.push(tag.innerHTML);
// })
// join the tags with a pipe
// if (activeTags.length > 0) {
// tagString = '_Tags-';
// tagString += tags.join('|');
// }
// // append empty or populated tags
// fileName += `${tagString}`;
// add the file extension
fileName += `.png`
return fileName
}
stopImageBtn.addEventListener('click', async function() {
try {
let res = await fetch('/image/stop')
} catch (e) {
console.log(e)
}
stopImageBtn.style.display = 'none'
makeImageBtn.style.display = 'block'
taskStopped = true
})
soundToggle.addEventListener('click', handleBoolSettingChange(SOUND_ENABLED_KEY))
soundToggle.checked = isSoundEnabled()
saveToDiskField.checked = isSaveToDiskEnabled()
diskPathField.disabled = !saveToDiskField.checked
useFaceCorrectionField.addEventListener('click', handleBoolSettingChange(USE_FACE_CORRECTION_KEY))
useFaceCorrectionField.checked = isFaceCorrectionEnabled()
useUpscalingField.checked = isUpscalingEnabled()
upscaleModelField.disabled = !useUpscalingField.checked
showOnlyFilteredImageField.addEventListener('click', handleBoolSettingChange(SHOW_ONLY_FILTERED_IMAGE_KEY))
showOnlyFilteredImageField.checked = isShowOnlyFilteredImageEnabled()
useCPUField.addEventListener('click', handleBoolSettingChange(USE_CPU_KEY))
useCPUField.checked = isUseCPUEnabled()
useFullPrecisionField.addEventListener('click', handleBoolSettingChange(USE_FULL_PRECISION_KEY))
useFullPrecisionField.checked = isUseFullPrecisionEnabled()
turboField.addEventListener('click', handleBoolSettingChange(USE_TURBO_MODE_KEY))
turboField.checked = isUseTurboModeEnabled()
streamImageProgressField.addEventListener('click', handleBoolSettingChange(STREAM_IMAGE_PROGRESS_KEY))
streamImageProgressField.checked = isStreamImageProgressEnabled()
diskPathField.addEventListener('change', handleStringSettingChange(DISK_PATH_KEY))
saveToDiskField.addEventListener('click', function(e) {
diskPathField.disabled = !this.checked
handleBoolSettingChange(SAVE_TO_DISK_KEY)(e)
})
useUpscalingField.addEventListener('click', function(e) {
upscaleModelField.disabled = !this.checked
handleBoolSettingChange(USE_UPSCALING_KEY)(e)
})
function setPanelOpen(panelHandle) {
let panelContents = panelHandle.nextElementSibling
panelHandle.classList.add('active')
panelContents.style.display = 'block'
}
if (isAdvancedPanelOpenEnabled()) {
setPanelOpen(advancedPanelHandle)
}
if (isModifiersPanelOpenEnabled()) {
setPanelOpen(modifiersPanelHandle)
}
makeImageBtn.addEventListener('click', makeImage)
function updateGuidanceScale() {
guidanceScaleField.value = guidanceScaleSlider.value / 10
}
function updateGuidanceScaleSlider() {
if (guidanceScaleField.value < 0) {
guidanceScaleField.value = 0
} else if (guidanceScaleField.value > 50) {
guidanceScaleField.value = 50
}
guidanceScaleSlider.value = guidanceScaleField.value * 10
}
guidanceScaleSlider.addEventListener('input', updateGuidanceScale)
guidanceScaleField.addEventListener('input', updateGuidanceScaleSlider)
updateGuidanceScale()
function updatePromptStrength() {
promptStrengthField.value = promptStrengthSlider.value / 100
}
function updatePromptStrengthSlider() {
if (promptStrengthField.value < 0) {
promptStrengthField.value = 0
} else if (promptStrengthField.value > 0.99) {
promptStrengthField.value = 0.99
}
promptStrengthSlider.value = promptStrengthField.value * 100
}
promptStrengthSlider.addEventListener('input', updatePromptStrength)
promptStrengthField.addEventListener('input', updatePromptStrengthSlider)
updatePromptStrength()
useBetaChannelField.addEventListener('click', async function(e) {
if (serverStatus !== 'online') {
logError('The server is still starting up..')
alert('The server is still starting up..')
e.preventDefault()
return false
}
let updateBranch = (this.checked ? 'beta' : 'main')
try {
let res = await fetch('/app_config', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
'update_branch': updateBranch
})
})
res = await res.json()
console.log('set config status response', res)
} catch (e) {
console.log('set config status error', e)
}
})
async function getAppConfig() {
try {
let res = await fetch('/app_config')
config = await res.json()
if (config.update_branch === 'beta') {
useBetaChannelField.checked = true
updateBranchLabel.innerText = "(beta)"
}
console.log('get config status response', config)
} catch (e) {
console.log('get config status error', e)
}
}
function checkRandomSeed() {
if (randomSeedField.checked) {
seedField.disabled = true
seedField.value = "0"
} else {
seedField.disabled = false
}
}
randomSeedField.addEventListener('input', checkRandomSeed)
checkRandomSeed()
function showInitImagePreview() {
if (initImageSelector.files.length === 0) {
initImagePreviewContainer.style.display = 'none'
// inpaintingEditorContainer.style.display = 'none'
promptStrengthContainer.style.display = 'none'
// maskSetting.style.display = 'none'
return
}
let reader = new FileReader()
let file = initImageSelector.files[0]
reader.addEventListener('load', function() {
// console.log(file.name, reader.result)
initImagePreview.src = reader.result
initImagePreviewContainer.style.display = 'block'
inpaintingEditorContainer.style.display = 'none'
promptStrengthContainer.style.display = 'block'
samplerSelectionContainer.style.display = 'none'
// maskSetting.checked = false
})
if (file) {
reader.readAsDataURL(file)
}
}
initImageSelector.addEventListener('change', showInitImagePreview)
showInitImagePreview()
initImagePreview.addEventListener('load', function() {
inpaintingEditorCanvasBackground.style.backgroundImage = "url('" + this.src + "')"
// maskSetting.style.display = 'block'
// inpaintingEditorContainer.style.display = 'block'
})
initImageClearBtn.addEventListener('click', function() {
initImageSelector.value = null
// maskImageSelector.value = null
initImagePreview.src = ''
// maskImagePreview.src = ''
maskSetting.checked = false
initImagePreviewContainer.style.display = 'none'
// inpaintingEditorContainer.style.display = 'none'
// maskImagePreviewContainer.style.display = 'none'
// maskSetting.style.display = 'none'
promptStrengthContainer.style.display = 'none'
samplerSelectionContainer.style.display = 'block'
})
maskSetting.addEventListener('click', function() {
inpaintingEditorContainer.style.display = (this.checked ? 'block' : 'none')
})
// function showMaskImagePreview() {
// if (maskImageSelector.files.length === 0) {
// // maskImagePreviewContainer.style.display = 'none'
// return
// }
// let reader = new FileReader()
// let file = maskImageSelector.files[0]
// reader.addEventListener('load', function() {
// // maskImagePreview.src = reader.result
// // maskImagePreviewContainer.style.display = 'block'
// })
// if (file) {
// reader.readAsDataURL(file)
// }
// }
// maskImageSelector.addEventListener('change', showMaskImagePreview)
// showMaskImagePreview()
// maskImageClearBtn.addEventListener('click', function() {
// maskImageSelector.value = null
// maskImagePreview.src = ''
// // maskImagePreviewContainer.style.display = 'none'
// })
// https://stackoverflow.com/a/8212878
function millisecondsToStr(milliseconds) {
function numberEnding (number) {
return (number > 1) ? 's' : '';
}
var temp = Math.floor(milliseconds / 1000);
var hours = Math.floor((temp %= 86400) / 3600);
var s = ''
if (hours) {
s += hours + ' hour' + numberEnding(hours) + ' ';
}
var minutes = Math.floor((temp %= 3600) / 60);
if (minutes) {
s += minutes + ' minute' + numberEnding(minutes) + ' ';
}
var seconds = temp % 60;
if (!hours && minutes < 4 && seconds) {
s += seconds + ' second' + numberEnding(seconds);
}
return s;
}
function createCollapsibles(node) {
if (!node) {
node = document
}
let collapsibles = node.querySelectorAll(".collapsible")
collapsibles.forEach(function(c) {
let handle = document.createElement('span')
handle.className = 'collapsible-handle'
if (c.className.indexOf('active') !== -1) {
handle.innerHTML = '➖' // minus
} else {
handle.innerHTML = '➕' // plus
}
c.insertBefore(handle, c.firstChild)
c.addEventListener('click', function() {
this.classList.toggle("active")
let content = this.nextElementSibling
if (content.style.display === "block") {
content.style.display = "none"
handle.innerHTML = '➕' // plus
} else {
content.style.display = "block"
handle.innerHTML = '➖' // minus
}
if (this == advancedPanelHandle) {
let state = (content.style.display === 'block' ? 'true' : 'false')
localStorage.setItem(ADVANCED_PANEL_OPEN_KEY, state)
} else if (this == modifiersPanelHandle) {
let state = (content.style.display === 'block' ? 'true' : 'false')
localStorage.setItem(MODIFIERS_PANEL_OPEN_KEY, state)
}
})
})
}
createCollapsibles()
function refreshTagsList() {
editorModifierTagsList.innerHTML = '';
if (activeTags.length == 0) {
editorTagsContainer.style.display = 'none';
return;
} else {
editorTagsContainer.style.display = 'block';
}
activeTags.forEach((tag, index) => {
tag.element.querySelector('.modifier-card-image-overlay').innerText = '-';
tag.element.classList.add('modifier-card-tiny');
editorModifierTagsList.appendChild(tag.element);
tag.element.addEventListener('click', () => {
let idx = activeTags.indexOf(tag);
if (idx !== -1) {
activeTags[idx].originElement.classList.remove(activeCardClass);
activeTags[idx].originElement.querySelector('.modifier-card-image-overlay').innerText = '+';
activeTags.splice(idx, 1);
refreshTagsList();
}
});
});
let brk = document.createElement('br')
brk.style.clear = 'both'
editorModifierTagsList.appendChild(brk)
}
async function getDiskPath() {
try {
let diskPath = getSavedDiskPath()
if (diskPath !== '') {
diskPathField.value = diskPath
return
}
let res = await fetch('/output_dir')
if (res.status === 200) {
res = await res.json()
res = res[0]
document.querySelector('#diskPath').value = res
}
} catch (e) {
console.log('error fetching output dir path', e)
}
}
function createModifierCard(name, previews) {
const modifierCard = document.createElement('div');
modifierCard.className = 'modifier-card';
modifierCard.innerHTML = `