Merge branch 'comfyui' into merge_comfy

master
Abdullah Alfaraj 2023-11-30 07:37:27 +03:00
commit 27f60ea2a7
51 changed files with 11984 additions and 585 deletions

View File

@ -11,7 +11,11 @@
"Select Lora": "选择 Lora",
"use lora in your prompt": "在提示中使用 lora",
"Generate": "生成",
"Generate txt2img": "生成 txt2img",
"Generate Txt2Img": "生成 Txt2Img",
"Generate Img2Img": "生成 Img2Img",
"Generate Inpaint": "生成 Inpaint",
"Generate Outpaint": "生成 Outpaint",
"outpaint": "outpaint",
"Progress...": "进度...",
"Toggle the visibility of the Preview Image on the canvas": "切换画布上预览图像的可见性",
"Move and reSize the highlighted layer to fit into the Selection Area": "移动和调整突出显示的图层以适合选择区域",
@ -67,6 +71,7 @@
"Random": "随机",
"Last": "最后",
"Show Samplers": "显示采样器",
"Sampling Steps:": "Sampling Steps:",
"Select A Script": "选择脚本",
"Activate": "激活",
"Viewer": "查看器",
@ -181,4 +186,4 @@
"Delete all generated images from the canvas": "在画布上删除所有生成图像",
"Keep only the highlighted images": "在画布上保留选中的图像",
"Generate More": "生成更多"
}
}

View File

@ -702,6 +702,43 @@
}
</style>
<!-- searchable menu CSS -->
<style>
#mySearch {
width: 100%;
font-size: 18px;
padding: 5px;
color: white;
background-color: #222;
font-weight: bold;
}
#mySearch:focus {
border: 1px solid;
border-color: #7a97e4;
}
#myMenu {
list-style-type: none;
padding: 0;
margin: 0;
max-height: 150px;
overflow-y: scroll;
background-color: #222;
}
#myMenu li {
color: white;
}
#myMenu li a {
padding: 12px;
text-decoration: none;
color: white;
display: block;
font-weight: bold;
}
#myMenu li a:hover {
background-color: #333;
}
</style>
<body>
<!-- <sp-textarea id="tool_tip" open placement="top">use this when you want to fill empty areas of the canvas</sp-textarea> -->
<!-- <sp-tooltip id="tool_tip" open placement="top">use this when you want to fill empty areas of the canvas</sp-tooltip> -->
@ -1142,6 +1179,9 @@
<div id="PresetTabContainer"></div>
<sp-divider class="line-divider" size="large"></sp-divider>
<sp-divider class="line-divider" size="large"></sp-divider>
<div id="ComfyUIContainer"></div>
<sp-divider class="line-divider" size="large"></sp-divider>
<sp-divider class="line-divider" size="large"></sp-divider>
</div>
</div>
</div>
@ -1163,6 +1203,7 @@
<div class="lexicaContainer"></div>
<sp-divider class="line-divider" size="large"></sp-divider>
<sp-divider class="line-divider" size="large"></sp-divider>
<!-- <div id="ComfyUIContainer"></div> -->
</div>
</uxp-panel>

View File

@ -70,6 +70,7 @@ const {
logger,
toJS,
viewer,
viewer_util,
preview,
// session_ts,
session_store,
@ -93,6 +94,11 @@ const {
stores,
lexica,
api_ts,
comfyui,
comfyui_util,
comfyui_main_ui,
comfyapi,
} = require('./typescripts/dist/bundle')
const io = require('./utility/io')
@ -532,8 +538,6 @@ let g_selection = {}
let g_b_use_smart_object = true // true to keep layer as smart objects, false to rasterize them
let g_sd_options_obj = new sd_options.SdOptions()
g_sd_options_obj.getOptions()
let g_controlnet_max_models
let g_generation_session = new session.GenerationSession(0) //session manager
@ -1795,9 +1799,13 @@ async function openFileFromUrl(url, format = 'gif') {
// Save the image to a temporary file
const tempFolder = await storage.localFileSystem.getTemporaryFolder()
const tempFile = await tempFolder.createFile(`temp.${format}`, {
overwrite: true,
})
const randomNumber = Math.floor(Math.random() * 1000000000) // generates a random number between 0 and 999999999
const tempFile = await tempFolder.createFile(
`temp_${randomNumber}.${format}`,
{
overwrite: true,
}
)
await tempFile.write(arrayBuffer)
// Open the file in Photoshop

View File

@ -1,7 +1,7 @@
{
"id": "auto.photoshop.stable.diffusion.plugin",
"name": "Auto Photoshop Stable Diffusion Plugin",
"version": "1.3.3",
"version": "1.4.0",
"host": {
"app": "PS",
"minVersion": "24.0.0"

18
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "auto-photoshop-stable-diffusion-plugin",
"version": "1.3.3",
"version": "1.4.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "auto-photoshop-stable-diffusion-plugin",
"version": "1.3.3",
"version": "1.4.0",
"license": "Apache-2.0",
"dependencies": {
"@types/photoshop": "^24.5.1",
@ -9272,15 +9272,15 @@
}
},
"node_modules/typescript": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
"integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=12.20"
"node": ">=14.17"
}
},
"node_modules/undefsafe": {
@ -16652,9 +16652,9 @@
}
},
"typescript": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
"integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw=="
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz",
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w=="
},
"undefsafe": {
"version": "2.0.5",

View File

@ -30,6 +30,7 @@
"@babel/plugin-transform-react-jsx": "^7.21.5",
"@svgr/webpack": "^8.0.1",
"babel-loader": "^9.1.2",
"chalk": "^5.3.0",
"clean-webpack-plugin": "^4.0.0",
"commander": "^11.0.0",
"copy-webpack-plugin": "^11.0.0",
@ -45,7 +46,6 @@
"url-loader": "^4.1.1",
"webpack": "^5.82.1",
"webpack-cli": "^5.1.1",
"chalk": "^5.3.0",
"yazl": "^2.5.1"
},
"scripts": {
@ -64,4 +64,4 @@
"url": "https://github.com/AbdullahAlfaraj/Auto-Photoshop-StableDiffusion-Plugin/issues"
},
"homepage": "https://github.com/AbdullahAlfaraj/Auto-Photoshop-StableDiffusion-Plugin#readme"
}
}

View File

@ -6,8 +6,7 @@
"batch_size": 1,
"batch_count": 1,
"steps": 20,
"width": 512,
"height": 512,
"ratio": 1,
"cfg": 7,
"b_width_height_link": true,
@ -28,13 +27,5 @@
"hr_upscaler": "",
"selection_mode": "ratio"
},
"controlnet_tab_preset": [
{},
{},
{},
{},
{},
{},
{}
]
}
"controlnet_tab_preset": [{}, {}, {}, {}, {}, {}, {}]
}

View File

@ -6,8 +6,6 @@
"batch_size": 1,
"batch_count": 1,
"steps": 20,
"width": 512,
"height": 512,
"ratio": 1,
"cfg": 7,
"b_width_height_link": true,
@ -28,13 +26,5 @@
"hr_upscaler": "",
"selection_mode": "ratio"
},
"controlnet_tab_preset": [
{},
{},
{},
{},
{},
{},
{}
]
}
"controlnet_tab_preset": [{}, {}, {}, {}, {}, {}, {}]
}

View File

