forked from extern/easydiffusion
Merge branch 'beta' of https://github.com/cmdr2/stable-diffusion-ui.git into multi-gpu
# Conflicts: # ui/media/js/main.js # ui/sd_internal/runtime.py # ui/server.py
This commit is contained in:
commit
849d1d7ebd
@ -6,6 +6,7 @@
|
|||||||
<link rel="icon" type="image/png" href="/media/images/favicon-32x32.png" sizes="32x32">
|
<link rel="icon" type="image/png" href="/media/images/favicon-32x32.png" sizes="32x32">
|
||||||
<link rel="stylesheet" href="/media/css/fonts.css?v=1">
|
<link rel="stylesheet" href="/media/css/fonts.css?v=1">
|
||||||
<link rel="stylesheet" href="/media/css/themes.css?v=1">
|
<link rel="stylesheet" href="/media/css/themes.css?v=1">
|
||||||
|
<link rel="stylesheet" href="/media/css/auto-save.css?v=1">
|
||||||
<link rel="stylesheet" href="/media/css/main.css?v=1">
|
<link rel="stylesheet" href="/media/css/main.css?v=1">
|
||||||
<link rel="stylesheet" href="/media/css/modifier-thumbnails.css?v=1">
|
<link rel="stylesheet" href="/media/css/modifier-thumbnails.css?v=1">
|
||||||
<link rel="stylesheet" href="/media/css/fontawesome-all.min.css?v=1">
|
<link rel="stylesheet" href="/media/css/fontawesome-all.min.css?v=1">
|
||||||
@ -17,7 +18,7 @@
|
|||||||
<div id="container">
|
<div id="container">
|
||||||
<div id="top-nav">
|
<div id="top-nav">
|
||||||
<div id="logo">
|
<div id="logo">
|
||||||
<h1>Stable Diffusion UI <small>v2.29 <span id="updateBranchLabel"></span></small></h1>
|
<h1>Stable Diffusion UI <small>v2.3.2 <span id="updateBranchLabel"></span></small></h1>
|
||||||
</div>
|
</div>
|
||||||
<ul id="top-nav-items">
|
<ul id="top-nav-items">
|
||||||
<li class="dropdown">
|
<li class="dropdown">
|
||||||
@ -107,7 +108,7 @@
|
|||||||
<ul id="editor-settings-entries" class="collapsible-content">
|
<ul id="editor-settings-entries" class="collapsible-content">
|
||||||
<li><table>
|
<li><table>
|
||||||
<tr><b class="settings-subheader">Image Settings</b></tr>
|
<tr><b class="settings-subheader">Image Settings</b></tr>
|
||||||
<tr class="pl-5"><td><label for="seed">Seed:</label></td><td><input id="seed" name="seed" size="10" value="30000"> <input id="random_seed" name="random_seed" type="checkbox" checked> <label for="random_seed">Random</label></td></tr>
|
<tr class="pl-5"><td><label for="seed">Seed:</label></td><td><input id="seed" name="seed" size="10" value="30000"> <input id="random_seed" name="random_seed" type="checkbox" checked><label for="random_seed">Random</label></td></tr>
|
||||||
<tr class="pl-5"><td><label for="num_outputs_total">Number of Images:</label></td><td><input id="num_outputs_total" name="num_outputs_total" value="1" size="1"> <label><small>(total)</small></label> <input id="num_outputs_parallel" name="num_outputs_parallel" value="1" size="1"> <label for="num_outputs_parallel"><small>(in parallel)</small></label></td></tr>
|
<tr class="pl-5"><td><label for="num_outputs_total">Number of Images:</label></td><td><input id="num_outputs_total" name="num_outputs_total" value="1" size="1"> <label><small>(total)</small></label> <input id="num_outputs_parallel" name="num_outputs_parallel" value="1" size="1"> <label for="num_outputs_parallel"><small>(in parallel)</small></label></td></tr>
|
||||||
<tr class="pl-5"><td><label for="stable_diffusion_model">Model:</label></td><td>
|
<tr class="pl-5"><td><label for="stable_diffusion_model">Model:</label></td><td>
|
||||||
<select id="stable_diffusion_model" name="stable_diffusion_model">
|
<select id="stable_diffusion_model" name="stable_diffusion_model">
|
||||||
@ -251,11 +252,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
<script src="media/js/utils.js?v=1"></script>
|
<script src="media/js/plugins.js?v=1"></script>
|
||||||
|
<script src="media/js/utils.js?v=3"></script>
|
||||||
<script src="media/js/inpainting-editor.js?v=1"></script>
|
<script src="media/js/inpainting-editor.js?v=1"></script>
|
||||||
<script src="media/js/image-modifiers.js"></script>
|
<script src="media/js/image-modifiers.js"></script>
|
||||||
<script src="media/js/auto-save.js?v=1"></script>
|
<script src="media/js/auto-save.js?v=1"></script>
|
||||||
<script src="media/js/main.js?v=1"></script>
|
<script src="media/js/main.js?v=3"></script>
|
||||||
<script src="media/js/themes.js?v=1"></script>
|
<script src="media/js/themes.js?v=1"></script>
|
||||||
<script>
|
<script>
|
||||||
async function init() {
|
async function init() {
|
||||||
@ -264,6 +266,7 @@ async function init() {
|
|||||||
await getAppConfig()
|
await getAppConfig()
|
||||||
await getModels()
|
await getModels()
|
||||||
await initSettings()
|
await initSettings()
|
||||||
|
await loadUIPlugins()
|
||||||
|
|
||||||
setInterval(healthCheck, HEALTH_PING_INTERVAL * 1000)
|
setInterval(healthCheck, HEALTH_PING_INTERVAL * 1000)
|
||||||
healthCheck()
|
healthCheck()
|
||||||
|
@ -257,11 +257,6 @@ function logError(msg, res, outputMsg) {
|
|||||||
console.log('request error', res)
|
console.log('request error', res)
|
||||||
setStatus('request', 'error', 'error')
|
setStatus('request', 'error', 'error')
|
||||||
}
|
}
|
||||||
function asyncDelay(timeout) {
|
|
||||||
return new Promise(function(resolve, reject) {
|
|
||||||
setTimeout(resolve, timeout, true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function playSound() {
|
function playSound() {
|
||||||
const audio = new Audio('/media/ding.mp3')
|
const audio = new Audio('/media/ding.mp3')
|
||||||
@ -366,34 +361,42 @@ function showImages(reqBody, res, outputContainer, livePreview) {
|
|||||||
const imageSeedLabel = imageItemElem.querySelector('.imgSeedLabel')
|
const imageSeedLabel = imageItemElem.querySelector('.imgSeedLabel')
|
||||||
imageSeedLabel.innerText = 'Seed: ' + req.seed
|
imageSeedLabel.innerText = 'Seed: ' + req.seed
|
||||||
|
|
||||||
const buttons = {
|
let buttons = [
|
||||||
'imgUseBtn': { html: 'Use as Input', click: getUseAsInputHandler(imageItemElem) },
|
{ text: 'Use as Input', on_click: onUseAsInputClick },
|
||||||
'imgSaveBtn': { html: 'Download', click: getSaveImageHandler(imageItemElem, req['output_format']) },
|
{ text: 'Download', on_click: onDownloadImageClick },
|
||||||
'imgX2Btn': { html: 'Double Size', click: getStartNewTaskHandler(req, imageItemElem, 'img2img_X2') },
|
{ text: 'Make Similar Images', on_click: onMakeSimilarClick },
|
||||||
'imgRedoBtn': { html: 'Redo', click: getStartNewTaskHandler(req, imageItemElem, 'img2img') },
|
{ text: 'Draw another 25 steps', on_click: onContinueDrawingClick },
|
||||||
}
|
{ text: 'Upscale', on_click: onUpscaleClick, filter: (req, img) => !req.use_upscale },
|
||||||
if (!req.use_upscale) {
|
{ text: 'Fix Faces', on_click: onFixFacesClick, filter: (req, img) => !req.use_face_correction }
|
||||||
buttons.upscaleBtn = { html: 'Upscale', click: getStartNewTaskHandler(req, imageItemElem, 'upscale') }
|
]
|
||||||
}
|
|
||||||
|
// include the plugins
|
||||||
|
buttons = buttons.concat(PLUGINS['IMAGE_INFO_BUTTONS'])
|
||||||
|
|
||||||
const imgItemInfo = imageItemElem.querySelector('.imgItemInfo')
|
const imgItemInfo = imageItemElem.querySelector('.imgItemInfo')
|
||||||
const createButton = function(name, btnInfo) {
|
const img = imageItemElem.querySelector('img')
|
||||||
|
const createButton = function(btnInfo) {
|
||||||
const newButton = document.createElement('button')
|
const newButton = document.createElement('button')
|
||||||
newButton.classList.add(name)
|
|
||||||
newButton.classList.add('tasksBtns')
|
newButton.classList.add('tasksBtns')
|
||||||
newButton.innerHTML = btnInfo.html
|
newButton.innerText = btnInfo.text
|
||||||
newButton.addEventListener('click', btnInfo.click)
|
newButton.addEventListener('click', function() {
|
||||||
|
btnInfo.on_click(req, img)
|
||||||
|
})
|
||||||
imgItemInfo.appendChild(newButton)
|
imgItemInfo.appendChild(newButton)
|
||||||
}
|
}
|
||||||
Object.keys(buttons).forEach((name) => createButton(name, buttons[name]))
|
buttons.forEach(btn => {
|
||||||
|
if (btn.filter && btn.filter(req, img) === false) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
createButton(btn)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function getUseAsInputHandler(imageItemElem) {
|
function onUseAsInputClick(req, img) {
|
||||||
return function() {
|
const imgData = img.src
|
||||||
const imageElem = imageItemElem.querySelector('img')
|
|
||||||
const imgData = imageElem.src
|
|
||||||
const imageSeed = imageElem.getAttribute('data-seed')
|
|
||||||
|
|
||||||
initImageSelector.value = null
|
initImageSelector.value = null
|
||||||
initImagePreview.src = imgData
|
initImagePreview.src = imgData
|
||||||
@ -403,79 +406,80 @@ function getUseAsInputHandler(imageItemElem) {
|
|||||||
promptStrengthContainer.style.display = 'table-row'
|
promptStrengthContainer.style.display = 'table-row'
|
||||||
maskSetting.checked = false
|
maskSetting.checked = false
|
||||||
samplerSelectionContainer.style.display = 'none'
|
samplerSelectionContainer.style.display = 'none'
|
||||||
|
|
||||||
// maskSetting.style.display = 'block'
|
|
||||||
|
|
||||||
// randomSeedField.checked = false
|
|
||||||
// seedField.value = imageSeed
|
|
||||||
// seedField.disabled = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSaveImageHandler(imageItemElem, outputFormat) {
|
function onDownloadImageClick(req, img) {
|
||||||
return function() {
|
const imgData = img.src
|
||||||
const imageElem = imageItemElem.querySelector('img')
|
const imageSeed = img.getAttribute('data-seed')
|
||||||
const imgData = imageElem.src
|
const imagePrompt = img.getAttribute('data-prompt')
|
||||||
const imageSeed = imageElem.getAttribute('data-seed')
|
const imageInferenceSteps = img.getAttribute('data-steps')
|
||||||
const imagePrompt = imageElem.getAttribute('data-prompt')
|
const imageGuidanceScale = img.getAttribute('data-guidance')
|
||||||
const imageInferenceSteps = imageElem.getAttribute('data-steps')
|
|
||||||
const imageGuidanceScale = imageElem.getAttribute('data-guidance')
|
|
||||||
|
|
||||||
const imgDownload = document.createElement('a')
|
const imgDownload = document.createElement('a')
|
||||||
imgDownload.download = createFileName(imagePrompt, imageSeed, imageInferenceSteps, imageGuidanceScale, outputFormat)
|
imgDownload.download = createFileName(imagePrompt, imageSeed, imageInferenceSteps, imageGuidanceScale, req['output_format'])
|
||||||
imgDownload.href = imgData
|
imgDownload.href = imgData
|
||||||
imgDownload.click()
|
imgDownload.click()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
function getStartNewTaskHandler(reqBody, imageItemElem, mode) {
|
|
||||||
return function() {
|
function modifyCurrentRequest(req, reqDiff) {
|
||||||
if (!isServerAvailable()) {
|
|
||||||
alert('The server is not available.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const imageElem = imageItemElem.querySelector('img')
|
|
||||||
const newTaskRequest = getCurrentUserRequest()
|
const newTaskRequest = getCurrentUserRequest()
|
||||||
switch (mode) {
|
|
||||||
case 'img2img':
|
newTaskRequest.reqBody = Object.assign({}, req, reqDiff, {
|
||||||
case 'img2img_X2':
|
use_cpu: useCPUField.checked
|
||||||
newTaskRequest.reqBody = Object.assign({}, reqBody, {
|
|
||||||
num_outputs: 1,
|
|
||||||
use_cpu: useCPUField.checked,
|
|
||||||
})
|
})
|
||||||
if (!newTaskRequest.reqBody.init_image || mode === 'img2img_X2') {
|
|
||||||
newTaskRequest.reqBody.sampler = 'ddim'
|
|
||||||
newTaskRequest.reqBody.prompt_strength = '0.5'
|
|
||||||
newTaskRequest.reqBody.init_image = imageElem.src
|
|
||||||
delete newTaskRequest.reqBody.mask
|
|
||||||
} else {
|
|
||||||
newTaskRequest.reqBody.seed = 1 + newTaskRequest.reqBody.seed
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
if (useUpscalingField.checked) {
|
|
||||||
newTaskRequest.reqBody.use_upscale = upscaleModelField.value
|
|
||||||
} else {
|
|
||||||
delete newTaskRequest.reqBody.use_upscale
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
newTaskRequest.seed = newTaskRequest.reqBody.seed
|
newTaskRequest.seed = newTaskRequest.reqBody.seed
|
||||||
newTaskRequest.numOutputsTotal = 1
|
|
||||||
newTaskRequest.batchCount = 1
|
return newTaskRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMakeSimilarClick(req, img) {
|
||||||
|
const newTaskRequest = modifyCurrentRequest(req, {
|
||||||
|
num_outputs: 1,
|
||||||
|
num_inference_steps: 50,
|
||||||
|
guidance_scale: 7.5,
|
||||||
|
prompt_strength: 0.7,
|
||||||
|
init_image: img.src,
|
||||||
|
seed: Math.floor(Math.random() * 10000000)
|
||||||
|
})
|
||||||
|
|
||||||
|
newTaskRequest.numOutputsTotal = 5
|
||||||
|
newTaskRequest.batchCount = 5
|
||||||
|
|
||||||
|
delete newTaskRequest.reqBody.mask
|
||||||
|
|
||||||
createTask(newTaskRequest)
|
createTask(newTaskRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enqueueImageVariationTask(req, img, reqDiff) {
|
||||||
|
const imageSeed = img.getAttribute('data-seed')
|
||||||
|
|
||||||
|
const newTaskRequest = modifyCurrentRequest(req, reqDiff, {
|
||||||
|
num_outputs: 1, // this can be user-configurable in the future
|
||||||
|
seed: imageSeed
|
||||||
|
})
|
||||||
|
|
||||||
|
newTaskRequest.numOutputsTotal = 1 // this can be user-configurable in the future
|
||||||
|
newTaskRequest.batchCount = 1
|
||||||
|
|
||||||
|
createTask(newTaskRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUpscaleClick(req, img) {
|
||||||
|
enqueueImageVariationTask(req, img, {
|
||||||
|
use_upscale: upscaleModelField.value
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFixFacesClick(req, img) {
|
||||||
|
enqueueImageVariationTask(req, img, {
|
||||||
|
use_face_correction: 'GFPGANv1.3'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function onContinueDrawingClick(req, img) {
|
||||||
|
enqueueImageVariationTask(req, img, {
|
||||||
|
num_inference_steps: parseInt(req.num_inference_steps) + 25
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// makes a single image. don't call this directly, use makeImage() instead
|
// makes a single image. don't call this directly, use makeImage() instead
|
||||||
@ -484,6 +488,11 @@ async function doMakeImage(task) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
const reqBody = task.reqBody
|
const reqBody = task.reqBody
|
||||||
const batchCount = task.batchCount
|
const batchCount = task.batchCount
|
||||||
const outputContainer = document.createElement('div')
|
const outputContainer = document.createElement('div')
|
||||||
@ -509,11 +518,12 @@ async function doMakeImage(task) {
|
|||||||
})
|
})
|
||||||
renderRequest = await res.json()
|
renderRequest = await res.json()
|
||||||
// status_code 503, already a task running.
|
// status_code 503, already a task running.
|
||||||
} while (res.status === 503 && await asyncDelay(30 * 1000))
|
} while (res.status === 503 && await asyncDelay(RETRY_DELAY_IF_SERVER_IS_BUSY))
|
||||||
if (typeof renderRequest?.stream !== 'string') {
|
if (typeof renderRequest?.stream !== 'string') {
|
||||||
console.log('Endpoint response: ', renderRequest)
|
console.log('Endpoint response: ', renderRequest)
|
||||||
throw new Error(renderRequest.detail || 'Endpoint response does not contains a response stream url.')
|
throw new Error(renderRequest.detail || 'Endpoint response does not contains a response stream url.')
|
||||||
}
|
}
|
||||||
|
|
||||||
task['taskStatusLabel'].innerText = "Waiting"
|
task['taskStatusLabel'].innerText = "Waiting"
|
||||||
task['taskStatusLabel'].classList.add('waitingTaskLabel')
|
task['taskStatusLabel'].classList.add('waitingTaskLabel')
|
||||||
task['taskStatusLabel'].classList.remove('activeTaskLabel')
|
task['taskStatusLabel'].classList.remove('activeTaskLabel')
|
||||||
@ -523,7 +533,7 @@ async function doMakeImage(task) {
|
|||||||
if (!isServerAvailable()) {
|
if (!isServerAvailable()) {
|
||||||
throw new Error('Connexion with server lost.')
|
throw new Error('Connexion with server lost.')
|
||||||
}
|
}
|
||||||
} while (serverState.time > (Date.now() - (10 * 1000)) && serverState.task !== renderRequest.task)
|
} while (Date.now() < (serverState.time + SERVER_STATE_VALIDITY_DURATION) && serverState.task !== renderRequest.task)
|
||||||
switch(serverState.session) {
|
switch(serverState.session) {
|
||||||
case 'pending':
|
case 'pending':
|
||||||
case 'running':
|
case 'running':
|
||||||
@ -535,9 +545,10 @@ async function doMakeImage(task) {
|
|||||||
default:
|
default:
|
||||||
throw new Error('Unexpected server task state: ' + serverState.session || 'Undefined')
|
throw new Error('Unexpected server task state: ' + serverState.session || 'Undefined')
|
||||||
}
|
}
|
||||||
|
|
||||||
while (serverState.task === renderRequest.task && serverState.session === 'pending') {
|
while (serverState.task === renderRequest.task && serverState.session === 'pending') {
|
||||||
// Wait for task to start on server.
|
// Wait for task to start on server.
|
||||||
await asyncDelay(1500)
|
await asyncDelay(TASK_START_DELAY_ON_SERVER)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Task started!
|
// Task started!
|
||||||
@ -631,7 +642,7 @@ async function doMakeImage(task) {
|
|||||||
}
|
}
|
||||||
if (readComplete && finalJSON.length <= 0) {
|
if (readComplete && finalJSON.length <= 0) {
|
||||||
if (res.status === 200) {
|
if (res.status === 200) {
|
||||||
await asyncDelay(1000)
|
await asyncDelay(RETRY_DELAY_IF_BUFFER_IS_EMPTY)
|
||||||
res = await fetch(renderRequest.stream, {
|
res = await fetch(renderRequest.stream, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
@ -747,7 +758,7 @@ async function checkTasks() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (genSeeds) {
|
if (genSeeds) {
|
||||||
newTask.reqBody.seed = startSeed + (i * newTask.reqBody.num_outputs)
|
newTask.reqBody.seed = parseInt(startSeed) + (i * newTask.reqBody.num_outputs)
|
||||||
newTask.seed = newTask.reqBody.seed
|
newTask.seed = newTask.reqBody.seed
|
||||||
} else if (newTask.seed !== newTask.reqBody.seed) {
|
} else if (newTask.seed !== newTask.reqBody.seed) {
|
||||||
newTask.seed = newTask.reqBody.seed
|
newTask.seed = newTask.reqBody.seed
|
||||||
@ -944,14 +955,34 @@ function getPrompts() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prompts = prompts.split('\n')
|
prompts = prompts.split('\n')
|
||||||
|
prompts = prompts.map(prompt => prompt.trim())
|
||||||
|
prompts = prompts.filter(prompt => prompt !== '')
|
||||||
|
|
||||||
let promptsToMake = []
|
let promptsToMake = applySetOperator(prompts)
|
||||||
prompts.forEach(prompt => {
|
promptsToMake = applyPermuteOperator(promptsToMake)
|
||||||
prompt = prompt.trim()
|
|
||||||
if (prompt === '') {
|
if (activeTags.length <= 0) {
|
||||||
return
|
return promptsToMake
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const promptTags = activeTags.map(x => x.name).join(", ")
|
||||||
|
return promptsToMake.map((prompt) => `${prompt}, ${promptTags}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
function applySetOperator(prompts) {
|
||||||
|
let promptsToMake = []
|
||||||
|
let braceExpander = new BraceExpander()
|
||||||
|
prompts.forEach(prompt => {
|
||||||
|
let expandedPrompts = braceExpander.expand(prompt)
|
||||||
|
promptsToMake = promptsToMake.concat(expandedPrompts)
|
||||||
|
})
|
||||||
|
|
||||||
|
return promptsToMake
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPermuteOperator(prompts) {
|
||||||
|
let promptsToMake = []
|
||||||
|
prompts.forEach(prompt => {
|
||||||
let promptMatrix = prompt.split('|')
|
let promptMatrix = prompt.split('|')
|
||||||
prompt = promptMatrix.shift().trim()
|
prompt = promptMatrix.shift().trim()
|
||||||
promptsToMake.push(prompt)
|
promptsToMake.push(prompt)
|
||||||
@ -964,11 +995,8 @@ function getPrompts() {
|
|||||||
promptsToMake = promptsToMake.concat(promptPermutations)
|
promptsToMake = promptsToMake.concat(promptPermutations)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (activeTags.length <= 0) {
|
|
||||||
return promptsToMake
|
return promptsToMake
|
||||||
}
|
|
||||||
const promptTags = activeTags.map(x => x.name).join(", ")
|
|
||||||
return promptsToMake.map((prompt) => `${prompt}, ${promptTags}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function permutePrompts(promptBase, promptMatrix) {
|
function permutePrompts(promptBase, promptMatrix) {
|
||||||
@ -1150,6 +1178,7 @@ function updateGuidanceScaleSlider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
guidanceScaleSlider.value = guidanceScaleField.value * 10
|
guidanceScaleSlider.value = guidanceScaleField.value * 10
|
||||||
|
guidanceScaleSlider.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
|
|
||||||
guidanceScaleSlider.addEventListener('input', updateGuidanceScale)
|
guidanceScaleSlider.addEventListener('input', updateGuidanceScale)
|
||||||
@ -1168,6 +1197,7 @@ function updatePromptStrengthSlider() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
promptStrengthSlider.value = promptStrengthField.value * 100
|
promptStrengthSlider.value = promptStrengthField.value * 100
|
||||||
|
promptStrengthSlider.dispatchEvent(new Event("change"))
|
||||||
}
|
}
|
||||||
|
|
||||||
promptStrengthSlider.addEventListener('input', updatePromptStrength)
|
promptStrengthSlider.addEventListener('input', updatePromptStrength)
|
||||||
@ -1177,7 +1207,7 @@ updatePromptStrength()
|
|||||||
useBetaChannelField.addEventListener('click', async function(e) {
|
useBetaChannelField.addEventListener('click', async function(e) {
|
||||||
if (!isServerAvailable()) {
|
if (!isServerAvailable()) {
|
||||||
// logError('The server is still starting up..')
|
// logError('The server is still starting up..')
|
||||||
alert('The server is not available.')
|
alert('The server is still starting up..')
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
47
ui/media/js/plugins.js
Normal file
47
ui/media/js/plugins.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
const PLUGIN_API_VERSION = "1.0"
|
||||||
|
|
||||||
|
const PLUGINS = {
|
||||||
|
/**
|
||||||
|
* Register new buttons to show on each output image.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* PLUGINS['IMAGE_INFO_BUTTONS'].push({
|
||||||
|
* text: 'Make a Similar Image',
|
||||||
|
* on_click: function(origRequest, image) {
|
||||||
|
* let newTaskRequest = getCurrentUserRequest()
|
||||||
|
* newTaskRequest.reqBody = Object.assign({}, origRequest, {
|
||||||
|
* init_image: image.src,
|
||||||
|
* prompt_strength: 0.7,
|
||||||
|
* seed: Math.floor(Math.random() * 10000000)
|
||||||
|
* })
|
||||||
|
* newTaskRequest.seed = newTaskRequest.reqBody.seed
|
||||||
|
* createTask(newTaskRequest)
|
||||||
|
* },
|
||||||
|
* filter: function(origRequest, image) {
|
||||||
|
* // this is an optional function. return true/false to show/hide the button
|
||||||
|
* // if this function isn't set, the button will always be visible
|
||||||
|
* return true
|
||||||
|
* }
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
IMAGE_INFO_BUTTONS: []
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadUIPlugins() {
|
||||||
|
try {
|
||||||
|
let res = await fetch('/get/ui_plugins')
|
||||||
|
if (res.status === 200) {
|
||||||
|
res = await res.json()
|
||||||
|
res.forEach(pluginPath => {
|
||||||
|
let script = document.createElement('script')
|
||||||
|
script.src = pluginPath + '?t=' + Date.now()
|
||||||
|
|
||||||
|
console.log('loading plugin', pluginPath)
|
||||||
|
|
||||||
|
document.head.appendChild(script)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('error fetching plugin paths', e)
|
||||||
|
}
|
||||||
|
}
|
@ -101,3 +101,182 @@ function millisecondsToStr(milliseconds) {
|
|||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://rosettacode.org/wiki/Brace_expansion#JavaScript
|
||||||
|
function BraceExpander() {
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
// Index of any closing brace matching the opening
|
||||||
|
// brace at iPosn,
|
||||||
|
// with the indices of any immediately-enclosed commas.
|
||||||
|
function bracePair(tkns, iPosn, iNest, lstCommas) {
|
||||||
|
if (iPosn >= tkns.length || iPosn < 0) return null;
|
||||||
|
|
||||||
|
var t = tkns[iPosn],
|
||||||
|
n = (t === '{') ? (
|
||||||
|
iNest + 1
|
||||||
|
) : (t === '}' ? (
|
||||||
|
iNest - 1
|
||||||
|
) : iNest),
|
||||||
|
lst = (t === ',' && iNest === 1) ? (
|
||||||
|
lstCommas.concat(iPosn)
|
||||||
|
) : lstCommas;
|
||||||
|
|
||||||
|
return n ? bracePair(tkns, iPosn + 1, n, lst) : {
|
||||||
|
close: iPosn,
|
||||||
|
commas: lst
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse of a SYNTAGM subtree
|
||||||
|
function andTree(dctSofar, tkns) {
|
||||||
|
if (!tkns.length) return [dctSofar, []];
|
||||||
|
|
||||||
|
var dctParse = dctSofar ? dctSofar : {
|
||||||
|
fn: and,
|
||||||
|
args: []
|
||||||
|
},
|
||||||
|
|
||||||
|
head = tkns[0],
|
||||||
|
tail = head ? tkns.slice(1) : [],
|
||||||
|
|
||||||
|
dctBrace = head === '{' ? bracePair(
|
||||||
|
tkns, 0, 0, []
|
||||||
|
) : null,
|
||||||
|
|
||||||
|
lstOR = dctBrace && (
|
||||||
|
dctBrace.close
|
||||||
|
) && dctBrace.commas.length ? (
|
||||||
|
splitAt(dctBrace.close + 1, tkns)
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
return andTree({
|
||||||
|
fn: and,
|
||||||
|
args: dctParse.args.concat(
|
||||||
|
lstOR ? (
|
||||||
|
orTree(dctParse, lstOR[0], dctBrace.commas)
|
||||||
|
) : head
|
||||||
|
)
|
||||||
|
}, lstOR ? (
|
||||||
|
lstOR[1]
|
||||||
|
) : tail);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse of a PARADIGM subtree
|
||||||
|
function orTree(dctSofar, tkns, lstCommas) {
|
||||||
|
if (!tkns.length) return [dctSofar, []];
|
||||||
|
var iLast = lstCommas.length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
fn: or,
|
||||||
|
args: splitsAt(
|
||||||
|
lstCommas, tkns
|
||||||
|
).map(function (x, i) {
|
||||||
|
var ts = x.slice(
|
||||||
|
1, i === iLast ? (
|
||||||
|
-1
|
||||||
|
) : void 0
|
||||||
|
);
|
||||||
|
|
||||||
|
return ts.length ? ts : [''];
|
||||||
|
}).map(function (ts) {
|
||||||
|
return ts.length > 1 ? (
|
||||||
|
andTree(null, ts)[0]
|
||||||
|
) : ts[0];
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of unescaped braces and commas, and remaining strings
|
||||||
|
function tokens(str) {
|
||||||
|
// Filter function excludes empty splitting artefacts
|
||||||
|
var toS = function (x) {
|
||||||
|
return x.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
return str.split(/(\\\\)/).filter(toS).reduce(function (a, s) {
|
||||||
|
return a.concat(s.charAt(0) === '\\' ? s : s.split(
|
||||||
|
/(\\*[{,}])/
|
||||||
|
).filter(toS));
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// PARSE TREE OPERATOR (1 of 2)
|
||||||
|
// Each possible head * each possible tail
|
||||||
|
function and(args) {
|
||||||
|
var lng = args.length,
|
||||||
|
head = lng ? args[0] : null,
|
||||||
|
lstHead = "string" === typeof head ? (
|
||||||
|
[head]
|
||||||
|
) : head;
|
||||||
|
|
||||||
|
return lng ? (
|
||||||
|
1 < lng ? lstHead.reduce(function (a, h) {
|
||||||
|
return a.concat(
|
||||||
|
and(args.slice(1)).map(function (t) {
|
||||||
|
return h + t;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}, []) : lstHead
|
||||||
|
) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// PARSE TREE OPERATOR (2 of 2)
|
||||||
|
// Each option flattened
|
||||||
|
function or(args) {
|
||||||
|
return args.reduce(function (a, b) {
|
||||||
|
return a.concat(b);
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
// One list split into two (first sublist length n)
|
||||||
|
function splitAt(n, lst) {
|
||||||
|
return n < lst.length + 1 ? [
|
||||||
|
lst.slice(0, n), lst.slice(n)
|
||||||
|
] : [lst, []];
|
||||||
|
}
|
||||||
|
|
||||||
|
// One list split into several (sublist lengths [n])
|
||||||
|
function splitsAt(lstN, lst) {
|
||||||
|
return lstN.reduceRight(function (a, x) {
|
||||||
|
return splitAt(x, a[0]).concat(a.slice(1));
|
||||||
|
}, [lst]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value of the parse tree
|
||||||
|
function evaluated(e) {
|
||||||
|
return typeof e === 'string' ? e :
|
||||||
|
e.fn(e.args.map(evaluated));
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON prettyprint (for parse tree, token list etc)
|
||||||
|
function pp(e) {
|
||||||
|
return JSON.stringify(e, function (k, v) {
|
||||||
|
return typeof v === 'function' ? (
|
||||||
|
'[function ' + v.name + ']'
|
||||||
|
) : v;
|
||||||
|
}, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ----------------------- MAIN ------------------------
|
||||||
|
|
||||||
|
// s -> [s]
|
||||||
|
this.expand = function(s) {
|
||||||
|
// BRACE EXPRESSION PARSED
|
||||||
|
var dctParse = andTree(null, tokens(s))[0];
|
||||||
|
|
||||||
|
// ABSTRACT SYNTAX TREE LOGGED
|
||||||
|
// console.log(pp(dctParse));
|
||||||
|
|
||||||
|
// AST EVALUATED TO LIST OF STRINGS
|
||||||
|
return evaluated(dctParse);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function asyncDelay(timeout) {
|
||||||
|
return new Promise(function(resolve, reject) {
|
||||||
|
setTimeout(resolve, timeout, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -576,7 +576,7 @@ def do_mk_img(req: Request):
|
|||||||
if (len(filters_applied) > 0):
|
if (len(filters_applied) > 0):
|
||||||
filtered_image = Image.fromarray(x_sample)
|
filtered_image = Image.fromarray(x_sample)
|
||||||
filtered_img_data = img_to_base64_str(filtered_image, req.output_format)
|
filtered_img_data = img_to_base64_str(filtered_image, req.output_format)
|
||||||
response_image = ResponseImage(data=filtered_img_data, seed=req.seed)
|
response_image = ResponseImage(data=filtered_img_data, seed=opt_seed)
|
||||||
res.images.append(response_image)
|
res.images.append(response_image)
|
||||||
if req.save_to_disk_path is not None:
|
if req.save_to_disk_path is not None:
|
||||||
filtered_img_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], img_id, req.output_format, "_".join(filters_applied))
|
filtered_img_out_path = get_base_path(req.save_to_disk_path, req.session_id, prompts[0], img_id, req.output_format, "_".join(filters_applied))
|
||||||
|
@ -7,7 +7,7 @@ Notes:
|
|||||||
import json
|
import json
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
TASK_TTL = 15 * 60 # Discard last session's task timeout
|
TASK_TTL = 15 * 60 # seconds, Discard last session's task timeout
|
||||||
|
|
||||||
import queue, threading, time, weakref
|
import queue, threading, time, weakref
|
||||||
from typing import Any, Generator, Hashable, Optional, Union
|
from typing import Any, Generator, Hashable, Optional, Union
|
||||||
|
17
ui/server.py
17
ui/server.py
@ -16,6 +16,7 @@ sys.path.append(os.path.dirname(SD_UI_DIR))
|
|||||||
|
|
||||||
CONFIG_DIR = os.path.abspath(os.path.join(SD_UI_DIR, '..', 'scripts'))
|
CONFIG_DIR = os.path.abspath(os.path.join(SD_UI_DIR, '..', 'scripts'))
|
||||||
MODELS_DIR = os.path.abspath(os.path.join(SD_DIR, '..', 'models'))
|
MODELS_DIR = os.path.abspath(os.path.join(SD_DIR, '..', 'models'))
|
||||||
|
UI_PLUGINS_DIR = os.path.abspath(os.path.join(SD_DIR, '..', 'plugins', 'ui'))
|
||||||
|
|
||||||
OUTPUT_DIRNAME = "Stable Diffusion UI" # in the user's home folder
|
OUTPUT_DIRNAME = "Stable Diffusion UI" # in the user's home folder
|
||||||
TASK_TTL = 15 * 60 # Discard last session's task timeout
|
TASK_TTL = 15 * 60 # Discard last session's task timeout
|
||||||
@ -47,11 +48,15 @@ app = FastAPI()
|
|||||||
modifiers_cache = None
|
modifiers_cache = None
|
||||||
outpath = os.path.join(os.path.expanduser("~"), OUTPUT_DIRNAME)
|
outpath = os.path.join(os.path.expanduser("~"), OUTPUT_DIRNAME)
|
||||||
|
|
||||||
|
os.makedirs(UI_PLUGINS_DIR, exist_ok=True)
|
||||||
|
|
||||||
# don't show access log entries for URLs that start with the given prefix
|
# don't show access log entries for URLs that start with the given prefix
|
||||||
ACCESS_LOG_SUPPRESS_PATH_PREFIXES = ['/ping', '/image', '/modifier-thumbnails']
|
ACCESS_LOG_SUPPRESS_PATH_PREFIXES = ['/ping', '/image', '/modifier-thumbnails']
|
||||||
|
|
||||||
NOCACHE_HEADERS={"Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0"}
|
NOCACHE_HEADERS={"Cache-Control": "no-cache, no-store, must-revalidate", "Pragma": "no-cache", "Expires": "0"}
|
||||||
app.mount('/media', StaticFiles(directory=os.path.join(SD_UI_DIR, 'media/')), name="media")
|
|
||||||
|
app.mount('/media', StaticFiles(directory=os.path.join(SD_UI_DIR, 'media')), name="media")
|
||||||
|
app.mount('/plugins', StaticFiles(directory=UI_PLUGINS_DIR), name="plugins")
|
||||||
|
|
||||||
config_cached = None
|
config_cached = None
|
||||||
config_last_mod_time = 0
|
config_last_mod_time = 0
|
||||||
@ -211,6 +216,15 @@ def getModels():
|
|||||||
|
|
||||||
return models
|
return models
|
||||||
|
|
||||||
|
def getUIPlugins():
|
||||||
|
plugins = []
|
||||||
|
|
||||||
|
for file in os.listdir(UI_PLUGINS_DIR):
|
||||||
|
if file.endswith('.plugin.js'):
|
||||||
|
plugins.append(f'/plugins/{file}')
|
||||||
|
|
||||||
|
return plugins
|
||||||
|
|
||||||
@app.get('/get/{key:path}')
|
@app.get('/get/{key:path}')
|
||||||
def read_web_data(key:str=None):
|
def read_web_data(key:str=None):
|
||||||
if not key: # /get without parameters, stable-diffusion easter egg.
|
if not key: # /get without parameters, stable-diffusion easter egg.
|
||||||
@ -224,6 +238,7 @@ def read_web_data(key:str=None):
|
|||||||
return JSONResponse(getModels(), headers=NOCACHE_HEADERS)
|
return JSONResponse(getModels(), headers=NOCACHE_HEADERS)
|
||||||
elif key == 'modifiers': return FileResponse(os.path.join(SD_UI_DIR, 'modifiers.json'), headers=NOCACHE_HEADERS)
|
elif key == 'modifiers': return FileResponse(os.path.join(SD_UI_DIR, 'modifiers.json'), headers=NOCACHE_HEADERS)
|
||||||
elif key == 'output_dir': return JSONResponse({ 'output_dir': outpath }, headers=NOCACHE_HEADERS)
|
elif key == 'output_dir': return JSONResponse({ 'output_dir': outpath }, headers=NOCACHE_HEADERS)
|
||||||
|
elif key == 'ui_plugins': return JSONResponse(getUIPlugins(), headers=NOCACHE_HEADERS)
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status_code=404, detail=f'Request for unknown {key}') # HTTP404 Not Found
|
raise HTTPException(status_code=404, detail=f'Request for unknown {key}') # HTTP404 Not Found
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user