2022-10-31 04:21:39 +01:00
"use strict" // Opt in to a restricted variant of JavaScript
2022-11-09 10:51:41 +01:00
const EXT _REGEX = /(?:\.([^.]+))?$/
const TEXT _EXTENSIONS = [ 'txt' , 'json' ]
const IMAGE _EXTENSIONS = [ 'jpg' , 'jpeg' , 'png' , 'bmp' , 'tiff' , 'tif' , 'tga' ]
2022-11-04 01:34:12 +01:00
function parseBoolean ( stringValue ) {
if ( typeof stringValue === 'boolean' ) {
return stringValue
}
if ( typeof stringValue === 'number' ) {
return stringValue !== 0
}
if ( typeof stringValue !== 'string' ) {
return false
}
switch ( stringValue ? . toLowerCase ( ) ? . trim ( ) ) {
case "true" :
case "yes" :
2022-11-05 18:33:38 +01:00
case "on" :
2022-11-04 01:34:12 +01:00
case "1" :
return true ;
case "false" :
case "no" :
2022-11-05 18:33:38 +01:00
case "off" :
2022-11-04 01:34:12 +01:00
case "0" :
case null :
case undefined :
return false ;
}
try {
return Boolean ( JSON . parse ( stringValue ) ) ;
} catch {
return Boolean ( stringValue )
}
}
2022-10-31 04:21:39 +01:00
const TASK _MAPPING = {
prompt : { name : 'Prompt' ,
setUI : ( prompt ) => {
promptField . value = prompt
} ,
readUI : ( ) => promptField . value ,
parse : ( val ) => val
} ,
negative _prompt : { name : 'Negative Prompt' ,
setUI : ( negative _prompt ) => {
negativePromptField . value = negative _prompt
} ,
readUI : ( ) => negativePromptField . value ,
parse : ( val ) => val
} ,
2022-11-22 09:06:43 +01:00
active _tags : { name : "Image Modifiers" ,
setUI : ( active _tags ) => {
refreshModifiersState ( active _tags )
} ,
readUI : ( ) => activeTags . map ( x => x . name ) ,
parse : ( val ) => val
} ,
2022-10-31 04:21:39 +01:00
width : { name : 'Width' ,
setUI : ( width ) => {
2022-10-31 04:35:42 +01:00
const oldVal = widthField . value
2022-10-31 04:21:39 +01:00
widthField . value = width
2022-10-31 04:35:42 +01:00
if ( ! widthField . value ) {
widthField . value = oldVal
}
2022-10-31 04:21:39 +01:00
} ,
readUI : ( ) => parseInt ( widthField . value ) ,
parse : ( val ) => parseInt ( val )
} ,
height : { name : 'Height' ,
setUI : ( height ) => {
2022-10-31 04:35:42 +01:00
const oldVal = heightField . value
2022-10-31 04:21:39 +01:00
heightField . value = height
2022-10-31 04:35:42 +01:00
if ( ! heightField . value ) {
heightField . value = oldVal
}
2022-10-31 04:21:39 +01:00
} ,
readUI : ( ) => parseInt ( heightField . value ) ,
parse : ( val ) => parseInt ( val )
} ,
seed : { name : 'Seed' ,
setUI : ( seed ) => {
if ( ! seed ) {
randomSeedField . checked = true
2022-11-03 08:03:50 +01:00
seedField . disabled = true
2022-11-30 04:04:42 +01:00
seedField . value = 0
2022-10-31 04:21:39 +01:00
return
}
randomSeedField . checked = false
2022-11-03 08:03:50 +01:00
seedField . disabled = false
2022-10-31 04:21:39 +01:00
seedField . value = seed
} ,
2022-11-30 04:04:42 +01:00
readUI : ( ) => parseInt ( seedField . value ) , // just return the value the user is seeing in the UI
2022-10-31 04:21:39 +01:00
parse : ( val ) => parseInt ( val )
} ,
num _inference _steps : { name : 'Steps' ,
setUI : ( num _inference _steps ) => {
numInferenceStepsField . value = num _inference _steps
} ,
readUI : ( ) => parseInt ( numInferenceStepsField . value ) ,
parse : ( val ) => parseInt ( val )
} ,
guidance _scale : { name : 'Guidance Scale' ,
setUI : ( guidance _scale ) => {
guidanceScaleField . value = guidance _scale
2022-11-02 15:53:48 +01:00
updateGuidanceScaleSlider ( )
2022-10-31 04:21:39 +01:00
} ,
readUI : ( ) => parseFloat ( guidanceScaleField . value ) ,
parse : ( val ) => parseFloat ( val )
} ,
prompt _strength : { name : 'Prompt Strength' ,
setUI : ( prompt _strength ) => {
promptStrengthField . value = prompt _strength
2022-11-02 15:53:48 +01:00
updatePromptStrengthSlider ( )
2022-10-31 04:21:39 +01:00
} ,
readUI : ( ) => parseFloat ( promptStrengthField . value ) ,
parse : ( val ) => parseFloat ( val )
} ,
init _image : { name : 'Initial Image' ,
setUI : ( init _image ) => {
initImagePreview . src = init _image
} ,
readUI : ( ) => initImagePreview . src ,
parse : ( val ) => val
} ,
mask : { name : 'Mask' ,
setUI : ( mask ) => {
2022-12-06 09:26:51 +01:00
setTimeout ( ( ) => { // add a delay to insure this happens AFTER the main image loads (which reloads the inpainter)
imageInpainter . setImg ( mask )
} , 250 )
2022-10-31 04:21:39 +01:00
maskSetting . checked = Boolean ( mask )
} ,
2022-12-06 09:26:51 +01:00
readUI : ( ) => ( maskSetting . checked ? imageInpainter . getImg ( ) : undefined ) ,
2022-10-31 04:21:39 +01:00
parse : ( val ) => val
} ,
use _face _correction : { name : 'Use Face Correction' ,
setUI : ( use _face _correction ) => {
2022-11-04 01:34:12 +01:00
useFaceCorrectionField . checked = parseBoolean ( use _face _correction )
2022-10-31 04:21:39 +01:00
} ,
readUI : ( ) => useFaceCorrectionField . checked ,
2022-11-04 01:34:12 +01:00
parse : ( val ) => parseBoolean ( val )
2022-10-31 04:21:39 +01:00
} ,
use _upscale : { name : 'Use Upscaling' ,
setUI : ( use _upscale ) => {
2022-11-04 01:34:51 +01:00
const oldVal = upscaleModelField . value
2022-10-31 04:21:39 +01:00
upscaleModelField . value = use _upscale
2022-11-04 01:34:51 +01:00
if ( upscaleModelField . value ) { // Is a valid value for the field.
useUpscalingField . checked = true
2022-11-08 14:16:55 +01:00
upscaleModelField . disabled = false
2022-11-04 01:34:51 +01:00
} else { // Not a valid value, restore the old value and disable the filter.
2022-11-08 14:16:55 +01:00
upscaleModelField . disabled = true
2022-11-04 01:34:51 +01:00
upscaleModelField . value = oldVal
useUpscalingField . checked = false
}
2022-10-31 04:21:39 +01:00
} ,
readUI : ( ) => ( useUpscalingField . checked ? upscaleModelField . value : undefined ) ,
parse : ( val ) => val
} ,
sampler : { name : 'Sampler' ,
setUI : ( sampler ) => {
samplerField . value = sampler
} ,
readUI : ( ) => samplerField . value ,
parse : ( val ) => val
} ,
use _stable _diffusion _model : { name : 'Stable Diffusion model' ,
setUI : ( use _stable _diffusion _model ) => {
2022-10-31 04:35:42 +01:00
const oldVal = stableDiffusionModelField . value
2022-10-31 04:41:26 +01:00
2022-11-19 09:56:44 +01:00
use _stable _diffusion _model = getModelPath ( use _stable _diffusion _model , [ '.ckpt' ] )
2022-10-31 04:21:39 +01:00
stableDiffusionModelField . value = use _stable _diffusion _model
2022-10-31 04:41:26 +01:00
2022-10-31 04:35:42 +01:00
if ( ! stableDiffusionModelField . value ) {
stableDiffusionModelField . value = oldVal
}
2022-10-31 04:21:39 +01:00
} ,
readUI : ( ) => stableDiffusionModelField . value ,
parse : ( val ) => val
} ,
2022-11-19 09:56:44 +01:00
use _vae _model : { name : 'VAE model' ,
setUI : ( use _vae _model ) => {
const oldVal = vaeModelField . value
if ( use _vae _model !== '' ) {
use _vae _model = getModelPath ( use _vae _model , [ '.vae.pt' , '.ckpt' ] )
use _vae _model = use _vae _model !== '' ? use _vae _model : oldVal
}
vaeModelField . value = use _vae _model
} ,
readUI : ( ) => vaeModelField . value ,
parse : ( val ) => val
} ,
2022-10-31 04:21:39 +01:00
2022-11-24 23:15:27 +01:00
num _outputs : { name : 'Parallel Images' ,
setUI : ( num _outputs ) => {
numOutputsParallelField . value = num _outputs
2022-10-31 04:21:39 +01:00
} ,
readUI : ( ) => parseInt ( numOutputsParallelField . value ) ,
parse : ( val ) => val
} ,
use _cpu : { name : 'Use CPU' ,
setUI : ( use _cpu ) => {
useCPUField . checked = use _cpu
} ,
readUI : ( ) => useCPUField . checked ,
parse : ( val ) => val
} ,
turbo : { name : 'Turbo' ,
setUI : ( turbo ) => {
turboField . checked = turbo
} ,
readUI : ( ) => turboField . checked ,
parse : ( val ) => Boolean ( val )
} ,
stream _image _progress : { name : 'Stream Image Progress' ,
setUI : ( stream _image _progress ) => {
streamImageProgressField . checked = ( parseInt ( numOutputsTotalField . value ) > 50 ? false : stream _image _progress )
} ,
readUI : ( ) => streamImageProgressField . checked ,
parse : ( val ) => Boolean ( val )
} ,
show _only _filtered _image : { name : 'Show only the corrected/upscaled image' ,
setUI : ( show _only _filtered _image ) => {
showOnlyFilteredImageField . checked = show _only _filtered _image
} ,
readUI : ( ) => showOnlyFilteredImageField . checked ,
parse : ( val ) => Boolean ( val )
} ,
output _format : { name : 'Output Format' ,
setUI : ( output _format ) => {
outputFormatField . value = output _format
} ,
readUI : ( ) => outputFormatField . value ,
parse : ( val ) => val
} ,
save _to _disk _path : { name : 'Save to disk path' ,
setUI : ( save _to _disk _path ) => {
saveToDiskField . checked = Boolean ( save _to _disk _path )
diskPathField . value = save _to _disk _path
} ,
readUI : ( ) => diskPathField . value ,
parse : ( val ) => val
}
}
2022-11-18 11:08:17 +01:00
function restoreTaskToUI ( task , fieldsToSkip ) {
fieldsToSkip = fieldsToSkip || [ ]
2022-10-31 04:21:39 +01:00
if ( 'numOutputsTotal' in task ) {
numOutputsTotalField . value = task . numOutputsTotal
}
if ( 'seed' in task ) {
randomSeedField . checked = false
seedField . value = task . seed
}
if ( ! ( 'reqBody' in task ) ) {
return
}
for ( const key in TASK _MAPPING ) {
2022-11-18 11:08:17 +01:00
if ( key in task . reqBody && ! fieldsToSkip . includes ( key ) ) {
2022-10-31 04:21:39 +01:00
TASK _MAPPING [ key ] . setUI ( task . reqBody [ key ] )
}
}
2022-11-18 11:08:17 +01:00
// restore the original tag
promptField . value = task . reqBody . original _prompt || task . reqBody . prompt
// properly reset checkboxes
if ( ! ( 'use_face_correction' in task . reqBody ) ) {
useFaceCorrectionField . checked = false
}
if ( ! ( 'use_upscale' in task . reqBody ) ) {
useUpscalingField . checked = false
}
if ( ! ( 'mask' in task . reqBody ) ) {
maskSetting . checked = false
}
upscaleModelField . disabled = ! useUpscalingField . checked
// Show the source picture if present
initImagePreview . src = ( task . reqBody . init _image == undefined ? '' : task . reqBody . init _image )
if ( IMAGE _REGEX . test ( initImagePreview . src ) ) {
2022-12-06 09:26:51 +01:00
if ( Boolean ( task . reqBody . mask ) ) {
setTimeout ( ( ) => { // add a delay to insure this happens AFTER the main image loads (which reloads the inpainter)
imageInpainter . setImg ( task . reqBody . mask )
} , 250 )
}
2022-11-18 11:08:17 +01:00
}
2022-10-31 04:21:39 +01:00
}
2022-10-31 06:13:04 +01:00
function readUI ( ) {
2022-10-31 06:02:23 +01:00
const reqBody = { }
for ( const key in TASK _MAPPING ) {
reqBody [ key ] = TASK _MAPPING [ key ] . readUI ( )
}
return {
'numOutputsTotal' : parseInt ( numOutputsTotalField . value ) ,
'seed' : TASK _MAPPING [ 'seed' ] . readUI ( ) ,
'reqBody' : reqBody
}
}
2022-11-19 09:56:44 +01:00
function getModelPath ( filename , extensions )
{
let pathIdx = filename . lastIndexOf ( '/' ) // Linux, Mac paths
if ( pathIdx < 0 ) {
pathIdx = filename . lastIndexOf ( '\\' ) // Windows paths.
}
if ( pathIdx >= 0 ) {
filename = filename . slice ( pathIdx + 1 )
}
extensions . forEach ( ext => {
if ( filename . endsWith ( ext ) ) {
filename = filename . slice ( 0 , filename . length - ext . length )
}
} )
2022-11-24 22:47:35 +01:00
return filename
2022-11-19 09:56:44 +01:00
}
2022-10-31 06:02:23 +01:00
2022-10-31 04:21:39 +01:00
const TASK _TEXT _MAPPING = {
width : 'Width' ,
height : 'Height' ,
seed : 'Seed' ,
num _inference _steps : 'Steps' ,
guidance _scale : 'Guidance Scale' ,
prompt _strength : 'Prompt Strength' ,
use _face _correction : 'Use Face Correction' ,
use _upscale : 'Use Upscaling' ,
sampler : 'Sampler' ,
negative _prompt : 'Negative Prompt' ,
use _stable _diffusion _model : 'Stable Diffusion model'
}
const afterPromptRe = /^\s*Width\s*:\s*\d+\s*(?:\r\n|\r|\n)+\s*Height\s*:\s*\d+\s*(\r\n|\r|\n)+Seed\s*:\s*\d+\s*$/igm
function parseTaskFromText ( str ) {
const taskReqBody = { }
// Prompt
afterPromptRe . lastIndex = 0
2022-10-31 06:01:56 +01:00
const match = afterPromptRe . exec ( str )
2022-10-31 04:21:39 +01:00
if ( match ) {
let prompt = str . slice ( 0 , match . index )
str = str . slice ( prompt . length )
taskReqBody . prompt = prompt . trim ( )
console . log ( 'Prompt:' , taskReqBody . prompt )
}
for ( const key in TASK _TEXT _MAPPING ) {
const name = TASK _TEXT _MAPPING [ key ] ;
let val = undefined
2022-11-08 14:14:11 +01:00
const reName = new RegExp ( ` ${ name } \\ *: \\ *(.*)(?: \\ r \\ n| \\ r| \\ n)* ` , 'igm' )
const match = reName . exec ( str ) ;
if ( match ) {
str = str . slice ( 0 , match . index ) + str . slice ( match . index + match [ 0 ] . length )
val = match [ 1 ]
2022-10-31 04:21:39 +01:00
}
2022-11-08 14:14:11 +01:00
if ( val !== undefined ) {
2022-10-31 04:21:39 +01:00
taskReqBody [ key ] = TASK _MAPPING [ key ] . parse ( val . trim ( ) )
console . log ( TASK _MAPPING [ key ] . name + ':' , taskReqBody [ key ] )
if ( ! str ) {
2022-10-31 06:01:56 +01:00
break
2022-10-31 04:21:39 +01:00
}
}
}
if ( Object . keys ( taskReqBody ) . length <= 0 ) {
return undefined
}
const task = { reqBody : taskReqBody }
if ( 'seed' in taskReqBody ) {
task . seed = taskReqBody . seed
}
return task
}
2022-11-17 07:44:11 +01:00
async function parseContent ( text ) {
text = text . trim ( ) ;
if ( text . startsWith ( '{' ) && text . endsWith ( '}' ) ) {
2022-10-31 04:21:39 +01:00
try {
2022-11-17 07:44:11 +01:00
const task = JSON . parse ( text )
2022-10-31 04:21:39 +01:00
restoreTaskToUI ( task )
2022-11-17 08:16:20 +01:00
return true
2022-10-31 04:21:39 +01:00
} catch ( e ) {
2022-11-17 07:44:11 +01:00
console . warn ( ` JSON text content couldn't be parsed. ` , e )
2022-10-31 04:21:39 +01:00
}
2022-11-17 08:16:20 +01:00
return false
2022-10-31 04:21:39 +01:00
}
// Normal txt file.
2022-11-17 07:44:11 +01:00
const task = parseTaskFromText ( text )
2022-10-31 04:21:39 +01:00
if ( task ) {
restoreTaskToUI ( task )
2022-11-17 08:16:20 +01:00
return true
2022-10-31 04:21:39 +01:00
} else {
2022-11-17 07:44:11 +01:00
console . warn ( ` Raw text content couldn't be parsed. ` )
2022-11-17 08:16:20 +01:00
return false
2022-10-31 04:21:39 +01:00
}
}
2022-11-17 07:44:11 +01:00
async function readFile ( file , i ) {
2022-11-22 17:09:47 +01:00
console . log ( ` Event %o reading file[ ${ i } ]: ${ file . name } ... ` )
2022-11-17 07:44:11 +01:00
const fileContent = ( await file . text ( ) ) . trim ( )
return await parseContent ( fileContent )
}
2022-10-31 04:21:39 +01:00
function dropHandler ( ev ) {
console . log ( 'Content dropped...' )
2022-11-08 13:44:26 +01:00
let items = [ ]
2022-10-31 04:21:39 +01:00
if ( ev ? . dataTransfer ? . items ) { // Use DataTransferItemList interface
2022-11-08 13:44:26 +01:00
items = Array . from ( ev . dataTransfer . items )
items = items . filter ( item => item . kind === 'file' )
items = items . map ( item => item . getAsFile ( ) )
2022-10-31 04:21:39 +01:00
} else if ( ev ? . dataTransfer ? . files ) { // Use DataTransfer interface
2022-11-08 13:44:26 +01:00
items = Array . from ( ev . dataTransfer . files )
}
2022-11-09 10:51:41 +01:00
items . forEach ( item => { item . file _ext = EXT _REGEX . exec ( item . name . toLowerCase ( ) ) [ 1 ] } )
let text _items = items . filter ( item => TEXT _EXTENSIONS . includes ( item . file _ext ) )
let image _items = items . filter ( item => IMAGE _EXTENSIONS . includes ( item . file _ext ) )
2022-11-08 13:44:26 +01:00
2022-11-09 10:51:41 +01:00
if ( image _items . length > 0 && ev . target == initImageSelector ) {
return // let the event bubble up, so that the Init Image filepicker can receive this
2022-10-31 04:21:39 +01:00
}
2022-11-09 10:51:41 +01:00
ev . preventDefault ( ) // Prevent default behavior (Prevent file/content from being opened)
text _items . forEach ( readFile )
2022-10-31 04:21:39 +01:00
}
function dragOverHandler ( ev ) {
console . log ( 'Content in drop zone' )
// Prevent default behavior (Prevent file/content from being opened)
ev . preventDefault ( )
ev . dataTransfer . dropEffect = "copy"
let img = new Image ( )
img . src = location . host + '/media/images/favicon-32x32.png'
ev . dataTransfer . setDragImage ( img , 16 , 16 )
}
document . addEventListener ( "drop" , dropHandler )
document . addEventListener ( "dragover" , dragOverHandler )
2022-10-31 06:02:23 +01:00
2022-11-03 08:10:31 +01:00
const TASK _REQ _NO _EXPORT = [
"use_cpu" ,
"turbo" ,
"save_to_disk_path"
]
2022-11-17 07:44:11 +01:00
const resetSettings = document . getElementById ( 'reset-image-settings' )
2022-11-03 08:10:31 +01:00
2022-11-17 07:44:11 +01:00
function checkReadTextClipboardPermission ( result ) {
if ( result . state != "granted" && result . state != "prompt" ) {
2022-11-14 01:23:04 +01:00
return
}
2022-11-17 07:44:11 +01:00
// PASTE ICON
const pasteIcon = document . createElement ( 'i' )
pasteIcon . className = 'fa-solid fa-paste section-button'
pasteIcon . innerHTML = ` <span class="simple-tooltip right">Paste Image Settings</span> `
pasteIcon . addEventListener ( 'click' , async ( event ) => {
event . stopPropagation ( )
// Add css class 'active'
pasteIcon . classList . add ( 'active' )
// In 350 ms remove the 'active' class
asyncDelay ( 350 ) . then ( ( ) => pasteIcon . classList . remove ( 'active' ) )
// Retrieve clipboard content and try to parse it
const text = await navigator . clipboard . readText ( ) ;
await parseContent ( text )
} )
resetSettings . parentNode . insertBefore ( pasteIcon , resetSettings )
2022-11-14 01:23:04 +01:00
}
2022-11-17 07:44:11 +01:00
navigator . permissions . query ( { name : "clipboard-read" } ) . then ( checkReadTextClipboardPermission , ( reason ) => console . log ( 'clipboard-read is not available. %o' , reason ) )
2022-11-14 01:23:04 +01:00
2022-11-17 08:20:01 +01:00
document . addEventListener ( 'paste' , async ( event ) => {
2022-11-17 08:36:14 +01:00
if ( event . target ) {
const targetTag = event . target . tagName . toLowerCase ( )
// Disable when targeting input elements.
if ( targetTag === 'input' || targetTag === 'textarea' ) {
return
}
}
2022-11-17 08:16:20 +01:00
const paste = ( event . clipboardData || window . clipboardData ) . getData ( 'text' )
const selection = window . getSelection ( )
2022-11-17 08:20:01 +01:00
if ( selection . toString ( ) . trim ( ) . length <= 0 && await parseContent ( paste ) ) {
2022-11-17 08:36:14 +01:00
event . preventDefault ( )
return
2022-11-17 08:16:20 +01:00
}
2022-11-17 08:36:14 +01:00
} )
2022-11-14 01:23:04 +01:00
// Adds a copy and a paste icon if the browser grants permission to write to clipboard.
2022-10-31 06:02:23 +01:00
function checkWriteToClipboardPermission ( result ) {
2022-11-17 07:44:11 +01:00
if ( result . state != "granted" && result . state != "prompt" ) {
return
2022-10-31 06:02:23 +01:00
}
2022-11-17 07:44:11 +01:00
// COPY ICON
const copyIcon = document . createElement ( 'i' )
copyIcon . className = 'fa-solid fa-clipboard section-button'
copyIcon . innerHTML = ` <span class="simple-tooltip right">Copy Image Settings</span> `
copyIcon . addEventListener ( 'click' , ( event ) => {
event . stopPropagation ( )
// Add css class 'active'
copyIcon . classList . add ( 'active' )
// In 350 ms remove the 'active' class
asyncDelay ( 350 ) . then ( ( ) => copyIcon . classList . remove ( 'active' ) )
const uiState = readUI ( )
TASK _REQ _NO _EXPORT . forEach ( ( key ) => delete uiState . reqBody [ key ] )
if ( uiState . reqBody . init _image && ! IMAGE _REGEX . test ( uiState . reqBody . init _image ) ) {
delete uiState . reqBody . init _image
delete uiState . reqBody . prompt _strength
}
navigator . clipboard . writeText ( JSON . stringify ( uiState , undefined , 4 ) )
} )
resetSettings . parentNode . insertBefore ( copyIcon , resetSettings )
2022-10-31 06:02:23 +01:00
}
2022-11-17 07:44:11 +01:00
// Determine which access we have to the clipboard. Clipboard access is only available on localhost or via TLS.
2022-10-31 06:02:23 +01:00
navigator . permissions . query ( { name : "clipboard-write" } ) . then ( checkWriteToClipboardPermission , ( e ) => {
if ( e instanceof TypeError && typeof navigator ? . clipboard ? . writeText === 'function' ) {
// Fix for firefox https://bugzilla.mozilla.org/show_bug.cgi?id=1560373
checkWriteToClipboardPermission ( { state : "granted" } )
}
} )