@ -3,9 +3,6 @@ const { base64ToBase64Url } = require('./utility/general')
const py_re = require('./utility/sdapi/python_replacement')
const Enum = require('./enum')
const { control_net } = require('./typescripts/dist/bundle')
const { mapPluginSettingsToControlNet, getEnableControlNet, getModuleDetail } =
control_net
const api = require('./utility/api')
//javascript plugin can't read images from local directory so we send a request to local server to read the image file and send it back to plugin as image string base64
@ -359,186 +356,6 @@ async function requestExtraSingleImage(payload) {
}
}
//REFACTOR: reuse the same code for (requestControlNetTxt2Img,requestControlNetImg2Img)
async function requestControlNetTxt2Img(plugin_settings) {
console.log('requestControlNetTxt2Img: ')
// const full_url = `${g_sd_url}/controlnet/txt2img`
const full_url = `${g_sd_url}/sdapi/v1/txt2img`
const control_net_settings = mapPluginSettingsToControlNet(plugin_settings)
let control_networks = []
// let active_control_networks = 0
for (let index = 0; index < g_controlnet_max_models; index++) {
if (!getEnableControlNet(index)) {
control_networks[index] = false
continue
}
control_networks[index] = true
if (!control_net_settings['controlnet_units'][index]['input_image']) {
app.showAlert('you need to add a valid ControlNet input image')
throw 'you need to add a valid ControlNet input image'
}
if (!control_net_settings['controlnet_units'][index]['module']) {
app.showAlert('you need to select a valid ControlNet Module')
throw 'you need to select a valid ControlNet Module'
}
if (
(!control_net_settings['controlnet_units'][index]['model'] &&
!getModuleDetail()[
control_net_settings['controlnet_units'][index]['module']
].model_free) ||
control_net_settings['controlnet_units'][index]['model'] === 'none'
) {
app.showAlert('you need to select a valid ControlNet Model')
throw 'you need to select a valid ControlNet Model'
}
// active_control_networks++
}
let request = await fetch(full_url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(control_net_settings),
})
let json = await request.json()
console.log('json:', json)
//update the mask in controlNet tab
const numOfImages = json['images'].length
let numberOfAnnotations =
numOfImages - g_generation_session.last_settings.batch_size
if (numberOfAnnotations < 0) numberOfAnnotations = 0
const base64_mask = json['images'].slice(numOfImages - numberOfAnnotations)
let mask_index = 0
for (let index = 0; index < control_networks.length; index++) {
if (
control_networks[index] == false ||
mask_index >= numberOfAnnotations
)
continue
control_net.setControlDetectMapSrc(base64_mask[mask_index], index)
g_generation_session.controlNetMask[index] = base64_mask[mask_index]
mask_index++
}
// g_generation_session.controlNetMask = base64_mask
const standard_response = await py_re.convertToStandardResponse(
control_net_settings,
json['images'].slice(0, numOfImages - numberOfAnnotations),
plugin_settings['uniqueDocumentId']
)
console.log('standard_response:', standard_response)
return standard_response
}
//REFACTOR: reuse the same code for (requestControlNetTxt2Img,requestControlNetImg2Img)
async function requestControlNetImg2Img(plugin_settings) {
console.log('requestControlNetImg2Img: ')
const full_url = `${g_sd_url}/sdapi/v1/img2img`
const control_net_settings = mapPluginSettingsToControlNet(plugin_settings)
// let control_networks = 0
let control_networks = []
for (let index = 0; index < g_controlnet_max_models; index++) {
if (!getEnableControlNet(index)) {
control_networks[index] = false
continue
}
control_networks[index] = true
if (!control_net_settings['controlnet_units'][index]['input_image']) {
app.showAlert('you need to add a valid ControlNet input image')
throw 'you need to add a valid ControlNet input image'
}
if (!control_net_settings['controlnet_units'][index]['module']) {
app.showAlert('you need to select a valid ControlNet Module')
throw 'you need to select a valid ControlNet Module'
}
if (
(!control_net_settings['controlnet_units'][index]['model'] &&
!getModuleDetail()[
control_net_settings['controlnet_units'][index]['module']
].model_free) ||
control_net_settings['controlnet_units'][index]['model'] === 'none'
) {
app.showAlert('you need to select a valid ControlNet Model')
throw 'you need to select a valid ControlNet Model'
}
}
let request = await fetch(full_url, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(control_net_settings),
// body: JSON.stringify(payload),
})
let json = await request.json()
console.log('json:', json)
//update the mask in controlNet tab
const numOfImages = json['images'].length
let numberOfAnnotations =
numOfImages - g_generation_session.last_settings.batch_size
if (numberOfAnnotations < 0) numberOfAnnotations = 0
// To fix a bug: when Ultimate SD Upscale is active and running, the detection maps wont be retrieved.
// So set its value to 0 to avoid the result images being loaded in the annotation map interface.
if (
scripts.script_store.isInstalled() &&
scripts.script_store.is_active &&
scripts.script_store.selected_script_name !== 'None' &&
scripts.script_store.is_selected_script_available
) {
numberOfAnnotations = 0
}
const base64_mask = json['images'].slice(numOfImages - numberOfAnnotations)
let mask_index = 0
for (let index = 0; index < control_networks.length; index++) {
if (
control_networks[index] == false ||
mask_index >= numberOfAnnotations
)
continue
control_net.setControlDetectMapSrc(base64_mask[mask_index], index)
g_generation_session.controlNetMask[index] = base64_mask[mask_index]
mask_index++
}
// g_generation_session.controlNetMask = base64_mask
const standard_response = await py_re.convertToStandardResponse(
control_net_settings,
json['images'].slice(0, numOfImages - numberOfAnnotations),
plugin_settings['uniqueDocumentId']
)
console.log('standard_response:', standard_response)
// //get all images except last because it's the mask
// for (const image of json['images'].slice(0, -1)) {
// await io.IO.base64ToLayer(image)
// }
return standard_response
}
async function isWebuiRunning() {
console.log('isWebuiRunning: ')
let json = []
@ -577,7 +394,5 @@ module.exports = {
// requestHordeStatus,
requestExtraSingleImage,
requestControlNetTxt2Img,
requestControlNetImg2Img,
isWebuiRunning,
}

View File

@ -324,6 +324,24 @@ async function channelToSelectionExe(channel_name = 'mask') {
}
}
function keepRatio(selectionInfo, offset) {
// Calculate the current width and height
let width = selectionInfo.right - selectionInfo.left
let height = selectionInfo.bottom - selectionInfo.top
// Calculate the new coordinates with the offset
selectionInfo.right += offset
selectionInfo.left -= offset
selectionInfo.bottom += offset
selectionInfo.top -= offset
// Update width and height
selectionInfo.width = width + 2 * offset
selectionInfo.height = height + 2 * offset
return selectionInfo
}
function makeSquare(selectionInfo, offset) {
// Calculate the current width and height
let width = selectionInfo.right - selectionInfo.left
@ -349,12 +367,20 @@ function makeSquare(selectionInfo, offset) {
return selectionInfo
}
async function inpaintLassoInitImageAndMask(channel_name = 'mask', offset = 0) {
async function inpaintLassoInitImageAndMask(
channel_name = 'mask',
offset = 0,
make_square = true
) {
const selectionInfo = await psapi.getSelectionInfoExe()
//convert the selection box into square box so that you have best output results
const squareSelection = makeSquare(selectionInfo, offset)
const newSelection = make_square
? makeSquare(selectionInfo, offset)
: keepRatio(selectionInfo, offset)
//correct width and height sliders, since this is lasso mode.
await calcWidthHeightFromSelection(squareSelection)
await calcWidthHeightFromSelection(newSelection)
async function getImageFromCanvas() {
const width = html_manip.getWidth()
@ -363,7 +389,7 @@ async function inpaintLassoInitImageAndMask(channel_name = 'mask', offset = 0) {
const base64 = await io.IO.getSelectionFromCanvasAsBase64Interface_New(
width,
height,
squareSelection,
newSelection,
true
)
return base64
@ -396,7 +422,7 @@ async function inpaintLassoInitImageAndMask(channel_name = 'mask', offset = 0) {
synchronousExecution: true,
})
// const selection_info = await psapi.getSelectionInfoExe()
mask_base64 = await fillSelectionWhiteOutsideBlack(squareSelection)
mask_base64 = await fillSelectionWhiteOutsideBlack(newSelection)
})
//save laso selection to channel

View File

@ -21,6 +21,7 @@ import { ErrorBoundary } from '../util/errorBoundary'
import { ScriptMode } from '../util/ts/enum'
import './style/after_detailer.css'
import Locale from '../locale/locale'
declare let g_sd_url: string
@ -133,8 +134,7 @@ export class AfterDetailerComponent extends React.Component<{
Automatic1111 webui
</sp-label>
<button
className="btnSquare refreshButton"
id="btnResetSettings"
className="btnSquare refreshButton btnResetSettings"
title="Refresh the ADetailer Extension"
onClick={this.handleRefresh}
></button>
@ -150,12 +150,11 @@ export class AfterDetailerComponent extends React.Component<{
store.updateProperty('is_enabled', event.target.checked)
}}
>
{'Activate'}
{Locale('Activate')}
</sp-checkbox>
<SpMenu
title="model"
items={store.data.model_list}
// disabled={script_store.disabled}
// style="width: 199px; margin-right: 5px"
label_item="Select a ADetailer Model"
// id={'model_list'}
@ -219,7 +218,6 @@ export class AfterDetailerComponent extends React.Component<{
<SpMenu
title="controlnet inpaint model"
items={store.data.controlnet_models}
// disabled={script_store.disabled}
// style="width: 199px; margin-right: 5px"
label_item="Select a ControlNet Model"
// id={'model_list'}

View File

@ -0,0 +1,177 @@
import { app } from 'photoshop'
import settings_tab from '../settings/settings'
import { requestGet, requestPost } from '../util/ts/api'
import { base64UrlToBase64 } from '../util/ts/general'
interface ComfyError {
type: string
message: string
details: string
extra_info: any
}
interface NodeError {
errors: ComfyError[]
dependent_outputs: string[]
class_type: string
}
export interface ComfyResult {
error?: ComfyError
node_errors?: { [key: string]: NodeError }
}
class ComfyAPI {
private object_info: Record<string, any> = {}
comfy_url: string
status: boolean = false
constructor(comfy_url: string) {
this.comfy_url = comfy_url
}
async init() {
try {
this.object_info = await this.initializeObjectInfo(this.comfy_url)
this.status = true
return this.object_info
} catch (e) {
console.error(e)
app.showAlert(`${e}`)
this.status = false
}
}
setUrl(comfy_url: string) {
this.comfy_url = comfy_url
}
async refresh() {
this.object_info = await this.initializeObjectInfo(this.comfy_url)
}
async queue() {
const res = await requestGet(`${this.comfy_url}/queue`)
const queue_running = res?.queue_running ? res?.queue_running : []
const queue_pending = res?.queue_pending ? res?.queue_pending : []
return { queue_running, queue_pending }
}
async prompt(prompt: any) {
try {
const payload = {
prompt: prompt,
}
const res = await requestPost(`${this.comfy_url}/prompt`, payload)
return res
} catch (e) {
console.error(e)
}
}
async getHistory(prompt_id: string = '') {
try {
const url = `http://127.0.0.1:8188/history/${prompt_id}`
const res = await requestGet(`${this.comfy_url}/history`)
return res
} catch (e) {
console.error(e)
}
}
async view(
filename: string,
type: string = 'output',
subfolder: string = ''
): Promise<string> {
const ab: ArrayBuffer = await requestGet(
`${this.comfy_url}/view?subfolder=${subfolder}&type=${type}&filename=${filename}`
)
return Buffer.from(ab).toString('base64')
}
async initializeObjectInfo(comfy_url: string) {
try {
const full_url = `${comfy_url}/object_info`
const object_info = await requestGet(full_url)
if (!object_info)
throw `can not request from comfyui url: ${comfy_url}`
return object_info
} catch (e) {
console.error(e)
throw e
}
}
getObjectInfo() {
try {
return this.object_info
} catch (e) {
console.error(e)
throw e
}
}
getReadableError(result: ComfyResult): string {
const parseError = (error: any) =>
`Error: ${error.message}\n${
error.details ? `Details: ${error.details}\n` : ''
}`
let errorMessage = result.error ? parseError(result.error) : ''
if (result.node_errors) {
for (const [node_id, node_error] of Object.entries(
result.node_errors
)) {
errorMessage += `Node ${node_id}:\n${node_error.errors
.map(parseError)
.join('')}`
}
}
return errorMessage
}
private getData(path: string[]): any[] {
let data = []
try {
let obj = this.object_info
for (const p of path) {
obj = obj[p]
}
data = obj[0]
} catch (e) {
console.error(
`Failed to get data from path ${path.join('.')}: ${e}`
)
}
return data
}
getModels(): any[] {
return this.getData([
'CheckpointLoader',
'input',
'required',
'ckpt_name',
])
}
getVAEs(): any[] {
return this.getData(['VAELoader', 'input', 'required', 'vae_name'])
}
getSamplerNames(): string[] {
return this.getData(['KSampler', 'input', 'required', 'sampler_name'])
}
getHiResUpscalers(): string[] {
return this.getData([
'LatentUpscaleBy',
'input',
'required',
'upscale_method',
])
}
getLoras(): string[] {
return this.getData(['LoraLoader', 'input', 'required', 'lora_name'])
}
async interrupt() {
const res = await requestPost(`${this.comfy_url}/interrupt`, {})
console.log('res: ', res)
return res
}
}
export default {
ComfyAPI,
comfy_api: new ComfyAPI(settings_tab.store.data.comfy_url),
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,299 @@
{
"3": {
"inputs": {
"seed": 532429388244110,
"steps": 20,
"cfg": 8,
"sampler_name": "dpmpp_sde",
"scheduler": "karras",
"denoise": 0.72,
"model": [
"76",
0
],
"positive": [
"91",
3
],
"negative": [
"91",
4
],
"latent_image": [
"63",
0
]
},
"class_type": "KSampler"
},
"6": {
"inputs": {
"text": [
"76",
2
],
"clip": [
"76",
1
]
},
"class_type": "CLIPTextEncode"
},
"7": {
"inputs": {
"text": [
"78",
2
],
"clip": [
"78",
1
]
},
"class_type": "CLIPTextEncode"
},
"8": {
"inputs": {
"samples": [
"3",
0
],
"vae": [
"57",
0
]
},
"class_type": "VAEDecode"
},
"9": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
},
"class_type": "SaveImage"
},
"11": {
"inputs": {
"seed": 4820153955672,
"steps": 20,
"cfg": 8,
"sampler_name": "dpmpp_2m",
"scheduler": "simple",
"denoise": 0.4,
"model": [
"76",
0
],
"positive": [
"91",
3
],
"negative": [
"91",
4
],
"latent_image": [
"55",
0
]
},
"class_type": "KSampler"
},
"12": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"13",
0
]
},
"class_type": "SaveImage"
},
"13": {
"inputs": {
"samples": [
"11",
0
],
"vae": [
"57",
0
]
},
"class_type": "VAEDecode"
},
"16": {
"inputs": {
"ckpt_name": "dreamshaper_8.safetensors"
},
"class_type": "CheckpointLoaderSimple"
},
"55": {
"inputs": {
"upscale_method": "nearest-exact",
"scale_by": 2,
"samples": [
"3",
0
]
},
"class_type": "LatentUpscaleBy"
},
"57": {
"inputs": {
"vae_name": "MoistMix.vae.pt"
},
"class_type": "VAELoader"
},
"58": {
"inputs": {
"image": "Layer 3 (1).png",
"choose file to upload": "image"
},
"class_type": "LoadImage"
},
"59": {
"inputs": {
"pixels": [
"60",
0
],
"vae": [
"57",
0
]
},
"class_type": "VAEEncode"
},
"60": {
"inputs": {
"upscale_method": "nearest-exact",
"width": 512,
"height": 512,
"crop": "disabled",
"image": [
"58",
0
]
},
"class_type": "ImageScale"
},
"63": {
"inputs": {
"amount": 1,
"samples": [
"59",
0
]
},
"class_type": "RepeatLatentBatch"
},
"76": {
"inputs": {
"prompt": "",
"model": [
"78",
0
],
"clip": [
"109",
0
]
},
"class_type": "LoadLorasFromPrompt"
},
"78": {
"inputs": {
"prompt": "bad hands, text, watermark",
"model": [
"16",
0
],
"clip": [
"109",
0
]
},
"class_type": "LoadLorasFromPrompt"
},
"88": {
"inputs": {
"images": [
"91",
0
]
},
"class_type": "PreviewImage"
},
"89": {
"inputs": {
"images": [
"91",
1
]
},
"class_type": "PreviewImage"
},
"90": {
"inputs": {
"images": [
"91",
2
]
},
"class_type": "PreviewImage"
},
"91": {
"inputs": {
"is_enabled_1": "disable",
"preprocessor_name_1": "CannyEdgePreprocessor",
"control_net_name_1": "control_lora_rank128_v11p_sd15_canny_fp16.safetensors",
"strength_1": 1,
"threshold_a_1": 100,
"threshold_b_1": 200,
"start_percent_1": 0,
"end_percent_1": 1,
"resolution_1": 512,
"is_enabled_2": "disable",
"preprocessor_name_2": "OpenposePreprocessor",
"control_net_name_2": "control_lora_rank128_v11p_sd15_openpose_fp16.safetensors",
"strength_2": 1,
"threshold_a_2": 0,
"threshold_b_2": 0,
"start_percent_2": 0,
"end_percent_2": 1,
"resolution_2": 512,
"is_enabled_3": "disable",
"preprocessor_name_3": "InpaintPreprocessor",
"control_net_name_3": "control_lora_rank128_v11p_sd15_inpaint_fp16.safetensors",
"strength_3": 1,
"threshold_a_3": 0,
"threshold_b_3": 0,
"start_percent_3": 0,
"end_percent_3": 1,
"resolution_3": 512,
"positive": [
"6",
0
],
"negative": [
"7",
0
]
},
"class_type": "ControlNetScript"
},
"109": {
"inputs": {
"stop_at_clip_layer": -1,
"clip": [
"16",
1
]
},
"class_type": "CLIPSetLastLayer"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,345 @@
{
"3": {
"inputs": {
"seed": [
"141",
0
],
"steps": 20,
"cfg": 7,
"sampler_name": "euler_ancestral",
"scheduler": "karras",
"denoise": 0.71,
"model": [
"76",
0
],
"positive": [
"159",
3
],
"negative": [
"159",
4
],
"latent_image": [
"160",
0
]
},
"class_type": "KSampler"
},
"6": {
"inputs": {
"text": [
"76",
2
],
"clip": [
"76",
1
]
},
"class_type": "CLIPTextEncode"
},
"7": {
"inputs": {
"text": [
"78",
2
],
"clip": [
"78",
1
]
},
"class_type": "CLIPTextEncode"
},
"8": {
"inputs": {
"samples": [
"3",
0
],
"vae": [
"57",
0
]
},
"class_type": "VAEDecode"
},
"9": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
},
"class_type": "SaveImage"
},
"11": {
"inputs": {
"seed": 4820153955672,
"steps": 20,
"cfg": 8,
"sampler_name": "dpmpp_2m",
"scheduler": "simple",
"denoise": 0.4,
"model": [
"76",
0
],
"positive": [
"159",
3
],
"negative": [
"159",
4
],
"latent_image": [
"55",
0
]
},
"class_type": "KSampler"
},
"12": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"13",
0
]
},
"class_type": "SaveImage"
},
"13": {
"inputs": {
"samples": [
"11",
0
],
"vae": [
"57",
0
]
},
"class_type": "VAEDecode"
},
"16": {
"inputs": {
"ckpt_name": "aniverse_v15Pruned.safetensors"
},
"class_type": "CheckpointLoaderSimple"
},
"55": {
"inputs": {
"upscale_method": "nearest-exact",
"scale_by": 2,
"samples": [
"3",
0
]
},
"class_type": "LatentUpscaleBy"
},
"57": {
"inputs": {
"vae_name": "vae-ft-mse-840000-ema-pruned.safetensors"
},
"class_type": "VAELoader"
},
"58": {
"inputs": {
"image": "04b380cb676f37e52d5963a0982edd5e.jpg",
"choose file to upload": "image"
},
"class_type": "LoadImage"
},
"76": {
"inputs": {
"prompt": "young teen:1.2, (cute girl:1.2)",
"model": [
"78",
0
],
"clip": [
"162",
0
]
},
"class_type": "LoadLorasFromPrompt"
},
"78": {
"inputs": {
"prompt": "bad hands, text, watermark",
"model": [
"16",
0
],
"clip": [
"162",
0
]
},
"class_type": "LoadLorasFromPrompt"
},
"83": {
"inputs": {
"image": "pasted/image (4).png",
"choose file to upload": "image"
},
"class_type": "LoadImage"
},
"135": {
"inputs": {
"content_mask": "original",
"width": 512,
"height": 768,
"seed": [
"141",
0
],
"init_image": [
"58",
0
],
"mask": [
"83",
0
],
"vae": [
"57",
0
]
},
"class_type": "ContentMaskLatent"
},
"141": {
"inputs": {
"seed": 519416762507340
},
"class_type": "APS_Seed"
},
"155": {
"inputs": {
"width": 512,
"height": 768,
"batch_size": 1
},
"class_type": "EmptyLatentImage"
},
"156": {
"inputs": {
"images": [
"159",
0
]
},
"class_type": "PreviewImage"
},
"157": {
"inputs": {
"images": [
"159",
1
]
},
"class_type": "PreviewImage"
},
"158": {
"inputs": {
"images": [
"159",
2
]
},
"class_type": "PreviewImage"
},
"159": {
"inputs": {
"is_enabled_1": "disable",
"preprocessor_name_1": "OpenposePreprocessor",
"control_net_name_1": "control_lora_rank128_v11p_sd15_openpose_fp16.safetensors",
"strength_1": 1,
"threshold_a_1": 100,
"threshold_b_1": 200,
"start_percent_1": 0,
"end_percent_1": 1,
"resolution_1": 512,
"is_enabled_2": "disable",
"preprocessor_name_2": "OpenposePreprocessor",
"control_net_name_2": "control_lora_rank128_v11p_sd15_openpose_fp16.safetensors",
"strength_2": 1,
"threshold_a_2": 0,
"threshold_b_2": 0,
"start_percent_2": 0,
"end_percent_2": 1,
"resolution_2": 512,
"is_enabled_3": "enable",
"preprocessor_name_3": "InpaintPreprocessor",
"control_net_name_3": "control_lora_rank128_v11p_sd15_inpaint_fp16.safetensors",
"strength_3": 1,
"threshold_a_3": 0,
"threshold_b_3": 0,
"start_percent_3": 0,
"end_percent_3": 1,
"resolution_3": 512,
"positive": [
"6",
0
],
"negative": [
"7",
0
],
"image_1": [
"58",
0
],
"mask_1": [
"83",
0
],
"image_2": [
"58",
0
],
"mask_2": [
"83",
0
],
"image_3": [
"58",
0
],
"mask_3": [
"83",
0
]
},
"class_type": "ControlNetScript"
},
"160": {
"inputs": {
"amount": 1,
"samples": [
"135",
0
]
},
"class_type": "RepeatLatentBatch"
},
"162": {
"inputs": {
"stop_at_clip_layer": -1,
"clip": [
"16",
1
]
},
"class_type": "CLIPSetLastLayer"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,570 @@
import txt2img from './txt2img_workflow.json'
import txt2img_api from './txt2img_api.json'
import img2img from './img2img_workflow.json'
import img2img_api from './img2img_api.json'
import inpaint from './inpaint_workflow.json'
import inpaint_api from './inpaint_api.json'
import vae_settings from '../settings/vae'
import sd_tab_util from '../sd_tab/util'
import comfyui_util from './util'
import util from './util'
import { store } from './util'
import { base64UrlToBase64, copyJson } from '../util/ts/general'
import { session_store } from '../stores'
import ControlNetStore from '../controlnet/store'
import { setControlDetectMapSrc } from '../controlnet/entry'
// Function to parse metadata from a title string
function parseMetadata(title: string) {
if (!title) return {}
// Split the title into parts using the "|" character and trim whitespace from each part
var parts = title.split('|').map((part) => part.trim())
// Take the last part as the metadata
var metadataPart = parts[parts.length - 1]
// Initialize an empty object to store the key-value pairs
var result: Record<string, string> = {}
// If there is more than one part, there is metadata to parse
if (parts.length > 1) {
// Split the metadata into pairs using the "," character and trim whitespace from each pair
var pairs = metadataPart.split(',').map((pair) => pair.trim())
// For each pair...
for (var i = 0; i < pairs.length; i++) {
// If the pair includes a ":" character, it is a key-value pair
if (pairs[i].includes(':')) {
// Split the pair into a key and a value using the ":" character and trim whitespace
var pair = pairs[i].split(':').map((part) => part.trim())
// Add the key-value pair to the result object
result[pair[0]] = pair[1]
}
}
}
// Return the result object containing the key-value pairs
return result
}
function getInput(node: any, name: string) {
const input = node.inputs.filter((input: any) => {
return input?.widget?.name === name
})?.[0]
return input
}
function getNode(nodes: any[], node_id: string) {
const node = nodes.filter((node: any) => {
return parseInt(node.id) === parseInt(node_id)
})?.[0]
return node
}
function getNodeByNameId(nodes: any[], node_name_id: string) {
const node = nodes.filter((node: any) => {
const node_metadata = parseMetadata(node.title)
return node_metadata.id === node_name_id
})?.[0]
return node
}
function getPromptNodeByNameId(
nodes: any[], //nodes from workflow.json
prompt: any, //prompt from api.json
node_name_id: string // name_id I'm using to get access to nodes by their name
) {
const node = getNodeByNameId(nodes, node_name_id)
const prompt_node = node?.id ? prompt[node.id] : {}
return prompt_node
}
function setInputValue(
nodes: any[],
prompt: any,
node_name_id: string,
input_name: string,
new_value: any
) {
try {
var prompt_node = getPromptNodeByNameId(nodes, prompt, node_name_id)
prompt_node.inputs[input_name] = new_value
} catch (e) {
console.error(
`Node Name ID:\n${node_name_id}\n` +
`Input Name:\n${input_name}\n` +
`New Value:\n${new_value}\n` +
`Prompt Node:\n${prompt_node}\n` +
`Error:\n${e}`
)
}
}
function getLink(links: any[], link_id: number) {
return links.filter((link: any) => {
return link[0] === link_id
})?.[0]
}
function getNodesFromLink(link: any) {
return {
from_node: { id: link[1], input_index: link[2] },
to_node: { id: link[3], input_index: link[4] },
}
}
function mutePromptNode(nodes: any[], prompt: any, node_name_id: string) {
const node = getNodeByNameId(nodes, node_name_id)
delete prompt[node.id]
return prompt
}
const txt2img_map: Record<string, any> = {
model: 'checkpoint.ckpt_name',
comfy_clip_skip: 'clip_skip.stop_at_clip_layer',
vae: 'vae.vae_name',
width: 'latent_image.width',
height: 'latent_image.height',
batch_size: 'latent_image.batch_size',
prompt: 'multi_loras_positive_prompt.prompt',
negative_prompt: 'multi_loras_negative_prompt.prompt',
//sampler node
seed: 'sampler.seed',
steps: 'sampler.steps',
cfg_scale: 'sampler.cfg',
sampler_index: 'sampler.sampler_name',
// scheduler: 'normal',
// denoising_strength: 'sampler.denoise', // keep it at default value 1.0
//hires_node node:
hr_scale: 'scaler.scale_by',
upscale_method: 'nearest_exact',
hr_seed: 'hires_sampler.seed',
hr_second_pass_steps: 'hires_sampler.steps',
// hr_cfg: 'hires_sampler.cfg', // keep at default value 0.5
// hr_sampler_name: 'hires_sampler.sampler_name',
// hr_scheduler: 'normal',
hr_denoising_strength: 'hires_sampler.denoise',
}
const controlnet_txt2img_map: Record<string, any> = {
comfy_input_image: 'controlnet_script.image', //map controlnet_units[unit_index].input_image from base64 string to comfy image filename
comfy_mask: 'controlnet_script.mask', //map controlnet_units[unit_index].mask from base64 string to comfy image filename
comfy_enabled: 'controlnet_script.is_enabled', //map controlnet_units[unit_index].enabled from boolean [true,false] to ['enable', 'disable']
module: 'controlnet_script.preprocessor_name',
model: 'controlnet_script.control_net_name',
weight: 'controlnet_script.strength',
guidance_start: 'controlnet_script.start_percent',
guidance_end: 'controlnet_script.end_percent',
processor_res: 'controlnet_script.resolution',
threshold_a: 'controlnet_script.threshold_a',
threshold_b: 'controlnet_script.threshold_b',
}
const img2img_map: Record<string, any> = {
init_image: 'init_image.image', // note: this is not init_images but init_image
model: 'checkpoint.ckpt_name',
comfy_clip_skip: 'clip_skip.stop_at_clip_layer',
vae: 'vae.vae_name',
width: 'init_image_scale.width',
height: 'init_image_scale.height',
batch_size: 'latent_batch.amount',
// prompt: 'positive_prompt.text',
prompt: 'multi_loras_positive_prompt.prompt',
negative_prompt: 'multi_loras_negative_prompt.prompt',
//sampler node
seed: 'sampler.seed',
steps: 'sampler.steps',
cfg_scale: 'sampler.cfg',
sampler_index: 'sampler.sampler_name',
// scheduler: 'normal',
denoising_strength: 'sampler.denoise',
//hires_node node:
hr_scale: 'scaler.scale_by',
upscale_method: 'nearest_exact',
hr_seed: 'hires_sampler.seed',
hr_second_pass_steps: 'hires_sampler.steps',
// hr_cfg: 'hires_sampler.cfg', // keep at default value 0.5
// hr_sampler_name: 'hires_sampler.sampler_name',
// hr_scheduler: 'normal',
hr_denoising_strength: 'hires_sampler.denoise',
}
const inpaint_map: Record<string, any> = {
init_image: 'init_image.image', // note: this is not init_images but init_image
comfy_mask: 'mask_image.image',
model: 'checkpoint.ckpt_name',
comfy_clip_skip: 'clip_skip.stop_at_clip_layer',
vae: 'vae.vae_name',
width: 'content_mask_latent.width',
height: 'content_mask_latent.height',
batch_size: 'latent_batch.amount',
// prompt: 'positive_prompt.text',
prompt: 'multi_loras_positive_prompt.prompt',
negative_prompt: 'multi_loras_negative_prompt.prompt',
comfy_content_mask: 'content_mask_latent.content_mask',
//sampler node
seed: 'first_pass_seed.seed',
steps: 'sampler.steps',
cfg_scale: 'sampler.cfg',
sampler_index: 'sampler.sampler_name',
// scheduler: 'normal',
denoising_strength: 'sampler.denoise',
//hires_node node:
hr_scale: 'scaler.scale_by',
upscale_method: 'nearest_exact',
hr_seed: 'hires_sampler.seed',
hr_second_pass_steps: 'hires_sampler.steps',
// hr_cfg: 'hires_sampler.cfg', // keep at default value 0.5
// hr_sampler_name: 'hires_sampler.sampler_name',
// hr_scheduler: 'normal',
hr_denoising_strength: 'hires_sampler.denoise',
}
async function reuseOrUploadComfyImage(
base64: string,
all_uploaded_images: Record<string, any>
) {
let image_name: string = ''
if (all_uploaded_images[base64]) {
image_name = all_uploaded_images[base64]
} else {
const new_loaded_image = await util.uploadImage(false, base64)
console.log('new_loaded_image: ', new_loaded_image)
if (new_loaded_image) {
store.data.uploaded_images_list = [
...store.data.uploaded_images_list,
new_loaded_image.name,
]
image_name = new_loaded_image.name
all_uploaded_images[base64] = new_loaded_image.name
}
}
return image_name
}
async function addMissingSettings(plugin_settings: Record<string, any>) {
plugin_settings['vae'] = vae_settings.store.data.current_vae
plugin_settings['model'] = sd_tab_util.store.data.selected_model
plugin_settings['hr_denoising_strength'] =
sd_tab_util.store.data.hr_denoising_strength
plugin_settings['hr_sampler_name'] = sd_tab_util.store.data.sampler_name // use the same sampler for the first and second pass (hires) upscale sampling steps
plugin_settings['comfy_clip_skip'] = -1 * plugin_settings['clip_skip']
if ('init_images' in plugin_settings) {
const base64 = plugin_settings['init_images'][0]
plugin_settings['init_image'] = await reuseOrUploadComfyImage(
base64,
store.data.base64_to_uploaded_images_names
)
}
if ('mask' in plugin_settings) {
const base64 = plugin_settings['mask']
plugin_settings['comfy_mask'] = await reuseOrUploadComfyImage(
base64,
store.data.base64_to_uploaded_images_names
)
}
//calculate positive random seed if seed is -1
const random_seed: bigint = util.getRandomBigIntApprox(
0n,
18446744073709552000n
)
plugin_settings['seed'] =
parseInt(plugin_settings['seed']) === -1
? random_seed.toString()
: plugin_settings['seed'] // use the same as the main seed
session_store.data.last_seed = plugin_settings['seed']
plugin_settings['hr_seed'] = plugin_settings['seed']
return plugin_settings
}
async function addMissingControlnetSettings(
plugin_settings: Record<string, any>
) {
plugin_settings['disableControlNetTab'] =
ControlNetStore.disableControlNetTab
for (const unit of plugin_settings['controlnet_units']) {
unit['comfy_enabled'] =
!plugin_settings['disableControlNetTab'] && unit.enabled
? 'enable'
: 'disable'
unit['comfy_input_image'] = ''
unit['comfy_mask'] = ''
if ('input_image' in unit && unit['input_image'] !== '') {
const base64 = unit['input_image']
unit['comfy_input_image'] = await reuseOrUploadComfyImage(
base64,
store.data.base64_to_uploaded_images_names
)
}
if ('mask' in unit && unit['mask'] !== '') {
//if mask have been set manually
const base64 = unit['mask']
unit['comfy_mask'] = await reuseOrUploadComfyImage(
base64,
store.data.base64_to_uploaded_images_names
)
} else if ('comfy_mask' in plugin_settings) {
// use the mask from the main ui (inpaint and outpaint mode)
unit['comfy_mask'] = plugin_settings['comfy_mask']
}
//set model and module to 'None' if no item has been selection, so comfyui won't through an error
unit['model'] =
unit['model'] === '' || unit['comfy_enabled'] === 'disable'
? 'None'
: unit['model']
unit['module'] =
unit['module'] === '' || unit['comfy_enabled'] === 'disable'
? 'None'
: unit['module']
}
return plugin_settings
}
async function mapPluginSettingsToComfyuiPrompt(
nodes: any[],
prompt: any,
plugin_settings: any,
mode_map: any
) {
try {
// const plugin_param = 'steps'
plugin_settings = await addMissingSettings(plugin_settings)
function mapPluginInputToComfyInput(
plugin_settings: Record<string, any>,
plugin_param: string,
node_name_id: string,
input_name: string
) {
if (plugin_param in plugin_settings) {
setInputValue(
nodes,
prompt,
node_name_id,
input_name,
plugin_settings[plugin_param]
)
}
}
Object.keys(mode_map).forEach((plugin_param: string) => {
const [node_name_id, input_name] = mode_map[plugin_param].split('.')
mapPluginInputToComfyInput(
plugin_settings,
plugin_param,
node_name_id,
input_name
)
})
plugin_settings = await addMissingControlnetSettings(plugin_settings)
for (let i = 0; i < 3; ++i) {
const unit = plugin_settings['controlnet_units'][i]
// one for each controlnet unit
Object.keys(controlnet_txt2img_map).forEach(
(plugin_param: string) => {
let [node_name_id, input_name] =
controlnet_txt2img_map[plugin_param].split('.')
// if (
// node_name_id === 'controlnet_image' ||
// node_name_id === 'controlnet_mask'
// ) {
// // the input images and masks are each in separate nodes
// node_name_id = `${node_name_id}_${i + 1}` //ex: 'controlnet_image.image' -> controlnet_image_1.image
// } else {
// //all other inputs present in the controlnet script
// input_name = `${input_name}_${i + 1}` //ex: preprocessor_name -> preprocessor_name_1
// }
input_name = `${input_name}_${i + 1}` //ex: preprocessor_name -> preprocessor_name_1
mapPluginInputToComfyInput(
unit,
plugin_param,
node_name_id,
input_name
)
}
)
}
} catch (e) {
console.error(e)
}
return prompt
}
async function generateComfyMode(
nodes: any[],
api_prompt: Record<string, any>,
plugin_settings: Record<string, any>,
mode_map: Record<string, string>
): Promise<{ image_base64_list: string[]; image_url_list: string[] }> {
let image_url_list: string[] = []
let image_base64_list: string[] = []
try {
// const controlnet_settings =
// mapPluginSettingsToControlNet(plugin_settings)
// console.log('controlnet_settings:', controlnet_settings)
const prompt = await mapPluginSettingsToComfyuiPrompt(
nodes,
copyJson(api_prompt),
plugin_settings,
mode_map
)
const final_prompt = copyJson(prompt)
if (!plugin_settings['enable_hr']) {
//get node_id
const hire_output_node = getNodeByNameId(nodes, 'hires_output')
delete final_prompt[hire_output_node.id]
}
const separated_output_node_ids: string[] = []
const node_id_to_controlnet_unit_index: Record<string, number> = {}
for (const [index, unit] of plugin_settings[
'controlnet_units'
].entries()) {
const node_name_id = `preprocessor_output_${index + 1}`
const node = getNodeByNameId(nodes, node_name_id)
const node_id = node.id.toString()
node_id_to_controlnet_unit_index[node_id] = index
if (unit['comfy_enabled'] === 'disable') {
mutePromptNode(nodes, final_prompt, node_name_id)
} else if (unit['comfy_enabled'] === 'enable') {
separated_output_node_ids.push(node_id)
}
}
// for (let i = 0; i < 3; ++i) {
// const unit = plugin_settings['controlnet_units'][i]
// if (unit['input_image'] === '') {
// mutePromptNode(nodes, final_prompt, `controlnet_image_${i + 1}`)
// }
// if (unit['mask'] === '') {
// mutePromptNode(nodes, final_prompt, `controlnet_mask_${i + 1}`)
// }
// }
console.log('final_prompt: ', final_prompt)
const { outputs, separated_outputs } =
await comfyui_util.postPromptAndGetBase64JsonResult(
final_prompt,
separated_output_node_ids
)
if (outputs) {
image_url_list = Object.values(outputs).flat()
image_base64_list = image_url_list.map((image_url) => {
return base64UrlToBase64(image_url)
})
}
if (separated_outputs) {
Object.entries(separated_outputs).forEach(([node_id, images]) => {
const controlnet_unit_index =
node_id_to_controlnet_unit_index[node_id]
setControlDetectMapSrc(
base64UrlToBase64(images[0]),
controlnet_unit_index
)
})
}
} catch (e) {
console.error(e)
}
return { image_base64_list, image_url_list }
}
async function generateComfyTxt2Img(
plugin_settings: any
): Promise<{ image_base64_list: string[]; image_url_list: string[] }> {
return generateComfyMode(
txt2img.nodes,
txt2img_api,
plugin_settings,
txt2img_map
)
}
async function generateComfyImg2Img(
plugin_settings: any
): Promise<{ image_base64_list: string[]; image_url_list: string[] }> {
return generateComfyMode(
img2img.nodes,
img2img_api,
plugin_settings,
img2img_map
)
}
async function generateComfyInpaint(
plugin_settings: any
): Promise<{ image_base64_list: string[]; image_url_list: string[] }> {
if ('inpainting_fill' in plugin_settings) {
const index = plugin_settings['inpainting_fill']
const content_mask_option = [
'fill',
'original',
'latent_noise',
'latent_nothing',
]
plugin_settings['comfy_content_mask'] = content_mask_option[index]
// const content_mask = [
// ['fill', ''],
// ['original', 'content_mask_original_output'],
// ['latent_noise', 'content_mask_latent_noise_output'],
// ['latent_nothing', 'content_mask_latent_nothing_output'],
// ]
// const comfy_node_name_id = content_mask[index][1]
// const content_mask_node = getNodeByNameId(
// inpaint.nodes,
// comfy_node_name_id
// )
// if (index > 0 && index <= 3 && content_mask_node) {
// plugin_settings['comfy_content_mask'] = [
// content_mask_node.id.toString(),
// 0,
// ]
// }
}
return generateComfyMode(
inpaint.nodes,
inpaint_api,
plugin_settings,
inpaint_map
)
}
export default {
parseMetadata,
getNode,
getInput,
getLink,
getNodesFromLink,
getNodeByNameId,
mapPluginSettingsToComfyuiPrompt,
getPromptNodeByNameId,
setInputValue,
addMissingSettings,
generateComfyTxt2Img,
generateComfyImg2Img,
generateComfyInpaint,
txt2img,
txt2img_api,
img2img,
img2img_api,
inpaint,
inpaint_api,
}

View File

@ -0,0 +1,167 @@
{
"8": {
"inputs": {
"samples": [
"50",
0
],
"vae": [
"32",
0
]
},
"class_type": "VAEDecode"
},
"14": {
"inputs": {
"ckpt_name": "dreamshaper_8.safetensors"
},
"class_type": "CheckpointLoaderSimple"
},
"32": {
"inputs": {
"vae_name": "vae-ft-mse-840000-ema-pruned.safetensors"
},
"class_type": "VAELoader"
},
"50": {
"inputs": {
"add_noise": "enable",
"noise_seed": 538408362410054,
"steps": 30,
"cfg": 7,
"sampler_name": "dpmpp_2m",
"scheduler": "karras",
"start_at_step": 0,
"end_at_step": 10000,
"return_with_leftover_noise": "disable",
"model": [
"210",
0
],
"positive": [
"211",
0
],
"negative": [
"212",
0
],
"latent_image": [
"95",
0
]
},
"class_type": "KSamplerAdvanced"
},
"95": {
"inputs": {
"width": 512,
"height": 512,
"batch_size": 1
},
"class_type": "EmptyLatentImage"
},
"192": {
"inputs": {
"images": [
"8",
0
]
},
"class_type": "PreviewImage"
},
"204": {
"inputs": {
"weight": 0.5,
"noise": 0.33,
"weight_type": "original",
"start_at": 0,
"end_at": 1,
"ipadapter": [
"205",
0
],
"clip_vision": [
"206",
0
],
"image": [
"207",
0
],
"model": [
"14",
0
]
},
"class_type": "IPAdapterApply"
},
"205": {
"inputs": {
"ipadapter_file": "ip-adapter-plus_sd15.bin"
},
"class_type": "IPAdapterModelLoader"
},
"206": {
"inputs": {
"clip_name": "model.safetensors"
},
"class_type": "CLIPVisionLoader"
},
"207": {
"inputs": {
"interpolation": "LANCZOS",
"crop_position": "top",
"sharpening": 0.15,
"image": [
"209",
0
]
},
"class_type": "PrepImageForClipVision"
},
"209": {
"inputs": {
"image": "ComfyUI_temp_cqoqp_00001_ (1).png",
"choose file to upload": "image"
},
"class_type": "LoadImage"
},
"210": {
"inputs": {
"block_number": 3,
"downscale_factor": 1.5,
"start_percent": 0,
"end_percent": 0.45,
"downscale_after_skip": true,
"downscale_method": "bicubic",
"upscale_method": "bicubic",
"model": [
"204",
0
]
},
"class_type": "PatchModelAddDownscale"
},
"211": {
"inputs": {
"text": "",
"clip": [
"14",
1
]
},
"class_type": "CLIPTextEncode"
},
"212": {
"inputs": {
"text": "blurry, low quality",
"clip": [
"14",
1
]
},
"class_type": "CLIPTextEncode"
}
}

View File

@ -0,0 +1,180 @@
{
"1": {
"inputs": {
"ckpt_name": "dreamshaper_8.safetensors"
},
"class_type": "CheckpointLoaderSimple"
},
"2": {
"inputs": {
"vae_name": "vae-ft-mse-840000-ema-pruned.safetensors"
},
"class_type": "VAELoader"
},
"3": {
"inputs": {
"ipadapter_file": "ip-adapter-plus_sd15.bin"
},
"class_type": "IPAdapterModelLoader"
},
"4": {
"inputs": {
"clip_name": "model.safetensors"
},
"class_type": "CLIPVisionLoader"
},
"6": {
"inputs": {
"image": "ComfyUI_temp_cqoqp_00001_ (1).png",
"choose file to upload": "image"
},
"class_type": "LoadImage"
},
"7": {
"inputs": {
"text": "beautiful renaissance girl, detailed",
"clip": [
"1",
1
]
},
"class_type": "CLIPTextEncode"
},
"8": {
"inputs": {
"text": "blurry, horror",
"clip": [
"1",
1
]
},
"class_type": "CLIPTextEncode"
},
"9": {
"inputs": {
"seed": 857545940756658,
"steps": 35,
"cfg": 5,
"sampler_name": "ddim",
"scheduler": "ddim_uniform",
"denoise": 1,
"model": [
"13",
0
],
"positive": [
"7",
0
],
"negative": [
"8",
0
],
"latent_image": [
"10",
0
]
},
"class_type": "KSampler"
},
"10": {
"inputs": {
"width": 512,
"height": 512,
"batch_size": 4
},
"class_type": "EmptyLatentImage"
},
"11": {
"inputs": {
"samples": [
"9",
0
],
"vae": [
"2",
0
]
},
"class_type": "VAEDecode"
},
"12": {
"inputs": {
"filename_prefix": "IPAdapter",
"images": [
"11",
0
]
},
"class_type": "SaveImage"
},
"13": {
"inputs": {
"weight": 1,
"weight_type": "original",
"start_at": 0,
"end_at": 1,
"ipadapter": [
"3",
0
],
"embeds": [
"14",
0
],
"model": [
"1",
0
]
},
"class_type": "IPAdapterApplyEncoded"
},
"14": {
"inputs": {
"ipadapter_plus": true,
"noise": 0.31,
"weight_1": [
"16",
0
],
"weight_2": [
"17",
0
],
"weight_3": 1,
"weight_4": 1,
"clip_vision": [
"4",
0
],
"image_1": [
"15",
0
],
"image_2": [
"6",
0
]
},
"class_type": "IPAdapterEncoder"
},
"15": {
"inputs": {
"image": "animation-deku-my-hero-academia.jpg",
"choose file to upload": "image"
},
"class_type": "LoadImage"
},
"16": {
"inputs": {
"Value": 1
},
"class_type": "Float"
},
"17": {
"inputs": {
"Value": 0.38
},
"class_type": "Float"
}
}

View File

@ -0,0 +1,182 @@
{
"2": {
"inputs": {
"vae_name": "vae-ft-mse-840000-ema-pruned.safetensors"
},
"class_type": "VAELoader"
},
"4": {
"inputs": {
"stop_at_clip_layer": -1,
"clip": [
"32",
1
]
},
"class_type": "CLIPSetLastLayer"
},
"6": {
"inputs": {
"text": "embedding:BadDream, ",
"clip": [
"4",
0
]
},
"class_type": "CLIPTextEncode"
},
"7": {
"inputs": {
"seed": 888888891,
"steps": 8,
"cfg": 1.5,
"sampler_name": "lcm",
"scheduler": "sgm_uniform",
"denoise": 1,
"model": [
"36",
0
],
"positive": [
"38",
0
],
"negative": [
"6",
0
],
"latent_image": [
"9",
0
]
},
"class_type": "KSampler"
},
"9": {
"inputs": {
"width": 512,
"height": 512,
"batch_size": 110
},
"class_type": "EmptyLatentImage"
},
"10": {
"inputs": {
"samples": [
"7",
0
],
"vae": [
"2",
0
]
},
"class_type": "VAEDecode"
},
"32": {
"inputs": {
"ckpt_name": "dreamshaper_8.safetensors"
},
"class_type": "CheckpointLoaderSimple"
},
"33": {
"inputs": {
"context_length": 16,
"context_stride": 1,
"context_overlap": 4,
"context_schedule": "uniform",
"closed_loop": false
},
"class_type": "ADE_AnimateDiffUniformContextOptions"
},
"36": {
"inputs": {
"model_name": "mm_sd_v15_v2.ckpt",
"beta_schedule": "sqrt_linear (AnimateDiff)",
"motion_scale": 1,
"apply_v2_models_properly": false,
"model": [
"42",
0
],
"context_options": [
"33",
0
]
},
"class_type": "ADE_AnimateDiffLoaderWithContext"
},
"37": {
"inputs": {
"frame_rate": 8,
"loop_count": 0,
"filename_prefix": "aaa_readme",
"format": "image/gif",
"pingpong": false,
"save_image": true,
"crf": 20,
"save_metadata": true,
"videopreview": {
"hidden": false,
"paused": false,
"params": {
"filename": "aaa_readme_00024.gif",
"subfolder": "",
"type": "output",
"format": "image/gif"
}
},
"images": [
"10",
0
]
},
"class_type": "VHS_VideoCombine"
},
"38": {
"inputs": {
"text": "\"0\" : \"Spring, flowers, smile\",\n\"20\" : \"Spring, flowers, smile\",\n\"30\" : \"Summer, sun, happy, windy\",\n\"50\" : \"Summer, sun, happy, windy\",\n\"60\" : \"Autumn, yellow leaves, laugh\",\n\"80\" : \"Autumn, yellow leaves, laugh\",\n\"90\" : \"Winter, wind, snow, smile, seductive\",\n\"110\" : \"Winter, wind snow, smile, seductive\"",
"max_frames": 110,
"print_output": false,
"pre_text": "25 year old woman, t-shirt",
"app_text": "",
"start_frame": 0,
"pw_a": 0,
"pw_b": 0,
"pw_c": 0,
"pw_d": 0,
"clip": [
"4",
0
]
},
"class_type": "BatchPromptSchedule"
},
"41": {
"inputs": {
"lora_name": "lcm_lora_sd15.safetensors",
"strength_model": 1,
"strength_clip": 1,
"model": [
"32",
0
],
"clip": [
"32",
1
]
},
"class_type": "LoraLoader"
},
"42": {
"inputs": {
"sampling": "lcm",
"zsnr": false,
"model": [
"41",
0
]
},
"class_type": "ModelSamplingDiscrete"
}
}

View File

@ -0,0 +1,141 @@
{
"2": {
"inputs": {
"vae_name": "vae-ft-mse-840000-ema-pruned.safetensors"
},
"class_type": "VAELoader"
},
"3": {
"inputs": {
"text": "girl astronaut walking on the moon. ",
"clip": [
"4",
0
]
},
"class_type": "CLIPTextEncode"
},
"4": {
"inputs": {
"stop_at_clip_layer": -2,
"clip": [
"32",
1
]
},
"class_type": "CLIPSetLastLayer"
},
"6": {
"inputs": {
"text": "(worst quality, low quality: 1.4)",
"clip": [
"4",
0
]
},
"class_type": "CLIPTextEncode"
},
"7": {
"inputs": {
"seed": 888888889,
"steps": 20,
"cfg": 8,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1,
"model": [
"27",
0
],
"positive": [
"3",
0
],
"negative": [
"6",
0
],
"latent_image": [
"9",
0
]
},
"class_type": "KSampler"
},
"9": {
"inputs": {
"width": 512,
"height": 512,
"batch_size": 16
},
"class_type": "EmptyLatentImage"
},
"10": {
"inputs": {
"samples": [
"7",
0
],
"vae": [
"2",
0
]
},
"class_type": "VAEDecode"
},
"27": {
"inputs": {
"model_name": "mm_sd_v14.ckpt",
"beta_schedule": "sqrt_linear (AnimateDiff)",
"motion_scale": 1,
"apply_v2_models_properly": false,
"model": [
"32",
0
]
},
"class_type": "ADE_AnimateDiffLoaderWithContext"
},
"32": {
"inputs": {
"ckpt_name": "cardosAnime_v20.safetensors"
},
"class_type": "CheckpointLoaderSimple"
},
"35": {
"inputs": {
"frame_rate": 8,
"loop_count": 0,
"filename_prefix": "aaa_readme",
"format": "image/gif",
"pingpong": false,
"save_image": true,
"crf": 20,
"save_metadata": false,
"videopreview": {
"hidden": false,
"paused": false,
"params": {
"filename": "aaa_readme_00022.gif",
"subfolder": "",
"type": "output",
"format": "image/gif"
}
},
"images": [
"10",
0
]
},
"class_type": "VHS_VideoCombine"
},
"37": {
"inputs": {
"images": [
"10",
0
]
},
"class_type": "PreviewImage"
}
}

View File

@ -0,0 +1,24 @@
{
"43": {
"inputs": {
"seed": 927413224602369,
"steps": 4,
"cfg": 8,
"height": 512,
"width": 512,
"num_images": 4,
"use_fp16": true,
"positive_prompt": "cute cat"
},
"class_type": "LCM_Sampler"
},
"44": {
"inputs": {
"images": [
"43",
0
]
},
"class_type": "PreviewImage"
}
}

View File

@ -0,0 +1,333 @@
{
"1": {
"inputs": {
"image": "000662ed61a84c86fc8a3fe69d38a6e2 (1).jpg",
"choose file to upload": "image"
},
"class_type": "LoadImage"
},
"2": {
"inputs": {
"left": 112,
"top": 112,
"right": 104,
"bottom": 208,
"feathering": 20,
"image": [
"50",
0
]
},
"class_type": "ImagePadForOutpaint"
},
"3": {
"inputs": {
"images": [
"2",
0
]
},
"class_type": "PreviewImage"
},
"5": {
"inputs": {
"width": [
"12",
2
],
"height": [
"12",
1
],
"batch_size": 1
},
"class_type": "EmptyLatentImage"
},
"6": {
"inputs": {
"pixels": [
"2",
0
],
"vae": [
"7",
0
]
},
"class_type": "VAEEncode"
},
"7": {
"inputs": {
"vae_name": "vae-ft-mse-840000-ema-pruned.safetensors"
},
"class_type": "VAELoader"
},
"12": {
"inputs": {
"value": [
"2",
0
]
},
"class_type": "ImpactImageInfo"
},
"14": {
"inputs": {
"x": 0,
"y": 0,
"resize_source": true,
"destination": [
"6",
0
],
"source": [
"5",
0
],
"mask": [
"2",
1
]
},
"class_type": "LatentCompositeMasked"
},
"15": {
"inputs": {
"samples": [
"14",
0
],
"vae": [
"7",
0
]
},
"class_type": "VAEDecode"
},
"19": {
"inputs": {
"images": [
"15",
0
]
},
"class_type": "PreviewImage"
},
"20": {
"inputs": {
"strength": 1,
"conditioning": [
"27",
4
],
"control_net": [
"21",
0
],
"image": [
"47",
0
]
},
"class_type": "ControlNetApply"
},
"21": {
"inputs": {
"control_net_name": "control_v11p_sd15_inpaint_fp16.safetensors"
},
"class_type": "ControlNetLoader"
},
"22": {
"inputs": {
"ckpt_name": "aniverse_v15Pruned.safetensors"
},
"class_type": "CheckpointLoaderSimple"
},
"23": {
"inputs": {
"model": [
"22",
0
],
"clip": [
"22",
1
],
"vae": [
"22",
2
],
"positive": [
"24",
0
],
"negative": [
"25",
0
]
},
"class_type": "ToBasicPipe"
},
"24": {
"inputs": {
"text": "",
"clip": [
"22",
1
]
},
"class_type": "CLIPTextEncode"
},
"25": {
"inputs": {
"text": "nsfw",
"clip": [
"22",
1
]
},
"class_type": "CLIPTextEncode"
},
"27": {
"inputs": {
"basic_pipe": [
"23",
0
]
},
"class_type": "FromBasicPipe_v2"
},
"29": {
"inputs": {
"basic_pipe": [
"27",
0
],
"positive": [
"20",
0
]
},
"class_type": "EditBasicPipe"
},
"30": {
"inputs": {
"seed": 949895177872699,
"steps": 20,
"cfg": 8,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1,
"basic_pipe": [
"29",
0
],
"latent_image": [
"62",
0
]
},
"class_type": "ImpactKSamplerBasicPipe"
},
"31": {
"inputs": {
"samples": [
"30",
1
],
"vae": [
"30",
2
]
},
"class_type": "VAEDecode"
},
"32": {
"inputs": {
"images": [
"31",
0
]
},
"class_type": "PreviewImage"
},
"33": {
"inputs": {
"samples": [
"14",
0
],
"mask": [
"2",
1
]
},
"class_type": "SetLatentNoiseMask"
},
"47": {
"inputs": {
"image": [
"2",
0
],
"mask": [
"2",
1
]
},
"class_type": "InpaintPreprocessor"
},
"50": {
"inputs": {
"upscale_method": "nearest-exact",
"scale_by": 0.3,
"image": [
"1",
0
]
},
"class_type": "ImageScaleBy"
},
"52": {
"inputs": {
"clip_vision": [
"53",
0
],
"image": [
"1",
0
]
},
"class_type": "CLIPVisionEncode"
},
"53": {
"inputs": {
"clip_name": "SD1.5/pytorch_model.bin"
},
"class_type": "CLIPVisionLoader"
},
"56": {
"inputs": {
"pixels": [
"2",
0
],
"vae": [
"7",
0
]
},
"class_type": "VAEEncode"
},
"62": {
"inputs": {
"amount": 2,
"samples": [
"33",
0
]
},
"class_type": "RepeatLatentBatch"
}
}

View File

@ -0,0 +1,264 @@
{
"3": {
"inputs": {
"seed": 945794611996037,
"steps": 12,
"cfg": 8,
"sampler_name": "dpmpp_sde",
"scheduler": "normal",
"denoise": 1,
"model": [
"58",
0
],
"positive": [
"81",
3
],
"negative": [
"81",
4
],
"latent_image": [
"5",
0
]
},
"class_type": "KSampler"
},
"5": {
"inputs": {
"width": 512,
"height": 512,
"batch_size": 1
},
"class_type": "EmptyLatentImage"
},
"6": {
"inputs": {
"text": [
"58",
2
],
"clip": [
"58",
1
]
},
"class_type": "CLIPTextEncode"
},
"7": {
"inputs": {
"text": [
"60",
2
],
"clip": [
"60",
1
]
},
"class_type": "CLIPTextEncode"
},
"8": {
"inputs": {
"samples": [
"3",
0
],
"vae": [
"57",
0
]
},
"class_type": "VAEDecode"
},
"9": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"8",
0
]
},
"class_type": "SaveImage"
},
"11": {
"inputs": {
"seed": 702670332387358,
"steps": 14,
"cfg": 8,
"sampler_name": "dpmpp_2m",
"scheduler": "simple",
"denoise": 0.5,
"model": [
"58",
0
],
"positive": [
"81",
3
],
"negative": [
"81",
4
],
"latent_image": [
"55",
0
]
},
"class_type": "KSampler"
},
"12": {
"inputs": {
"filename_prefix": "ComfyUI",
"images": [
"13",
0
]
},
"class_type": "SaveImage"
},
"13": {
"inputs": {
"samples": [
"11",
0
],
"vae": [
"57",
0
]
},
"class_type": "VAEDecode"
},
"16": {
"inputs": {
"ckpt_name": "dreamshaper_8.safetensors"
},
"class_type": "CheckpointLoaderSimple"
},
"55": {
"inputs": {
"upscale_method": "nearest-exact",
"scale_by": 2,
"samples": [
"3",
0
]
},
"class_type": "LatentUpscaleBy"
},
"57": {
"inputs": {
"vae_name": "vae-ft-mse-840000-ema-pruned.safetensors"
},
"class_type": "VAELoader"
},
"58": {
"inputs": {
"prompt": "<lora:lora_delta_small:0.7> lora_delta girl lora_bright_style girl masterpiece HDR victorian portrait painting of woman, blonde hair, mountain nature, blue sky",
"model": [
"60",
0
],
"clip": [
"98",
0
]
},
"class_type": "LoadLorasFromPrompt"
},
"60": {
"inputs": {
"prompt": "bad hands, text, watermark",
"model": [
"16",
0
],
"clip": [
"98",
0
]
},
"class_type": "LoadLorasFromPrompt"
},
"81": {
"inputs": {
"is_enabled_1": "disable",
"preprocessor_name_1": "CannyEdgePreprocessor",
"control_net_name_1": "control_lora_rank128_v11p_sd15_canny_fp16.safetensors",
"strength_1": 1,
"threshold_a_1": 100,
"threshold_b_1": 200,
"start_percent_1": 0,
"end_percent_1": 1,
"resolution_1": 512,
"is_enabled_2": "disable",
"preprocessor_name_2": "OpenposePreprocessor",
"control_net_name_2": "control_lora_rank128_v11p_sd15_openpose_fp16.safetensors",
"strength_2": 1,
"threshold_a_2": 0,
"threshold_b_2": 0,
"start_percent_2": 0,
"end_percent_2": 1,
"resolution_2": 512,
"is_enabled_3": "disable",
"preprocessor_name_3": "InpaintPreprocessor",
"control_net_name_3": "control_lora_rank128_v11p_sd15_inpaint_fp16.safetensors",
"strength_3": 1,
"threshold_a_3": 0,
"threshold_b_3": 0,
"start_percent_3": 0,
"end_percent_3": 1,
"resolution_3": 512,
"positive": [
"6",
0
],
"negative": [
"7",
0
]
},
"class_type": "ControlNetScript"
},
"91": {
"inputs": {
"images": [
"81",
0
]
},
"class_type": "PreviewImage"
},
"92": {
"inputs": {
"images": [
"81",
1
]
},
"class_type": "PreviewImage"
},
"93": {
"inputs": {
"images": [
"81",
2
]
},
"class_type": "PreviewImage"
},
"98": {
"inputs": {
"stop_at_clip_layer": -1,
"clip": [
"16",
1
]
},
"class_type": "CLIPSetLastLayer"
}
}

