2022-10-11 04:31:47 +02:00
"use strict" // Opt in to a restricted variant of JavaScript
2022-09-23 16:18:48 +02:00
const HEALTH _PING _INTERVAL = 5 // seconds
const MAX _INIT _IMAGE _DIMENSION = 768
2022-11-10 12:04:01 +01:00
const MIN _GPUS _TO _SHOW _SELECTION = 2
2022-09-23 16:18:48 +02:00
const IMAGE _REGEX = new RegExp ( 'data:image/[A-Za-z]+;base64' )
2022-10-14 09:43:33 +02:00
let sessionId = Date . now ( )
2022-09-23 16:18:48 +02:00
let promptField = document . querySelector ( '#prompt' )
2022-10-07 20:16:56 +02:00
let promptsFromFileSelector = document . querySelector ( '#prompt_from_file' )
let promptsFromFileBtn = document . querySelector ( '#promptsFromFileBtn' )
2022-09-27 15:39:28 +02:00
let negativePromptField = document . querySelector ( '#negative_prompt' )
2022-09-23 16:18:48 +02:00
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" )
2022-10-17 08:10:01 +02:00
let initImageSizeBox = document . querySelector ( "#init_image_size_box" )
2022-09-23 16:18:48 +02:00
let maskImageSelector = document . querySelector ( "#mask" )
let maskImagePreview = document . querySelector ( "#mask_preview" )
let turboField = document . querySelector ( '#turbo' )
let useCPUField = document . querySelector ( '#use_cpu' )
2022-11-14 06:53:22 +01:00
let autoPickGPUsField = document . querySelector ( '#auto_pick_gpus' )
2022-11-09 14:47:44 +01:00
let useGPUsField = document . querySelector ( '#use_gpus' )
2022-09-23 16:18:48 +02:00
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" )
2022-10-06 10:58:02 +02:00
let stableDiffusionModelField = document . querySelector ( '#stable_diffusion_model' )
2022-11-08 12:24:15 +01:00
let vaeModelField = document . querySelector ( '#vae_model' )
2022-10-06 11:35:34 +02:00
let outputFormatField = document . querySelector ( '#output_format' )
2022-09-23 16:18:48 +02:00
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' )
2022-09-27 14:39:07 +02:00
let initialText = document . querySelector ( "#initial-text" )
let previewTools = document . querySelector ( "#preview-tools" )
let clearAllPreviewsBtn = document . querySelector ( "#clear-all-previews" )
2022-09-23 16:18:48 +02:00
// 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' )
2022-09-27 14:39:07 +02:00
let imagePreview = document . querySelector ( "#preview" )
2022-09-23 16:18:48 +02:00
2022-09-27 14:39:07 +02:00
// let previewPrompt = document.querySelector('#preview-prompt')
2022-09-23 16:18:48 +02:00
let showConfigToggle = document . querySelector ( '#configToggleBtn' )
// let configBox = document.querySelector('#config')
2022-09-27 14:39:07 +02:00
// let outputMsg = document.querySelector('#outputMsg')
2022-09-23 16:18:48 +02:00
let soundToggle = document . querySelector ( '#sound_toggle' )
let serverStatusColor = document . querySelector ( '#server-status-color' )
let serverStatusMsg = document . querySelector ( '#server-status-msg' )
2022-09-24 14:01:36 +02:00
document . querySelector ( '.drawing-board-control-navigation-back' ) . innerHTML = '<i class="fa-solid fa-rotate-left"></i>'
document . querySelector ( '.drawing-board-control-navigation-forward' ) . innerHTML = '<i class="fa-solid fa-rotate-right"></i>'
2022-09-23 16:18:48 +02:00
let maskResetButton = document . querySelector ( '.drawing-board-control-navigation-reset' )
maskResetButton . innerHTML = 'Clear'
maskResetButton . style . fontWeight = 'normal'
maskResetButton . style . fontSize = '10pt'
2022-10-15 11:48:12 +02:00
let serverState = { 'status' : 'Offline' , 'time' : Date . now ( ) }
2022-09-28 09:47:45 +02:00
let bellPending = false
2022-09-27 14:39:07 +02:00
let taskQueue = [ ]
2022-09-27 16:35:22 +02:00
let currentTask = null
2022-09-23 16:18:48 +02:00
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 ( ) {
2022-10-19 05:49:58 +02:00
return getSetting ( "sound_toggle" )
2022-09-23 16:18:48 +02:00
}
function getSavedDiskPath ( ) {
2022-10-19 05:49:58 +02:00
return getSetting ( "diskPath" )
2022-10-08 14:52:01 +02:00
}
2022-09-23 16:18:48 +02:00
function setStatus ( statusType , msg , msgType ) {
2022-10-14 09:47:25 +02:00
}
2022-09-23 16:18:48 +02:00
2022-10-14 09:47:25 +02:00
function setServerStatus ( msgType , msg ) {
switch ( msgType ) {
case 'online' :
serverStatusColor . style . color = 'green'
serverStatusMsg . style . color = 'green'
serverStatusMsg . innerText = 'Stable Diffusion is ' + msg
break
case 'busy' :
2022-10-17 14:22:55 +02:00
serverStatusColor . style . color = 'rgb(200, 139, 0)'
serverStatusMsg . style . color = 'rgb(200, 139, 0)'
2022-10-14 09:47:25 +02:00
serverStatusMsg . innerText = 'Stable Diffusion is ' + msg
break
case 'error' :
serverStatusColor . style . color = 'red'
serverStatusMsg . style . color = 'red'
serverStatusMsg . innerText = 'Stable Diffusion has stopped'
break
}
}
function isServerAvailable ( ) {
if ( typeof serverState !== 'object' ) {
return false
}
switch ( serverState . status ) {
case 'LoadingModel' :
case 'Rendering' :
case 'Online' :
return true
default :
return false
2022-09-23 16:18:48 +02:00
}
}
2022-09-27 14:39:07 +02:00
function logMsg ( msg , level , outputMsg ) {
2022-10-12 03:53:08 +02:00
if ( outputMsg . hasChildNodes ( ) ) {
outputMsg . appendChild ( document . createElement ( 'br' ) )
}
2022-09-23 16:18:48 +02:00
if ( level === 'error' ) {
2022-10-12 03:53:08 +02:00
outputMsg . innerHTML += '<span style="color: red">Error: ' + msg + '</span>'
2022-09-23 16:18:48 +02:00
} else if ( level === 'warn' ) {
2022-10-12 03:53:08 +02:00
outputMsg . innerHTML += '<span style="color: orange">Warning: ' + msg + '</span>'
2022-09-23 16:18:48 +02:00
} else {
2022-10-12 03:53:08 +02:00
outputMsg . innerText += msg
2022-09-23 16:18:48 +02:00
}
console . log ( level , msg )
}
2022-09-27 14:39:07 +02:00
function logError ( msg , res , outputMsg ) {
logMsg ( msg , 'error' , outputMsg )
2022-09-23 16:18:48 +02:00
console . log ( 'request error' , res )
setStatus ( 'request' , 'error' , 'error' )
}
function playSound ( ) {
const audio = new Audio ( '/media/ding.mp3' )
audio . volume = 0.2
2022-10-20 06:12:01 +02:00
var promise = audio . play ( )
2022-10-17 08:10:01 +02:00
if ( promise !== undefined ) {
promise . then ( _ => { } ) . catch ( error => {
2022-10-20 06:12:01 +02:00
console . warn ( "browser blocked autoplay" )
} )
2022-10-17 08:10:01 +02:00
}
2022-09-23 16:18:48 +02:00
}
async function healthCheck ( ) {
try {
2022-10-14 09:47:25 +02:00
let res = undefined
if ( sessionId ) {
res = await fetch ( '/ping?session_id=' + sessionId )
2022-09-23 16:18:48 +02:00
} else {
2022-10-14 09:47:25 +02:00
res = await fetch ( '/ping' )
}
serverState = await res . json ( )
2022-10-15 11:48:12 +02:00
if ( typeof serverState !== 'object' || typeof serverState . status !== 'string' ) {
serverState = { 'status' : 'Offline' , 'time' : Date . now ( ) }
2022-10-17 03:32:59 +02:00
setServerStatus ( 'error' , 'offline' )
2022-10-15 11:48:12 +02:00
return
}
2022-10-14 09:47:25 +02:00
// Set status
switch ( serverState . status ) {
case 'Init' :
// Wait for init to complete before updating status.
break
case 'Online' :
setServerStatus ( 'online' , 'ready' )
break
case 'LoadingModel' :
2022-10-17 14:34:46 +02:00
setServerStatus ( 'busy' , 'loading..' )
2022-10-14 09:47:25 +02:00
break
case 'Rendering' :
2022-10-17 14:34:46 +02:00
setServerStatus ( 'busy' , 'rendering..' )
2022-10-14 09:47:25 +02:00
break
default : // Unavailable
setServerStatus ( 'error' , serverState . status . toLowerCase ( ) )
break
2022-09-23 16:18:48 +02:00
}
2022-10-14 09:47:25 +02:00
serverState . time = Date . now ( )
2022-09-23 16:18:48 +02:00
} catch ( e ) {
2022-10-15 11:48:12 +02:00
serverState = { 'status' : 'Offline' , 'time' : Date . now ( ) }
2022-10-14 09:47:25 +02:00
setServerStatus ( 'error' , 'offline' )
2022-09-23 16:18:48 +02:00
}
}
2022-10-10 08:32:27 +02:00
function showImages ( reqBody , res , outputContainer , livePreview ) {
2022-09-29 09:38:42 +02:00
let imageItemElements = outputContainer . querySelectorAll ( '.imgItem' )
2022-10-10 08:32:27 +02:00
if ( typeof res != 'object' ) return
2022-09-29 10:22:48 +02:00
res . output . reverse ( )
2022-09-27 16:23:19 +02:00
res . output . forEach ( ( result , index ) => {
2022-10-17 11:52:54 +02:00
const imageData = result ? . data || result ? . path + '?t=' + Date . now ( ) ,
2022-10-15 04:46:31 +02:00
imageSeed = result ? . seed ,
2022-10-17 09:34:33 +02:00
imagePrompt = reqBody . prompt ,
imageInferenceSteps = reqBody . num _inference _steps ,
imageGuidanceScale = reqBody . guidance _scale ,
imageWidth = reqBody . width ,
imageHeight = reqBody . height ;
2022-10-15 04:46:31 +02:00
2022-09-27 23:05:34 +02:00
if ( ! imageData . includes ( '/' ) ) {
2022-09-27 16:23:19 +02:00
// res contained no data for the image, stop execution
2022-09-29 09:38:42 +02:00
setStatus ( 'request' , 'invalid image' , 'error' )
return
2022-09-25 01:55:11 +02:00
}
2022-09-29 09:31:18 +02:00
let imageItemElem = ( index < imageItemElements . length ? imageItemElements [ index ] : null )
2022-09-27 16:23:19 +02:00
if ( ! imageItemElem ) {
2022-09-29 09:38:42 +02:00
imageItemElem = document . createElement ( 'div' )
imageItemElem . className = 'imgItem'
2022-09-27 16:23:19 +02:00
imageItemElem . innerHTML = `
< div class = "imgContainer" >
< img / >
< div class = "imgItemInfo" >
< span class = "imgSeedLabel" > < / s p a n >
< / d i v >
2022-09-25 01:55:11 +02:00
< / d i v >
2022-09-29 09:38:42 +02:00
`
2022-10-10 08:32:27 +02:00
outputContainer . appendChild ( imageItemElem )
}
const imageElem = imageItemElem . querySelector ( 'img' )
imageElem . src = imageData
imageElem . width = parseInt ( imageWidth )
imageElem . height = parseInt ( imageHeight )
2022-10-15 04:46:31 +02:00
imageElem . setAttribute ( 'data-prompt' , imagePrompt )
imageElem . setAttribute ( 'data-steps' , imageInferenceSteps )
imageElem . setAttribute ( 'data-guidance' , imageGuidanceScale )
2022-10-10 08:32:27 +02:00
const imageInfo = imageItemElem . querySelector ( '.imgItemInfo' )
imageInfo . style . visibility = ( livePreview ? 'hidden' : 'visible' )
if ( 'seed' in result && ! imageElem . hasAttribute ( 'data-seed' ) ) {
const req = Object . assign ( { } , reqBody , {
seed : result ? . seed || reqBody . seed
} )
imageElem . setAttribute ( 'data-seed' , req . seed )
const imageSeedLabel = imageItemElem . querySelector ( '.imgSeedLabel' )
imageSeedLabel . innerText = 'Seed: ' + req . seed
2022-10-19 17:51:19 +02:00
let buttons = [
{ text : 'Use as Input' , on _click : onUseAsInputClick } ,
{ text : 'Download' , on _click : onDownloadImageClick } ,
2022-10-19 18:28:51 +02:00
{ text : 'Make Similar Images' , on _click : onMakeSimilarClick } ,
2022-10-19 18:38:42 +02:00
{ text : 'Draw another 25 steps' , on _click : onContinueDrawingClick } ,
2022-10-20 08:44:09 +02:00
{ text : 'Upscale' , on _click : onUpscaleClick , filter : ( req , img ) => ! req . use _upscale } ,
2022-10-19 18:32:59 +02:00
{ text : 'Fix Faces' , on _click : onFixFacesClick , filter : ( req , img ) => ! req . use _face _correction }
2022-10-19 17:51:19 +02:00
]
2022-10-19 16:20:05 +02:00
// include the plugins
2022-10-19 17:51:19 +02:00
buttons = buttons . concat ( PLUGINS [ 'IMAGE_INFO_BUTTONS' ] )
2022-10-19 16:20:05 +02:00
2022-10-10 01:56:51 +02:00
const imgItemInfo = imageItemElem . querySelector ( '.imgItemInfo' )
2022-10-19 16:40:45 +02:00
const img = imageItemElem . querySelector ( 'img' )
2022-10-19 17:51:19 +02:00
const createButton = function ( btnInfo ) {
2022-10-10 01:56:51 +02:00
const newButton = document . createElement ( 'button' )
newButton . classList . add ( 'tasksBtns' )
2022-10-19 14:53:34 +02:00
newButton . innerText = btnInfo . text
2022-10-19 13:56:35 +02:00
newButton . addEventListener ( 'click' , function ( ) {
2022-10-19 16:20:05 +02:00
btnInfo . on _click ( req , img )
2022-10-19 13:56:35 +02:00
} )
2022-10-10 01:56:51 +02:00
imgItemInfo . appendChild ( newButton )
}
2022-10-19 17:51:19 +02:00
buttons . forEach ( btn => {
2022-10-19 16:40:45 +02:00
if ( btn . filter && btn . filter ( req , img ) === false ) {
return
}
2022-10-19 17:51:19 +02:00
createButton ( btn )
2022-10-19 16:40:45 +02:00
} )
2022-09-27 16:23:19 +02:00
}
2022-09-29 09:38:42 +02:00
} )
2022-09-23 16:18:48 +02:00
}
2022-10-19 16:20:05 +02:00
function onUseAsInputClick ( req , img ) {
const imgData = img . src
2022-09-29 09:31:18 +02:00
2022-10-19 13:56:35 +02:00
initImageSelector . value = null
initImagePreview . src = imgData
2022-09-29 09:31:18 +02:00
2022-10-19 13:56:35 +02:00
initImagePreviewContainer . style . display = 'block'
inpaintingEditorContainer . style . display = 'none'
promptStrengthContainer . style . display = 'table-row'
maskSetting . checked = false
samplerSelectionContainer . style . display = 'none'
2022-09-29 09:31:18 +02:00
}
2022-10-19 16:20:05 +02:00
function onDownloadImageClick ( req , img ) {
const imgData = img . src
const imageSeed = img . getAttribute ( 'data-seed' )
const imagePrompt = img . getAttribute ( 'data-prompt' )
const imageInferenceSteps = img . getAttribute ( 'data-steps' )
const imageGuidanceScale = img . getAttribute ( 'data-guidance' )
2022-10-19 13:56:35 +02:00
const imgDownload = document . createElement ( 'a' )
imgDownload . download = createFileName ( imagePrompt , imageSeed , imageInferenceSteps , imageGuidanceScale , req [ 'output_format' ] )
imgDownload . href = imgData
imgDownload . click ( )
2022-09-29 09:31:18 +02:00
}
2022-10-19 13:56:35 +02:00
2022-10-30 19:09:12 +01:00
function modifyCurrentRequest ( ... reqDiff ) {
2022-10-20 11:40:34 +02:00
const newTaskRequest = getCurrentUserRequest ( )
2022-10-19 13:56:35 +02:00
2022-10-30 19:09:12 +01:00
newTaskRequest . reqBody = Object . assign ( newTaskRequest . reqBody , ... reqDiff , {
2022-10-20 11:40:34 +02:00
use _cpu : useCPUField . checked
} )
newTaskRequest . seed = newTaskRequest . reqBody . seed
return newTaskRequest
}
function onMakeSimilarClick ( req , img ) {
const newTaskRequest = modifyCurrentRequest ( req , {
2022-10-19 13:56:35 +02:00
num _outputs : 1 ,
num _inference _steps : 50 ,
guidance _scale : 7.5 ,
prompt _strength : 0.7 ,
2022-10-19 16:20:05 +02:00
init _image : img . src ,
2022-10-19 13:56:35 +02:00
seed : Math . floor ( Math . random ( ) * 10000000 )
} )
newTaskRequest . numOutputsTotal = 5
newTaskRequest . batchCount = 5
delete newTaskRequest . reqBody . mask
createTask ( newTaskRequest )
}
2022-10-20 11:40:34 +02:00
function enqueueImageVariationTask ( req , img , reqDiff ) {
2022-10-19 18:28:51 +02:00
const imageSeed = img . getAttribute ( 'data-seed' )
2022-10-20 11:40:34 +02:00
const newTaskRequest = modifyCurrentRequest ( req , reqDiff , {
num _outputs : 1 , // this can be user-configurable in the future
2022-10-19 18:28:51 +02:00
seed : imageSeed
} )
2022-10-20 11:40:34 +02:00
newTaskRequest . numOutputsTotal = 1 // this can be user-configurable in the future
2022-10-19 18:28:51 +02:00
newTaskRequest . batchCount = 1
createTask ( newTaskRequest )
}
2022-10-20 11:40:34 +02:00
function onUpscaleClick ( req , img ) {
enqueueImageVariationTask ( req , img , {
use _upscale : upscaleModelField . value
2022-10-19 18:32:59 +02:00
} )
2022-10-20 11:40:34 +02:00
}
2022-10-19 18:32:59 +02:00
2022-10-20 11:40:34 +02:00
function onFixFacesClick ( req , img ) {
enqueueImageVariationTask ( req , img , {
use _face _correction : 'GFPGANv1.3'
} )
2022-10-19 18:32:59 +02:00
}
2022-10-19 18:38:42 +02:00
function onContinueDrawingClick ( req , img ) {
2022-10-20 11:40:34 +02:00
enqueueImageVariationTask ( req , img , {
num _inference _steps : parseInt ( req . num _inference _steps ) + 25
2022-10-19 18:38:42 +02:00
} )
}
2022-09-23 16:18:48 +02:00
// makes a single image. don't call this directly, use makeImage() instead
2022-09-27 14:39:07 +02:00
async function doMakeImage ( task ) {
if ( task . stopped ) {
2022-09-23 16:18:48 +02:00
return
}
2022-10-20 13:52:01 +02:00
const RETRY _DELAY _IF _BUFFER _IS _EMPTY = 1000 // ms
const RETRY _DELAY _IF _SERVER _IS _BUSY = 30 * 1000 // ms, status_code 503, already a task running
const TASK _START _DELAY _ON _SERVER = 1500 // ms
const SERVER _STATE _VALIDITY _DURATION = 10 * 1000 // ms
2022-09-27 14:39:07 +02:00
const reqBody = task . reqBody
const batchCount = task . batchCount
2022-09-29 10:13:25 +02:00
const outputContainer = document . createElement ( 'div' )
outputContainer . className = 'img-batch'
2022-09-29 10:22:48 +02:00
task . outputContainer . insertBefore ( outputContainer , task . outputContainer . firstChild )
2022-09-27 14:39:07 +02:00
const outputMsg = task [ 'outputMsg' ]
const previewPrompt = task [ 'previewPrompt' ]
const progressBar = task [ 'progressBar' ]
2022-10-28 02:03:09 +02:00
const progressBarInner = progressBar . querySelector ( "div" )
2022-09-27 14:39:07 +02:00
2022-10-11 21:32:06 +02:00
let res = undefined
2022-09-23 16:18:48 +02:00
try {
2022-10-14 09:47:25 +02:00
let renderRequest = undefined
do {
res = await fetch ( '/render' , {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json'
} ,
body : JSON . stringify ( reqBody )
} )
renderRequest = await res . json ( )
// status_code 503, already a task running.
2022-10-21 02:36:45 +02:00
} while ( res . status === 503 && await asyncDelay ( RETRY _DELAY _IF _SERVER _IS _BUSY ) )
2022-10-20 13:52:01 +02:00
2022-10-14 09:47:25 +02:00
if ( typeof renderRequest ? . stream !== 'string' ) {
console . log ( 'Endpoint response: ' , renderRequest )
2022-10-23 03:19:42 +02:00
throw new Error ( renderRequest ? . detail || 'Endpoint response does not contains a response stream url.' )
2022-10-14 09:47:25 +02:00
}
2022-10-20 13:52:01 +02:00
2022-10-17 14:22:55 +02:00
task [ 'taskStatusLabel' ] . innerText = "Waiting"
2022-10-14 10:18:34 +02:00
task [ 'taskStatusLabel' ] . classList . add ( 'waitingTaskLabel' )
task [ 'taskStatusLabel' ] . classList . remove ( 'activeTaskLabel' )
2022-10-14 09:47:25 +02:00
do { // Wait for server status to update.
await asyncDelay ( 250 )
if ( ! isServerAvailable ( ) ) {
throw new Error ( 'Connexion with server lost.' )
}
2022-10-20 13:52:01 +02:00
} while ( Date . now ( ) < ( serverState . time + SERVER _STATE _VALIDITY _DURATION ) && serverState . task !== renderRequest . task )
2022-10-22 18:31:14 +02:00
switch ( serverState . session ) {
case 'pending' :
case 'running' :
case 'buffer' :
// Normal expected messages.
break
case 'completed' :
console . warn ( 'Server %o render request %o completed unexpectedly' , serverState , renderRequest )
break // Continue anyway to try to read cached result.
case 'error' :
console . error ( 'Server %o render request %o has failed' , serverState , renderRequest )
break // Still valid, Update UI with error message
case 'stopped' :
console . log ( 'Server %o render request %o was stopped' , serverState , renderRequest )
2022-10-17 14:11:27 +02:00
return false
2022-10-22 18:31:14 +02:00
default :
throw new Error ( 'Unexpected server task state: ' + serverState . session || 'Undefined' )
2022-10-14 09:47:25 +02:00
}
2022-10-20 13:52:01 +02:00
2022-10-15 11:48:12 +02:00
while ( serverState . task === renderRequest . task && serverState . session === 'pending' ) {
2022-10-14 10:47:13 +02:00
// Wait for task to start on server.
2022-10-20 13:52:01 +02:00
await asyncDelay ( TASK _START _DELAY _ON _SERVER )
2022-10-14 10:47:13 +02:00
}
2022-10-14 09:47:25 +02:00
// Task started!
res = await fetch ( renderRequest . stream , {
2022-09-23 16:18:48 +02:00
headers : {
'Content-Type' : 'application/json'
} ,
} )
2022-10-14 09:47:25 +02:00
task [ 'taskStatusLabel' ] . innerText = "Processing"
task [ 'taskStatusLabel' ] . classList . add ( 'activeTaskLabel' )
task [ 'taskStatusLabel' ] . classList . remove ( 'waitingTaskLabel' )
let stepUpdate = undefined
2022-09-23 16:18:48 +02:00
let reader = res . body . getReader ( )
let textDecoder = new TextDecoder ( )
let finalJSON = ''
2022-10-12 00:38:23 +02:00
let readComplete = false
2022-10-14 09:47:25 +02:00
while ( ! readComplete || finalJSON . length > 0 ) {
let t = Date . now ( )
2022-10-12 00:38:23 +02:00
let jsonStr = ''
if ( ! readComplete ) {
const { value , done } = await reader . read ( )
if ( done ) {
readComplete = true
}
if ( value ) {
jsonStr = textDecoder . decode ( value )
}
2022-10-11 22:42:27 +02:00
}
2022-10-14 09:47:25 +02:00
stepUpdate = undefined
2022-09-23 16:18:48 +02:00
try {
2022-10-11 22:42:27 +02:00
// hack for a middleman buffering all the streaming updates, and unleashing them on the poor browser in one shot.
2022-10-12 09:08:25 +02:00
// this results in having to parse JSON like {"step": 1}{"step": 2}{"step": 3}{"ste...
// which is obviously invalid and can happen at any point while rendering.
2022-10-12 08:57:09 +02:00
// So we need to extract only the next {} section
2022-10-12 06:33:00 +02:00
if ( finalJSON . length > 0 ) {
// Append new data when required
if ( jsonStr . length > 0 ) {
jsonStr = finalJSON + jsonStr
} else {
jsonStr = finalJSON
}
finalJSON = ''
}
// Find next delimiter
2022-10-11 22:42:27 +02:00
let lastChunkIdx = jsonStr . indexOf ( '}{' )
if ( lastChunkIdx !== - 1 ) {
2022-10-12 06:33:00 +02:00
finalJSON = jsonStr . substring ( 0 , lastChunkIdx + 1 )
jsonStr = jsonStr . substring ( lastChunkIdx + 1 )
2022-10-11 21:32:06 +02:00
} else {
2022-10-12 06:33:00 +02:00
finalJSON = jsonStr
2022-10-11 22:42:27 +02:00
jsonStr = ''
2022-10-11 19:48:18 +02:00
}
2022-10-12 06:33:00 +02:00
// Try to parse
stepUpdate = ( finalJSON . length > 0 ? JSON . parse ( finalJSON ) : undefined )
2022-10-11 22:42:27 +02:00
finalJSON = jsonStr
2022-09-23 16:18:48 +02:00
} catch ( e ) {
2022-10-12 02:08:44 +02:00
if ( e instanceof SyntaxError && ! readComplete ) {
2022-10-11 22:42:27 +02:00
finalJSON += jsonStr
} else {
throw e
}
2022-09-23 16:18:48 +02:00
}
2022-10-11 22:42:27 +02:00
if ( typeof stepUpdate === 'object' && 'step' in stepUpdate ) {
let batchSize = stepUpdate . total _steps
let overallStepCount = stepUpdate . step + task . batchesDone * batchSize
let totalSteps = batchCount * batchSize
let percent = 100 * ( overallStepCount / totalSteps )
percent = ( percent > 100 ? 100 : percent )
percent = percent . toFixed ( 0 )
2022-10-18 12:42:17 +02:00
let timeTaken = stepUpdate . step _time // sec
2022-10-11 22:42:27 +02:00
let stepsRemaining = totalSteps - overallStepCount
stepsRemaining = ( stepsRemaining < 0 ? 0 : stepsRemaining )
2022-10-18 12:42:17 +02:00
let timeRemaining = ( timeTaken === - 1 ? '' : stepsRemaining * timeTaken * 1000 ) // ms
2022-10-11 22:42:27 +02:00
outputMsg . innerHTML = ` Batch ${ task . batchesDone + 1 } of ${ batchCount } `
outputMsg . innerHTML += ` . Generating image(s): ${ percent } % `
timeRemaining = ( timeTaken !== - 1 ? millisecondsToStr ( timeRemaining ) : '' )
outputMsg . innerHTML += ` . Time remaining (approx): ${ timeRemaining } `
outputMsg . style . display = 'block'
2022-10-28 02:03:09 +02:00
progressBarInner . style . width = ` ${ percent } % `
if ( percent == 100 ) {
task . progressBar . style . height = "0px"
task . progressBar . style . border = "0px solid var(--background-color3)"
2022-10-28 07:47:08 +02:00
task . progressBar . classList . remove ( "active" )
2022-10-28 02:03:09 +02:00
}
2022-10-11 22:42:27 +02:00
if ( stepUpdate . output !== undefined ) {
showImages ( reqBody , stepUpdate , outputContainer , true )
}
}
2022-10-14 09:47:25 +02:00
if ( stepUpdate ? . status ) {
break
}
if ( readComplete && finalJSON . length <= 0 ) {
if ( res . status === 200 ) {
2022-10-20 13:52:01 +02:00
await asyncDelay ( RETRY _DELAY _IF _BUFFER _IS _EMPTY )
2022-10-14 09:47:25 +02:00
res = await fetch ( renderRequest . stream , {
headers : {
'Content-Type' : 'application/json'
} ,
} )
reader = res . body . getReader ( )
readComplete = false
} else {
console . log ( 'Stream stopped: ' , res )
}
}
2022-09-23 16:18:48 +02:00
}
2022-10-12 02:10:40 +02:00
if ( typeof stepUpdate === 'object' && stepUpdate . status !== 'succeeded' ) {
2022-10-11 21:32:06 +02:00
let msg = ''
2022-10-12 02:10:40 +02:00
if ( 'detail' in stepUpdate && typeof stepUpdate . detail === 'string' && stepUpdate . detail . length > 0 ) {
msg = stepUpdate . detail
2022-10-11 21:32:06 +02:00
if ( msg . toLowerCase ( ) . includes ( 'out of memory' ) ) {
msg += ` <br/><br/>
< b > Suggestions < / b > :
< br / >
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 . < br / >
2. Try disabling the '<em>Turbo mode</em>' under '<em>Advanced Settings</em>' . < br / >
3. Try generating a smaller image . < br / > `
2022-09-23 16:18:48 +02:00
}
2022-10-11 21:32:06 +02:00
} else {
2022-10-14 09:47:25 +02:00
msg = ` Unexpected Read Error:<br/><pre>StepUpdate: ${ JSON . stringify ( stepUpdate , undefined , 4 ) } </pre> `
2022-09-23 16:18:48 +02:00
}
2022-10-11 21:32:06 +02:00
logError ( msg , res , outputMsg )
2022-10-12 00:40:05 +02:00
return false
2022-09-23 16:18:48 +02:00
}
2022-10-12 02:10:40 +02:00
if ( typeof stepUpdate !== 'object' || ! res || res . status != 200 ) {
2022-10-14 09:47:25 +02:00
if ( ! isServerAvailable ( ) ) {
2022-10-12 02:10:40 +02:00
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 , outputMsg )
} else if ( typeof res === 'object' ) {
let msg = 'Stable Diffusion had an error reading the response: '
try { // 'Response': body stream already read
msg += 'Read: ' + await res . text ( )
} catch ( e ) {
2022-10-14 09:47:25 +02:00
msg += 'Unexpected end of stream. '
2022-10-12 02:10:40 +02:00
}
if ( finalJSON ) {
msg += 'Buffered data: ' + finalJSON
}
logError ( msg , res , outputMsg )
} else {
2022-10-14 09:47:25 +02:00
let msg = ` Unexpected Read Error:<br/><pre>Response: ${ res } <br/>StepUpdate: ${ typeof stepUpdate === 'object' ? JSON . stringify ( stepUpdate , undefined , 4 ) : stepUpdate } </pre> `
logError ( msg , res , outputMsg )
2022-10-12 02:10:40 +02:00
}
return false
}
2022-10-12 00:40:05 +02:00
showImages ( reqBody , stepUpdate , outputContainer , false )
2022-09-23 16:18:48 +02:00
} catch ( e ) {
console . log ( 'request error' , e )
2022-09-27 14:39:07 +02:00
logError ( 'Stable Diffusion had an error. Please check the logs in the command-line window. <br/><br/>' + e + '<br/><pre>' + e . stack + '</pre>' , res , outputMsg )
2022-09-23 16:18:48 +02:00
setStatus ( 'request' , 'error' , 'error' )
2022-10-11 22:42:27 +02:00
return false
}
2022-09-29 09:38:42 +02:00
return true
2022-09-23 16:18:48 +02:00
}
2022-09-27 14:39:07 +02:00
async function checkTasks ( ) {
if ( taskQueue . length === 0 ) {
setStatus ( 'request' , 'done' , 'success' )
setTimeout ( checkTasks , 500 )
stopImageBtn . style . display = 'none'
2022-10-21 11:48:05 +02:00
renameMakeImageButton ( )
2022-09-23 16:18:48 +02:00
2022-09-27 16:35:22 +02:00
currentTask = null
2022-09-28 09:47:45 +02:00
if ( bellPending ) {
if ( isSoundEnabled ( ) ) {
playSound ( )
}
bellPending = false
2022-09-23 16:18:48 +02:00
}
2022-09-28 09:47:45 +02:00
2022-09-27 14:39:07 +02:00
return
2022-09-23 16:18:48 +02:00
}
2022-09-27 14:39:07 +02:00
setStatus ( 'request' , 'fetching..' )
2022-09-23 16:18:48 +02:00
2022-09-27 14:39:07 +02:00
stopImageBtn . style . display = 'block'
2022-10-21 11:48:05 +02:00
renameMakeImageButton ( )
2022-09-28 09:47:45 +02:00
bellPending = true
2022-09-27 14:39:07 +02:00
previewTools . style . display = 'block'
let task = taskQueue . pop ( )
2022-09-27 16:35:22 +02:00
currentTask = task
2022-09-27 14:39:07 +02:00
2022-10-14 09:43:33 +02:00
let time = Date . now ( )
2022-09-27 14:39:07 +02:00
let successCount = 0
task . isProcessing = true
task [ 'stopTask' ] . innerHTML = '<i class="fa-solid fa-circle-stop"></i> Stop'
2022-10-14 09:47:25 +02:00
task [ 'taskStatusLabel' ] . innerText = "Starting"
task [ 'taskStatusLabel' ] . classList . add ( 'waitingTaskLabel' )
2022-09-27 14:39:07 +02:00
2022-10-10 03:16:24 +02:00
const genSeeds = Boolean ( typeof task . reqBody . seed !== 'number' || ( task . reqBody . seed === task . seed && task . numOutputsTotal > 1 ) )
2022-10-10 06:29:46 +02:00
const startSeed = task . reqBody . seed || task . seed
2022-09-27 14:39:07 +02:00
for ( let i = 0 ; i < task . batchCount ; i ++ ) {
2022-10-20 06:12:01 +02:00
let newTask = task
2022-10-10 06:29:46 +02:00
if ( task . batchCount > 1 ) {
// Each output render batch needs it's own task instance to avoid altering the other runs after they are completed.
2022-10-11 04:28:34 +02:00
newTask = Object . assign ( { } , task , {
2022-10-10 06:29:46 +02:00
reqBody : Object . assign ( { } , task . reqBody )
} )
2022-10-10 03:18:27 +02:00
}
2022-10-10 03:16:24 +02:00
if ( genSeeds ) {
2022-10-19 18:28:51 +02:00
newTask . reqBody . seed = parseInt ( startSeed ) + ( i * newTask . reqBody . num _outputs )
2022-10-11 04:28:34 +02:00
newTask . seed = newTask . reqBody . seed
} else if ( newTask . seed !== newTask . reqBody . seed ) {
newTask . seed = newTask . reqBody . seed
2022-10-10 03:16:24 +02:00
}
2022-09-27 14:39:07 +02:00
2022-10-11 04:28:34 +02:00
let success = await doMakeImage ( newTask )
2022-09-27 14:39:07 +02:00
task . batchesDone ++
2022-10-12 05:15:06 +02:00
if ( ! task . isProcessing || ! success ) {
2022-09-27 16:35:22 +02:00
break
}
2022-09-27 14:39:07 +02:00
if ( success ) {
successCount ++
2022-09-23 16:18:48 +02:00
}
}
2022-09-27 14:39:07 +02:00
task . isProcessing = false
task [ 'stopTask' ] . innerHTML = '<i class="fa-solid fa-trash-can"></i> Remove'
2022-09-27 15:07:21 +02:00
task [ 'taskStatusLabel' ] . style . display = 'none'
2022-09-27 14:39:07 +02:00
2022-10-14 09:43:33 +02:00
time = Date . now ( ) - time
2022-09-27 14:39:07 +02:00
time /= 1000
if ( successCount === task . batchCount ) {
task . outputMsg . innerText = 'Processed ' + task . numOutputsTotal + ' images in ' + time + ' seconds'
2022-10-28 07:47:08 +02:00
task . progressBar . style . height = "0px"
task . progressBar . style . border = "0px solid var(--background-color3)"
task . progressBar . classList . remove ( "active" )
2022-09-27 14:39:07 +02:00
// setStatus('request', 'done', 'success')
2022-09-23 16:18:48 +02:00
} else {
2022-09-30 07:20:38 +02:00
if ( task . outputMsg . innerText . toLowerCase ( ) . indexOf ( 'error' ) === - 1 ) {
task . outputMsg . innerText = 'Task ended after ' + time + ' seconds'
}
2022-09-23 16:18:48 +02:00
}
2022-09-27 14:39:07 +02:00
if ( randomSeedField . checked ) {
seedField . value = task . seed
}
2022-09-23 16:18:48 +02:00
2022-09-27 16:35:22 +02:00
currentTask = null
2022-09-23 16:18:48 +02:00
2022-10-11 04:30:17 +02:00
if ( typeof requestIdleCallback === 'function' ) {
requestIdleCallback ( checkTasks , { timeout : 30 * 1000 } )
} else {
setTimeout ( checkTasks , 500 )
}
}
if ( typeof requestIdleCallback === 'function' ) {
requestIdleCallback ( checkTasks , { timeout : 30 * 1000 } )
} else {
2022-09-27 14:39:07 +02:00
setTimeout ( checkTasks , 10 )
2022-09-23 16:18:48 +02:00
}
2022-10-09 13:17:43 +02:00
function getCurrentUserRequest ( ) {
const numOutputsTotal = parseInt ( numOutputsTotalField . value )
const numOutputsParallel = parseInt ( numOutputsParallelField . value )
const seed = ( randomSeedField . checked ? Math . floor ( Math . random ( ) * 10000000 ) : parseInt ( seedField . value ) )
2022-09-23 16:18:48 +02:00
2022-10-09 13:17:43 +02:00
const newTask = {
isProcessing : false ,
2022-09-27 14:39:07 +02:00
stopped : false ,
2022-10-09 13:17:43 +02:00
batchesDone : 0 ,
numOutputsTotal : numOutputsTotal ,
batchCount : Math . ceil ( numOutputsTotal / numOutputsParallel ) ,
seed ,
reqBody : {
session _id : sessionId ,
seed ,
negative _prompt : negativePromptField . value . trim ( ) ,
num _outputs : numOutputsParallel ,
num _inference _steps : numInferenceStepsField . value ,
guidance _scale : guidanceScaleField . value ,
width : widthField . value ,
height : heightField . value ,
// allow_nsfw: allowNSFWField.checked,
turbo : turboField . checked ,
2022-11-09 14:47:44 +01:00
render _device : getCurrentRenderDeviceSelection ( ) ,
2022-10-09 13:17:43 +02:00
use _full _precision : useFullPrecisionField . checked ,
use _stable _diffusion _model : stableDiffusionModelField . value ,
2022-11-08 12:24:15 +01:00
use _vae _model : vaeModelField . value ,
2022-10-09 13:17:43 +02:00
stream _progress _updates : true ,
stream _image _progress : ( numOutputsTotal > 50 ? false : streamImageProgressField . checked ) ,
show _only _filtered _image : showOnlyFilteredImageField . checked ,
output _format : outputFormatField . value
}
2022-09-23 16:18:48 +02:00
}
if ( IMAGE _REGEX . test ( initImagePreview . src ) ) {
2022-10-09 13:17:43 +02:00
newTask . reqBody . init _image = initImagePreview . src
newTask . reqBody . prompt _strength = promptStrengthField . value
2022-09-23 16:18:48 +02:00
// if (IMAGE_REGEX.test(maskImagePreview.src)) {
2022-10-09 13:17:43 +02:00
// newTask.reqBody.mask = maskImagePreview.src
2022-09-23 16:18:48 +02:00
// }
if ( maskSetting . checked ) {
2022-10-09 13:17:43 +02:00
newTask . reqBody . mask = inpaintingEditor . getImg ( )
2022-09-23 16:18:48 +02:00
}
2022-10-09 13:17:43 +02:00
newTask . reqBody . sampler = 'ddim'
2022-09-23 16:18:48 +02:00
} else {
2022-10-09 13:17:43 +02:00
newTask . reqBody . sampler = samplerField . value
2022-09-23 16:18:48 +02:00
}
if ( saveToDiskField . checked && diskPathField . value . trim ( ) !== '' ) {
2022-10-09 13:17:43 +02:00
newTask . reqBody . save _to _disk _path = diskPathField . value . trim ( )
2022-09-23 16:18:48 +02:00
}
if ( useFaceCorrectionField . checked ) {
2022-10-09 13:17:43 +02:00
newTask . reqBody . use _face _correction = 'GFPGANv1.3'
2022-09-23 16:18:48 +02:00
}
if ( useUpscalingField . checked ) {
2022-10-09 13:17:43 +02:00
newTask . reqBody . use _upscale = upscaleModelField . value
}
return newTask
}
2022-11-09 14:47:44 +01:00
function getCurrentRenderDeviceSelection ( ) {
2022-11-14 06:53:22 +01:00
let selectedGPUs = $ ( '#use_gpus' ) . val ( )
if ( useCPUField . checked && ! autoPickGPUsField . checked ) {
2022-11-09 14:47:44 +01:00
return 'cpu'
}
2022-11-14 06:53:22 +01:00
if ( autoPickGPUsField . checked || selectedGPUs . length == 0 ) {
return 'auto'
2022-11-10 17:53:15 +01:00
}
2022-11-14 06:53:22 +01:00
2022-11-10 17:53:15 +01:00
return selectedGPUs . join ( ',' )
2022-11-09 14:47:44 +01:00
}
2022-10-09 13:17:43 +02:00
function makeImage ( ) {
2022-10-14 09:47:25 +02:00
if ( ! isServerAvailable ( ) ) {
alert ( 'The server is not available.' )
2022-10-09 13:17:43 +02:00
return
2022-09-23 16:18:48 +02:00
}
2022-10-09 13:17:43 +02:00
const taskTemplate = getCurrentUserRequest ( )
const newTaskRequests = [ ]
getPrompts ( ) . forEach ( ( prompt ) => newTaskRequests . push ( Object . assign ( { } , taskTemplate , {
reqBody : Object . assign ( { prompt : prompt } , taskTemplate . reqBody )
} ) ) )
newTaskRequests . forEach ( createTask )
2022-09-23 16:18:48 +02:00
2022-10-09 13:17:43 +02:00
initialText . style . display = 'none'
}
2022-09-23 16:18:48 +02:00
2022-10-09 13:17:43 +02:00
function createTask ( task ) {
let taskConfig = ` Seed: ${ task . seed } , Sampler: ${ task . reqBody . sampler } , Inference Steps: ${ task . reqBody . num _inference _steps } , Guidance Scale: ${ task . reqBody . guidance _scale } , Model: ${ task . reqBody . use _stable _diffusion _model } `
2022-11-08 12:24:15 +01:00
if ( task . reqBody . use _vae _model . trim ( ) !== '' ) {
taskConfig += ` , VAE: ${ task . reqBody . use _vae _model } `
}
if ( task . reqBody . negative _prompt . trim ( ) !== '' ) {
2022-10-09 13:17:43 +02:00
taskConfig += ` , Negative Prompt: ${ task . reqBody . negative _prompt } `
2022-09-27 15:39:28 +02:00
}
2022-10-09 13:17:43 +02:00
if ( task . reqBody . init _image !== undefined ) {
taskConfig += ` , Prompt Strength: ${ task . reqBody . prompt _strength } `
2022-09-27 14:39:07 +02:00
}
2022-10-10 01:59:34 +02:00
if ( task . reqBody . use _face _correction ) {
2022-10-09 13:17:43 +02:00
taskConfig += ` , Fix Faces: ${ task . reqBody . use _face _correction } `
2022-09-27 14:39:07 +02:00
}
2022-10-10 01:59:34 +02:00
if ( task . reqBody . use _upscale ) {
2022-10-09 13:17:43 +02:00
taskConfig += ` , Upscale: ${ task . reqBody . use _upscale } `
2022-09-23 16:18:48 +02:00
}
2022-09-27 14:39:07 +02:00
let taskEntry = document . createElement ( 'div' )
taskEntry . className = 'imageTaskContainer'
2022-11-09 04:22:14 +01:00
taskEntry . innerHTML = ` <div class="header-content panel collapsible active">
< div class = "taskStatusLabel" > Enqueued < / d i v >
< button class = "secondaryButton stopTask" > < i class = "fa-solid fa-trash-can" > < / i > R e m o v e < / b u t t o n >
< div class = "preview-prompt collapsible active" > < / d i v >
< div class = "taskConfig" > $ { taskConfig } < / d i v >
2022-09-28 10:14:48 +02:00
< div class = "outputMsg" > < / d i v >
2022-10-28 02:03:09 +02:00
< div class = "progress-bar active" > < div > < / d i v > < / d i v >
2022-11-09 04:22:14 +01:00
< / d i v >
< div class = "collapsible-content" >
2022-09-28 10:14:48 +02:00
< div class = "img-preview" >
< / d i v > `
createCollapsibles ( taskEntry )
2022-09-27 14:39:07 +02:00
task [ 'taskStatusLabel' ] = taskEntry . querySelector ( '.taskStatusLabel' )
task [ 'outputContainer' ] = taskEntry . querySelector ( '.img-preview' )
task [ 'outputMsg' ] = taskEntry . querySelector ( '.outputMsg' )
task [ 'previewPrompt' ] = taskEntry . querySelector ( '.preview-prompt' )
2022-10-28 02:03:09 +02:00
task [ 'progressBar' ] = taskEntry . querySelector ( '.progress-bar' )
2022-09-27 14:39:07 +02:00
task [ 'stopTask' ] = taskEntry . querySelector ( '.stopTask' )
2022-11-10 11:03:39 +01:00
task [ 'stopTask' ] . addEventListener ( 'click' , async function ( e ) {
e . stopPropagation ( )
2022-09-27 14:39:07 +02:00
if ( task [ 'isProcessing' ] ) {
2022-09-27 16:35:22 +02:00
task . isProcessing = false
2022-10-28 07:47:08 +02:00
task . progressBar . classList . remove ( "active" )
2022-09-27 14:39:07 +02:00
try {
2022-10-14 09:47:25 +02:00
let res = await fetch ( '/image/stop?session_id=' + sessionId )
2022-09-27 14:39:07 +02:00
} catch ( e ) {
console . log ( e )
}
} else {
let idx = taskQueue . indexOf ( task )
if ( idx >= 0 ) {
taskQueue . splice ( idx , 1 )
}
2022-09-23 16:18:48 +02:00
2022-09-27 14:39:07 +02:00
taskEntry . remove ( )
2022-09-23 16:18:48 +02:00
}
2022-09-27 14:39:07 +02:00
} )
2022-09-23 16:18:48 +02:00
2022-09-27 14:39:07 +02:00
imagePreview . insertBefore ( taskEntry , previewTools . nextSibling )
2022-09-23 16:18:48 +02:00
2022-10-09 13:17:43 +02:00
task . previewPrompt . innerText = task . reqBody . prompt
2022-10-18 15:32:34 +02:00
if ( task . previewPrompt . innerText . trim ( ) === '' ) {
task . previewPrompt . innerHTML = ' ' // allows the results to be collapsed
}
2022-09-23 16:18:48 +02:00
2022-09-27 14:39:07 +02:00
taskQueue . unshift ( task )
2022-09-23 16:18:48 +02:00
}
2022-10-08 12:26:56 +02:00
function getPrompts ( ) {
let prompts = promptField . value
2022-10-18 15:32:34 +02:00
if ( prompts . trim ( ) === '' ) {
return [ '' ]
}
2022-10-08 12:26:56 +02:00
prompts = prompts . split ( '\n' )
2022-10-19 10:20:05 +02:00
prompts = prompts . map ( prompt => prompt . trim ( ) )
prompts = prompts . filter ( prompt => prompt !== '' )
2022-10-08 12:26:56 +02:00
2022-10-19 10:20:05 +02:00
let promptsToMake = applySetOperator ( prompts )
promptsToMake = applyPermuteOperator ( promptsToMake )
if ( activeTags . length <= 0 ) {
return promptsToMake
}
const promptTags = activeTags . map ( x => x . name ) . join ( ", " )
return promptsToMake . map ( ( prompt ) => ` ${ prompt } , ${ promptTags } ` )
}
function applySetOperator ( prompts ) {
2022-10-08 12:26:56 +02:00
let promptsToMake = [ ]
2022-10-19 10:20:05 +02:00
let braceExpander = new BraceExpander ( )
2022-10-08 12:26:56 +02:00
prompts . forEach ( prompt => {
2022-10-19 10:20:05 +02:00
let expandedPrompts = braceExpander . expand ( prompt )
promptsToMake = promptsToMake . concat ( expandedPrompts )
} )
return promptsToMake
}
2022-10-08 12:26:56 +02:00
2022-10-19 10:20:05 +02:00
function applyPermuteOperator ( prompts ) {
let promptsToMake = [ ]
prompts . forEach ( prompt => {
2022-10-08 12:26:56 +02:00
let promptMatrix = prompt . split ( '|' )
prompt = promptMatrix . shift ( ) . trim ( )
promptsToMake . push ( prompt )
promptMatrix = promptMatrix . map ( p => p . trim ( ) )
promptMatrix = promptMatrix . filter ( p => p !== '' )
if ( promptMatrix . length > 0 ) {
let promptPermutations = permutePrompts ( prompt , promptMatrix )
promptsToMake = promptsToMake . concat ( promptPermutations )
}
} )
2022-10-19 10:20:05 +02:00
return promptsToMake
2022-10-08 12:26:56 +02:00
}
function permutePrompts ( promptBase , promptMatrix ) {
let prompts = [ ]
let permutations = permute ( promptMatrix )
permutations . forEach ( perm => {
let prompt = promptBase
if ( perm . length > 0 ) {
let promptAddition = perm . join ( ', ' )
if ( promptAddition . trim ( ) === '' ) {
return
}
prompt += ', ' + promptAddition
}
prompts . push ( prompt )
} )
return prompts
}
2022-09-23 16:18:48 +02:00
// create a file name with embedded prompt and metadata
// for easier cateloging and comparison
2022-10-15 04:46:31 +02:00
function createFileName ( prompt , seed , steps , guidance , outputFormat ) {
2022-09-23 16:18:48 +02:00
// Most important information is the prompt
2022-10-15 04:46:31 +02:00
let underscoreName = prompt . replace ( /[^a-zA-Z0-9]/g , '_' )
2022-09-23 16:18:48 +02:00
underscoreName = underscoreName . substring ( 0 , 100 )
2022-10-15 04:46:31 +02:00
//const steps = numInferenceStepsField.value
//const guidance = guidanceScaleField.value
2022-09-23 16:18:48 +02:00
// name and the top level metadata
let fileName = ` ${ underscoreName } _Seed- ${ seed } _Steps- ${ steps } _Guidance- ${ guidance } `
// add the tags
2022-09-29 09:38:42 +02:00
// let tags = []
// let tagString = ''
2022-09-23 16:18:48 +02:00
// document.querySelectorAll(modifyTagsSelector).forEach(function(tag) {
2022-09-29 09:38:42 +02:00
// tags.push(tag.innerHTML)
2022-09-23 16:18:48 +02:00
// })
// join the tags with a pipe
// if (activeTags.length > 0) {
2022-09-29 09:38:42 +02:00
// tagString = '_Tags-'
// tagString += tags.join('|')
2022-09-23 16:18:48 +02:00
// }
// // append empty or populated tags
2022-09-29 09:38:42 +02:00
// fileName += `${tagString}`
2022-09-23 16:18:48 +02:00
// add the file extension
2022-10-06 11:35:34 +02:00
fileName += '.' + ( outputFormat === 'png' ? 'png' : 'jpeg' )
2022-09-23 16:18:48 +02:00
return fileName
}
2022-09-27 14:39:07 +02:00
async function stopAllTasks ( ) {
taskQueue . forEach ( task => {
task . isProcessing = false
} )
taskQueue = [ ]
2022-09-27 16:35:22 +02:00
if ( currentTask !== null ) {
currentTask . isProcessing = false
}
2022-09-23 16:18:48 +02:00
try {
2022-10-17 14:06:20 +02:00
let res = await fetch ( '/image/stop?session_id=' + sessionId )
2022-09-23 16:18:48 +02:00
} catch ( e ) {
console . log ( e )
}
2022-09-27 14:39:07 +02:00
}
clearAllPreviewsBtn . addEventListener ( 'click' , async function ( ) {
await stopAllTasks ( )
2022-09-23 16:18:48 +02:00
2022-09-27 14:39:07 +02:00
let taskEntries = document . querySelectorAll ( '.imageTaskContainer' )
taskEntries . forEach ( task => {
task . remove ( )
} )
2022-09-23 16:18:48 +02:00
2022-09-27 14:39:07 +02:00
previewTools . style . display = 'none'
initialText . style . display = 'block'
} )
stopImageBtn . addEventListener ( 'click' , async function ( ) {
await stopAllTasks ( )
2022-09-23 16:18:48 +02:00
} )
2022-10-18 18:39:11 +02:00
widthField . addEventListener ( 'change' , onDimensionChange )
heightField . addEventListener ( 'change' , onDimensionChange )
2022-10-21 11:48:05 +02:00
function renameMakeImageButton ( ) {
let totalImages = Math . max ( parseInt ( numOutputsTotalField . value ) , parseInt ( numOutputsParallelField . value ) )
let imageLabel = 'Image'
if ( totalImages > 1 ) {
imageLabel = totalImages + ' Images'
}
if ( taskQueue . length == 0 ) {
makeImageBtn . innerText = 'Make ' + imageLabel
} else {
makeImageBtn . innerText = 'Enqueue Next ' + imageLabel
}
}
numOutputsTotalField . addEventListener ( 'change' , renameMakeImageButton )
numOutputsParallelField . addEventListener ( 'change' , renameMakeImageButton )
2022-10-18 18:39:11 +02:00
function onDimensionChange ( ) {
if ( ! maskSetting . checked ) {
return
}
let widthValue = parseInt ( widthField . value )
let heightValue = parseInt ( heightField . value )
resizeInpaintingEditor ( widthValue , heightValue )
}
2022-09-23 16:18:48 +02:00
2022-10-29 03:25:54 +02:00
diskPathField . disabled = ! saveToDiskField . checked
saveToDiskField . addEventListener ( 'change' , function ( e ) {
2022-09-23 16:18:48 +02:00
diskPathField . disabled = ! this . checked
} )
2022-10-29 03:25:54 +02:00
upscaleModelField . disabled = ! useUpscalingField . checked
useUpscalingField . addEventListener ( 'change' , function ( e ) {
2022-09-23 16:18:48 +02:00
upscaleModelField . disabled = ! this . checked
} )
makeImageBtn . addEventListener ( 'click' , makeImage )
2022-11-10 10:29:01 +01:00
document . onkeydown = function ( e ) {
if ( e . ctrlKey && e . code === 'Enter' ) {
makeImage ( )
e . preventDefault ( )
}
}
2022-09-23 16:18:48 +02:00
function updateGuidanceScale ( ) {
guidanceScaleField . value = guidanceScaleSlider . value / 10
2022-10-20 06:12:01 +02:00
guidanceScaleField . dispatchEvent ( new Event ( "change" ) )
2022-09-23 16:18:48 +02:00
}
function updateGuidanceScaleSlider ( ) {
if ( guidanceScaleField . value < 0 ) {
guidanceScaleField . value = 0
} else if ( guidanceScaleField . value > 50 ) {
guidanceScaleField . value = 50
}
guidanceScaleSlider . value = guidanceScaleField . value * 10
2022-10-20 11:56:18 +02:00
guidanceScaleSlider . dispatchEvent ( new Event ( "change" ) )
2022-09-23 16:18:48 +02:00
}
guidanceScaleSlider . addEventListener ( 'input' , updateGuidanceScale )
guidanceScaleField . addEventListener ( 'input' , updateGuidanceScaleSlider )
updateGuidanceScale ( )
function updatePromptStrength ( ) {
promptStrengthField . value = promptStrengthSlider . value / 100
2022-10-20 06:12:01 +02:00
promptStrengthField . dispatchEvent ( new Event ( "change" ) )
2022-09-23 16:18:48 +02:00
}
function updatePromptStrengthSlider ( ) {
if ( promptStrengthField . value < 0 ) {
promptStrengthField . value = 0
} else if ( promptStrengthField . value > 0.99 ) {
promptStrengthField . value = 0.99
}
promptStrengthSlider . value = promptStrengthField . value * 100
2022-10-20 11:56:18 +02:00
promptStrengthSlider . dispatchEvent ( new Event ( "change" ) )
2022-09-23 16:18:48 +02:00
}
promptStrengthSlider . addEventListener ( 'input' , updatePromptStrength )
promptStrengthField . addEventListener ( 'input' , updatePromptStrengthSlider )
updatePromptStrength ( )
2022-11-09 14:47:44 +01:00
useCPUField . addEventListener ( 'click' , function ( ) {
let gpuSettingEntry = getParameterSettingsEntry ( 'use_gpus' )
2022-11-14 06:53:22 +01:00
let autoPickGPUSettingEntry = getParameterSettingsEntry ( 'auto_pick_gpus' )
2022-11-09 14:47:44 +01:00
if ( this . checked ) {
gpuSettingEntry . style . display = 'none'
2022-11-14 06:53:22 +01:00
autoPickGPUSettingEntry . style . display = 'none'
autoPickGPUsField . setAttribute ( 'data-old-value' , autoPickGPUsField . checked )
autoPickGPUsField . checked = false
2022-11-10 17:53:15 +01:00
} else if ( useGPUsField . options . length >= MIN _GPUS _TO _SHOW _SELECTION ) {
2022-11-09 14:47:44 +01:00
gpuSettingEntry . style . display = ''
2022-11-14 06:53:22 +01:00
autoPickGPUSettingEntry . style . display = ''
2022-11-14 08:32:36 +01:00
let oldVal = autoPickGPUsField . getAttribute ( 'data-old-value' )
if ( oldVal === null || oldVal === undefined ) { // the UI started with CPU selected by default
autoPickGPUsField . checked = true
} else {
autoPickGPUsField . checked = ( oldVal === 'true' )
}
2022-11-14 08:44:33 +01:00
gpuSettingEntry . style . display = ( autoPickGPUsField . checked ? 'none' : '' )
2022-11-14 06:53:22 +01:00
}
} )
useGPUsField . addEventListener ( 'click' , function ( ) {
let selectedGPUs = $ ( '#use_gpus' ) . val ( )
autoPickGPUsField . checked = ( selectedGPUs . length === 0 )
} )
autoPickGPUsField . addEventListener ( 'click' , function ( ) {
if ( this . checked ) {
$ ( '#use_gpus' ) . val ( [ ] )
2022-11-09 14:47:44 +01:00
}
2022-11-14 08:32:36 +01:00
2022-11-14 08:44:33 +01:00
let gpuSettingEntry = getParameterSettingsEntry ( 'use_gpus' )
gpuSettingEntry . style . display = ( this . checked ? 'none' : '' )
2022-11-09 14:47:44 +01:00
} )
2022-10-28 16:36:44 +02:00
async function changeAppConfig ( configDelta ) {
// if (!isServerAvailable()) {
// // logError('The server is still starting up..')
// alert('The server is still starting up..')
// e.preventDefault()
// return false
// }
2022-09-23 16:18:48 +02:00
try {
let res = await fetch ( '/app_config' , {
method : 'POST' ,
headers : {
'Content-Type' : 'application/json'
} ,
2022-10-28 16:36:44 +02:00
body : JSON . stringify ( configDelta )
2022-09-23 16:18:48 +02:00
} )
res = await res . json ( )
console . log ( 'set config status response' , res )
} catch ( e ) {
console . log ( 'set config status error' , e )
}
2022-10-28 16:36:44 +02:00
}
useBetaChannelField . addEventListener ( 'click' , async function ( e ) {
let updateBranch = ( this . checked ? 'beta' : 'main' )
await changeAppConfig ( {
'update_branch' : updateBranch
} )
} )
2022-09-23 16:18:48 +02:00
async function getAppConfig ( ) {
try {
2022-10-15 07:32:53 +02:00
let res = await fetch ( '/get/app_config' )
2022-10-11 04:27:15 +02:00
const config = await res . json ( )
2022-09-23 16:18:48 +02:00
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 )
}
}
2022-10-06 10:58:02 +02:00
async function getModels ( ) {
try {
2022-11-08 12:24:15 +01:00
var sd _model _setting _key = "stable_diffusion_model"
var vae _model _setting _key = "vae_model"
var selectedSDModel = SETTINGS [ sd _model _setting _key ] . value
var selectedVaeModel = SETTINGS [ vae _model _setting _key ] . value
2022-10-15 07:32:53 +02:00
let res = await fetch ( '/get/models' )
2022-10-11 04:27:15 +02:00
const models = await res . json ( )
2022-10-06 10:58:02 +02:00
2022-11-08 12:24:15 +01:00
console . log ( 'get models response' , models )
2022-10-06 10:58:02 +02:00
let modelOptions = models [ 'options' ]
let stableDiffusionOptions = modelOptions [ 'stable-diffusion' ]
2022-10-28 16:36:44 +02:00
let vaeOptions = modelOptions [ 'vae' ]
2022-11-08 12:24:15 +01:00
vaeOptions . unshift ( '' ) // add a None option
2022-10-28 16:36:44 +02:00
function createModelOptions ( modelField , selectedModel ) {
return function ( modelName ) {
let modelOption = document . createElement ( 'option' )
modelOption . value = modelName
2022-11-08 12:24:15 +01:00
modelOption . innerText = modelName !== '' ? modelName : 'None'
2022-10-06 10:58:02 +02:00
2022-10-28 16:36:44 +02:00
if ( modelName === selectedModel ) {
modelOption . selected = true
}
2022-10-06 10:58:02 +02:00
2022-10-28 16:36:44 +02:00
modelField . appendChild ( modelOption )
2022-10-06 10:58:02 +02:00
}
2022-10-28 16:36:44 +02:00
}
2022-10-06 10:58:02 +02:00
2022-11-08 12:24:15 +01:00
stableDiffusionOptions . forEach ( createModelOptions ( stableDiffusionModelField , selectedSDModel ) )
vaeOptions . forEach ( createModelOptions ( vaeModelField , selectedVaeModel ) )
2022-10-06 10:58:02 +02:00
2022-10-20 06:12:01 +02:00
// TODO: set default for model here too
2022-11-08 12:24:15 +01:00
SETTINGS [ sd _model _setting _key ] . default = stableDiffusionOptions [ 0 ]
if ( getSetting ( sd _model _setting _key ) == '' || SETTINGS [ sd _model _setting _key ] . value == '' ) {
setSetting ( sd _model _setting _key , stableDiffusionOptions [ 0 ] )
2022-10-20 06:12:01 +02:00
}
2022-10-06 10:58:02 +02:00
} catch ( e ) {
console . log ( 'get models error' , e )
}
}
2022-09-23 16:18:48 +02:00
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 ]
2022-10-17 08:10:01 +02:00
reader . addEventListener ( 'load' , function ( event ) {
2022-09-23 16:18:48 +02:00
// console.log(file.name, reader.result)
initImagePreview . src = reader . result
initImagePreviewContainer . style . display = 'block'
inpaintingEditorContainer . style . display = 'none'
2022-10-16 00:30:04 +02:00
promptStrengthContainer . style . display = 'table-row'
2022-09-23 16:18:48 +02:00
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'
2022-10-17 08:10:01 +02:00
initImageSizeBox . textContent = initImagePreview . naturalWidth + " x " + initImagePreview . naturalHeight
initImageSizeBox . style . display = 'block'
2022-09-23 16:18:48 +02:00
} )
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'
2022-10-17 08:10:01 +02:00
samplerSelectionContainer . style . display = 'table-row'
initImageSizeBox . style . display = 'none'
2022-09-23 16:18:48 +02:00
} )
maskSetting . addEventListener ( 'click' , function ( ) {
inpaintingEditorContainer . style . display = ( this . checked ? 'block' : 'none' )
2022-10-18 18:39:11 +02:00
onDimensionChange ( )
2022-09-23 16:18:48 +02:00
} )
2022-10-07 20:16:56 +02:00
promptsFromFileBtn . addEventListener ( 'click' , function ( ) {
promptsFromFileSelector . click ( )
} )
promptsFromFileSelector . addEventListener ( 'change' , function ( ) {
if ( promptsFromFileSelector . files . length === 0 ) {
return
}
let reader = new FileReader ( )
let file = promptsFromFileSelector . files [ 0 ]
reader . addEventListener ( 'load' , function ( ) {
promptField . value = reader . result
} )
if ( file ) {
reader . readAsText ( file )
}
} )
2022-09-23 16:18:48 +02:00
async function getDiskPath ( ) {
try {
2022-10-20 06:12:01 +02:00
var diskPath = getSetting ( "diskPath" )
if ( diskPath == '' || diskPath == undefined || diskPath == "undefined" ) {
let res = await fetch ( '/get/output_dir' )
if ( res . status === 200 ) {
res = await res . json ( )
res = res . output _dir
setSetting ( "diskPath" , res )
}
2022-09-23 16:18:48 +02:00
}
} catch ( e ) {
console . log ( 'error fetching output dir path' , e )
}
}
2022-11-09 14:47:44 +01:00
async function getDevices ( ) {
try {
let res = await fetch ( '/get/devices' )
if ( res . status === 200 ) {
res = await res . json ( )
let allDeviceIds = Object . keys ( res [ 'all' ] ) . filter ( d => d !== 'cpu' )
let activeDeviceIds = Object . keys ( res [ 'active' ] ) . filter ( d => d !== 'cpu' )
if ( activeDeviceIds . length === 0 ) {
useCPUField . checked = true
}
2022-11-14 08:32:36 +01:00
if ( allDeviceIds . length < MIN _GPUS _TO _SHOW _SELECTION || useCPUField . checked ) {
2022-11-09 14:47:44 +01:00
let gpuSettingEntry = getParameterSettingsEntry ( 'use_gpus' )
gpuSettingEntry . style . display = 'none'
2022-11-14 06:53:22 +01:00
let autoPickGPUSettingEntry = getParameterSettingsEntry ( 'auto_pick_gpus' )
autoPickGPUSettingEntry . style . display = 'none'
2022-11-14 08:32:36 +01:00
}
2022-11-09 14:47:44 +01:00
2022-11-14 08:32:36 +01:00
if ( allDeviceIds . length === 0 ) {
useCPUField . checked = true
useCPUField . disabled = true // no compatible GPUs, so make the CPU mandatory
2022-11-09 14:47:44 +01:00
}
2022-11-14 06:53:22 +01:00
autoPickGPUsField . checked = ( res [ 'config' ] === 'auto' )
2022-11-09 14:47:44 +01:00
2022-11-14 06:53:22 +01:00
useGPUsField . innerHTML = ''
2022-11-09 14:47:44 +01:00
allDeviceIds . forEach ( device => {
let deviceName = res [ 'all' ] [ device ]
2022-11-14 06:53:22 +01:00
let deviceOption = ` <option value=" ${ device } "> ${ deviceName } </option> `
2022-11-09 14:47:44 +01:00
useGPUsField . insertAdjacentHTML ( 'beforeend' , deviceOption )
} )
2022-11-14 06:53:22 +01:00
2022-11-14 08:32:36 +01:00
if ( autoPickGPUsField . checked ) {
2022-11-14 08:44:33 +01:00
let gpuSettingEntry = getParameterSettingsEntry ( 'use_gpus' )
gpuSettingEntry . style . display = 'none'
2022-11-14 08:32:36 +01:00
} else {
2022-11-14 06:53:22 +01:00
$ ( '#use_gpus' ) . val ( activeDeviceIds )
}
2022-11-09 14:47:44 +01:00
}
} catch ( e ) {
console . log ( 'error fetching devices' , e )
}
}
2022-10-29 03:25:54 +02:00
2022-10-29 01:48:32 +02:00
/* setup popup handlers */
document . querySelectorAll ( '.popup' ) . forEach ( popup => {
popup . addEventListener ( 'click' , event => {
if ( event . target == popup ) {
popup . classList . remove ( "active" )
}
} )
var closeButton = popup . querySelector ( ".close-button" )
if ( closeButton ) {
closeButton . addEventListener ( 'click' , ( ) => {
popup . classList . remove ( "active" )
} )
}
} )
2022-11-09 04:54:41 +01:00
var tabElements = [ ] ;
document . querySelectorAll ( ".tab" ) . forEach ( tab => {
var name = tab . id . replace ( "tab-" , "" ) ;
var content = document . getElementById ( ` tab-content- ${ name } ` )
tabElements . push ( {
name : name ,
tab : tab ,
content : content
} )
tab . addEventListener ( "click" , event => {
if ( ! tab . classList . contains ( "active" ) ) {
tabElements . forEach ( tabInfo => {
if ( tabInfo . tab . classList . contains ( "active" ) ) {
tabInfo . tab . classList . toggle ( "active" )
tabInfo . content . classList . toggle ( "active" )
}
} )
tab . classList . toggle ( "active" )
content . classList . toggle ( "active" )
}
} )
} )
2022-11-10 23:23:20 +01:00
window . addEventListener ( "beforeunload" , function ( e ) {
const msg = "Unsaved pictures will be lost!" ;
let elementList = document . getElementsByClassName ( "imageTaskContainer" ) ;
if ( elementList . length != 0 ) {
e . preventDefault ( ) ;
( e || window . event ) . returnValue = msg ;
return msg ;
} else {
return true ;
}
} ) ;
2022-10-22 05:08:19 +02:00
createCollapsibles ( )