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 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"
2022-10-08 14:52:01 +02:00
const OUTPUT _FORMAT _KEY = "outputFormat"
2022-09-23 16:18:48 +02:00
const HEALTH _PING _INTERVAL = 5 // seconds
const MAX _INIT _IMAGE _DIMENSION = 768
2022-10-08 13:23:47 +02:00
const INPAINTING _EDITOR _SIZE = 450
2022-09-23 16:18:48 +02:00
const IMAGE _REGEX = new RegExp ( 'data:image/[A-Za-z]+;base64' )
let sessionId = new Date ( ) . getTime ( )
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" )
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" )
2022-10-06 10:58:02 +02:00
let stableDiffusionModelField = document . querySelector ( '#stable_diffusion_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' )
let editorModifierEntries = document . querySelector ( '#editor-modifiers-entries' )
let editorModifierTagsList = document . querySelector ( '#editor-inputs-tags-list' )
let editorTagsContainer = document . querySelector ( '#editor-inputs-tags-container' )
2022-09-27 14:39:07 +02:00
let imagePreview = document . querySelector ( "#preview" )
2022-09-23 16:18:48 +02:00
let previewImageField = document . querySelector ( '#preview-image' )
2022-09-29 09:38:42 +02:00
previewImageField . onchange = ( ) => changePreviewImages ( previewImageField . value )
2022-09-23 16:18:48 +02:00
let modifierCardSizeSlider = document . querySelector ( '#modifier-card-size-slider' )
2022-09-29 09:38:42 +02:00
modifierCardSizeSlider . onchange = ( ) => resizeModifierCards ( modifierCardSizeSlider . value )
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')
// let progressBar = document.querySelector("#progressBar")
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' )
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' )
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'
let serverStatus = 'offline'
let activeTags = [ ]
let modifiers = [ ]
let lastPromptUsed = ''
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
2022-09-29 09:38:42 +02:00
const modifierThumbnailPath = 'media/modifier-thumbnails'
const activeCardClass = 'modifier-card-active'
2022-09-23 16:18:48 +02:00
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 )
}
2022-10-08 14:52:01 +02:00
function getOutputFormat ( ) {
return getLocalStorageItem ( OUTPUT _FORMAT _KEY , 'jpeg' )
}
2022-09-23 16:18:48 +02:00
function setStatus ( statusType , msg , msgType ) {
if ( statusType !== 'server' ) {
2022-09-29 09:38:42 +02:00
return
2022-09-23 16:18:48 +02:00
}
if ( msgType == 'error' ) {
// msg = '<span style="color: red">' + msg + '<span>'
serverStatusColor . style . color = 'red'
serverStatusMsg . style . color = 'red'
serverStatusMsg . innerText = 'Stable Diffusion has stopped'
} else if ( msgType == 'success' ) {
// msg = '<span style="color: green">' + msg + '<span>'
serverStatusColor . style . color = 'green'
serverStatusMsg . style . color = 'green'
serverStatusMsg . innerText = 'Stable Diffusion is ready'
serverStatus = 'online'
}
}
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
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' )
}
}
2022-10-08 11:53:44 +02:00
function resizeInpaintingEditor ( ) {
2022-10-08 13:55:24 +02:00
if ( ! maskSetting . checked ) {
return
}
2022-10-08 11:53:44 +02:00
let widthValue = parseInt ( widthField . value )
let heightValue = parseInt ( heightField . value )
if ( widthValue === heightValue ) {
widthValue = INPAINTING _EDITOR _SIZE
heightValue = INPAINTING _EDITOR _SIZE
} else if ( widthValue > heightValue ) {
heightValue = ( heightValue / widthValue ) * INPAINTING _EDITOR _SIZE
widthValue = INPAINTING _EDITOR _SIZE
} else {
widthValue = ( widthValue / heightValue ) * INPAINTING _EDITOR _SIZE
heightValue = INPAINTING _EDITOR _SIZE
}
2022-10-08 14:42:25 +02:00
if ( inpaintingEditor . opts . aspectRatio === ( widthValue / heightValue ) . toFixed ( 3 ) ) {
// Same ratio, don't reset the canvas.
return
}
inpaintingEditor . opts . aspectRatio = ( widthValue / heightValue ) . toFixed ( 3 )
2022-10-08 13:17:15 +02:00
2022-10-08 11:53:44 +02:00
inpaintingEditorContainer . style . width = widthValue + 'px'
inpaintingEditorContainer . style . height = heightValue + 'px'
inpaintingEditor . opts . enlargeYourContainer = true
2022-10-08 13:23:47 +02:00
inpaintingEditor . opts . size = inpaintingEditor . ctx . lineWidth
2022-10-08 11:53:44 +02:00
inpaintingEditor . resize ( )
2022-10-08 13:17:15 +02:00
inpaintingEditor . ctx . lineCap = "round"
inpaintingEditor . ctx . lineJoin = "round"
2022-10-08 11:53:44 +02:00
inpaintingEditor . ctx . lineWidth = inpaintingEditor . opts . size
inpaintingEditor . setColor ( inpaintingEditor . opts . color )
}
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-10 08:32:27 +02:00
const imageData = result ? . data || result ? . path + '?t=' + new Date ( ) . getTime ( )
const imageWidth = reqBody . width
const imageHeight = reqBody . height
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 )
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-10 01:56:51 +02:00
const buttons = {
'imgUseBtn' : { html : 'Use as Input' , click : getUseAsInputHandler ( imageItemElem ) } ,
'imgSaveBtn' : { html : 'Download' , click : getSaveImageHandler ( imageItemElem , req [ 'output_format' ] ) } ,
2022-10-10 10:06:15 +02:00
'imgX2Btn' : { html : 'Double Size' , click : getStartNewTaskHandler ( req , imageItemElem , 'img2img_X2' ) } ,
'imgRedoBtn' : { html : 'Redo' , click : getStartNewTaskHandler ( req , imageItemElem , 'img2img' ) } ,
2022-10-10 01:56:51 +02:00
}
if ( ! req . use _upscale ) {
2022-10-10 01:58:16 +02:00
buttons . upscaleBtn = { html : 'Upscale' , click : getStartNewTaskHandler ( req , imageItemElem , 'upscale' ) }
2022-10-10 01:56:51 +02:00
}
const imgItemInfo = imageItemElem . querySelector ( '.imgItemInfo' )
const createButton = function ( name , btnInfo ) {
const newButton = document . createElement ( 'button' )
newButton . classList . add ( name )
newButton . classList . add ( 'tasksBtns' )
newButton . innerHTML = btnInfo . html
newButton . addEventListener ( 'click' , btnInfo . click )
imgItemInfo . appendChild ( newButton )
}
Object . keys ( buttons ) . forEach ( ( name ) => createButton ( name , buttons [ name ] ) )
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-09-29 09:31:18 +02:00
function getUseAsInputHandler ( imageItemElem ) {
return function ( ) {
const imageElem = imageItemElem . querySelector ( 'img' )
2022-09-29 09:38:42 +02:00
const imgData = imageElem . src
2022-09-29 09:31:18 +02:00
const imageSeed = imageElem . getAttribute ( 'data-seed' )
2022-09-29 09:38:42 +02:00
initImageSelector . value = null
initImagePreview . src = imgData
2022-09-29 09:31:18 +02:00
2022-09-29 09:38:42 +02:00
initImagePreviewContainer . style . display = 'block'
inpaintingEditorContainer . style . display = 'none'
promptStrengthContainer . style . display = 'block'
maskSetting . checked = false
2022-09-29 09:42:01 +02:00
samplerSelectionContainer . style . display = 'none'
2022-09-29 09:31:18 +02:00
2022-09-29 09:38:42 +02:00
// maskSetting.style.display = 'block'
2022-09-29 09:31:18 +02:00
2022-09-29 09:38:42 +02:00
randomSeedField . checked = false
seedField . value = imageSeed
seedField . disabled = false
2022-09-29 09:31:18 +02:00
}
}
2022-10-06 11:35:34 +02:00
function getSaveImageHandler ( imageItemElem , outputFormat ) {
2022-09-29 09:31:18 +02:00
return function ( ) {
const imageElem = imageItemElem . querySelector ( 'img' )
2022-09-29 09:38:42 +02:00
const imgData = imageElem . src
2022-09-29 09:31:18 +02:00
const imageSeed = imageElem . getAttribute ( 'data-seed' )
2022-09-29 09:38:42 +02:00
const imgDownload = document . createElement ( 'a' )
2022-10-06 11:35:34 +02:00
imgDownload . download = createFileName ( imageSeed , outputFormat )
2022-09-29 09:38:42 +02:00
imgDownload . href = imgData
imgDownload . click ( )
2022-09-29 09:31:18 +02:00
}
}
2022-10-10 01:58:16 +02:00
function getStartNewTaskHandler ( reqBody , imageItemElem , mode ) {
2022-10-09 13:19:00 +02:00
return function ( ) {
if ( serverStatus !== 'online' ) {
alert ( 'The server is still starting up..' )
return
}
const imageElem = imageItemElem . querySelector ( 'img' )
const newTaskRequest = getCurrentUserRequest ( )
2022-10-10 01:58:16 +02:00
switch ( mode ) {
case 'img2img' :
2022-10-10 10:06:15 +02:00
case 'img2img_X2' :
2022-10-10 01:58:16 +02:00
newTaskRequest . reqBody = Object . assign ( { } , reqBody , {
num _outputs : 1 ,
prompt _strength : '0.5' ,
} )
2022-10-11 04:29:15 +02:00
if ( ! newTaskRequest . reqBody . init _image || mode === 'img2img_X2' ) {
newTaskRequest . reqBody . sampler = 'ddim'
newTaskRequest . reqBody . init _image = imageElem . src
} else {
newTaskRequest . reqBody . seed = 1 + newTaskRequest . reqBody . seed
}
2022-10-10 10:06:15 +02:00
if ( mode === 'img2img_X2' ) {
newTaskRequest . reqBody . width = reqBody . width * 2
newTaskRequest . reqBody . height = reqBody . height * 2
newTaskRequest . reqBody . num _inference _steps = Math . min ( 100 , reqBody . num _inference _steps * 2 )
2022-10-11 04:29:15 +02:00
if ( useUpscalingField . checked ) {
newTaskRequest . reqBody . use _upscale = upscaleModelField . value
} else {
delete newTaskRequest . reqBody . use _upscale
}
2022-10-10 10:06:15 +02:00
}
2022-10-10 01:58:16 +02:00
break
case 'upscale' :
newTaskRequest . reqBody = Object . assign ( { } , reqBody , {
num _outputs : 1 ,
//use_face_correction: 'GFPGANv1.3',
use _upscale : upscaleModelField . value ,
} )
break
default :
throw new Error ( "Unknown upscale mode: " + mode )
}
2022-10-09 13:19:00 +02:00
newTaskRequest . seed = newTaskRequest . reqBody . seed
newTaskRequest . numOutputsTotal = 1
newTaskRequest . batchCount = 1
createTask ( newTaskRequest )
}
}
2022-09-29 09:31:18 +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-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-11 21:32:06 +02:00
let res = undefined
let stepUpdate = undefined
2022-09-23 16:18:48 +02:00
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
2022-10-12 00:38:23 +02:00
let readComplete = false
2022-09-23 16:18:48 +02:00
while ( true ) {
2022-10-11 22:42:27 +02:00
let t = new Date ( ) . getTime ( )
2022-10-12 00:38:23 +02:00
let jsonStr = ''
if ( ! readComplete ) {
const { value , done } = await reader . read ( )
if ( done ) {
readComplete = true
}
if ( done && finalJSON . length <= 0 && ! value ) {
break
}
if ( value ) {
jsonStr = textDecoder . decode ( value )
}
2022-10-11 22:42:27 +02:00
}
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.
// 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 = jsonStr . indexOf ( '}{' )
if ( lastChunkIdx !== - 1 ) {
finalJSON += jsonStr . substring ( 0 , lastChunkIdx + 1 )
jsonStr = jsonStr . substring ( lastChunkIdx + 2 )
2022-10-11 21:32:06 +02:00
} else {
2022-10-11 22:42:27 +02:00
finalJSON += jsonStr
jsonStr = ''
2022-10-11 19:48:18 +02:00
}
2022-10-11 22:42:27 +02:00
stepUpdate = JSON . parse ( finalJSON )
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-12 00:38:23 +02:00
if ( readComplete && finalJSON . length <= 0 ) {
2022-10-11 22:42:27 +02:00
break
}
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-12 00:38:23 +02:00
let timeTaken = ( prevTime === - 1 ? - 1 : t - prevTime )
2022-10-11 22:42:27 +02:00
let stepsRemaining = totalSteps - overallStepCount
stepsRemaining = ( stepsRemaining < 0 ? 0 : stepsRemaining )
let timeRemaining = ( timeTaken === - 1 ? '' : stepsRemaining * timeTaken ) // ms
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'
if ( stepUpdate . output !== undefined ) {
showImages ( reqBody , stepUpdate , outputContainer , true )
}
} else {
finalJSON = jsonStr
}
prevTime = t
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-12 02:10:40 +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 ) {
if ( serverStatus !== 'online' ) {
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 ) {
msg += 'No error response. '
}
if ( finalJSON ) {
msg += 'Buffered data: ' + finalJSON
}
logError ( msg , res , outputMsg )
} else {
msg = ` Unexpected Read Error:<br/><pre>Response: ${ res } <br/>StepUpdate: ${ typeof stepUpdate === 'object' ? JSON . stringify ( stepUpdate , undefined , 4 ) : stepUpdate } </pre> `
}
progressBar . style . display = 'none'
return false
}
2022-10-12 00:40:05 +02:00
lastPromptUsed = reqBody [ 'prompt' ]
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' )
progressBar . style . display = 'none'
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'
makeImageBtn . innerHTML = 'Make Image'
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'
makeImageBtn . innerHTML = 'Enqueue Next Image'
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
let time = new Date ( ) . getTime ( )
let successCount = 0
task . isProcessing = true
task [ 'stopTask' ] . innerHTML = '<i class="fa-solid fa-circle-stop"></i> Stop'
2022-09-27 15:07:21 +02:00
task [ 'taskStatusLabel' ] . innerText = "Processing"
task [ 'taskStatusLabel' ] . className += " activeTaskLabel"
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-11 04:28:34 +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-11 04:28:34 +02:00
newTask . reqBody . seed = startSeed + ( i * newTask . reqBody . num _outputs )
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-11 04:28:34 +02:00
if ( ! newTask . isProcessing ) {
task . isProcessing = false
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
time = new Date ( ) . getTime ( ) - time
time /= 1000
if ( successCount === task . batchCount ) {
task . outputMsg . innerText = 'Processed ' + task . numOutputsTotal + ' images in ' + time + ' seconds'
// 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 ,
use _cpu : useCPUField . checked ,
use _full _precision : useFullPrecisionField . checked ,
use _stable _diffusion _model : stableDiffusionModelField . value ,
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
}
function makeImage ( ) {
if ( serverStatus !== 'online' ) {
alert ( 'The server is still starting up..' )
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-09-27 15:39:28 +02:00
if ( negativePromptField . value . 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'
taskEntry . innerHTML = ` <div class="taskStatusLabel">Enqueued</div>
< button class = "secondaryButton stopTask" > < i class = "fa-solid fa-trash-can" > < / i > R e m o v e < / b u t t o n >
2022-09-28 10:14:48 +02:00
< div class = "preview-prompt collapsible active" > < / d i v >
2022-09-27 14:39:07 +02:00
< div class = "taskConfig" > $ { taskConfig } < / d i v >
2022-09-28 10:14:48 +02:00
< div class = "collapsible-content" style = "display: block" >
< div class = "outputMsg" > < / d i v >
< div class = "progressBar" > < / d i v >
< 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' )
task [ 'progressBar' ] = taskEntry . querySelector ( '.progressBar' )
task [ 'stopTask' ] = taskEntry . querySelector ( '.stopTask' )
task [ 'stopTask' ] . addEventListener ( 'click' , async function ( ) {
if ( task [ 'isProcessing' ] ) {
2022-09-27 16:35:22 +02:00
task . isProcessing = false
2022-09-27 14:39:07 +02:00
try {
let res = await fetch ( '/image/stop' )
} 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-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
prompts = prompts . split ( '\n' )
let promptsToMake = [ ]
prompts . forEach ( prompt => {
prompt = prompt . trim ( )
if ( prompt === '' ) {
return
}
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-09 13:17:43 +02:00
const promptTags = ( activeTags . length > 0 ? activeTags . map ( x => x . name ) . join ( ", " ) : "" )
return promptsToMake . map ( ( prompt ) => ` ${ prompt } , ${ promptTags } ` )
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
}
function permute ( arr ) {
let permutations = [ ]
let n = arr . length
let n _permutations = Math . pow ( 2 , n )
for ( let i = 0 ; i < n _permutations ; i ++ ) {
let perm = [ ]
let mask = Number ( i ) . toString ( 2 ) . padStart ( n , '0' )
for ( let idx = 0 ; idx < mask . length ; idx ++ ) {
if ( mask [ idx ] === '1' && arr [ idx ] . trim ( ) !== '' ) {
perm . push ( arr [ idx ] )
}
}
if ( perm . length > 0 ) {
permutations . push ( perm )
}
}
return permutations
}
2022-09-23 16:18:48 +02:00
// create a file name with embedded prompt and metadata
// for easier cateloging and comparison
2022-10-06 11:35:34 +02:00
function createFileName ( seed , outputFormat ) {
2022-09-23 16:18:48 +02:00
// 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
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 {
let res = await fetch ( '/image/stop' )
} 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
} )
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 ( )
2022-10-08 14:52:01 +02:00
outputFormatField . addEventListener ( 'change' , handleStringSettingChange ( OUTPUT _FORMAT _KEY ) )
outputFormatField . value = getOutputFormat ( )
2022-09-23 16:18:48 +02:00
diskPathField . addEventListener ( 'change' , handleStringSettingChange ( DISK _PATH _KEY ) )
2022-10-08 11:53:44 +02:00
widthField . addEventListener ( 'change' , resizeInpaintingEditor )
heightField . addEventListener ( 'change' , resizeInpaintingEditor )
2022-09-23 16:18:48 +02:00
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' ) {
2022-09-27 14:39:07 +02:00
// logError('The server is still starting up..')
2022-09-23 16:18:48 +02:00
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' )
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 {
let res = await fetch ( '/models' )
2022-10-11 04:27:15 +02:00
const models = await res . json ( )
2022-10-06 10:58:02 +02:00
let activeModel = models [ 'active' ]
let modelOptions = models [ 'options' ]
let stableDiffusionOptions = modelOptions [ 'stable-diffusion' ]
stableDiffusionOptions . forEach ( modelName => {
let modelOption = document . createElement ( 'option' )
modelOption . value = modelName
modelOption . innerText = modelName
if ( modelName === activeModel [ 'stable-diffusion' ] ) {
modelOption . selected = true
}
stableDiffusionModelField . appendChild ( modelOption )
} )
2022-10-11 04:27:15 +02:00
console . log ( 'get models response' , models )
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 ]
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' )
2022-10-08 13:56:01 +02:00
resizeInpaintingEditor ( )
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
// 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 ) {
2022-09-29 09:38:42 +02:00
return ( number > 1 ) ? 's' : ''
2022-09-23 16:18:48 +02:00
}
2022-09-29 09:38:42 +02:00
var temp = Math . floor ( milliseconds / 1000 )
var hours = Math . floor ( ( temp %= 86400 ) / 3600 )
2022-09-23 16:18:48 +02:00
var s = ''
if ( hours ) {
2022-09-29 09:38:42 +02:00
s += hours + ' hour' + numberEnding ( hours ) + ' '
2022-09-23 16:18:48 +02:00
}
2022-09-29 09:38:42 +02:00
var minutes = Math . floor ( ( temp %= 3600 ) / 60 )
2022-09-23 16:18:48 +02:00
if ( minutes ) {
2022-09-29 09:38:42 +02:00
s += minutes + ' minute' + numberEnding ( minutes ) + ' '
2022-09-23 16:18:48 +02:00
}
2022-09-29 09:38:42 +02:00
var seconds = temp % 60
2022-09-23 16:18:48 +02:00
if ( ! hours && minutes < 4 && seconds ) {
2022-09-29 09:38:42 +02:00
s += seconds + ' second' + numberEnding ( seconds )
2022-09-23 16:18:48 +02:00
}
2022-09-29 09:38:42 +02:00
return s
2022-09-23 16:18:48 +02:00
}
2022-09-28 10:14:48 +02:00
// https://gomakethings.com/finding-the-next-and-previous-sibling-elements-that-match-a-selector-with-vanilla-js/
function getNextSibling ( elem , selector ) {
// Get the next sibling element
var sibling = elem . nextElementSibling
// If there's no selector, return the first sibling
if ( ! selector ) return sibling
// If the sibling matches our selector, use it
// If not, jump to the next sibling and continue the loop
while ( sibling ) {
if ( sibling . matches ( selector ) ) return sibling
sibling = sibling . nextElementSibling
}
}
2022-09-23 16:18:48 +02:00
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" )
2022-09-28 10:14:48 +02:00
let content = getNextSibling ( this , '.collapsible-content' )
2022-09-23 16:18:48 +02:00
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 ( ) {
2022-09-29 09:38:42 +02:00
editorModifierTagsList . innerHTML = ''
2022-09-23 16:18:48 +02:00
if ( activeTags . length == 0 ) {
2022-09-29 09:38:42 +02:00
editorTagsContainer . style . display = 'none'
return
2022-09-23 16:18:48 +02:00
} else {
2022-09-29 09:38:42 +02:00
editorTagsContainer . style . display = 'block'
2022-09-23 16:18:48 +02:00
}
activeTags . forEach ( ( tag , index ) => {
2022-09-29 09:38:42 +02:00
tag . element . querySelector ( '.modifier-card-image-overlay' ) . innerText = '-'
tag . element . classList . add ( 'modifier-card-tiny' )
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
editorModifierTagsList . appendChild ( tag . element )
2022-09-23 16:18:48 +02:00
tag . element . addEventListener ( 'click' , ( ) => {
2022-09-29 09:38:42 +02:00
let idx = activeTags . indexOf ( tag )
2022-09-23 16:18:48 +02:00
if ( idx !== - 1 ) {
2022-09-29 09:38:42 +02:00
activeTags [ idx ] . originElement . classList . remove ( activeCardClass )
activeTags [ idx ] . originElement . querySelector ( '.modifier-card-image-overlay' ) . innerText = '+'
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
activeTags . splice ( idx , 1 )
refreshTagsList ( )
2022-09-23 16:18:48 +02:00
}
2022-09-29 09:38:42 +02:00
} )
} )
2022-09-23 16:18:48 +02:00
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 ) {
2022-09-29 09:38:42 +02:00
const modifierCard = document . createElement ( 'div' )
modifierCard . className = 'modifier-card'
2022-09-23 16:18:48 +02:00
modifierCard . innerHTML = `
< div class = "modifier-card-overlay" > < / d i v >
< div class = "modifier-card-image-container" >
< div class = "modifier-card-image-overlay" > + < / d i v >
< p class = "modifier-card-error-label" > < / p >
< img onerror = "this.remove()" alt = "Modifier Image" class = "modifier-card-image" >
< / d i v >
< div class = "modifier-card-container" >
< div class = "modifier-card-label" > < p > < / p > < / d i v >
2022-09-29 09:38:42 +02:00
< / d i v > `
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
const image = modifierCard . querySelector ( '.modifier-card-image' )
const errorText = modifierCard . querySelector ( '.modifier-card-error-label' )
const label = modifierCard . querySelector ( '.modifier-card-label' )
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
errorText . innerText = 'No Image'
2022-09-23 16:18:48 +02:00
if ( typeof previews == 'object' ) {
image . src = previews [ 0 ] ; // portrait
2022-09-29 09:38:42 +02:00
image . setAttribute ( 'preview-type' , 'portrait' )
2022-09-23 16:18:48 +02:00
} else {
2022-09-29 09:38:42 +02:00
image . remove ( )
2022-09-23 16:18:48 +02:00
}
2022-09-29 09:38:42 +02:00
const maxLabelLength = 30
const nameWithoutBy = name . replace ( 'by ' , '' )
2022-09-23 16:18:48 +02:00
if ( nameWithoutBy . length <= maxLabelLength ) {
2022-09-29 09:38:42 +02:00
label . querySelector ( 'p' ) . innerText = nameWithoutBy
2022-09-23 16:18:48 +02:00
} else {
2022-09-29 09:38:42 +02:00
const tooltipText = document . createElement ( 'span' )
tooltipText . className = 'tooltip-text'
tooltipText . innerText = name
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
label . classList . add ( 'tooltip' )
label . appendChild ( tooltipText )
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
label . querySelector ( 'p' ) . innerText = nameWithoutBy . substring ( 0 , maxLabelLength ) + '...'
2022-09-23 16:18:48 +02:00
}
2022-09-29 09:38:42 +02:00
return modifierCard
2022-09-23 16:18:48 +02:00
}
function changePreviewImages ( val ) {
2022-09-29 09:38:42 +02:00
const previewImages = document . querySelectorAll ( '.modifier-card-image-container img' )
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
let previewArr = [ ]
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
modifiers . map ( x => x . modifiers ) . forEach ( x => previewArr . push ( ... x . map ( m => m . previews ) ) )
2022-09-23 16:18:48 +02:00
previewArr = previewArr . map ( x => {
2022-09-29 09:38:42 +02:00
let obj = { }
2022-09-23 16:18:48 +02:00
x . forEach ( preview => {
2022-09-29 09:38:42 +02:00
obj [ preview . name ] = preview . path
} )
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
return obj
} )
2022-09-23 16:18:48 +02:00
previewImages . forEach ( previewImage => {
2022-09-29 09:38:42 +02:00
const currentPreviewType = previewImage . getAttribute ( 'preview-type' )
const relativePreviewPath = previewImage . src . split ( modifierThumbnailPath + '/' ) . pop ( )
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
const previews = previewArr . find ( preview => relativePreviewPath == preview [ currentPreviewType ] )
2022-09-23 16:18:48 +02:00
if ( typeof previews == 'object' ) {
2022-09-29 09:38:42 +02:00
let preview = null
2022-09-23 16:18:48 +02:00
if ( val == 'portrait' ) {
2022-09-29 09:38:42 +02:00
preview = previews . portrait
2022-09-23 16:18:48 +02:00
}
else if ( val == 'landscape' ) {
2022-09-29 09:38:42 +02:00
preview = previews . landscape
2022-09-23 16:18:48 +02:00
}
if ( preview != null ) {
2022-09-29 09:38:42 +02:00
previewImage . src = ` ${ modifierThumbnailPath } / ${ preview } `
previewImage . setAttribute ( 'preview-type' , val )
2022-09-23 16:18:48 +02:00
}
}
2022-09-29 09:38:42 +02:00
} )
2022-09-23 16:18:48 +02:00
}
function resizeModifierCards ( val ) {
2022-09-29 09:38:42 +02:00
const cardSizePrefix = 'modifier-card-size_'
const modifierCardClass = 'modifier-card'
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
const modifierCards = document . querySelectorAll ( ` . ${ modifierCardClass } ` )
const cardSize = n => ` ${ cardSizePrefix } ${ n } `
2022-09-23 16:18:48 +02:00
modifierCards . forEach ( card => {
// remove existing size classes
2022-09-29 09:38:42 +02:00
const classes = card . className . split ( ' ' ) . filter ( c => ! c . startsWith ( cardSizePrefix ) )
card . className = classes . join ( ' ' ) . trim ( )
2022-09-23 16:18:48 +02:00
if ( val != 0 )
2022-09-29 09:38:42 +02:00
card . classList . add ( cardSize ( val ) )
} )
2022-09-23 16:18:48 +02:00
}
async function loadModifiers ( ) {
try {
2022-09-24 06:17:34 +02:00
let res = await fetch ( '/modifiers.json?v=2' )
2022-09-23 16:18:48 +02:00
if ( res . status === 200 ) {
res = await res . json ( )
modifiers = res ; // update global variable
2022-09-24 12:09:48 +02:00
res . forEach ( ( modifierGroup , idx ) => {
2022-09-29 09:38:42 +02:00
const title = modifierGroup . category
const modifiers = modifierGroup . modifiers
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
const titleEl = document . createElement ( 'h5' )
titleEl . className = 'collapsible'
titleEl . innerText = title
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
const modifiersEl = document . createElement ( 'div' )
modifiersEl . classList . add ( 'collapsible-content' , 'editor-modifiers-leaf' )
2022-09-23 16:18:48 +02:00
2022-09-24 12:09:48 +02:00
if ( idx == 0 ) {
titleEl . className += ' active'
modifiersEl . style . display = 'block'
}
2022-09-23 16:18:48 +02:00
modifiers . forEach ( modObj => {
2022-09-29 09:38:42 +02:00
const modifierName = modObj . modifier
const modifierPreviews = modObj ? . previews ? . map ( preview => ` ${ modifierThumbnailPath } / ${ preview . path } ` )
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
const modifierCard = createModifierCard ( modifierName , modifierPreviews )
2022-09-23 16:18:48 +02:00
if ( typeof modifierCard == 'object' ) {
2022-09-29 09:38:42 +02:00
modifiersEl . appendChild ( modifierCard )
2022-09-23 16:18:48 +02:00
modifierCard . addEventListener ( 'click' , ( ) => {
if ( activeTags . map ( x => x . name ) . includes ( modifierName ) ) {
// remove modifier from active array
2022-09-29 09:38:42 +02:00
activeTags = activeTags . filter ( x => x . name != modifierName )
modifierCard . classList . remove ( activeCardClass )
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
modifierCard . querySelector ( '.modifier-card-image-overlay' ) . innerText = '+'
2022-09-23 16:18:48 +02:00
} else {
// add modifier to active array
activeTags . push ( {
'name' : modifierName ,
'element' : modifierCard . cloneNode ( true ) ,
'originElement' : modifierCard ,
'previews' : modifierPreviews
2022-09-29 09:38:42 +02:00
} )
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
modifierCard . classList . add ( activeCardClass )
2022-09-23 16:18:48 +02:00
2022-09-29 09:38:42 +02:00
modifierCard . querySelector ( '.modifier-card-image-overlay' ) . innerText = '-'
2022-09-23 16:18:48 +02:00
}
2022-09-29 09:38:42 +02:00
refreshTagsList ( )
} )
2022-09-23 16:18:48 +02:00
}
2022-09-29 09:38:42 +02:00
} )
2022-09-23 16:18:48 +02:00
let brk = document . createElement ( 'br' )
brk . style . clear = 'both'
modifiersEl . appendChild ( brk )
let e = document . createElement ( 'div' )
e . appendChild ( titleEl )
e . appendChild ( modifiersEl )
editorModifierEntries . appendChild ( e )
} )
createCollapsibles ( editorModifierEntries )
}
} catch ( e ) {
console . log ( 'error fetching modifiers' , e )
}
2022-09-25 01:55:11 +02:00
}