File diff suppressed because it is too large Load Diff

547
typescripts/comfyui/util.ts Normal file
View File

@ -0,0 +1,547 @@
import { readdirSync, readFileSync } from 'fs'
import { requestPost } from '../util/ts/api'
import { storage } from 'uxp'
import { reaction, toJS } from 'mobx'
import { AStore } from '../main/astore'
import comfyapi from './comfyapi'
import { base64UrlToBase64 } from '../util/ts/general'
import { app } from 'photoshop'
export enum InputTypeEnum {
NumberField = 'NumberField',
TextField = 'TextField',
TextArea = 'TextArea',
Menu = 'Menu',
ImageBase64 = 'ImageBase64',
}
export interface ValidInput {
[key: string]: any
value: string | number
label: string
list?: any[]
type: InputTypeEnum
id?: string
}
export interface PhotoshopNode {
inputs: ValidInput[]
id: string
}
export interface ComfyUIConfig {
[key: string]: any
checkpoints: string[]
samplers: string[]
schedulers: string[]
}
export interface ComfyUINode {
inputs: any
class_type: string
}
// Assuming the json files are in a directory named 'native_workflows'
const dir = 'plugin:/typescripts/comfyui/native_workflows' // specify the directory containing the .json files
let workflows2: Record<string, any> = {}
readdirSync(dir).forEach((file) => {
if (file.endsWith('.json')) {
const fileContent = readFileSync(`${dir}/${file}`, 'utf8')
const fileNameWithoutExtension = file.slice(0, -5)
workflows2[fileNameWithoutExtension] = JSON.parse(fileContent)
}
})
export const store = new AStore({
comfyui_valid_nodes: {} as any, // comfyui nodes like structure that contain all info necessary to create plugin ui elements
uuids: {} as any,
comfyui_output_images: [] as string[], //store the output images from generation
comfyui_output_thumbnail_images: [] as string[], // store thumbnail size images
comfyui_config: {} as ComfyUIConfig, // all config data like samplers, checkpoints ...etc
workflow_path: '', // the path of an image that contains prompt information
workflow_dir_path: '', // the path of the directory that contains all workflow files
// workflows_paths: [] as string[],
// workflows_names: [] as string[],
workflows: {} as any,
selected_workflow_name: '', // the selected workflow from the workflow menu
current_prompt: {} as any, // current prompt extracted from the workflow
thumbnail_image_size: 100,
load_image_nodes: {} as any, //our custom loadImageBase64 nodes, we need to substitute comfyui LoadImage nodes with before generating a prompt
// load_image_base64_strings: {} as any, //images the user added to the plugin comfy ui
object_info: undefined as any,
current_prompt2: {} as any,
current_prompt2_output: {} as any,
output_thumbnail_image_size: {} as Record<string, number>,
uploaded_images_base64_url: [] as string[],
current_uploaded_image: {} as Record<string, string>, //key: node_id, value: base64_url; only used in UI to show the selected images for LoadImage nodes
current_uploaded_video: {} as Record<string, string>,
uploaded_images_list: [] as string[], // store an array of all images in the comfy's input directory
uploaded_video_list: [] as string[], // store the name of .gif and .mp4 videos in comfy's input directory and subcategories
nodes_order: [] as string[], // nodes with smaller index will be rendered first,
can_edit_nodes: false as boolean,
nodes_label: {} as Record<string, string>,
workflows2: workflows2 as Record<string, any>,
user_custom_workflow: {} as Record<string, any>,
progress_value: 0,
is_random_seed: {} as Record<string, boolean>,
last_moved: undefined as string | undefined, // the last node that has been moved in the edit mode
base64_to_uploaded_images_names: {} as Record<string, any>,
can_generate: true as boolean,
})
interface Workflow {}
export function getNodes(workflow: Workflow) {
// Object.values(workflow).forEach((node) => {
// console.log(node.class_type)
// })
return Object.entries(workflow)
}
export enum ComfyInputType {
TextField = 'TextField',
TextArea = 'TextArea',
Menu = 'Menu',
Number = 'Number',
Slider = 'Slider',
BigNumber = 'BigNumber',
TextFieldNumber = 'TextFieldNumber',
Skip = 'Skip',
Seed = 'Seed',
CheckBox = 'CheckBox',
}
export enum ComfyNodeType {
LoadImage = 'LoadImage',
LoadVideo = 'LoadVideo',
Normal = 'Normal',
Skip = 'Skip',
}
interface ComfyOutputImage {
filename: string
subfolder: string
type: string
}
export function getNodeType(node_name: any) {
let node_type: ComfyNodeType = ComfyNodeType.Normal
switch (node_name) {
case 'LoadImage':
node_type = ComfyNodeType.LoadImage
break
case 'LoadVideo':
node_type = ComfyNodeType.LoadVideo
break
default:
break
}
return node_type
}
export function parseComfyInput(
name: string,
input_info: any,
prompt_value: any // the default value, set in the prompt api
): {
type: ComfyInputType
config: any
} {
let input_type: ComfyInputType = ComfyInputType.Skip
let input_config = input_info?.[1] || void 0
if (input_info === undefined) {
return { type: input_type, config: input_config }
}
try {
const value = input_info[0]
if (
(name === 'seed' || name === 'noise_seed') &&
!Array.isArray(prompt_value)
) {
input_type = ComfyInputType.Seed // similar to big number
input_config = input_info[1]
} else if (typeof value === 'string') {
if (value === 'FLOAT' && !Array.isArray(prompt_value)) {
if (Number.isSafeInteger(input_config?.max)) {
input_type = ComfyInputType.Slider
input_config = input_info[1]
} else {
input_type = ComfyInputType.TextFieldNumber
input_config = input_info[1]
}
} else if (value === 'INT' && !Array.isArray(prompt_value)) {
if (input_info[1].max > Number.MAX_SAFE_INTEGER) {
input_type = ComfyInputType.BigNumber
input_config = input_info[1]
} else {
input_type = ComfyInputType.TextFieldNumber
input_config = input_info[1]
}
} else if (value === 'STRING' && !Array.isArray(prompt_value)) {
if (input_info[1]?.multiline) {
input_type = ComfyInputType.TextArea
input_config = input_info[1]
} else {
input_type = ComfyInputType.TextField
input_config = input_info[1]
}
} else if (value === 'BOOLEAN' && !Array.isArray(prompt_value)) {
input_type = ComfyInputType.CheckBox
input_config = input_info[1]
}
} else if (Array.isArray(value)) {
input_type = ComfyInputType.Menu
input_config = value
}
} catch (e) {
console.error(
`name:${name},
input_info:${input_info},
prompt_value:${prompt_value}`,
e
)
}
return { type: input_type, config: input_config }
}
export function makeHtmlInput() {}
async function getHistory(prompt_id: string) {
while (true) {
const res = await comfyapi.comfy_api.queue()
if (res.queue_pending.length || res.queue_running.length) {
await new Promise((resolve) => setTimeout(resolve, 500))
} else {
break
}
await new Promise((resolve) => setTimeout(resolve, 500))
}
const history = await comfyapi.comfy_api.getHistory(prompt_id)
return history
}
export async function postPromptAndGetBase64JsonResult(
prompt: Record<string, any>,
separated_output_nodes: string[] = []
): Promise<{
outputs: Record<string, any> | undefined
separated_outputs: Record<string, any> | undefined
}> {
try {
const res = await comfyapi.comfy_api.prompt(prompt)
if (!res) {
throw new Error(
`Unable to establish a connection to ComfyUI at the provided address: ${comfyapi.comfy_api.comfy_url}. Please ensure that ComfyUI is online and the URL is correct.`
)
}
if (res.error) {
const readable_error = comfyapi.comfy_api.getReadableError(res)
throw new Error(readable_error)
}
const prompt_id = res.prompt_id
const history = await getHistory(prompt_id)
const promptInfo = history[prompt_id]
if (Object.keys(promptInfo.outputs).length === 0) {
throw new Error(
`No images were generated. Please check the ComfyUI console for potential errors.`
)
}
const { outputs, separated_outputs } =
await mapComfyOutputToStoreOutput(
promptInfo.outputs,
separated_output_nodes
)
// // [4][0] for output id.
// const fileName = promptInfo.outputs[promptInfo.prompt[4][0]].images[0].filename
// const resultB64 = await ComfyApi.view(this, fileName);
// resultImages.push(resultB64)
// if (option.imageFinishCallback) {
// try { option.imageFinishCallback(resultB64, index) } catch (e) { }
// }
// }
return { outputs, separated_outputs }
} catch (e) {
console.error(e)
app.showAlert(`${e}`)
return { outputs: undefined, separated_outputs: undefined }
}
}
export const getFileFormat = (fileName: string): string =>
fileName.includes('.') ? fileName.split('.').pop()! : ''
export async function base64UrlFromComfy({
filename,
type,
subfolder,
}: ComfyOutputImage) {
const base64 = await comfyapi.comfy_api.view(filename, type, subfolder)
return base64Url(base64, getFileFormat(filename))
}
export function base64UrlFromFileName(base64: string, filename: string) {
return base64Url(base64, getFileFormat(filename))
}
export function base64Url(base64: string, format: string = 'png') {
return `data:image/${format};base64,${base64}`
}
export function generatePrompt(prompt: Record<string, any>) {
prompt
}
export function updateOutput(output: any, output_store_obj: any) {
// store.data.current_prompt2_output[26] = [image, image]
output_store_obj = output
}
export async function mapComfyOutputToStoreOutput(
comfy_output: Record<string, any>,
separated_output_nodes: string[] = []
) {
const outputs: Record<string, any> = {}
const separated_outputs: Record<string, any> = {}
for (let key in comfy_output) {
let base64_url_list = await Promise.all(
(Object.values(comfy_output[key]).flat() as ComfyOutputImage[]).map(
async (output: ComfyOutputImage) => {
try {
if (['png'].includes(extractFormat(output.filename))) {
return await base64UrlFromComfy(output)
} else if (
['gif'].includes(extractFormat(output.filename))
) {
const url = `${comfyapi.comfy_api.comfy_url}/view?subfolder=${output.subfolder}&type=${output.type}&filename=${output.filename}`
return url
}
} catch (e) {
console.error(output, e)
return ''
}
}
)
)
base64_url_list = base64_url_list.filter((item) => item !== '') // Filter out empty strings
if (separated_output_nodes.includes(key)) {
separated_outputs[key] = [
...(separated_outputs[key] || []),
...base64_url_list,
]
} else {
outputs[key] = [...(outputs[key] || []), ...base64_url_list]
}
}
return { outputs, separated_outputs }
}
interface LooseObject {
[key: string]: any
}
function isSameStructure(obj1: LooseObject, obj2: LooseObject): boolean {
// Get keys
const keys1 = Object.keys(obj1)
const keys2 = Object.keys(obj2)
// Check if both objects have the same number of keys
if (keys1.length !== keys2.length) {
return false
}
// Check if all keys in obj1 exist in obj2 and have the same structure
for (let i = 0; i < keys1.length; i++) {
const key = keys1[i]
// Check if the key exists in obj2
if (!obj2.hasOwnProperty(key)) {
return false
}
// If the value of this key is an object, check their structure recursively
if (
typeof obj1[key] === 'object' &&
obj1[key] !== null &&
typeof obj2[key] === 'object' &&
obj2[key] !== null
) {
if (!isSameStructure(obj1[key], obj2[key])) {
return false
}
}
}
// If all checks passed, the structures are the same
return true
}
function extractFormat(input: string) {
let format: string = ''
if (input.includes('data:')) {
// Case for dataURL
format = input.split(':')[1].split(';')[0].split('/')[1]
} else if (input.includes('.')) {
// Case for file name with extension
format = input.split('.').pop() || ''
} else {
throw `input doesn't have an extension. input:${input}`
}
return format
}
async function uploadImagePost(
buffer: any,
file_name: string,
subfolder: string = 'auto-photoshop-plugin'
) {
try {
const full_url = comfyapi.comfy_api.comfy_url + '/upload/image'
var formData = new FormData()
formData.append('image', buffer, file_name)
formData.append('subfolder', subfolder)
var requestOptions = {
method: 'POST',
// header: myHeaders,
body: formData,
}
//@ts-ignore
const res = await fetch(full_url, requestOptions)
console.log(res.status, full_url)
const contentType = res.headers.get('Content-Type')
if (contentType?.indexOf('json') != -1) {
return res.json()
} else {
if (res.status == 200) {
return res.arrayBuffer()
} else {
return res.text()
}
}
} catch (e) {
console.error(e)
}
}
async function uploadImage(b_from_disk = false, imgBase64: string) {
try {
let content, name
if (b_from_disk) {
const res = await readFile()
content = res.content
name = res.name
} else {
//from canvas
//@ts-ignore
const buffer = _base64ToArrayBuffer(imgBase64)
content = buffer
name = 'buffer.png'
}
// console.log('content: ', content)
// console.log('name: ', name)
const uploaded_image = await uploadImagePost(content, name, '')
return uploaded_image
} catch (e) {
console.error(e)
}
}
async function readFile() {
const entry = await storage.localFileSystem.getFileForOpening() // Prompts the user to select a file
// const contents = await entry.read('binary') // Reads the file as a string
const contents = await entry.read({
format: storage.formats.binary,
})
return { content: contents, name: entry.name }
}
function getRandomBigIntApprox(min: bigint, max: bigint): bigint {
min = BigInt(min)
max = BigInt(max)
const range = Number(max - min)
const rand = Math.floor(Math.random() * range)
return BigInt(rand) + min
}
function runRandomSeedScript() {
Object.entries(toJS(store.data.is_random_seed)).forEach(
([node_id, is_random]) => {
if (is_random) {
const random_seed: bigint = getRandomBigIntApprox(
0n,
18446744073709552000n
)
Object.keys(store.data.current_prompt2[node_id].inputs).forEach(
(name: string) => {
if (['seed', 'noise_seed'].includes(name)) {
store.data.current_prompt2[node_id].inputs[name] =
random_seed.toString()
// Usage
}
}
)
}
}
)
}
async function maskExpansion(
base64_mask: string,
expansion: number,
blur: number
) {
const prompt = {
'1': {
inputs: {
mask: base64_mask,
expansion: expansion,
blur: blur,
},
class_type: 'MaskExpansion',
},
'6': {
inputs: {
images: ['1', 0],
},
class_type: 'PreviewImage',
},
}
try {
const { outputs, separated_outputs } =
await postPromptAndGetBase64JsonResult(prompt)
if (outputs) {
const expanded_mask = outputs['6'][0]
return base64UrlToBase64(expanded_mask)
}
// html_manip.setInitImageMaskSrc(expanded_mask)
} catch (e) {
console.error(e)
}
return base64_mask
}
export default {
uploadImage,
uploadImagePost,
getNodes,
parseComfyInput,
getNodeType,
base64Url,
getFileFormat,
base64UrlFromComfy,
generatePrompt,
updateOutput,
getHistory,
mapComfyOutputToStoreOutput,
postPromptAndGetBase64JsonResult,
isSameStructure,
extractFormat,
getRandomBigIntApprox,
runRandomSeedScript,
maskExpansion,
workflows2,
ComfyInputType,
ComfyNodeType,
store,
}

