Merge pull request #256 from cmdr2/beta

Prompt Queue and Negative Weights
This commit is contained in:
cmdr2 2022-09-28 14:40:23 +05:30 committed by GitHub
commit ed6c59b58a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 289 additions and 109 deletions

View File

@ -4,7 +4,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/png" href="/media/favicon-16x16.png" sizes="16x16">
<link rel="icon" type="image/png" href="/media/favicon-32x32.png" sizes="32x32">
<link rel="stylesheet" href="/media/main.css?v=4">
<link rel="stylesheet" href="/media/main.css?v=10">
<link rel="stylesheet" href="/media/modifier-thumbnails.css?v=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css">
<link rel="stylesheet" href="/media/drawingboard.min.css">
@ -15,7 +15,7 @@
<div id="container">
<div id="top-nav">
<div id="logo">
<h1>Stable Diffusion UI <small>v2.17 <span id="updateBranchLabel"></span></small></h1>
<h1>Stable Diffusion UI <small>v2.19 <span id="updateBranchLabel"></span></small></h1>
</div>
<ul id="top-nav-items">
<li class="dropdown">
@ -78,7 +78,7 @@
</div>
<button id="makeImage">Make Image</button>
<button id="stopImage">Stop</button>
<button id="stopImage" class="secondaryButton">Stop All</button>
</div>
<div class="line-separator">&nbsp;</div>
@ -153,8 +153,13 @@
<br/>
<li><b class="settings-subheader">Prompt Settings</b></li>
<li class="pl-5"><label for="negative_prompt">Negative Prompt:</label> <input id="negative_prompt" name="negative_prompt" size="55"></li>
<br/>
<li><b class="settings-subheader">Render Settings</b></li>
<li class="pl-5"><input id="stream_image_progress" name="stream_image_progress" type="checkbox"> <label for="stream_image_progress">Show a live preview of the image <small>(consumes more VRAM, slightly slower image generation)</small></label></li>
<li class="pl-5"><input id="stream_image_progress" name="stream_image_progress" type="checkbox"> <label for="stream_image_progress">Show a live preview of the image <small>(uses more VRAM, slightly slower image creation)</small></label></li>
<li class="pl-5"><input id="use_face_correction" name="use_face_correction" type="checkbox" checked> <label for="use_face_correction">Fix incorrect faces and eyes <small>(uses GFPGAN)</small></label></li>
<li class="pl-5">
<input id="use_upscale" name="use_upscale" type="checkbox"> <label for="use_upscale">Upscale the image to 4x resolution using </label>
@ -185,15 +190,11 @@
</div>
<div id="preview" class="col-free">
<div id="preview-prompt">
<div id="initial-text">
Type a prompt and press the "Make Image" button.<br/><br/>You can set an "Initial Image" if you want to guide the AI.<br/><br/>You can also add modifiers like "Realistic", "Pencil Sketch", "ArtStation" etc by browsing through the "Image Modifiers" section and selecting the desired modifiers.<br/><br/>Click "Advanced Settings" for additional settings like seed, image size, number of images to generate etc.<br/><br/>Enjoy! :)
</div>
<div id="initial-text">
Type a prompt and press the "Make Image" button.<br/><br/>You can set an "Initial Image" if you want to guide the AI.<br/><br/>You can also add modifiers like "Realistic", "Pencil Sketch", "ArtStation" etc by browsing through the "Image Modifiers" section and selecting the desired modifiers.<br/><br/>Click "Advanced Settings" for additional settings like seed, image size, number of images to generate etc.<br/><br/>Enjoy! :)
</div>
<div id="outputMsg"></div>
<div id="progressBar"></div>
<div id="current-images" class="img-preview">
<div id="preview-tools">
<button id="clear-all-previews" class="secondaryButton"><i class="fa-solid fa-trash-can"></i> Clear All</button>
</div>
</div>
</div>
@ -212,7 +213,7 @@
</div>
</body>
<script src="media/main.js?v=6"></script>
<script src="media/main.js?v=14"></script>
<script>
async function init() {
await loadModifiers()

View File

@ -54,7 +54,7 @@ label {
.editor-slider {
vertical-align: middle;
}
#outputMsg {
.outputMsg {
font-size: small;
padding-bottom: 3pt;
}
@ -165,7 +165,7 @@ label {
display: none;
}
#stopImage:hover {
background: rgb(214, 32, 0);
background: rgb(177, 27, 0);
}
.flex-container {
display: flex;
@ -174,7 +174,7 @@ label {
flex: 50%;
}
.col-fixed-10 {
flex: 0 0 400pt;
flex: 0 0 380pt;
}
.col-free {
flex: 1;
@ -212,7 +212,7 @@ label {
padding-bottom: 10pt;
}
#preview {
margin-left: 20pt;
margin-left: 10pt;
}
img {
box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.15), 0 6px 20px 0 rgba(0, 0, 0, 0.15);
@ -246,7 +246,7 @@ img {
padding-left: 2pt;
font-size: 10pt;
}
#preview-prompt {
.preview-prompt {
font-size: 16pt;
margin-bottom: 10pt;
}
@ -365,4 +365,48 @@ img {
}
.dropdown:hover .dropdown-content {
display: block;
}
.imageTaskContainer {
border: 1px solid #333;
margin-bottom: 10pt;
padding: 5pt;
border-radius: 5pt;
box-shadow: 0 20px 28px 0 rgba(0, 0, 0, 0.15), 0 6px 20px 0 rgba(0, 0, 0, 0.15);
}
.taskStatusLabel {
float: left;
font-size: 8pt;
background:rgb(44, 45, 48);
border: 1px solid rgb(61, 62, 66);
padding: 2pt 4pt;
border-radius: 2pt;
margin-right: 5pt;
}
.activeTaskLabel {
background:rgb(0, 90, 30);
border: 1px solid rgb(0, 75, 19);
color:rgb(204, 255, 217)
}
.secondaryButton {
background: rgb(132, 8, 0);
border: 1px solid rgb(122, 29, 0);
color: rgb(255, 221, 255);
padding: 3pt 6pt;
border-radius: 5px;
}
.secondaryButton:hover {
background: rgb(177, 27, 0);
}
.stopTask {
float: right;
}
#preview-tools {
display: none;
padding: 4pt;
}
.taskConfig {
font-size: 10pt;
color: #aaa;
margin-bottom: 5pt;
}

View File

@ -18,6 +18,7 @@ const IMAGE_REGEX = new RegExp('data:image/[A-Za-z]+;base64')
let sessionId = new Date().getTime()
let promptField = document.querySelector('#prompt')
let negativePromptField = document.querySelector('#negative_prompt')
let numOutputsTotalField = document.querySelector('#num_outputs_total')
let numOutputsParallelField = document.querySelector('#num_outputs_parallel')
let numInferenceStepsField = document.querySelector('#num_inference_steps')
@ -57,6 +58,10 @@ let initImagePreviewContainer = document.querySelector('#init_image_preview_cont
let initImageClearBtn = document.querySelector('.init_image_clear')
let promptStrengthContainer = document.querySelector('#prompt_strength_container')
let initialText = document.querySelector("#initial-text")
let previewTools = document.querySelector("#preview-tools")
let clearAllPreviewsBtn = document.querySelector("#clear-all-previews")
// let maskSetting = document.querySelector('#editor-inputs-mask_setting')
// let maskImagePreviewContainer = document.querySelector('#mask_preview_container')
// let maskImageClearBtn = document.querySelector('#mask_clear')
@ -66,18 +71,19 @@ let editorModifierEntries = document.querySelector('#editor-modifiers-entries')
let editorModifierTagsList = document.querySelector('#editor-inputs-tags-list')
let editorTagsContainer = document.querySelector('#editor-inputs-tags-container')
let imagePreview = document.querySelector("#preview")
let previewImageField = document.querySelector('#preview-image')
previewImageField.onchange = () => changePreviewImages(previewImageField.value);
let modifierCardSizeSlider = document.querySelector('#modifier-card-size-slider')
modifierCardSizeSlider.onchange = () => resizeModifierCards(modifierCardSizeSlider.value);
let previewPrompt = document.querySelector('#preview-prompt')
// let previewPrompt = document.querySelector('#preview-prompt')
let showConfigToggle = document.querySelector('#configToggleBtn')
// let configBox = document.querySelector('#config')
let outputMsg = document.querySelector('#outputMsg')
let progressBar = document.querySelector("#progressBar")
// let outputMsg = document.querySelector('#outputMsg')
// let progressBar = document.querySelector("#progressBar")
let soundToggle = document.querySelector('#sound_toggle')
@ -108,8 +114,10 @@ let serverStatus = 'offline'
let activeTags = []
let modifiers = []
let lastPromptUsed = ''
let taskStopped = true
let batchesDone = 0
let bellPending = false
let taskQueue = []
let currentTask = null
const modifierThumbnailPath = 'media/modifier-thumbnails';
const activeCardClass = 'modifier-card-active';
@ -211,7 +219,7 @@ function setStatus(statusType, msg, msgType) {
}
}
function logMsg(msg, level) {
function logMsg(msg, level, outputMsg) {
if (level === 'error') {
outputMsg.innerHTML = '<span style="color: red">Error: ' + msg + '</span>'
} else if (level === 'warn') {
@ -223,8 +231,8 @@ function logMsg(msg, level) {
console.log(level, msg)
}
function logError(msg, res) {
logMsg(msg, 'error')
function logError(msg, res, outputMsg) {
logMsg(msg, 'error', outputMsg)
console.log('request error', res)
setStatus('request', 'error', 'error')
@ -251,7 +259,7 @@ async function healthCheck() {
}
}
function makeImageElement(width, height) {
function makeImageElement(width, height, outputContainer) {
let imgItem = document.createElement('div')
imgItem.className = 'imgItem'
@ -260,17 +268,25 @@ function makeImageElement(width, height) {
img.height = parseInt(height)
imgItem.appendChild(img)
imagesContainer.insertBefore(imgItem, imagesContainer.firstChild)
outputContainer.insertBefore(imgItem, outputContainer.firstChild)
return imgItem
}
// makes a single image. don't call this directly, use makeImage() instead
async function doMakeImage(reqBody, batchCount) {
if (taskStopped) {
async function doMakeImage(task) {
if (task.stopped) {
return
}
const reqBody = task.reqBody
const batchCount = task.batchCount
const outputContainer = task.outputContainer
const outputMsg = task['outputMsg']
const previewPrompt = task['previewPrompt']
const progressBar = task['progressBar']
let res = ''
let seed = reqBody['seed']
let numOutputs = parseInt(reqBody['num_outputs'])
@ -279,7 +295,7 @@ async function doMakeImage(reqBody, batchCount) {
function makeImageContainers(numImages) {
for (let i = images.length; i < numImages; i++) {
images.push(makeImageElement(reqBody.width, reqBody.height))
images.push(makeImageElement(reqBody.width, reqBody.height, outputContainer))
}
}
@ -316,7 +332,7 @@ async function doMakeImage(reqBody, batchCount) {
finalJSON += jsonStr
} else {
let batchSize = stepUpdate.total_steps
let overallStepCount = stepUpdate.step + batchesDone * batchSize
let overallStepCount = stepUpdate.step + task.batchesDone * batchSize
let totalSteps = batchCount * batchSize
let percent = 100 * (overallStepCount / totalSteps)
percent = (percent > 100 ? 100 : percent)
@ -326,7 +342,7 @@ async function doMakeImage(reqBody, batchCount) {
stepsRemaining = (stepsRemaining < 0 ? 0 : stepsRemaining)
timeRemaining = (timeTaken === -1 ? '' : stepsRemaining * timeTaken) // ms
outputMsg.innerHTML = `Batch ${batchesDone+1} of ${batchCount}`
outputMsg.innerHTML = `Batch ${task.batchesDone+1} of ${batchCount}`
outputMsg.innerHTML += `. Generating image(s): ${percent}%`
timeRemaining = (timeTaken !== -1 ? millisecondsToStr(timeRemaining) : '')
@ -351,7 +367,7 @@ async function doMakeImage(reqBody, batchCount) {
prevTime = t
} catch (e) {
logError('Stable Diffusion had an error. Please check the logs in the command-line window.', res)
logError('Stable Diffusion had an error. Please check the logs in the command-line window.', res, outputMsg)
res = undefined
throw e
}
@ -359,9 +375,9 @@ async function doMakeImage(reqBody, batchCount) {
if (res.status != 200) {
if (serverStatus === 'online') {
logError('Stable Diffusion had an error: ' + await res.text(), res)
logError('Stable Diffusion had an error: ' + await res.text(), res, outputMsg)
} else {
logError("Stable Diffusion is still starting up, please wait. If this goes on beyond a few minutes, Stable Diffusion has probably crashed. Please check the error message in the command-line window.", res)
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)
}
res = undefined
progressBar.style.display = 'none'
@ -398,13 +414,13 @@ async function doMakeImage(reqBody, batchCount) {
} else {
msg = res
}
logError(msg, res)
logError(msg, res, outputMsg)
res = undefined
}
}
} catch (e) {
console.log('request error', e)
logError('Stable Diffusion had an error. Please check the logs in the command-line window. <br/><br/>' + e + '<br/><pre>' + e.stack + '</pre>', res)
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)
setStatus('request', 'error', 'error')
progressBar.style.display = 'none'
res = undefined
@ -493,48 +509,96 @@ async function doMakeImage(reqBody, batchCount) {
return true
}
function validateInput() {
let width = parseInt(widthField.value)
let height = parseInt(heightField.value)
async function checkTasks() {
if (taskQueue.length === 0) {
setStatus('request', 'done', 'success')
setTimeout(checkTasks, 500)
stopImageBtn.style.display = 'none'
makeImageBtn.innerHTML = 'Make Image'
if (IMAGE_REGEX.test(initImagePreview.src)) {
if (initImagePreview.naturalWidth > MAX_INIT_IMAGE_DIMENSION || initImagePreview.naturalHeight > MAX_INIT_IMAGE_DIMENSION) {
return {'isValid': false, 'warning': `The dimensions of your initial image are very large, and can cause 'Out of Memory' errors! Please ensure that its dimensions are equal (or smaller) than the desired output image.
<br/><br/>
Your initial image size is ${initImagePreview.naturalWidth}x${initImagePreview.naturalHeight} pixels. Please try to keep it smaller than ${MAX_INIT_IMAGE_DIMENSION}x${MAX_INIT_IMAGE_DIMENSION}.`}
currentTask = null
if (bellPending) {
if (isSoundEnabled()) {
playSound()
}
bellPending = false
}
}
return {'isValid': true}
}
async function makeImage() {
if (serverStatus !== 'online') {
logError('The server is still starting up..')
return
}
let validation = validateInput()
if (validation['isValid']) {
outputMsg.innerHTML = 'Starting..'
} else {
if (validation['error']) {
logError(validation['error'])
return
} else if (validation['warning']) {
logMsg(validation['warning'], 'warn')
}
}
setStatus('request', 'fetching..')
makeImageBtn.innerHTML = 'Processing..'
makeImageBtn.disabled = true
makeImageBtn.style.display = 'none'
stopImageBtn.style.display = 'block'
makeImageBtn.innerHTML = 'Enqueue Next Image'
bellPending = true
taskStopped = false
batchesDone = 0
previewTools.style.display = 'block'
let task = taskQueue.pop()
currentTask = task
let time = new Date().getTime()
let successCount = 0
task.isProcessing = true
task['stopTask'].innerHTML = '<i class="fa-solid fa-circle-stop"></i> Stop'
task['taskStatusLabel'].innerText = "Processing"
task['taskStatusLabel'].className += " activeTaskLabel"
console.log(task['taskStatusLabel'].className)
for (let i = 0; i < task.batchCount; i++) {
task.reqBody['seed'] = task.seed + (i * task.reqBody['num_outputs'])
let success = await doMakeImage(task)
task.batchesDone++
if (!task.isProcessing) {
break
}
if (success) {
successCount++
}
}
task.isProcessing = false
task['stopTask'].innerHTML = '<i class="fa-solid fa-trash-can"></i> Remove'
task['taskStatusLabel'].style.display = 'none'
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')
} else {
task.outputMsg.innerText = 'Task ended after ' + time + ' seconds'
}
if (randomSeedField.checked) {
seedField.value = task.seed
}
currentTask = null
setTimeout(checkTasks, 10)
}
setTimeout(checkTasks, 0)
async function makeImage() {
if (serverStatus !== 'online') {
alert('The server is still starting up..')
return
}
let task = {
stopped: false,
batchesDone: 0
}
let seed = (randomSeedField.checked ? Math.floor(Math.random() * 10000000) : parseInt(seedField.value))
let numOutputsTotal = parseInt(numOutputsTotalField.value)
@ -550,11 +614,10 @@ async function makeImage() {
prompt += ", " + promptTags;
}
previewPrompt.innerText = prompt
let reqBody = {
session_id: sessionId,
prompt: prompt,
negative_prompt: negativePromptField.value.trim(),
num_outputs: batchSize,
num_inference_steps: numInferenceStepsField.value,
guidance_scale: guidanceScaleField.value,
@ -597,45 +660,76 @@ async function makeImage() {
reqBody['use_upscale'] = upscaleModelField.value
}
let time = new Date().getTime()
imagesContainer.innerHTML = ''
let taskConfig = `Seed: ${seed}, Sampler: ${reqBody['sampler']}, Inference Steps: ${numInferenceStepsField.value}, Guidance Scale: ${guidanceScaleField.value}`
let successCount = 0
if (negativePromptField.value.trim() !== '') {
taskConfig += `, Negative Prompt: ${negativePromptField.value.trim()}`
}
for (let i = 0; i < batchCount; i++) {
reqBody['seed'] = seed + (i * batchSize)
if (reqBody['init_image'] !== undefined) {
taskConfig += `, Prompt Strength: ${promptStrengthField.value}`
}
let success = await doMakeImage(reqBody, batchCount)
batchesDone++
if (useFaceCorrectionField.checked) {
taskConfig += `, Fix Faces: ${reqBody['use_face_correction']}`
}
if (success) {
successCount++
if (useUpscalingField.checked) {
taskConfig += `, Upscale: ${reqBody['use_upscale']}`
}
task['reqBody'] = reqBody
task['seed'] = seed
task['batchCount'] = batchCount
task['isProcessing'] = false
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> Remove</button>
<div class="preview-prompt collapsible active"></div>
<div class="taskConfig">${taskConfig}</div>
<div class="collapsible-content" style="display: block">
<div class="outputMsg"></div>
<div class="progressBar"></div>
<div class="img-preview">
</div>`
createCollapsibles(taskEntry)
task['numOutputsTotal'] = numOutputsTotal
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']) {
task.isProcessing = false
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)
}
taskEntry.remove()
}
}
})
progressBar.style.display = 'none'
imagePreview.insertBefore(taskEntry, previewTools.nextSibling)
makeImageBtn.innerText = 'Make Image'
makeImageBtn.disabled = false
makeImageBtn.style.display = 'block'
stopImageBtn.style.display = 'none'
task['previewPrompt'].innerText = prompt
if (isSoundEnabled()) {
playSound()
}
taskQueue.unshift(task)
time = new Date().getTime() - time
time /= 1000
if (successCount === batchCount) {
outputMsg.innerText = 'Processed ' + numOutputsTotal + ' images in ' + time + ' seconds'
setStatus('request', 'done', 'success')
}
if (randomSeedField.checked) {
seedField.value = seed
}
initialText.style.display = 'none'
}
// create a file name with embedded prompt and metadata
@ -674,17 +768,37 @@ function createFileName() {
return fileName
}
stopImageBtn.addEventListener('click', async function() {
async function stopAllTasks() {
taskQueue.forEach(task => {
task.isProcessing = false
})
taskQueue = []
if (currentTask !== null) {
currentTask.isProcessing = false
}
try {
let res = await fetch('/image/stop')
} catch (e) {
console.log(e)
}
}
stopImageBtn.style.display = 'none'
makeImageBtn.style.display = 'block'
clearAllPreviewsBtn.addEventListener('click', async function() {
await stopAllTasks()
taskStopped = true
let taskEntries = document.querySelectorAll('.imageTaskContainer')
taskEntries.forEach(task => {
task.remove()
})
previewTools.style.display = 'none'
initialText.style.display = 'block'
})
stopImageBtn.addEventListener('click', async function() {
await stopAllTasks()
})
soundToggle.addEventListener('click', handleBoolSettingChange(SOUND_ENABLED_KEY))
@ -781,7 +895,7 @@ updatePromptStrength()
useBetaChannelField.addEventListener('click', async function(e) {
if (serverStatus !== 'online') {
logError('The server is still starting up..')
// logError('The server is still starting up..')
alert('The server is still starting up..')
e.preventDefault()
return false
@ -942,6 +1056,22 @@ function millisecondsToStr(milliseconds) {
return s;
}
// 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
}
}
function createCollapsibles(node) {
if (!node) {
node = document
@ -961,7 +1091,7 @@ function createCollapsibles(node) {
c.addEventListener('click', function() {
this.classList.toggle("active")
let content = this.nextElementSibling
let content = getNextSibling(this, '.collapsible-content')
if (content.style.display === "block") {
content.style.display = "none"
handle.innerHTML = '&#x2795;' // plus

View File

@ -3,6 +3,7 @@ import json
class Request:
session_id: str = "session"
prompt: str = ""
negative_prompt: str = ""
init_image: str = None # base64
mask: str = None # base64
num_outputs: int = 1
@ -30,6 +31,7 @@ class Request:
return {
"session_id": self.session_id,
"prompt": self.prompt,
"negative_prompt": self.negative_prompt,
"num_outputs": self.num_outputs,
"num_inference_steps": self.num_inference_steps,
"guidance_scale": self.guidance_scale,
@ -46,6 +48,7 @@ class Request:
return f'''
session_id: {self.session_id}
prompt: {self.prompt}
negative_prompt: {self.negative_prompt}
seed: {self.seed}
num_inference_steps: {self.num_inference_steps}
sampler: {self.sampler}

View File

@ -343,7 +343,7 @@ def do_mk_img(req: Request):
modelCS.to(device)
uc = None
if opt_scale != 1.0:
uc = modelCS.get_learned_conditioning(batch_size * [""])
uc = modelCS.get_learned_conditioning(batch_size * [req.negative_prompt])
if isinstance(prompts, tuple):
prompts = list(prompts)

View File

@ -39,6 +39,7 @@ app.mount('/media', StaticFiles(directory=os.path.join(SD_UI_DIR, 'media/')), na
class ImageRequest(BaseModel):
session_id: str = "session"
prompt: str = ""
negative_prompt: str = ""
init_image: str = None # base64
mask: str = None # base64
num_outputs: int = 1
@ -100,6 +101,7 @@ def image(req : ImageRequest):
r = Request()
r.session_id = req.session_id
r.prompt = req.prompt
r.negative_prompt = req.negative_prompt
r.init_image = req.init_image
r.mask = req.mask
r.num_outputs = req.num_outputs