View File

@ -11,6 +11,7 @@ import {
PreviewSvg,
SpSliderWithLabel,
SliderType,
SearchableMenu,
} from '../util/elements'
import ControlNetStore, { ControlnetMode, controlnetModes } from './store'
import { mapRange, versionCompare } from './util'
@ -135,18 +136,12 @@ export default class ControlNetUnit extends React.Component<
storeData.model = filters.default_model
}
}
onPreprocsesorChange(
event: any,
{ index, item }: { index: number; item: string }
) {
onPreprocsesorChange(item: string) {
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
storeData.module = item
}
onModelChange(
event: any,
{ index, item }: { index: number; item: string }
) {
onModelChange(item: string) {
const storeData =
this.props.appState.controlNetUnitData[this.props.index]
storeData.model = item
@ -806,7 +801,7 @@ export default class ControlNetUnit extends React.Component<
style={{ display: 'flex' }}
>
<div style={{ width: '50%', display: 'flex' }}>
<SpMenu
{/* <SpMenu
onChange={this.onPreprocsesorChange.bind(this)}
id={`mModulesMenuControlNet_${this.props.index}`}
items={storeData.module_list || ['none']}
@ -815,11 +810,27 @@ export default class ControlNetUnit extends React.Component<
storeData.module
)}
style={{ width: '100%' }}
/> */}
<SearchableMenu
allItems={storeData.module_list || ['none']}
placeholder={Locale('Select Module')}
selected_item={storeData.module}
onSelectItemFailure={() => {
const default_value =
// storeData.module_list[0] || 'None'
'None'
storeData.module = default_value
return default_value
}}
onChange={(item: any) => {
this.onPreprocsesorChange(item)
}}
/>
</div>
{!pd.model_free && (
<div style={{ width: '50%', display: 'flex' }}>
<SpMenu
{/* <SpMenu
onChange={this.onModelChange.bind(this)}
id={`mModelsMenuControlNet_${this.props.index}`}
items={storeData.model_list || []}
@ -828,6 +839,21 @@ export default class ControlNetUnit extends React.Component<
storeData.model
)}
style={{ width: '100%' }}
/> */}
<SearchableMenu
allItems={storeData.model_list || ['none']}
placeholder={Locale('Selec Model')}
selected_item={storeData.model}
onSelectItemFailure={() => {
const default_value =
// storeData.model_list[0] || 'None'
'None'
storeData.model = default_value
return default_value
}}
onChange={(item: any) => {
this.onModelChange(item)
}}
/>
</div>
)}

View File

@ -1,8 +1,9 @@
import { toJS } from 'mobx'
import { setControlImageSrc } from '../../utility/html_manip'
// import { session_ts } from '../entry'
// import * as session_ts from '../session/session'
import { store as session_store } from '../session/session_store'
import { Enum, api, python_replacement } from '../util/oldSystem'
import { Enum, api, io, python_replacement } from '../util/oldSystem'
import { GenerationModeEnum } from '../util/ts/enum'
import store, {
DefaultControlNetUnitData,
@ -14,6 +15,40 @@ const { getExtensionUrl } = python_replacement
declare const g_sd_config_obj: any
declare let g_sd_url: string
function convertComfyModuleDetailsToPluginModuleDetails(
comfy_module_details: Record<string, any>
) {
let outputJson: Record<string, any> = {}
for (let preprocessorName in comfy_module_details) {
let preprocessorConfig = comfy_module_details[preprocessorName]
let sliders = []
if (preprocessorConfig.resolution) {
sliders.push({
name: `${preprocessorName} Resolution`,
value: preprocessorConfig.resolution,
min: 64,
max: 2048,
})
}
if (preprocessorConfig.param_config) {
for (let paramName in preprocessorConfig.param_config) {
let paramConfig = preprocessorConfig.param_config[paramName]
sliders.push({
name: `${paramName}`,
value: preprocessorConfig[paramName],
min: paramConfig.min,
max: paramConfig.max,
})
}
}
outputJson[preprocessorName] = {
model_free: false,
sliders: sliders,
}
}
return outputJson
}
async function requestControlNetPreprocessors() {
const control_net_json = await api.requestGet(
`${g_sd_url}/controlnet/module_list?alias_names=true`
@ -111,6 +146,38 @@ async function initializeControlNetTab(controlnet_max_models: number) {
console.warn(e)
}
}
async function initializeControlNetTabComfyUI(
controlnet_max_models: number,
controlnet_models: string[],
preprocessor_list: string[],
preprocessorDetail: Record<string, any>
) {
store.maxControlNet = controlnet_max_models || store.maxControlNet
// store.controlnetApiVersion = await requestControlNetApiVersion()
try {
const models = controlnet_models
store.supportedModels = models || []
} catch (e) {
console.warn(e)
}
try {
store.supportedPreprocessors = preprocessor_list || []
store.preprocessorDetail =
convertComfyModuleDetailsToPluginModuleDetails(preprocessorDetail)
} catch (e) {
console.warn(e)
}
try {
store.controlNetUnitData.forEach((unitData) => {
unitData.module_list = store.supportedPreprocessors
unitData.model_list = store.supportedModels
})
} catch (e) {
console.warn(e)
}
}
function getEnableControlNet(index: number) {
if (typeof index == 'undefined')
@ -119,24 +186,33 @@ function getEnableControlNet(index: number) {
)
else return store.controlNetUnitData[index || 0].enabled
}
function mapPluginSettingsToControlNet(plugin_settings: any) {
async function mapPluginSettingsToControlNet(plugin_settings: any) {
const ps = plugin_settings // for shortness
let controlnet_units: any[] = []
function getControlNetInputImage(index: number) {
const controlNetUnits = store.controlNetUnitData
async function getControlNetInputImage(index: number) {
try {
const b_sync_input_image =
store.controlNetUnitData[index].auto_image
let input_image = store.controlNetUnitData[index].input_image
const b_sync_input_image = controlNetUnits[index].auto_image
let input_image = controlNetUnits[index].input_image
if (
b_sync_input_image &&
[GenerationModeEnum.Txt2Img].includes(session_store.data.mode)
) {
//conditions: 1) txt2img mode 2)auto image on 3)first generation of session
input_image = session_store.data.controlnet_input_image ?? ''
store.controlNetUnitData[index].input_image = input_image
store.controlNetUnitData[index].selection_info =
plugin_settings.selection_info
if (
session_store.data.generation_number === 1 &&
session_store.data.controlnet_input_image === ''
) {
session_store.data.controlnet_input_image =
await io.getImg2ImgInitImage()
}
if (session_store.data.controlnet_input_image !== '') {
input_image = session_store.data.controlnet_input_image
controlNetUnits[index].input_image = input_image
controlNetUnits[index].selection_info =
plugin_settings.selection_info
}
}
if (
b_sync_input_image &&
@ -149,13 +225,10 @@ function mapPluginSettingsToControlNet(plugin_settings: any) {
) {
// img2img mode
input_image = session_store.data.init_image
store.controlNetUnitData[index].input_image = input_image
store.controlNetUnitData[index].selection_info =
controlNetUnits[index].input_image = input_image
controlNetUnits[index].selection_info =
plugin_settings.selection_info
} else if (
b_sync_input_image &&
store.controlNetUnitData[index].enabled
) {
} else if (b_sync_input_image && controlNetUnits[index].enabled) {
//txt2img mode
}
@ -175,9 +248,9 @@ function mapPluginSettingsToControlNet(plugin_settings: any) {
//maskless mode
} else {
//mask related mode
store.controlNetUnitData[index].mask = '' // use the mask from the sd mode
controlNetUnits[index].mask = '' // use the mask from the sd mode
}
return store.controlNetUnitData[index].mask
return controlNetUnits[index].mask
} catch (e) {
console.warn(e)
}
@ -185,30 +258,29 @@ function mapPluginSettingsToControlNet(plugin_settings: any) {
for (let index = 0; index < store.maxControlNet; index++) {
controlnet_units[index] = {
enabled: getEnableControlNet(index),
input_image: getControlNetInputImage(index),
input_image: await getControlNetInputImage(index),
mask: getControlNetMask(index),
module: store.controlNetUnitData[index].module,
model: store.controlNetUnitData[index].model,
weight: store.controlNetUnitData[index].weight,
module: controlNetUnits[index].module,
model: controlNetUnits[index].model,
weight: controlNetUnits[index].weight,
resize_mode: 'Crop and Resize',
lowvram: store.controlNetUnitData[index].lowvram,
processor_res: store.controlNetUnitData[index].processor_res || 512,
threshold_a: store.controlNetUnitData[index].threshold_a,
threshold_b: store.controlNetUnitData[index].threshold_b,
lowvram: controlNetUnits[index].lowvram,
processor_res: controlNetUnits[index].processor_res || 512,
threshold_a: controlNetUnits[index].threshold_a,
threshold_b: controlNetUnits[index].threshold_b,
// guidance: ,
guidance_start: store.controlNetUnitData[index].guidance_start,
guidance_end: store.controlNetUnitData[index].guidance_end,
guidance_start: controlNetUnits[index].guidance_start,
guidance_end: controlNetUnits[index].guidance_end,
}
if (store.controlnetApiVersion > 1) {
//new controlnet v2
controlnet_units[index].control_mode =
store.controlNetUnitData[index].control_mode
controlNetUnits[index].control_mode
controlnet_units[index].pixel_perfect =
store.controlNetUnitData[index].pixel_perfect
controlNetUnits[index].pixel_perfect
} else {
// old controlnet v1
controlnet_units[index].guessmode =
store.controlNetUnitData[index].guessmode
controlnet_units[index].guessmode = controlNetUnits[index].guessmode
}
}
@ -284,6 +356,7 @@ export {
requestControlNetMaxUnits,
requestControlNetFiltersKeywords,
initializeControlNetTab,
initializeControlNetTabComfyUI,
getEnableControlNet,
mapPluginSettingsToControlNet,
getControlNetMaxModelsNumber,

View File

@ -6,12 +6,13 @@ export * as control_net from './controlnet/entry'
export * as after_detailer_script from './after_detailer/after_detailer'
export * as ultimate_sd_upscaler from './ultimate_sd_upscaler/ultimate_sd_upscaler'
export * as scripts from './ultimate_sd_upscaler/scripts'
export * as main from './main/main'
export * as controlnet_main from './controlnet/main'
export * as logger from './util/logger'
export * as image_search from './image_search/image_search'
export * as history from './history/history'
export * as viewer from './viewer/viewer'
export { default as viewer_util } from './viewer/viewer_util'
export * as session_ts from './session/session'
export { store as session_store } from './session/session_store'
export { store as sd_tab_store } from './sd_tab/util'
@ -37,5 +38,10 @@ export * as stores from './stores'
export { default as lexica } from './lexical/lexical'
export * as api_ts from './util/ts/api'
export * as comfyui from './comfyui/comfyui'
export { toJS } from 'mobx'
export { default as node_fs } from 'fs'
export { default as comfyui_util } from './comfyui/util'
export { default as comfyui_main_ui } from './comfyui/main_ui'
export { default as comfyapi } from './comfyui/comfyapi'

View File

@ -5,7 +5,13 @@ import { observer } from 'mobx-react'
import { GenerationModeEnum, ScriptMode } from '../util/ts/enum'
import { reaction } from 'mobx'
import { SpCheckBox, SpMenu, SpSlider, SpTextfield } from '../util/elements'
import {
SearchableMenu,
SpCheckBox,
SpMenu,
SpSlider,
SpTextfield,
} from '../util/elements'
import { ErrorBoundary } from '../util/errorBoundary'
import { Collapsible } from '../util/collapsible'
@ -32,6 +38,8 @@ import {
onHeightSliderInput,
heightSliderOnChangeEventHandler,
loadPresetSettings,
isHiResMode,
comfy_mask_content_config,
} from './util'
import { general } from '../util/oldSystem'
import { requestSwapModel, setInpaintMaskWeight } from '../util/ts/sdapi'
@ -44,6 +52,7 @@ import { getExpandedMask } from '../session/session'
import { mapRange } from '../controlnet/util'
import { store as preset_store } from '../preset/shared_ui_preset'
import Locale from '../locale/locale'
declare let g_version: string
@ -96,6 +105,22 @@ const Modes = observer(() => {
>
Lasso Mode
</SpCheckBox>
<SpCheckBox
style={{
marginLeft: '10px',
display: store.data.is_lasso_mode
? void 0
: 'none',
}}
onChange={() => {
helper_store.data.make_square =
!helper_store.data.make_square
}}
checked={helper_store.data.make_square}
// id={`chEnableControlNet_${this.props.index}`}
>
Make Square
</SpCheckBox>
<SpSlider
show-value="false"
@ -148,12 +173,9 @@ class SDTab extends React.Component<{}> {
async componentDidMount() {
try {
await refreshUI()
await refreshModels()
await initPlugin()
helper_store.data.loras = await requestLoraModels()
initInitMaskElement()
helper_store.data.hr_upscaler_list =
await requestGetHiResUpscalers()
const btnSquareClass = document.getElementsByClassName('btnSquare')
//REFACTOR: move to events.js
for (let btnSquareButton of btnSquareClass) {
@ -199,66 +221,85 @@ class SDTab extends React.Component<{}> {
}
return (
<div>
<div id="menu-bar-container" style={styles.menuBarContainer}>
<SpMenu
<div
id="menu-bar-container"
style={{
...styles.menuBarContainer,
width: '100%',
display: 'flex',
justifyContent: 'space-between',
}}
>
{/* <SpMenu
title="Stable Diffusion Models"
items={helper_store.data.models.map((model) => {
return model.model_name
})}
items={helper_store.data.models || []}
label_item="Select a Model"
style={{ ...styles.spMenu }}
selected_index={helper_store.data.models
.map((model) => {
return model.title
})
.indexOf(store.data.selected_model)}
selected_index={(
helper_store.data.models || []
).indexOf(store.data.selected_model)}
onChange={(id: any, value: any) => {
// console.log('onChange value: ', value)
// store.updateProperty('subject', value.item)
console.log('value:', value)
store.data.selected_model = value.item
//REFACTOR: move to events.js
const model_index = value.index
let model = helper_store.data.models[model_index]
// g_model_name = `${model.model_name}.ckpt`
requestSwapModel(store.data.selected_model)
}}
></SpMenu> */}
<SearchableMenu
allItems={helper_store.data.models}
placeholder={'Select a Model'}
selected_item={store.data.selected_model}
onSelectItemFailure={() => {
const default_value =
helper_store.data.models[0] || ''
store.data.selected_model = default_value
return default_value
}}
onChange={(item: any) => {
store.data.selected_model = item
requestSwapModel(store.data.selected_model)
}}
></SpMenu>
/>
<button
title="Refresh the plugin, only fixes minor issues."
id="btnRefreshModels"
style={styles.button}
onClick={async (e) => {
await refreshUI()
<div style={{ display: 'flex', alignItems: 'flex-start' }}>
<button
style={{ padding: '3px' }}
title="Refresh the plugin, only fixes minor issues."
id="btnRefreshModels"
// style={styles.button}
onClick={async (e) => {
await refreshUI()
tempDisableElement(e.target, 3000)
}}
>
Refresh
</button>
<button
title="Update the plugin if you encounter bugs. Get the latest features"
className="btnSquare"
id="btnUpdate"
style={styles.button}
onClick={async () => {
await updateClickEventHandler(g_version)
}}
>
Update
</button>
tempDisableElement(e.target, 3000)
}}
>
Refresh
</button>
<button
style={{ padding: '3px' }}
title="Update the plugin if you encounter bugs. Get the latest features"
className="btnSquare"
id="btnUpdate"
// style={styles.button}
onClick={async () => {
await updateClickEventHandler(g_version)
}}
>
Update
</button>
</div>
</div>
<div id="sdBtnContainer">
<SpMenu
{/* <SpMenu
title="use lora in your prompt"
style={{ ...styles.spMenu }}
items={helper_store.data.loras.map((lora: any) => {
return lora.name
})}
// items={helper_store.data.loras.map((lora: any) => {
// return lora.name
// })}
items={helper_store.data.loras}
label_item="Select Lora"
// selected_index={store.data.models
// .map((model) => {
@ -272,8 +313,19 @@ class SDTab extends React.Component<{}> {
positive: `${prompt} ${lora_prompt}`,
})
}}
></SpMenu>
<SpMenu
></SpMenu> */}
<SearchableMenu
allItems={helper_store.data.loras}
placeholder={'Select Lora'}
onChange={(item: any) => {
const lora_prompt = getLoraModelPrompt(item)
const prompt = multiPrompts.getPrompt().positive
multiPrompts.setPrompt({
positive: `${prompt} ${lora_prompt}`,
})
}}
/>
{/* <SpMenu
title="use textual inversion in your prompt"
style={{ ...styles.spMenu }}
items={helper_store.data.embeddings}
@ -284,7 +336,17 @@ class SDTab extends React.Component<{}> {
positive: `${prompt} ${value.item}`,
})
}}
></SpMenu>
></SpMenu> */}
<SearchableMenu
allItems={helper_store.data.embeddings || []}
placeholder={Locale('Select Textual Inversion')}
onChange={(item: any) => {
const prompt = multiPrompts.getPrompt().positive
multiPrompts.setPrompt({
positive: `${prompt} ${item}`,
})
}}
/>
<sp-checkbox
title="use {keyword} form the prompts library"
@ -341,7 +403,10 @@ class SDTab extends React.Component<{}> {
padding: '3px',
}}
>
<Collapsible defaultIsOpen={true} label={'Prompts'}>
<Collapsible
defaultIsOpen={true}
label={Locale('Prompts')}
>
<MultiTextArea />
</Collapsible>
</div>
@ -363,7 +428,7 @@ class SDTab extends React.Component<{}> {
: void 0
}
>
{config.name}
{Locale(config.name)}
</sp-radio>
)
})}
@ -465,7 +530,7 @@ class SDTab extends React.Component<{}> {
alignItems: 'flex-start',
}}
>
<sp-label>Batch Size:</sp-label>
<sp-label>{Locale('Batch Size:')}</sp-label>
<SpTextfield
style={{ width: '100%' }}
title="the number of images to generate at once.The larger the number more VRAM stable diffusion will use."
@ -491,7 +556,7 @@ class SDTab extends React.Component<{}> {
alignItems: 'flex-start',
}}
>
<sp-label>Batch Count:</sp-label>
<sp-label>{Locale('Batch Count:')}</sp-label>
<SpTextfield
style={{ width: '100%' }}
title="the number of images to generate in queue. The larger the number the longer will take."
@ -515,7 +580,7 @@ class SDTab extends React.Component<{}> {
}}
>
<sp-label id="sdLabelSampleStep">
Sampling Steps
{Locale('Sampling Steps:')}
</sp-label>
<SpTextfield
style={{ width: '100%' }}
@ -535,7 +600,7 @@ class SDTab extends React.Component<{}> {
<div id="selectionMode">
<div>
<sp-label id="rbSelectionModeLabel" slot="label">
Selection Mode:
{Locale('Selection Mode:')}
</sp-label>
</div>
@ -575,7 +640,7 @@ class SDTab extends React.Component<{}> {
}
}}
>
{selection_mode.name}
{Locale(selection_mode.name)}
</sp-radio>
)
}
@ -705,7 +770,7 @@ class SDTab extends React.Component<{}> {
}}
>
<sp-label slot="label" class="title">
Width:
{Locale('Width:')}
</sp-label>
<sp-label
class="labelNumber"
@ -746,7 +811,7 @@ class SDTab extends React.Component<{}> {
}}
>
<sp-label slot="label" class="title">
Height:
{Locale('Height:')}
</sp-label>
<sp-label
class="labelNumber"
@ -789,7 +854,7 @@ class SDTab extends React.Component<{}> {
}}
>
<sp-label slot="label" class="title">
CFG Scale:
{Locale('CFG Scale:')}
</sp-label>
</SpSlider>
@ -814,7 +879,7 @@ class SDTab extends React.Component<{}> {
}}
>
<sp-label slot="label" class="title">
Denoising Strength:
{Locale('Denoising Strength:')}
</sp-label>
<sp-label slot="label" id="lDenoisingStrength">
{store.data.denoising_strength.toFixed(2)}
@ -933,7 +998,9 @@ class SDTab extends React.Component<{}> {
}
}}
>
<sp-label slot="label">Mask Expansion:</sp-label>
<sp-label slot="label">
{Locale('Mask Expansion:')}
</sp-label>
</SpSlider>
<div style={{ display: 'flex' }}>
@ -997,30 +1064,31 @@ class SDTab extends React.Component<{}> {
<sp-label class="title" slot="label">
Mask Content:
</sp-label>
{mask_content_config.map(
(mask_content, index: number) => {
return (
<sp-radio
key={index}
class="rbMaskContent"
checked={
store.data
.inpainting_fill ===
{(settings_tab_ts.store.data
.selected_backend === 'Automatic1111'
? mask_content_config
: comfy_mask_content_config
).map((mask_content, index: number) => {
return (
<sp-radio
key={index}
class="rbMaskContent"
checked={
store.data.inpainting_fill ===
mask_content.value
? true
: void 0
}
value={mask_content.value}
onClick={(evt: any) => {
store.data.inpainting_fill =
mask_content.value
? true
: void 0
}
value={mask_content.value}
onClick={(evt: any) => {
store.data.inpainting_fill =
mask_content.value
}}
>
{mask_content.name}
</sp-radio>
)
}
)}
}}
>
{Locale(`${mask_content.name}`)}
</sp-radio>
)
})}
</sp-radio-group>
</div>
@ -1053,24 +1121,20 @@ class SDTab extends React.Component<{}> {
evt.target.checked
}}
>
Restore Faces
{Locale('Restore Faces')}
</SpCheckBox>
<SpCheckBox
class="checkbox"
id="chHiResFixs"
style={{
display: [ScriptMode.Txt2Img].includes(
store.data.rb_mode
)
? 'flex'
: 'none',
display: isHiResMode() ? 'flex' : 'none',
}}
checked={store.data.enable_hr}
onClick={(evt: any) => {
store.data.enable_hr = evt.target.checked
}}
>
Hi Res Fix
{Locale('Hi Res Fix')}
</SpCheckBox>
<SpCheckBox
class="checkbox"
@ -1080,16 +1144,14 @@ class SDTab extends React.Component<{}> {
store.data.tiling = evt.target.checked
}}
>
tiling
{Locale('Tiling')}
</SpCheckBox>
</div>
<div
id="HiResDiv"
style={{
display:
[ScriptMode.Txt2Img].includes(
store.data.rb_mode
) && store.data.enable_hr
isHiResMode() && store.data.enable_hr
? void 0
: 'none',
}}
@ -1304,7 +1366,9 @@ class SDTab extends React.Component<{}> {
<div>
<div style={{ display: 'flex' }}>
<sp-label id="sdLabelSeed">Seed:</sp-label>
<sp-label id="sdLabelSeed">
{Locale('Seed:')}
</sp-label>
<sp-textfield
id="tiSeed"
placeholder="Seed"
@ -1324,7 +1388,7 @@ class SDTab extends React.Component<{}> {
store.data.seed = '-1'
}}
>
Random
{Locale('Random')}
</button>
<button
className="btnSquare"
@ -1334,7 +1398,7 @@ class SDTab extends React.Component<{}> {
session_store.data.last_seed
}}
>
Last
{Locale('Last')}
</button>
</div>
<button
@ -1358,26 +1422,28 @@ class SDTab extends React.Component<{}> {
: 'none',
}}
>
<sp-label slot="label">Select Sampler:</sp-label>
{helper_store.data.sampler_list.map(
<sp-label slot="label">
{Locale('Select Sampler:')}
</sp-label>
{(helper_store.data.sampler_list || []).map(
(sampler: any, index: number) => {
return (
<sp-radio
class="rbSampler"
checked={
sampler.name ===
sampler ===
store.data.sampler_name
? true
: void 0
}
value={sampler.name}
value={sampler}
key={index}
onClick={(evt: any) => {
store.data.sampler_name =
sampler.name
sampler
}}
>
{sampler.name}
{sampler}
</sp-radio>
)
}

View File

@ -1,4 +1,5 @@
import { control_net, main } from '../entry'
import { control_net } from '../entry'
import vae_settings from '../settings/vae'
import { AStore } from '../main/astore'
import { script_store } from '../ultimate_sd_upscaler/scripts'
import { ScriptMode } from '../ultimate_sd_upscaler/ultimate_sd_upscaler'
@ -28,7 +29,7 @@ import { presetToStore } from '../util/ts/io'
import { refreshExtraUpscalers } from '../extra_page/extra_page'
import { readdirSync, readFileSync } from 'fs'
import comfyapi from '../comfyui/comfyapi'
declare let g_models: any[]
declare let g_automatic_status: any
declare let g_sd_options_obj: any
@ -76,6 +77,24 @@ export const mask_content_config = [
value: 3,
},
]
export const comfy_mask_content_config = [
// {
// name: 'fill',
// value: 0,
// },
{
name: 'original',
value: 1,
},
// {
// name: 'latent noise',
// value: 2,
// },
{
name: 'latent nothing',
value: 3,
},
]
export enum SelectionModeEnum {
Ratio = 'ratio',
Precise = 'precise',
@ -114,7 +133,7 @@ export const store = new AStore({
cfg: 7.0,
b_width_height_link: true,
denoising_strength: 0.7,
hr_denoising_strength: 0.7,
hr_denoising_strength: 0.5,
inpaint_full_res: false,
enable_hr: false,
sampler_name: 'Euler a',
@ -128,9 +147,11 @@ export const store = new AStore({
hr_resize_x: 512,
hr_resize_y: 512,
hr_second_pass_steps: 0,
hr_second_pass_steps: 20,
restore_faces: false,
inpainting_fill: mask_content_config[0].value,
// inpainting_fill: mask_content_config[0].value,
inpainting_fill: 1, //set original as default value
hr_upscaler: '',
selection_mode: selection_mode_config[0].value,
@ -166,7 +187,7 @@ export const default_preset = {
hr_resize_y: 512,
hr_second_pass_steps: 0,
// restore_faces: false,
inpainting_fill: 0,
inpainting_fill: 1,
hr_upscaler: '',
selection_mode: 'ratio',
},
@ -185,6 +206,7 @@ export const helper_store = new AStore({
native_presets: {},
base_size: 512 as number,
lasso_offset: 10 as number,
make_square: true as boolean,
})
export async function refreshModels() {
let b_result = false
@ -192,10 +214,13 @@ export async function refreshModels() {
g_models = await requestGetModels()
if (g_models.length > 0) {
b_result = true
helper_store.data.models = g_models
? g_models.map((model) => {
return model.title
})
: helper_store.data.models
}
helper_store.data.models = g_models
// for (let model of g_models) {
// // console.log(model.title)//Log
// // const menu_item_element = document.createElement('sp-menu-item')
@ -303,39 +328,19 @@ async function updateVersionUI() {
//REFACTOR: move to generation_settings.js
export async function initSamplers() {
let bStatus = false
try {
// let sampler_group = document.getElementById('sampler_group')!
// sampler_group.innerHTML = ''
let samplers = await requestGetSamplers()
if (!samplers) {
//if we failed to get the sampler list from auto1111, use the list stored in sampler.js
samplers = sampler_data.samplers
}
helper_store.data.sampler_list = samplers
// for (let sampler of samplers) {
// // console.log(sampler)//Log
// // sampler.name
// // <sp-radio class="rbSampler" value="Euler">Euler</sp-radio>
// // const rbSampler = document.createElement('sp-radio')
// // rbSampler.innerHTML = sampler.name
// // rbSampler.setAttribute('class', 'rbSampler')
// // rbSampler.setAttribute('value', sampler.name)
// // sampler_group.appendChild(rbSampler)
// //add click event on radio button for Sampler radio button, so that when a button is clicked it change g_sd_sampler globally
// }
// document
// .getElementsByClassName('rbSampler')[0]
// .setAttribute('checked', '')
if (samplers.length > 0) {
bStatus = true
helper_store.data.sampler_list = samplers.map((sampler: any) => {
return sampler.name
})
store.data.sampler_name = helper_store.data.sampler_list[0]
}
return samplers
} catch (e) {
console.warn(e)
}
return bStatus
return []
}
export function loadNativePreset() {
@ -363,47 +368,88 @@ export async function refreshUI() {
html_manip.setProxyServerStatus('disconnected', 'connected')
}
//@ts-ignore
g_automatic_status = await checkAutoStatus() // check the webui status regardless if alert are turned on or off
if (!settings_tab_ts.store.data.bTurnOffServerStatusAlert) {
//@ts-ignore
await displayNotification(g_automatic_status) // only show alert if the alert are turn on
}
const bSamplersStatus = await initSamplers()
await refreshModels()
helper_store.data.loras = await requestLoraModels()
helper_store.data.embeddings = await requestEmbeddings()
await refreshExtraUpscalers()
await setInpaintMaskWeight(1.0) //set the inpaint conditional mask to 1 when the on plugin start
//get the latest options
await g_sd_options_obj.getOptions()
//get the selected model
store.data.selected_model = g_sd_options_obj.getCurrentModel()
//update the ui with that model title
// const current_model_hash =
// html_manip.getModelHashByTitle(current_model_title)
// html_manip.autoFillInModel(current_model_hash)
//fetch the inpaint mask weight from sd webui and update the slider with it.
const inpainting_mask_weight =
await g_sd_options_obj.getInpaintingMaskWeight()
console.log('inpainting_mask_weight: ', inpainting_mask_weight)
html_manip.autoFillInInpaintMaskWeight(inpainting_mask_weight)
//init ControlNet Tab
helper_store.data.hr_upscaler_list = await requestGetHiResUpscalers()
g_controlnet_max_models = await control_net.requestControlNetMaxUnits()
await control_net.initializeControlNetTab(g_controlnet_max_models)
await main.populateVAE()
helper_store.data.native_presets = loadNativePreset()
if (settings_tab_ts.store.data.selected_backend === 'Automatic1111') {
//@ts-ignore
g_automatic_status = await checkAutoStatus() // check the webui status regardless if alert are turned on or off
if (!settings_tab_ts.store.data.bTurnOffServerStatusAlert) {
//@ts-ignore
await displayNotification(g_automatic_status) // only show alert if the alert are turn on
}
const bSamplersStatus = await initSamplers()
await refreshModels()
helper_store.data.loras = await requestLoraModels()
helper_store.data.embeddings = await requestEmbeddings()
await refreshExtraUpscalers()
await setInpaintMaskWeight(1.0) //set the inpaint conditional mask to 1 when the on plugin start
//get the latest options
await g_sd_options_obj.getOptions()
//get the selected model
store.data.selected_model = g_sd_options_obj.getCurrentModel()
//update the ui with that model title
// const current_model_hash =
// html_manip.getModelHashByTitle(current_model_title)
// html_manip.autoFillInModel(current_model_hash)
//fetch the inpaint mask weight from sd webui and update the slider with it.
const inpainting_mask_weight =
await g_sd_options_obj.getInpaintingMaskWeight()
console.log('inpainting_mask_weight: ', inpainting_mask_weight)
html_manip.autoFillInInpaintMaskWeight(inpainting_mask_weight)
//init ControlNet Tab
helper_store.data.hr_upscaler_list =
await requestGetHiResUpscalers()
g_controlnet_max_models =
await control_net.requestControlNetMaxUnits()
await control_net.initializeControlNetTab(g_controlnet_max_models)
await vae_settings.populateVAE()
}
if (settings_tab_ts.store.data.selected_backend === 'ComfyUI') {
await comfyapi.comfy_api.init()
helper_store.data.models = comfyapi.comfy_api.getModels()
store.data.selected_model = helper_store.data.models?.[0]
helper_store.data.sampler_list =
comfyapi.comfy_api.getSamplerNames()
store.data.sampler_name = helper_store.data.sampler_list?.[0]
vae_settings.store.data.vae_model_list =
comfyapi.comfy_api.getVAEs()
vae_settings.store.data.current_vae =
vae_settings.store.data.vae_model_list?.[0]
helper_store.data.hr_upscaler_list =
comfyapi.comfy_api.getHiResUpscalers()
store.data.hr_upscaler = helper_store.data.hr_upscaler_list?.[0]
helper_store.data.loras = comfyapi.comfy_api
.getLoras()
.map((lora) => lora.split('.')[0]) //remove .safetensor from loraname
const controlnet_models =
//@ts-ignorets
comfyapi.comfy_api.object_info?.ControlNetLoader.input.required
.control_net_name[0]
const preprocessor_list = //@ts-ignorets
comfyapi.comfy_api?.object_info?.ControlNetScript.input.required
.preprocessor_name_1[0]
const preprocessor_detail =
//@ts-ignorets
comfyapi.comfy_api?.object_info?.GetConfig.input.optional
.controlnet_config
control_net.initializeControlNetTabComfyUI(
3,
controlnet_models,
preprocessor_list,
preprocessor_detail
)
}
} catch (e) {
console.warn(e)
}
@ -437,7 +483,12 @@ export async function requestGetHiResUpscalers(): Promise<string[]> {
export async function requestLoraModels() {
const full_url = `${g_sd_url}/sdapi/v1/loras`
const lora_models = (await requestGet(full_url)) ?? []
let lora_models = (await requestGet(full_url)) ?? []
if (lora_models.length > 0) {
lora_models = lora_models.map((lora: any) => {
return lora.name
})
}
return lora_models
}
@ -688,6 +739,20 @@ export function loadPresetSettings(preset: any) {
// io_ts.presetToStore(preset?.controlnet_tab_preset, store)
}
}
export function isHiResMode() {
let is_hi_res_mode = false
if (settings_tab_ts.store.data.selected_backend === 'Automatic1111') {
is_hi_res_mode = [ScriptMode.Txt2Img].includes(store.data.rb_mode)
} else if (settings_tab_ts.store.data.selected_backend === 'ComfyUI') {
is_hi_res_mode = [
ScriptMode.Txt2Img,
ScriptMode.Img2Img,
ScriptMode.Inpaint,
ScriptMode.Outpaint,
].includes(store.data.rb_mode)
}
return is_hi_res_mode
}
export default {
store: store,

View File

@ -22,6 +22,7 @@ import {
mask_store as viewer_mask_store,
// init_store as viewer_init_store,
} from '../viewer/viewer_util'
import { settings_tab_ts } from '../entry'
declare let g_automatic_status: any
declare let g_current_batch_index: number
@ -44,7 +45,7 @@ export const GenerateButtons = observer(() => {
display: session_store.data.can_generate ? void 0 : 'none',
}}
>
Generate {modeDisplayNames[sd_tab_store.data.mode]}
{Locale(`Generate ${modeDisplayNames[sd_tab_store.data.mode]}`)}
</button>
{session_store.data.can_generate ? (
<button
@ -161,10 +162,13 @@ const canStartSession = async () => {
await psapi.reSelectMarqueeExe(session_store.data.selectionInfo)
}
}
//@ts-ignore
g_automatic_status = await checkAutoStatus()
//@ts-ignore
await displayNotification(g_automatic_status)
if (settings_tab_ts.store.data.selected_backend === 'Automatic1111') {
//@ts-ignore
g_automatic_status = await checkAutoStatus()
//@ts-ignore
await displayNotification(g_automatic_status)
}
} catch (e) {
console.warn(e)
}
@ -226,6 +230,7 @@ const handleGenerate = async () => {
console.error(e)
console.warn('output_images: ', output_images)
console.warn('response_json: ', response_json)
console.warn('ui_settings: ', ui_settings)
}
}

View File

@ -4,7 +4,8 @@ import * as scripts from '../ultimate_sd_upscaler/scripts'
import * as control_net from '../controlnet/entry'
import { store as session_store } from '../session/session_store'
import sd_tab_util from '../sd_tab/util'
import settings_tab from '../settings/settings'
import comfyui_main_ui from '../comfyui/main_ui'
import {
html_manip,
io,
@ -23,6 +24,9 @@ import {
} from '../controlnet/entry'
import { store as extra_page_store } from '../extra_page/extra_page'
import { requestPost } from '../util/ts/api'
import { comfyapi, settings_tab_ts } from '../entry'
import { newOutputImageName } from '../util/ts/general'
const executeAsModal = core.executeAsModal
declare let g_inpaint_mask_layer: any
@ -35,9 +39,17 @@ interface SessionData {
mask?: string
selectionInfo?: any
}
interface ImageInfo {
path: string
base64: string
auto_metadata: Record<string, any>
}
async function saveOutputImagesToDrive(images_info: any, settings: any) {
const base64OutputImages = [] //delete all previouse images, Note move this to session end ()
async function saveOutputImagesToDrive(
images_info: ImageInfo[],
settings: Record<string, any>
) {
const base64OutputImages = []
let index = 0
for (const image_info of images_info) {
const path = image_info['path']
@ -55,10 +67,32 @@ async function saveOutputImagesToDrive(images_info: any, settings: any) {
) //save the settings
index += 1
}
session_store.data.last_seed =
images_info?.length > 0 ? images_info[0]?.auto_metadata?.Seed : '-1'
if (settings_tab_ts.store.data.selected_backend === 'Automatic1111') {
session_store.data.last_seed =
images_info?.length > 0 ? images_info[0]?.auto_metadata?.Seed : '-1'
}
return base64OutputImages
}
async function saveOutputImagesToDriveComfy(
base64_images: string[],
settings: Record<string, any>
) {
let index = 0
const document_name = settings['uniqueDocumentId']
delete settings['alwayson_scripts']
for (const base64 of base64_images) {
const image_name = newOutputImageName()
await io.saveFileInSubFolder(base64, document_name, image_name) //save the output image
const json_file_name = `${image_name.split('.')[0]}.json`
await io.saveJsonFileInSubFolder(
settings,
document_name,
json_file_name
) //save the settings
index += 1
}
}
class Mode {
constructor() {}
@ -86,7 +120,13 @@ class Mode {
return base64OutputImages
}
static async interrupt() {
return await this.requestInterrupt()
//automatic1111
if (settings_tab.store.data.selected_backend === 'Automatic1111') {
return await this.requestInterrupt()
} else if (settings_tab.store.data.selected_backend === 'ComfyUI') {
await comfyapi.comfy_api.interrupt()
//comfy
}
}
static async requestInterrupt() {
@ -150,8 +190,9 @@ export class Txt2ImgMode extends Mode {
const full_url = `${g_sd_url}/sdapi/v1/txt2img`
const control_net_settings =
mapPluginSettingsToControlNet(plugin_settings)
const control_net_settings = await mapPluginSettingsToControlNet(
plugin_settings
)
let control_networks = []
// let active_control_networks = 0
for (let index = 0; index < g_controlnet_max_models; index++) {
@ -161,8 +202,15 @@ export class Txt2ImgMode extends Mode {
}
control_networks[index] = true
const is_inpaint_model: boolean =
control_net_settings['controlnet_units'][index][
'module'
].includes('inpaint')
if (
!control_net_settings['controlnet_units'][index]['input_image']
!control_net_settings['controlnet_units'][index][
'input_image'
] &&
!is_inpaint_model
) {
//@ts-ignore
app.showAlert('you need to add a valid ControlNet input image')
@ -248,26 +296,39 @@ export class Txt2ImgMode extends Mode {
// const b_enable_control_net = control_net.getEnableControlNet()
const b_enable_control_net = control_net.isControlNetModeEnable()
if (b_enable_control_net) {
//use control net
if (session_store.data.generation_number === 1) {
session_store.data.controlnet_input_image =
await io.getImg2ImgInitImage()
if (settings_tab.store.data.selected_backend === 'Automatic1111') {
if (b_enable_control_net) {
//use control net
if (session_store.data.generation_number === 1) {
session_store.data.controlnet_input_image =
await io.getImg2ImgInitImage()
}
// console.log(
// 'session_store.data.controlnet_input_image: ',
// session_store.data.controlnet_input_image
// )
response_json = await this.requestControlNetTxt2Img(
settings
)
} else {
response_json = await this.requestTxt2Img(settings) //this is automatic1111 txt2img
}
// console.log(
// 'session_store.data.controlnet_input_image: ',
// session_store.data.controlnet_input_image
// )
response_json = await this.requestControlNetTxt2Img(settings)
} else {
response_json = await this.requestTxt2Img(settings)
output_images = await this.processOutput(
response_json.images_info,
settings
)
} else if (settings_tab.store.data.selected_backend === 'ComfyUI') {
//request Txt2Img from comfyui
settings = await mapPluginSettingsToControlNet(settings)
const { image_base64_list, image_url_list } =
await comfyui_main_ui.generateComfyTxt2Img(settings)
output_images = image_base64_list
if (output_images) {
saveOutputImagesToDriveComfy(output_images, settings)
}
}
output_images = await this.processOutput(
response_json.images_info,
settings
)
} catch (e) {
console.warn(e)
console.warn('output_images: ', output_images)
@ -293,8 +354,9 @@ export class Img2ImgMode extends Mode {
//REFACTOR: reuse the same code for (requestControlNetTxt2Img,requestControlNetImg2Img)
static async requestControlNetImg2Img(plugin_settings: any) {
const full_url = `${g_sd_url}/sdapi/v1/img2img`
const control_net_settings =
mapPluginSettingsToControlNet(plugin_settings)
const control_net_settings = await mapPluginSettingsToControlNet(
plugin_settings
)
// let control_networks = 0
let control_networks = []
@ -304,8 +366,17 @@ export class Img2ImgMode extends Mode {
continue
}
control_networks[index] = true
const is_inpaint_model: boolean =
control_net_settings['controlnet_units'][index][
'module'
].includes('inpaint')
if (
!control_net_settings['controlnet_units'][index]['input_image']
!control_net_settings['controlnet_units'][index][
'input_image'
] &&
!is_inpaint_model
) {
//@ts-ignore
app.showAlert('you need to add a valid ControlNet input image')
@ -419,18 +490,42 @@ export class Img2ImgMode extends Mode {
let output_images
try {
//checks on index 0 as if not enabled ignores the rest
const b_enable_control_net = control_net.isControlNetModeEnable()
if (b_enable_control_net) {
//use control net
response_json = await this.requestControlNetImg2Img(settings)
} else {
response_json = await this.requestImg2Img(settings)
if (settings_tab.store.data.selected_backend === 'Automatic1111') {
const b_enable_control_net =
control_net.isControlNetModeEnable()
if (b_enable_control_net) {
//use control net
response_json = await this.requestControlNetImg2Img(
settings
)
} else {
response_json = await this.requestImg2Img(settings)
}
output_images = await this.processOutput(
response_json.images_info,
settings
)
} else if (settings_tab.store.data.selected_backend === 'ComfyUI') {
settings = await mapPluginSettingsToControlNet(settings)
if (settings?.mode === 'img2img') {
const { image_base64_list, image_url_list } =
await comfyui_main_ui.generateComfyImg2Img(settings)
output_images = image_base64_list
} else if (settings?.mode === 'inpaint') {
const { image_base64_list, image_url_list } =
await comfyui_main_ui.generateComfyInpaint(settings)
output_images = image_base64_list
} else if (settings?.mode === 'outpaint') {
const { image_base64_list, image_url_list } =
await comfyui_main_ui.generateComfyInpaint(settings)
output_images = image_base64_list
}
if (output_images) {
saveOutputImagesToDriveComfy(output_images, settings)
}
}
output_images = await this.processOutput(
response_json.images_info,
settings
)
} catch (e) {
console.warn(e)
console.warn('output_images: ', output_images)
@ -494,7 +589,8 @@ export class LassoInpaintMode extends Img2ImgMode {
}
const [init_image, mask] = await selection.inpaintLassoInitImageAndMask(
'mask',
sd_tab_util.helper_store.data.lasso_offset
sd_tab_util.helper_store.data.lasso_offset,
sd_tab_util.helper_store.data.make_square
)
const selectionInfo = await psapi.getSelectionInfoExe()

View File

@ -26,7 +26,8 @@ import {
updateViewerStoreImageAndThumbnail,
} from '../viewer/viewer_util'
import { sd_tab_store } from '../stores'
import settings_tab from '../settings/settings'
import comfyui_util from '../comfyui/util'
declare let g_inpaint_mask_layer: any
declare const g_image_not_found_url: string
declare let g_current_batch_index: number
@ -150,11 +151,19 @@ export async function getExpandedMask(
//only if mask is available and sharp_mask is off
// use blurry and expanded mask
const iterations = expansion_value
expanded_mask = await python_replacement.maskExpansionRequest(
mask,
iterations,
blur
)
if (settings_tab.store.data.selected_backend === 'Automatic1111') {
expanded_mask = await python_replacement.maskExpansionRequest(
mask,
iterations,
blur
)
} else if (settings_tab.store.data.selected_backend === 'ComfyUI') {
expanded_mask = await comfyui_util.maskExpansion(
mask,
iterations,
blur
)
}
}
// return expanded_mask
@ -182,6 +191,7 @@ export class Session {
store.data.current_session_id = await incrementSessionID()
store.data.mode = mode
store.data.rb_mode = sd_tab_store.data.rb_mode
if (modeToClassMap.hasOwnProperty(store.data.mode)) {
const { selectionInfo, init_image, mask } =
@ -296,6 +306,7 @@ export class Session {
selectionInfo,
init_image,
mask,
rb_mode: store.data.rb_mode, // set in initializeSession
})
//this should be part of initialization method or gettingSettings()
@ -350,6 +361,7 @@ export class Session {
init_image: store.data.init_image,
mask: store.data.preprocessed_mask,
selectionInfo: store.data.selectionInfo,
rb_mode: store.data.rb_mode, // set in initializeSession
}
var ui_settings = await this.getSettings(session_data)
if (
@ -395,7 +407,11 @@ export class Session {
}
static async getProgress() {
// Progress.startSudoProgress()
progress.Progress.startTimer(async () => {
const comfyProgress = async () => {
progress.store.data.progress_value += 1
}
const auto1111Progress = async () => {
try {
let json = await progress.requestProgress()
const can_update = progress.store.data.can_update
@ -422,7 +438,12 @@ export class Session {
} catch (e) {
console.warn(e)
}
}, 2000)
}
const [callback, timer] =
settings_tab.store.data.selected_backend === 'Automatic1111'
? [auto1111Progress, 2000]
: [comfyProgress, 1000]
progress.Progress.startTimer(callback, timer)
}
static async endProgress() {
await progress.Progress.endTimer(async () => {

View File

@ -1,5 +1,9 @@
import { AStore } from '../main/astore'
import { GenerationModeEnum, SelectionInfoType } from '../util/ts/enum'
import {
GenerationModeEnum,
ScriptMode,
SelectionInfoType,
} from '../util/ts/enum'
interface AStoreUISettings {
batch_size: number
@ -17,6 +21,7 @@ interface AStoreData {
preprocessed_mask: string //
sd_mask: string // mask send to sd as payload[mask]
mode: GenerationModeEnum
rb_mode: ScriptMode
ui_settings: AStoreUISettings
selectionInfo: SelectionInfoType | undefined //the session selection info
@ -47,6 +52,7 @@ export const store = new AStore<AStoreData>({
preprocessed_mask: '', //
sd_mask: '', // mask send to sd as payload[mask]
mode: GenerationModeEnum.Txt2Img,
rb_mode: ScriptMode.Txt2Img,
ui_settings: { batch_size: 1 },
selectionInfo: undefined, //the session selection info
current_selection_info: undefined, // any new selection, could be undefined too

View File

@ -3,7 +3,7 @@ import ReactDOM from 'react-dom/client'
import { observer } from 'mobx-react'
import { AStore } from '../main/astore'
import { SpCheckBox, SpMenu, SpSlider } from '../util/elements'
import { SpCheckBox, SpMenu, SpSlider, SpTextfield } from '../util/elements'
import Locale from '../locale/locale'
import globalStore from '../globalstore'
import { io } from '../util/oldSystem'
@ -14,6 +14,7 @@ import { ErrorBoundary } from '../util/errorBoundary'
import { MaskModeEnum, ScriptMode } from '../util/ts/enum'
import { store as progress_store } from '../session/progress'
import { requestPost } from '../util/ts/api'
import { comfyapi } from '../entry'
// import { Jimp } from '../util/oldSystem'
declare const Jimp: any // make sure you import jimp before importing settings.tsx
@ -81,6 +82,8 @@ interface AStoreData {
bTurnOffServerStatusAlert: boolean
CLIP_stop_at_last_layers: number
use_smart_object: boolean
selected_backend: 'Automatic1111' | 'ComfyUI'
comfy_url: string
}
export const store = new AStore<AStoreData>({
scale_interpolation_method: interpolationMethods.bilinear,
@ -97,6 +100,11 @@ export const store = new AStore<AStoreData>({
false,
CLIP_stop_at_last_layers: 1,
use_smart_object: true, // true to keep layer as smart objects, false to rasterize them
// selected_backend: 'Automatic1111' as 'Automatic1111' | 'ComfyUI',
selected_backend: (storage.localStorage.getItem('selected_backend') ||
'ComfyUI') as 'Automatic1111' | 'ComfyUI',
comfy_url:
storage.localStorage.getItem('comfy_url') || 'http://127.0.0.1:8188',
})
function onShouldLogToFileChange(event: any) {
@ -163,16 +171,66 @@ async function getOptions(): Promise<Options | null> {
@observer
export class Settings extends React.Component<{}> {
async componentDidMount(): Promise<void> {
const options = await getOptions()
if (store.data.selected_backend === 'Automatic1111') {
const options = await getOptions()
store.data.CLIP_stop_at_last_layers =
options?.CLIP_stop_at_last_layers ??
store.data.CLIP_stop_at_last_layers
store.data.CLIP_stop_at_last_layers =
options?.CLIP_stop_at_last_layers ??
store.data.CLIP_stop_at_last_layers
}
}
render() {
return (
<div style={{ width: '100%' }}>
<sp-label>ComfyUI Url:</sp-label>
<SpTextfield
type="text"
placeholder="http://127.0.0.1:8188"
// value={config.default}
value={store.data.comfy_url}
onChange={(event: any) => {
// store.data.search_query = event.target.value
let url = event.target.value.trim() // remove leading and trailing white spaces
url = url.replace(/[/\\]$/, '')
console.log(url)
store.data.comfy_url = url
comfyapi.comfy_api.setUrl(store.data.comfy_url)
storage.localStorage.setItem(
'comfy_url',
store.data.comfy_url
)
}}
></SpTextfield>
<sp-radio-group>
{['Automatic1111', 'ComfyUI'].map(
(backend: any, index: number) => {
return (
<sp-radio
key={index}
title={backend}
value={backend}
onClick={(evt: any) => {
store.data.selected_backend =
evt.target.value
storage.localStorage.setItem(
'selected_backend',
store.data.selected_backend
)
}}
checked={
store.data.selected_backend === backend
? true
: void 0
}
>
{backend}
</sp-radio>
)
}
)}
</sp-radio-group>
<SpMenu
title="select an interploation method for resizing images"
items={Object.keys(interpolationMethods)}
@ -197,7 +255,7 @@ export class Settings extends React.Component<{}> {
></SpMenu>
<div style={{ width: '100%' }}>
<sp-label>select language</sp-label>
<sp-label>{Locale('select language:')}</sp-label>
</div>
<SpMenu
title="select language"
@ -222,10 +280,7 @@ export class Settings extends React.Component<{}> {
onChange={onShouldLogToFileChange}
checked={store.data.should_log_to_file}
>
{
//@ts-ignore
Locale('Log Errors To File')
}
{Locale('Log Errors To File')}
</SpCheckBox>
</div>
@ -290,7 +345,7 @@ export class Settings extends React.Component<{}> {
}}
style={{ display: 'inline-flex' }}
>
Image Cfg Scale Slider
{Locale('Image Cfg Scale Slider')}
</sp-checkbox>
</div>
<div>
@ -301,12 +356,14 @@ export class Settings extends React.Component<{}> {
store.data.use_sharp_mask = evt.target.checked
}}
>
use sharp mask
{Locale('use sharp mask')}
</sp-checkbox>
</div>
<div>
<sp-radio-group selected={store.data.extension_type}>
<sp-label slot="label">Select Extension:</sp-label>
<sp-label slot="label">
{Locale('Select Extension:')}
</sp-label>
{[
ExtensionTypeEnum.ProxyServer,
ExtensionTypeEnum.Auto1111Extension,
@ -329,7 +386,7 @@ export class Settings extends React.Component<{}> {
evt.target.value
}}
>
{config[extension_type].label}
{Locale(config[extension_type].label)}
</sp-radio>
)
})}
@ -352,7 +409,7 @@ export class Settings extends React.Component<{}> {
)
}}
>
Turn Off Server Status Alert
{Locale('Turn Off Server Status Alert')}
</sp-checkbox>
</div>
<div>
@ -394,7 +451,7 @@ export class Settings extends React.Component<{}> {
: false
}}
>
Smart Object
{Locale('Smart Object')}
</sp-checkbox>
</div>
</div>

View File

@ -1,7 +1,7 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import { observer } from 'mobx-react'
import { AStore } from './astore'
import { AStore } from '../main/astore'
import { SpMenu } from '../util/elements'
import { api, python_replacement } from '../util/oldSystem'
@ -10,27 +10,11 @@ import '../locale/locale-for-old-html'
import { ErrorBoundary } from '../util/errorBoundary'
declare let g_sd_url: string
// class SDStore extends AStore {
// constructor(data: any) {
// super(data)
// }
// }
// const configValues = Object.entries(ui_config).reduce(
// (acc, [key, value]) => ({ ...acc, [key]: value.value }),
// {}
// )
// const default_values: any = {
// _: '',
// ...configValues,
// }
const default_values: any = {
vae_model_list: [],
current_vae: '',
}
export const store = new AStore(default_values)
export const store = new AStore({
vae_model_list: [] as string[],
current_vae: '' as string,
})
@observer
export class VAEComponent extends React.Component<{
@ -54,7 +38,6 @@ export class VAEComponent extends React.Component<{
<SpMenu
title="vae models"
items={store.data.vae_model_list}
// disabled={script_store.disabled}
// style="width: 199px; margin-right: 5px"
label_item="Select A VAE"
// id={'model_list'}
@ -111,3 +94,8 @@ vaeRoot.render(
</ErrorBoundary>
//</React.StrictMode>
)
export default {
store,
populateVAE,
}

View File

@ -137,8 +137,7 @@ class ToolBar extends React.Component<{}> {
}}
></button>
<button
className="btnSquare resetButton"
id="btnResetSettings"
className="btnSquare resetButton btnResetSettings"
title="reset the ui settings to their default values"
style={{ marginRight: '3px' }}
onClick={(evt: any) => {

View File

@ -11,7 +11,7 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
"jsx": "react", /* Specify what JSX code is generated. */
"experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */

View File

@ -8,6 +8,7 @@ import { SpMenu } from '../util/elements'
import * as ultimate_sd_upscale_script from './ultimate_sd_upscaler'
import { ScriptMode } from './ultimate_sd_upscaler'
import { ErrorBoundary } from '../util/errorBoundary'
import Locale from '../locale/locale'
export function toJsFunc(store: any) {
return toJS(store)
}
@ -118,7 +119,7 @@ class ScriptComponent extends React.Component<{}> {
<SpMenu
title="Scripts"
items={script_store.scripts_list}
disabled={script_store.disabled}
disabled_items={script_store.disabled}
// style="width: 199px; margin-right: 5px"
label_item="Select A Script"
id={'script_list'}
@ -139,7 +140,7 @@ class ScriptComponent extends React.Component<{}> {
script_store.setIsActive(event.target.checked)
}}
>
{'Activate'}
{Locale('Activate')}
</sp-checkbox>
<>
{script_store.selected_script_name === 'None' && <></>}

View File

@ -26,6 +26,8 @@ declare global {
'sp-textfield': any
'sp-action-button': any
'sp-progressbar': any
'sp-popover': any
'sp-dialog': any
}
}
}
@ -61,6 +63,7 @@ export class SpSliderWithLabel extends React.Component<{
output_value?: number // can be use to represent sd value
// slider_value?: number // it's slider value can be from 1 to 100
slider_type?: SliderType
disabled?: boolean
}> {
// const [sliderValue,setSliderValue] = useState<number>(0)
state = { output_value: this.props.output_value || 0, slider_value: 0 }
@ -162,8 +165,7 @@ export class SpSliderWithLabel extends React.Component<{
<div>
<SpSlider
show-value="false"
// id="slControlNetWeight_0"
class="slControlNetWeight_"
disabled={this.props.disabled ? true : void 0}
min={this.in_min}
max={this.in_max}
value={this.state.slider_value}
@ -197,7 +199,8 @@ export class SpMenu extends React.Component<{
title?: string
style?: CSSProperties
items?: string[]
disabled?: boolean[]
disabled?: boolean
disabled_items?: boolean[]
label_item?: string
onChange?: any
selected_index?: number
@ -238,6 +241,7 @@ export class SpMenu extends React.Component<{
title={this.props.title}
size={this.props.size || 'm'}
style={this.props.style}
disabled={this.props.disabled ? 'disabled' : undefined}
// style={{ width: '199px', marginRight: '5px' }}
>
<sp-menu id={this.props.id} slot="options">
@ -263,7 +267,7 @@ export class SpMenu extends React.Component<{
: undefined
}
disabled={
this.props.disabled?.[index]
this.props.disabled_items?.[index]
? 'disabled'
: undefined
}
@ -492,8 +496,7 @@ export const ScriptInstallComponent = observer(
Automatic1111 webui
</sp-label>
<button
className="btnSquare refreshButton"
id="btnResetSettings"
className="btnSquare refreshButton btnResetSettings"
title="Refresh the ADetailer Extension"
onClick={onRefreshHandler}
></button>
@ -501,3 +504,193 @@ export const ScriptInstallComponent = observer(
)
}
)
@observer
export class SearchableMenu extends React.Component<{
allItems: string[]
placeholder: string
onChange?: any
selected_item?: string
onSelectItemFailure?: any
// default_value?: string
// searchQuery: string
}> {
state = {
selectedItem: this.props.allItems ? this.props.allItems[0] : undefined,
searchItems: this.props.allItems,
// searchQuery: this.props.searchQuery,
searchQuery: '',
openMenu: false,
}
selectItem(selected_item: string) {
let final_value = this.props.allItems.includes(selected_item)
? selected_item
: this.props.onSelectItemFailure && this.props.onSelectItemFailure()
this.setState({ searchQuery: final_value })
}
componentDidMount(): void {
if (this.props.selected_item) {
// console.log('selected_item has changed:', this.props.selected_item)
this.selectItem(this.props.selected_item)
}
}
componentDidUpdate(prevProps: any, prevState: { searchQuery: string }) {
if (prevState.searchQuery !== this.state.searchQuery) {
this.searchItem(this.state.searchQuery)
}
if (prevProps.selected_item !== this.props.selected_item) {
console.log('selected_item has changed:', this.props.selected_item)
if (this.props.selected_item !== undefined) {
this.selectItem(this.props.selected_item)
}
}
}
// searchItem = (event: React.KeyboardEvent<HTMLInputElement>) => {
searchItem = (searchQuery: string) => {
console.log('onInput')
// const filter = event.currentTarget.value.toUpperCase()
const filter = searchQuery.toUpperCase()
const searchItems = this.props.allItems.filter((item) =>
item.toUpperCase().includes(filter)
)
this.setState({ searchItems: searchItems })
console.log(
'this.props.searchItems.length: ',
this.state.searchItems.length
)
this.setState({
openMenu: this.state.searchItems.length > 0 ? true : false,
})
console.log('this.state.openMenu: ', this.state.openMenu)
}
render() {
return (
<div
style={{
display: 'flex',
width: '100%',
zIndex: 1000,
// position: 'absolute',
}}
>
<div style={{ position: 'relative', flex: 1, width: '100%' }}>
<SpTextfield
type="text"
title={this.state.searchQuery}
style={{ width: '100%' }}
value={this.state.searchQuery}
placeholder={this.props.placeholder}
onInput={(evt: any) => {
this.setState({ searchQuery: evt.target.value })
// this.searchItem(evt.taret)
console.log('onInput:', evt.target.value)
console.log(
'this.state.searchQuery: ',
this.state.searchQuery
)
}}
onChange={(evt: any) => {
this.setState({ searchQuery: evt.target.value })
console.log('onChange:', evt.target.value)
console.log(
'this.state.searchQuery: ',
this.state.searchQuery
)
}}
onFocus={(evt: any) => {
console.log('onFocus:', evt.target.value)
if (evt.target.value === '') {
this.setState({
searchItems: this.props.allItems,
})
}
this.setState({ openMenu: true })
console.log(
'this.state.searchQuery: ',
this.state.searchQuery
)
}}
onBlur={(evt: any) => {
setTimeout(
() => {
console.log('onBlur:', evt.target.value)
const state_values: Record<string, any> = {}
if (
!this.props.allItems.includes(
this.state.searchQuery
)
) {
state_values.searchQuery = ''
if (this.props.onSelectItemFailure) {
state_values.searchQuery =
this.props.onSelectItemFailure()
}
}
state_values.openMenu = false
this.setState(state_values)
console.log(
'this.state.searchQuery: ',
this.state.searchQuery
)
},
200
)
}}
></SpTextfield>
<ul
id="myMenu"
style={{
// position: 'absolute',
width: '100%',
display: this.state.openMenu ? void 0 : 'none',
zIndex: 1000,
}}
>
{this.state.searchItems.map((item: string, index) => (
<li
key={index}
title={item}
onClick={() => {
console.log('onClick:')
this.setState({
searchQuery: item,
openMenu: false,
})
console.log(
'this.state.searchQuery: ',
this.state.searchQuery
)
console.log('item: ', item)
if (this.props.onChange) {
this.props.onChange(item)
}
}}
style={{ zIndex: 1000 }}
>
<a href="#" style={{ fontSize: '12px' }}>
{item}
</a>
</li>
))}
</ul>
</div>
</div>
)
}
}

View File

@ -1,4 +1,5 @@
import React, { Component, ErrorInfo, ReactNode } from 'react'
import { refreshUI } from '../sd_tab/util'
interface Props {
children: ReactNode
@ -6,14 +7,21 @@ interface Props {
interface State {
hasError: boolean
key: number
}
interface State {
hasError: boolean
key: number
}
export class ErrorBoundary extends Component<Props, State> {
public state: State = {
hasError: false,
key: 0,
}
public static getDerivedStateFromError(_: Error): State {
public static getDerivedStateFromError(_: Error): Partial<State> {
// When an error occurs, we update state so the next render will show the fallback UI.
return { hasError: true }
}
@ -21,11 +29,25 @@ export class ErrorBoundary extends Component<Props, State> {
console.error('Uncaught error:', error, errorInfo)
}
handleRefresh = () => {
this.setState((prevState) => ({
hasError: false, // reset the error state
key: prevState.key + 1, // increment key to remount children
}))
// await refreshUI()
}
public render() {
if (this.state.hasError) {
return <h1>Sorry.. there was an error</h1>
return (
<div>
<h1>Sorry.. there was an error</h1>
<button onClick={this.handleRefresh}>Refresh</button>
</div>
)
}
return this.props.children
// The key prop causes a remount of children when it changes
return <div key={this.state.key}>{this.props.children}</div>
}
}

View File

@ -58,7 +58,6 @@ export class Grid extends React.Component<{
event
)
}
this.props?.callback(index, event) //todo: is this a typo why do we call callback twice?
}
} catch (e) {
console.warn(

View File

@ -2,7 +2,7 @@ import { storage } from 'uxp'
declare let g_sd_url: string
export async function requestGet(url: string) {
let json = null
let data = null
const full_url = url
try {
@ -11,14 +11,31 @@ export async function requestGet(url: string) {
return null
}
json = await request.json()
const contentType = request.headers.get('content-type')
if (contentType && contentType.includes('application/json')) {
data = await request.json()
} else if (
contentType &&
contentType.includes('application/octet-stream')
) {
data = await request.arrayBuffer()
} else if (
contentType &&
(contentType.includes('image/jpeg') ||
contentType.includes('image/png'))
) {
data = await request.arrayBuffer()
} else {
data = await request.text()
}
// console.log('json: ', json)
// console.log('data: ', data)
} catch (e) {
console.warn(`issues requesting from ${full_url}`, e)
}
return json
return data
}
export async function requestPost(url: string, payload: any) {
let json = null

View File

@ -49,3 +49,21 @@ export function autoResize(textarea: any, text_content: string, delay = 300) {
export async function urlToCanvas(url: string, image_name = 'image.png') {
await io.IO.urlToLayer(url, image_name)
}
export const copyJson = (originalObject: any) =>
JSON.parse(JSON.stringify(originalObject))
export function base64ToBase64Url(base64_image: string) {
return 'data:image/png;base64,' + base64_image
}
export function base64UrlToBase64(base64_url: string) {
const base64_image = base64_url.replace('data:image/png;base64,', '')
return base64_image
}
export function newOutputImageName(format = 'png') {
const random_id = Math.floor(Math.random() * 100000000000 + 1) // Date.now() doesn't have enough resolution to avoid duplicate
const image_name = `output- ${Date.now()}-${random_id}.${format}`
console.log('generated image name:', image_name)
return image_name
}

View File

@ -61,8 +61,10 @@ export async function requestGetModels() {
let json = []
const full_url = `${g_sd_url}/sdapi/v1/sd-models`
try {
let request = await fetch(full_url)
json = await request.json()
let res = await fetch(full_url)
if (res.status == 200) {
json = await res.json()
}
console.log('models json:')
console.dir(json)
} catch (e) {

View File

@ -136,3 +136,9 @@ export const resetViewer = () => {
init_store.data.images = []
init_store.data.thumbnails = []
}
export default {
store,
init_store,
mask_store,
}

View File

@ -890,10 +890,17 @@ async function createThumbnail(base64Image, width = 100) {
return thumbnail
}
async function getImageFromCanvas() {
const width = html_manip.getWidth()
const height = html_manip.getHeight()
async function getImageFromCanvas(scale_to_sliders = true) {
let width
let height
const selectionInfo = await psapi.getSelectionInfoExe()
if (scale_to_sliders) {
width = html_manip.getWidth()
height = html_manip.getHeight()
} else {
width = selectionInfo.width
height = selectionInfo.height
}
const base64 = await io.IO.getSelectionFromCanvasAsBase64Interface_New(
width,
height,

View File

@ -1,4 +1,4 @@
{
"new_version": "v1.3.3",
"new_version": "v1.4.0",
"update_message": "Your version is outdated.Please visit our Github page and download the one click installer / .ccx file.\nRun the .ccx file and it will automatically update the current version of your plug in. No data will be lost."
}
}

View File

@ -183,7 +183,7 @@ async function getSettings(session_data) {
try {
const extension_type = settings_tab_ts.store.data.extension_type // get the extension type
payload['selection_info'] = session_data.selectionInfo
payload['selection_info'] = session_data?.selectionInfo
const numberOfBatchSize = parseInt(sd_tab_store.data.batch_size)
const prompt = multiPrompts.getPrompt().positive
@ -195,11 +195,14 @@ async function getSettings(session_data) {
function calculateSeed(init_seed, batch_index, batch_size) {
if (init_seed === -1) return -1
const seed = init_seed + batch_index * batch_size
// const seed = init_seed + batch_index * batch_size
const seed = init_seed + BigInt(batch_index) * BigInt(batch_size)
return seed
}
const init_seed = parseInt(sd_tab_store.data.seed)
// const init_seed = parseInt(sd_tab_store.data.seed)
const init_seed = BigInt(sd_tab_store.data.seed)
const seed = calculateSeed(
init_seed,
g_current_batch_index,
@ -247,7 +250,8 @@ async function getSettings(session_data) {
const sampler_name = sd_tab_store.data.sampler_name
const mode = sd_tab_store.data.rb_mode
const mode = session_data?.rb_mode || sd_tab_store.data.rb_mode // Use the 'rb_mode' from the session, if not available, fallback to the 'rb_mode' from the interface.
const b_restore_faces = sd_tab_store.data.restore_faces
let denoising_strength = h_denoising_strength
@ -412,6 +416,8 @@ async function getSettings(session_data) {
payload['original_negative_prompt'] = negative_prompt
}
payload['clip_skip'] =
settings_tab_ts.store.data.CLIP_stop_at_last_layers
payload = {
...payload,
// prompt: prompt,
@ -424,8 +430,8 @@ async function getSettings(session_data) {
denoising_strength: denoising_strength,
batch_size: numberOfBatchSize,
cfg_scale: cfg_scale,
seed: seed,
// mask_blur: mask_blur, // don't use auto1111 blur, instead use Auto-Photoshop-SD blur
seed: seed.toString(),
mask_blur: 4, //mask_blur, // don't use auto1111 blur, instead use Auto-Photoshop-SD blur
use_sharp_mask: use_sharp_mask,
use_prompt_shortcut: bUsePromptShortcut,
prompt_shortcut_ui_dict: prompt_shortcut_ui_dict,