Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
chenpangpang
ComfyUI
Commits
a49b5659
Unverified
Commit
a49b5659
authored
Mar 26, 2023
by
pythongosssss
Committed by
GitHub
Mar 26, 2023
Browse files
Merge branch 'comfyanonymous:master' into custom-node-socket
parents
8d0a1423
f5365c9c
Changes
15
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
714 additions
and
89 deletions
+714
-89
comfy/ldm/models/diffusion/ddim.py
comfy/ldm/models/diffusion/ddim.py
+1
-1
comfy/model_management.py
comfy/model_management.py
+22
-4
comfy/samplers.py
comfy/samplers.py
+1
-1
comfyui_screenshot.png
comfyui_screenshot.png
+0
-0
nodes.py
nodes.py
+67
-3
notebooks/comfyui_colab.ipynb
notebooks/comfyui_colab.ipynb
+83
-55
server.py
server.py
+1
-1
web/extensions/core/widgetInputs.js
web/extensions/core/widgetInputs.js
+362
-0
web/index.html
web/index.html
+2
-1
web/jsconfig.json
web/jsconfig.json
+9
-0
web/lib/litegraph.core.js
web/lib/litegraph.core.js
+3
-3
web/scripts/app.js
web/scripts/app.js
+41
-12
web/scripts/ui.js
web/scripts/ui.js
+51
-0
web/scripts/widgets.js
web/scripts/widgets.js
+46
-8
web/style.css
web/style.css
+25
-0
No files found.
comfy/ldm/models/diffusion/ddim.py
View file @
a49b5659
...
@@ -18,7 +18,7 @@ class DDIMSampler(object):
...
@@ -18,7 +18,7 @@ class DDIMSampler(object):
def
register_buffer
(
self
,
name
,
attr
):
def
register_buffer
(
self
,
name
,
attr
):
if
type
(
attr
)
==
torch
.
Tensor
:
if
type
(
attr
)
==
torch
.
Tensor
:
if
attr
.
device
!=
self
.
device
:
if
attr
.
device
!=
self
.
device
:
attr
=
attr
.
to
(
self
.
device
)
attr
=
attr
.
float
().
to
(
self
.
device
)
setattr
(
self
,
name
,
attr
)
setattr
(
self
,
name
,
attr
)
def
make_schedule
(
self
,
ddim_num_steps
,
ddim_discretize
=
"uniform"
,
ddim_eta
=
0.
,
verbose
=
True
):
def
make_schedule
(
self
,
ddim_num_steps
,
ddim_discretize
=
"uniform"
,
ddim_eta
=
0.
,
verbose
=
True
):
...
...
comfy/model_management.py
View file @
a49b5659
...
@@ -4,6 +4,7 @@ NO_VRAM = 1
...
@@ -4,6 +4,7 @@ NO_VRAM = 1
LOW_VRAM
=
2
LOW_VRAM
=
2
NORMAL_VRAM
=
3
NORMAL_VRAM
=
3
HIGH_VRAM
=
4
HIGH_VRAM
=
4
MPS
=
5
accelerate_enabled
=
False
accelerate_enabled
=
False
vram_state
=
NORMAL_VRAM
vram_state
=
NORMAL_VRAM
...
@@ -76,10 +77,16 @@ if set_vram_to == LOW_VRAM or set_vram_to == NO_VRAM:
...
@@ -76,10 +77,16 @@ if set_vram_to == LOW_VRAM or set_vram_to == NO_VRAM:
total_vram_available_mb
=
(
total_vram
-
1024
)
//
2
total_vram_available_mb
=
(
total_vram
-
1024
)
//
2
total_vram_available_mb
=
int
(
max
(
256
,
total_vram_available_mb
))
total_vram_available_mb
=
int
(
max
(
256
,
total_vram_available_mb
))
try
:
if
torch
.
backends
.
mps
.
is_available
():
vram_state
=
MPS
except
:
pass
if
"--cpu"
in
sys
.
argv
:
if
"--cpu"
in
sys
.
argv
:
vram_state
=
CPU
vram_state
=
CPU
print
(
"Set vram state to:"
,
[
"CPU"
,
"NO VRAM"
,
"LOW VRAM"
,
"NORMAL VRAM"
,
"HIGH VRAM"
][
vram_state
])
print
(
"Set vram state to:"
,
[
"CPU"
,
"NO VRAM"
,
"LOW VRAM"
,
"NORMAL VRAM"
,
"HIGH VRAM"
,
"MPS"
][
vram_state
])
current_loaded_model
=
None
current_loaded_model
=
None
...
@@ -128,6 +135,10 @@ def load_model_gpu(model):
...
@@ -128,6 +135,10 @@ def load_model_gpu(model):
current_loaded_model
=
model
current_loaded_model
=
model
if
vram_state
==
CPU
:
if
vram_state
==
CPU
:
pass
pass
elif
vram_state
==
MPS
:
mps_device
=
torch
.
device
(
"mps"
)
real_model
.
to
(
mps_device
)
pass
elif
vram_state
==
NORMAL_VRAM
or
vram_state
==
HIGH_VRAM
:
elif
vram_state
==
NORMAL_VRAM
or
vram_state
==
HIGH_VRAM
:
model_accelerated
=
False
model_accelerated
=
False
real_model
.
cuda
()
real_model
.
cuda
()
...
@@ -155,9 +166,10 @@ def load_controlnet_gpu(models):
...
@@ -155,9 +166,10 @@ def load_controlnet_gpu(models):
if
m
not
in
models
:
if
m
not
in
models
:
m
.
cpu
()
m
.
cpu
()
device
=
get_torch_device
()
current_gpu_controlnets
=
[]
current_gpu_controlnets
=
[]
for
m
in
models
:
for
m
in
models
:
current_gpu_controlnets
.
append
(
m
.
cuda
(
))
current_gpu_controlnets
.
append
(
m
.
to
(
device
))
def
load_if_low_vram
(
model
):
def
load_if_low_vram
(
model
):
...
@@ -173,6 +185,8 @@ def unload_if_low_vram(model):
...
@@ -173,6 +185,8 @@ def unload_if_low_vram(model):
return
model
return
model
def
get_torch_device
():
def
get_torch_device
():
if
vram_state
==
MPS
:
return
torch
.
device
(
"mps"
)
if
vram_state
==
CPU
:
if
vram_state
==
CPU
:
return
torch
.
device
(
"cpu"
)
return
torch
.
device
(
"cpu"
)
else
:
else
:
...
@@ -195,7 +209,7 @@ def get_free_memory(dev=None, torch_free_too=False):
...
@@ -195,7 +209,7 @@ def get_free_memory(dev=None, torch_free_too=False):
if
dev
is
None
:
if
dev
is
None
:
dev
=
get_torch_device
()
dev
=
get_torch_device
()
if
hasattr
(
dev
,
'type'
)
and
dev
.
type
==
'cpu'
:
if
hasattr
(
dev
,
'type'
)
and
(
dev
.
type
==
'cpu'
or
dev
.
type
==
'mps'
)
:
mem_free_total
=
psutil
.
virtual_memory
().
available
mem_free_total
=
psutil
.
virtual_memory
().
available
mem_free_torch
=
mem_free_total
mem_free_torch
=
mem_free_total
else
:
else
:
...
@@ -224,8 +238,12 @@ def cpu_mode():
...
@@ -224,8 +238,12 @@ def cpu_mode():
global
vram_state
global
vram_state
return
vram_state
==
CPU
return
vram_state
==
CPU
def
mps_mode
():
global
vram_state
return
vram_state
==
MPS
def
should_use_fp16
():
def
should_use_fp16
():
if
cpu_mode
():
if
cpu_mode
()
or
mps_mode
()
:
return
False
#TODO ?
return
False
#TODO ?
if
torch
.
cuda
.
is_bf16_supported
():
if
torch
.
cuda
.
is_bf16_supported
():
...
...
comfy/samplers.py
View file @
a49b5659
...
@@ -450,7 +450,7 @@ class KSampler:
...
@@ -450,7 +450,7 @@ class KSampler:
noise_mask
=
None
noise_mask
=
None
if
denoise_mask
is
not
None
:
if
denoise_mask
is
not
None
:
noise_mask
=
1.0
-
denoise_mask
noise_mask
=
1.0
-
denoise_mask
sampler
=
DDIMSampler
(
self
.
model
)
sampler
=
DDIMSampler
(
self
.
model
,
device
=
self
.
device
)
sampler
.
make_schedule_timesteps
(
ddim_timesteps
=
timesteps
,
verbose
=
False
)
sampler
.
make_schedule_timesteps
(
ddim_timesteps
=
timesteps
,
verbose
=
False
)
z_enc
=
sampler
.
stochastic_encode
(
latent_image
,
torch
.
tensor
([
len
(
timesteps
)
-
1
]
*
noise
.
shape
[
0
]).
to
(
self
.
device
),
noise
=
noise
,
max_denoise
=
max_denoise
)
z_enc
=
sampler
.
stochastic_encode
(
latent_image
,
torch
.
tensor
([
len
(
timesteps
)
-
1
]
*
noise
.
shape
[
0
]).
to
(
self
.
device
),
noise
=
noise
,
max_denoise
=
max_denoise
)
samples
,
_
=
sampler
.
sample_custom
(
ddim_timesteps
=
timesteps
,
samples
,
_
=
sampler
.
sample_custom
(
ddim_timesteps
=
timesteps
,
...
...
comfyui_screenshot.png
View replaced file @
8d0a1423
View file @
a49b5659
115 KB
|
W:
|
H:
110 KB
|
W:
|
H:
2-up
Swipe
Onion skin
nodes.py
View file @
a49b5659
...
@@ -241,8 +241,8 @@ class LoraLoader:
...
@@ -241,8 +241,8 @@ class LoraLoader:
return
{
"required"
:
{
"model"
:
(
"MODEL"
,),
return
{
"required"
:
{
"model"
:
(
"MODEL"
,),
"clip"
:
(
"CLIP"
,
),
"clip"
:
(
"CLIP"
,
),
"lora_name"
:
(
folder_paths
.
get_filename_list
(
"loras"
),
),
"lora_name"
:
(
folder_paths
.
get_filename_list
(
"loras"
),
),
"strength_model"
:
(
"FLOAT"
,
{
"default"
:
1.0
,
"min"
:
0.0
,
"max"
:
10.0
,
"step"
:
0.01
}),
"strength_model"
:
(
"FLOAT"
,
{
"default"
:
1.0
,
"min"
:
-
1
0.0
,
"max"
:
10.0
,
"step"
:
0.01
}),
"strength_clip"
:
(
"FLOAT"
,
{
"default"
:
1.0
,
"min"
:
0.0
,
"max"
:
10.0
,
"step"
:
0.01
}),
"strength_clip"
:
(
"FLOAT"
,
{
"default"
:
1.0
,
"min"
:
-
1
0.0
,
"max"
:
10.0
,
"step"
:
0.01
}),
}}
}}
RETURN_TYPES
=
(
"MODEL"
,
"CLIP"
)
RETURN_TYPES
=
(
"MODEL"
,
"CLIP"
)
FUNCTION
=
"load_lora"
FUNCTION
=
"load_lora"
...
@@ -752,7 +752,7 @@ class SaveImage:
...
@@ -752,7 +752,7 @@ class SaveImage:
full_output_folder
=
os
.
path
.
join
(
self
.
output_dir
,
subfolder
)
full_output_folder
=
os
.
path
.
join
(
self
.
output_dir
,
subfolder
)
if
os
.
path
.
commonpath
((
self
.
output_dir
,
os
.
path
.
real
path
(
full_output_folder
)))
!=
self
.
output_dir
:
if
os
.
path
.
commonpath
((
self
.
output_dir
,
os
.
path
.
abs
path
(
full_output_folder
)))
!=
self
.
output_dir
:
print
(
"Saving image outside the output folder is not allowed."
)
print
(
"Saving image outside the output folder is not allowed."
)
return
{}
return
{}
...
@@ -908,6 +908,69 @@ class ImageInvert:
...
@@ -908,6 +908,69 @@ class ImageInvert:
return
(
s
,)
return
(
s
,)
class
ImagePadForOutpaint
:
@
classmethod
def
INPUT_TYPES
(
s
):
return
{
"required"
:
{
"image"
:
(
"IMAGE"
,),
"left"
:
(
"INT"
,
{
"default"
:
0
,
"min"
:
0
,
"max"
:
MAX_RESOLUTION
,
"step"
:
64
}),
"top"
:
(
"INT"
,
{
"default"
:
0
,
"min"
:
0
,
"max"
:
MAX_RESOLUTION
,
"step"
:
64
}),
"right"
:
(
"INT"
,
{
"default"
:
0
,
"min"
:
0
,
"max"
:
MAX_RESOLUTION
,
"step"
:
64
}),
"bottom"
:
(
"INT"
,
{
"default"
:
0
,
"min"
:
0
,
"max"
:
MAX_RESOLUTION
,
"step"
:
64
}),
"feathering"
:
(
"INT"
,
{
"default"
:
40
,
"min"
:
0
,
"max"
:
MAX_RESOLUTION
,
"step"
:
1
}),
}
}
RETURN_TYPES
=
(
"IMAGE"
,
"MASK"
)
FUNCTION
=
"expand_image"
CATEGORY
=
"image"
def
expand_image
(
self
,
image
,
left
,
top
,
right
,
bottom
,
feathering
):
d1
,
d2
,
d3
,
d4
=
image
.
size
()
new_image
=
torch
.
zeros
(
(
d1
,
d2
+
top
+
bottom
,
d3
+
left
+
right
,
d4
),
dtype
=
torch
.
float32
,
)
new_image
[:,
top
:
top
+
d2
,
left
:
left
+
d3
,
:]
=
image
mask
=
torch
.
ones
(
(
d2
+
top
+
bottom
,
d3
+
left
+
right
),
dtype
=
torch
.
float32
,
)
t
=
torch
.
zeros
(
(
d2
,
d3
),
dtype
=
torch
.
float32
)
if
feathering
>
0
and
feathering
*
2
<
d2
and
feathering
*
2
<
d3
:
for
i
in
range
(
d2
):
for
j
in
range
(
d3
):
dt
=
i
if
top
!=
0
else
d2
db
=
d2
-
i
if
bottom
!=
0
else
d2
dl
=
j
if
left
!=
0
else
d3
dr
=
d3
-
j
if
right
!=
0
else
d3
d
=
min
(
dt
,
db
,
dl
,
dr
)
if
d
>=
feathering
:
continue
v
=
(
feathering
-
d
)
/
feathering
t
[
i
,
j
]
=
v
*
v
mask
[
top
:
top
+
d2
,
left
:
left
+
d3
]
=
t
return
(
new_image
,
mask
)
NODE_CLASS_MAPPINGS
=
{
NODE_CLASS_MAPPINGS
=
{
"KSampler"
:
KSampler
,
"KSampler"
:
KSampler
,
"CheckpointLoader"
:
CheckpointLoader
,
"CheckpointLoader"
:
CheckpointLoader
,
...
@@ -926,6 +989,7 @@ NODE_CLASS_MAPPINGS = {
...
@@ -926,6 +989,7 @@ NODE_CLASS_MAPPINGS = {
"LoadImageMask"
:
LoadImageMask
,
"LoadImageMask"
:
LoadImageMask
,
"ImageScale"
:
ImageScale
,
"ImageScale"
:
ImageScale
,
"ImageInvert"
:
ImageInvert
,
"ImageInvert"
:
ImageInvert
,
"ImagePadForOutpaint"
:
ImagePadForOutpaint
,
"ConditioningCombine"
:
ConditioningCombine
,
"ConditioningCombine"
:
ConditioningCombine
,
"ConditioningSetArea"
:
ConditioningSetArea
,
"ConditioningSetArea"
:
ConditioningSetArea
,
"KSamplerAdvanced"
:
KSamplerAdvanced
,
"KSamplerAdvanced"
:
KSamplerAdvanced
,
...
...
notebooks/comfyui_colab.ipynb
View file @
a49b5659
{
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": []
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
},
"accelerator": "GPU",
"gpuClass": "standard"
},
"cells": [
"cells": [
{
{
"cell_type": "markdown",
"cell_type": "markdown",
"source": [
"Git clone the repo and install the requirements. (ignore the pip errors about protobuf)"
],
"metadata": {
"metadata": {
"id": "aaaaaaaaaa"
"id": "aaaaaaaaaa"
}
},
"source": [
"Git clone the repo and install the requirements. (ignore the pip errors about protobuf)"
]
},
},
{
{
"cell_type": "code",
"cell_type": "code",
...
@@ -33,22 +17,55 @@
...
@@ -33,22 +17,55 @@
},
},
"outputs": [],
"outputs": [],
"source": [
"source": [
"!git clone https://github.com/comfyanonymous/ComfyUI\n",
"#@title Environment Setup\n",
"%cd ComfyUI\n",
"\n",
"!pip install xformers -r requirements.txt"
"from pathlib import Path\n",
"\n",
"OPTIONS = {}\n",
"\n",
"USE_GOOGLE_DRIVE = False #@param {type:\"boolean\"}\n",
"UPDATE_COMFY_UI = True #@param {type:\"boolean\"}\n",
"WORKSPACE = 'ComfyUI'\n",
"OPTIONS['USE_GOOGLE_DRIVE'] = USE_GOOGLE_DRIVE\n",
"OPTIONS['UPDATE_COMFY_UI'] = UPDATE_COMFY_UI\n",
"\n",
"if OPTIONS['USE_GOOGLE_DRIVE']:\n",
" !echo \"Mounting Google Drive...\"\n",
" %cd /\n",
" \n",
" from google.colab import drive\n",
" drive.mount('/content/drive')\n",
"\n",
" WORKSPACE = \"/content/drive/MyDrive/ComfyUI\"\n",
" %cd /content/drive/MyDrive\n",
"\n",
"![ ! -d $WORKSPACE ] && echo -= Initial setup ComfyUI =- && git clone https://github.com/comfyanonymous/ComfyUI\n",
"%cd $WORKSPACE\n",
"\n",
"if OPTIONS['UPDATE_COMFY_UI']:\n",
" !echo -= Updating ComfyUI =-\n",
" !git pull\n",
"\n",
"!echo -= Install dependencies =-\n",
"!pip -q install xformers -r requirements.txt"
]
]
},
},
{
{
"cell_type": "markdown",
"cell_type": "markdown",
"source": [
"Download some models/checkpoints/vae or custom comfyui nodes (uncomment the commands for the ones you want)"
],
"metadata": {
"metadata": {
"id": "cccccccccc"
"id": "cccccccccc"
}
},
"source": [
"Download some models/checkpoints/vae or custom comfyui nodes (uncomment the commands for the ones you want)"
]
},
},
{
{
"cell_type": "code",
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "dddddddddd"
},
"outputs": [],
"source": [
"source": [
"# Checkpoints\n",
"# Checkpoints\n",
"\n",
"\n",
...
@@ -110,26 +127,26 @@
...
@@ -110,26 +127,26 @@
"#!wget -c https://huggingface.co/sberbank-ai/Real-ESRGAN/resolve/main/RealESRGAN_x4.pth -P ./models/upscale_models/\n",
"#!wget -c https://huggingface.co/sberbank-ai/Real-ESRGAN/resolve/main/RealESRGAN_x4.pth -P ./models/upscale_models/\n",
"\n",
"\n",
"\n"
"\n"
],
]
"metadata": {
"id": "dddddddddd"
},
"execution_count": null,
"outputs": []
},
},
{
{
"cell_type": "markdown",
"cell_type": "markdown",
"metadata": {
"id": "kkkkkkkkkkkkkk"
},
"source": [
"source": [
"### Run ComfyUI with localtunnel (Recommended Way)\n",
"### Run ComfyUI with localtunnel (Recommended Way)\n",
"\n",
"\n",
"\n"
"\n"
],
]
"metadata": {
"id": "kkkkkkkkkkkkkk"
}
},
},
{
{
"cell_type": "code",
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "jjjjjjjjjjjjj"
},
"outputs": [],
"source": [
"source": [
"!npm install -g localtunnel\n",
"!npm install -g localtunnel\n",
"\n",
"\n",
...
@@ -154,15 +171,13 @@
...
@@ -154,15 +171,13 @@
"threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n",
"threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n",
"\n",
"\n",
"!python main.py --dont-print-server"
"!python main.py --dont-print-server"
],
]
"metadata": {
"id": "jjjjjjjjjjjjj"
},
"execution_count": null,
"outputs": []
},
},
{
{
"cell_type": "markdown",
"cell_type": "markdown",
"metadata": {
"id": "gggggggggg"
},
"source": [
"source": [
"### Run ComfyUI with colab iframe (use only in case the previous way with localtunnel doesn't work)\n",
"### Run ComfyUI with colab iframe (use only in case the previous way with localtunnel doesn't work)\n",
"\n",
"\n",
...
@@ -171,13 +186,15 @@
...
@@ -171,13 +186,15 @@
"If you want to open it in another window use the link.\n",
"If you want to open it in another window use the link.\n",
"\n",
"\n",
"Note that some UI features like live image previews won't work because the colab iframe blocks websockets."
"Note that some UI features like live image previews won't work because the colab iframe blocks websockets."
],
]
"metadata": {
"id": "gggggggggg"
}
},
},
{
{
"cell_type": "code",
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "hhhhhhhhhh"
},
"outputs": [],
"source": [
"source": [
"import threading\n",
"import threading\n",
"import time\n",
"import time\n",
...
@@ -198,12 +215,23 @@
...
@@ -198,12 +215,23 @@
"threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n",
"threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n",
"\n",
"\n",
"!python main.py --dont-print-server"
"!python main.py --dont-print-server"
],
]
"metadata": {
"id": "hhhhhhhhhh"
},
"execution_count": null,
"outputs": []
}
}
]
],
}
"metadata": {
\ No newline at end of file
"accelerator": "GPU",
"colab": {
"provenance": []
},
"gpuClass": "standard",
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
}
},
"nbformat": 4,
"nbformat_minor": 0
}
server.py
View file @
a49b5659
...
@@ -127,7 +127,7 @@ class PromptServer():
...
@@ -127,7 +127,7 @@ class PromptServer():
output_dir
=
os
.
path
.
join
(
os
.
path
.
dirname
(
os
.
path
.
realpath
(
__file__
)),
type
)
output_dir
=
os
.
path
.
join
(
os
.
path
.
dirname
(
os
.
path
.
realpath
(
__file__
)),
type
)
if
"subfolder"
in
request
.
rel_url
.
query
:
if
"subfolder"
in
request
.
rel_url
.
query
:
full_output_dir
=
os
.
path
.
join
(
output_dir
,
request
.
rel_url
.
query
[
"subfolder"
])
full_output_dir
=
os
.
path
.
join
(
output_dir
,
request
.
rel_url
.
query
[
"subfolder"
])
if
os
.
path
.
commonpath
((
os
.
path
.
real
path
(
full_output_dir
),
output_dir
))
!=
output_dir
:
if
os
.
path
.
commonpath
((
os
.
path
.
abs
path
(
full_output_dir
),
output_dir
))
!=
output_dir
:
return
web
.
Response
(
status
=
403
)
return
web
.
Response
(
status
=
403
)
output_dir
=
full_output_dir
output_dir
=
full_output_dir
...
...
web/extensions/core/widgetInputs.js
0 → 100644
View file @
a49b5659
import
{
ComfyWidgets
,
addRandomizeWidget
}
from
"
/scripts/widgets.js
"
;
import
{
app
}
from
"
/scripts/app.js
"
;
const
CONVERTED_TYPE
=
"
converted-widget
"
;
const
VALID_TYPES
=
[
"
STRING
"
,
"
combo
"
,
"
number
"
];
function
isConvertableWidget
(
widget
,
config
)
{
return
VALID_TYPES
.
includes
(
widget
.
type
)
||
VALID_TYPES
.
includes
(
config
[
0
]);
}
function
hideWidget
(
node
,
widget
,
suffix
=
""
)
{
widget
.
origType
=
widget
.
type
;
widget
.
origComputeSize
=
widget
.
computeSize
;
widget
.
origSerializeValue
=
widget
.
serializeValue
;
widget
.
computeSize
=
()
=>
[
0
,
-
4
];
// -4 is due to the gap litegraph adds between widgets automatically
widget
.
type
=
CONVERTED_TYPE
+
suffix
;
widget
.
serializeValue
=
()
=>
{
// Prevent serializing the widget if we have no input linked
const
{
link
}
=
node
.
inputs
.
find
((
i
)
=>
i
.
widget
?.
name
===
widget
.
name
);
if
(
link
==
null
)
{
return
undefined
;
}
return
widget
.
value
;
};
// Hide any linked widgets, e.g. seed+randomize
if
(
widget
.
linkedWidgets
)
{
for
(
const
w
of
widget
.
linkedWidgets
)
{
hideWidget
(
node
,
w
,
"
:
"
+
widget
.
name
);
}
}
}
function
showWidget
(
widget
)
{
widget
.
type
=
widget
.
origType
;
widget
.
computeSize
=
widget
.
origComputeSize
;
widget
.
serializeValue
=
widget
.
origSerializeValue
;
delete
widget
.
origType
;
delete
widget
.
origComputeSize
;
delete
widget
.
origSerializeValue
;
// Hide any linked widgets, e.g. seed+randomize
if
(
widget
.
linkedWidgets
)
{
for
(
const
w
of
widget
.
linkedWidgets
)
{
showWidget
(
w
);
}
}
}
function
convertToInput
(
node
,
widget
,
config
)
{
hideWidget
(
node
,
widget
);
const
{
linkType
}
=
getWidgetType
(
config
);
// Add input and store widget config for creating on primitive node
const
sz
=
node
.
size
;
node
.
addInput
(
widget
.
name
,
linkType
,
{
widget
:
{
name
:
widget
.
name
,
config
},
});
// Restore original size but grow if needed
node
.
setSize
([
Math
.
max
(
sz
[
0
],
node
.
size
[
0
]),
Math
.
max
(
sz
[
1
],
node
.
size
[
1
])]);
}
function
convertToWidget
(
node
,
widget
)
{
showWidget
(
widget
);
const
sz
=
node
.
size
;
node
.
removeInput
(
node
.
inputs
.
findIndex
((
i
)
=>
i
.
widget
?.
name
===
widget
.
name
));
// Restore original size but grow if needed
node
.
setSize
([
Math
.
max
(
sz
[
0
],
node
.
size
[
0
]),
Math
.
max
(
sz
[
1
],
node
.
size
[
1
])]);
}
function
getWidgetType
(
config
)
{
// Special handling for COMBO so we restrict links based on the entries
let
type
=
config
[
0
];
let
linkType
=
type
;
if
(
type
instanceof
Array
)
{
type
=
"
COMBO
"
;
linkType
=
linkType
.
join
(
"
,
"
);
}
return
{
type
,
linkType
};
}
app
.
registerExtension
({
name
:
"
Comfy.WidgetInputs
"
,
async
beforeRegisterNodeDef
(
nodeType
,
nodeData
,
app
)
{
// Add menu options to conver to/from widgets
const
origGetExtraMenuOptions
=
nodeType
.
prototype
.
getExtraMenuOptions
;
nodeType
.
prototype
.
getExtraMenuOptions
=
function
(
_
,
options
)
{
const
r
=
origGetExtraMenuOptions
?
origGetExtraMenuOptions
.
apply
(
this
,
arguments
)
:
undefined
;
if
(
this
.
widgets
)
{
let
toInput
=
[];
let
toWidget
=
[];
for
(
const
w
of
this
.
widgets
)
{
if
(
w
.
type
===
CONVERTED_TYPE
)
{
toWidget
.
push
({
content
:
`Convert
${
w
.
name
}
to widget`
,
callback
:
()
=>
convertToWidget
(
this
,
w
),
});
}
else
{
const
config
=
nodeData
?.
input
?.
required
[
w
.
name
]
||
[
w
.
type
,
w
.
options
||
{}];
if
(
isConvertableWidget
(
w
,
config
))
{
toInput
.
push
({
content
:
`Convert
${
w
.
name
}
to input`
,
callback
:
()
=>
convertToInput
(
this
,
w
,
config
),
});
}
}
}
if
(
toInput
.
length
)
{
options
.
push
(...
toInput
,
null
);
}
if
(
toWidget
.
length
)
{
options
.
push
(...
toWidget
,
null
);
}
}
return
r
;
};
// On initial configure of nodes hide all converted widgets
const
origOnConfigure
=
nodeType
.
prototype
.
onConfigure
;
nodeType
.
prototype
.
onConfigure
=
function
()
{
const
r
=
origOnConfigure
?
origOnConfigure
.
apply
(
this
,
arguments
)
:
undefined
;
if
(
this
.
inputs
)
{
for
(
const
input
of
this
.
inputs
)
{
if
(
input
.
widget
)
{
const
w
=
this
.
widgets
.
find
((
w
)
=>
w
.
name
===
input
.
widget
.
name
);
if
(
w
)
{
hideWidget
(
this
,
w
);
}
else
{
convertToWidget
(
this
,
input
)
}
}
}
}
return
r
;
};
function
isNodeAtPos
(
pos
)
{
for
(
const
n
of
app
.
graph
.
_nodes
)
{
if
(
n
.
pos
[
0
]
===
pos
[
0
]
&&
n
.
pos
[
1
]
===
pos
[
1
])
{
return
true
;
}
}
return
false
;
}
// Double click a widget input to automatically attach a primitive
const
origOnInputDblClick
=
nodeType
.
prototype
.
onInputDblClick
;
const
ignoreDblClick
=
Symbol
();
nodeType
.
prototype
.
onInputDblClick
=
function
(
slot
)
{
const
r
=
origOnInputDblClick
?
origOnInputDblClick
.
apply
(
this
,
arguments
)
:
undefined
;
const
input
=
this
.
inputs
[
slot
];
if
(
input
.
widget
&&
!
input
[
ignoreDblClick
])
{
const
node
=
LiteGraph
.
createNode
(
"
PrimitiveNode
"
);
app
.
graph
.
add
(
node
);
// Calculate a position that wont directly overlap another node
const
pos
=
[
this
.
pos
[
0
]
-
node
.
size
[
0
]
-
30
,
this
.
pos
[
1
]];
while
(
isNodeAtPos
(
pos
))
{
pos
[
1
]
+=
LiteGraph
.
NODE_TITLE_HEIGHT
;
}
node
.
pos
=
pos
;
node
.
connect
(
0
,
this
,
slot
);
node
.
title
=
input
.
name
;
// Prevent adding duplicates due to triple clicking
input
[
ignoreDblClick
]
=
true
;
setTimeout
(()
=>
{
delete
input
[
ignoreDblClick
];
},
300
);
}
return
r
;
};
},
registerCustomNodes
()
{
class
PrimitiveNode
{
constructor
()
{
this
.
addOutput
(
"
connect to widget input
"
,
"
*
"
);
this
.
serialize_widgets
=
true
;
this
.
isVirtualNode
=
true
;
}
applyToGraph
()
{
if
(
!
this
.
outputs
[
0
].
links
?.
length
)
return
;
// For each output link copy our value over the original widget value
for
(
const
l
of
this
.
outputs
[
0
].
links
)
{
const
linkInfo
=
app
.
graph
.
links
[
l
];
const
node
=
this
.
graph
.
getNodeById
(
linkInfo
.
target_id
);
const
input
=
node
.
inputs
[
linkInfo
.
target_slot
];
const
widgetName
=
input
.
widget
.
name
;
if
(
widgetName
)
{
const
widget
=
node
.
widgets
.
find
((
w
)
=>
w
.
name
===
widgetName
);
if
(
widget
)
{
widget
.
value
=
this
.
widgets
[
0
].
value
;
if
(
widget
.
callback
)
{
widget
.
callback
(
widget
.
value
,
app
.
canvas
,
node
,
app
.
canvas
.
graph_mouse
,
{});
}
}
}
}
}
onConnectionsChange
(
_
,
index
,
connected
)
{
if
(
connected
)
{
if
(
this
.
outputs
[
0
].
links
?.
length
)
{
if
(
!
this
.
widgets
?.
length
)
{
this
.
#
onFirstConnection
();
}
if
(
!
this
.
widgets
?.
length
&&
this
.
outputs
[
0
].
widget
)
{
// On first load it often cant recreate the widget as the other node doesnt exist yet
// Manually recreate it from the output info
this
.
#
createWidget
(
this
.
outputs
[
0
].
widget
.
config
);
}
}
}
else
if
(
!
this
.
outputs
[
0
].
links
?.
length
)
{
this
.
#
onLastDisconnect
();
}
}
onConnectOutput
(
slot
,
type
,
input
,
target_node
,
target_slot
)
{
// Fires before the link is made allowing us to reject it if it isn't valid
// No widget, we cant connect
if
(
!
input
.
widget
)
return
false
;
if
(
this
.
outputs
[
slot
].
links
?.
length
)
{
return
this
.
#
isValidConnection
(
input
);
}
}
#
onFirstConnection
()
{
// First connection can fire before the graph is ready on initial load so random things can be missing
const
linkId
=
this
.
outputs
[
0
].
links
[
0
];
const
link
=
this
.
graph
.
links
[
linkId
];
if
(
!
link
)
return
;
const
theirNode
=
this
.
graph
.
getNodeById
(
link
.
target_id
);
if
(
!
theirNode
||
!
theirNode
.
inputs
)
return
;
const
input
=
theirNode
.
inputs
[
link
.
target_slot
];
if
(
!
input
)
return
;
const
widget
=
input
.
widget
;
const
{
type
,
linkType
}
=
getWidgetType
(
widget
.
config
);
// Update our output to restrict to the widget type
this
.
outputs
[
0
].
type
=
linkType
;
this
.
outputs
[
0
].
name
=
type
;
this
.
outputs
[
0
].
widget
=
widget
;
this
.
#
createWidget
(
widget
.
config
,
theirNode
,
widget
.
name
);
}
#
createWidget
(
inputData
,
node
,
widgetName
)
{
let
type
=
inputData
[
0
];
if
(
type
instanceof
Array
)
{
type
=
"
COMBO
"
;
}
let
widget
;
if
(
type
in
ComfyWidgets
)
{
widget
=
(
ComfyWidgets
[
type
](
this
,
"
value
"
,
inputData
,
app
)
||
{}).
widget
;
}
else
{
widget
=
this
.
addWidget
(
type
,
"
value
"
,
null
,
()
=>
{},
{});
}
if
(
node
?.
widgets
&&
widget
)
{
const
theirWidget
=
node
.
widgets
.
find
((
w
)
=>
w
.
name
===
widgetName
);
if
(
theirWidget
)
{
widget
.
value
=
theirWidget
.
value
;
}
}
if
(
widget
.
type
===
"
number
"
)
{
addRandomizeWidget
(
this
,
widget
,
"
Random after every gen
"
);
}
// When our value changes, update other widgets to reflect our changes
// e.g. so LoadImage shows correct image
const
callback
=
widget
.
callback
;
const
self
=
this
;
widget
.
callback
=
function
()
{
const
r
=
callback
?
callback
.
apply
(
this
,
arguments
)
:
undefined
;
self
.
applyToGraph
();
return
r
;
};
// Grow our node if required
const
sz
=
this
.
computeSize
();
if
(
this
.
size
[
0
]
<
sz
[
0
])
{
this
.
size
[
0
]
=
sz
[
0
];
}
if
(
this
.
size
[
1
]
<
sz
[
1
])
{
this
.
size
[
1
]
=
sz
[
1
];
}
requestAnimationFrame
(()
=>
{
if
(
this
.
onResize
)
{
this
.
onResize
(
this
.
size
);
}
});
}
#
isValidConnection
(
input
)
{
// Only allow connections where the configs match
const
config1
=
this
.
outputs
[
0
].
widget
.
config
;
const
config2
=
input
.
widget
.
config
;
if
(
config1
[
0
]
!==
config2
[
0
])
return
false
;
for
(
const
k
in
config1
[
1
])
{
if
(
k
!==
"
default
"
)
{
if
(
config1
[
1
][
k
]
!==
config2
[
1
][
k
])
{
return
false
;
}
}
}
return
true
;
}
#
onLastDisconnect
()
{
// We cant remove + re-add the output here as if you drag a link over the same link
// it removes, then re-adds, causing it to break
this
.
outputs
[
0
].
type
=
"
*
"
;
this
.
outputs
[
0
].
name
=
"
connect to widget input
"
;
delete
this
.
outputs
[
0
].
widget
;
if
(
this
.
widgets
)
{
// Allow widgets to cleanup
for
(
const
w
of
this
.
widgets
)
{
if
(
w
.
onRemove
)
{
w
.
onRemove
();
}
}
this
.
widgets
.
length
=
0
;
}
}
}
LiteGraph
.
registerNodeType
(
"
PrimitiveNode
"
,
Object
.
assign
(
PrimitiveNode
,
{
title
:
"
Primitive
"
,
})
);
PrimitiveNode
.
category
=
"
utils
"
;
},
});
web/index.html
View file @
a49b5659
<!DOCTYPE html>
<!DOCTYPE html>
<html
lang=
"en"
>
<html
lang=
"en"
>
<head>
<head>
<meta
charset=
"UTF-8"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0, user-scalable=no"
>
<link
rel=
"stylesheet"
type=
"text/css"
href=
"lib/litegraph.css"
/>
<link
rel=
"stylesheet"
type=
"text/css"
href=
"lib/litegraph.css"
/>
<link
rel=
"stylesheet"
type=
"text/css"
href=
"style.css"
/>
<link
rel=
"stylesheet"
type=
"text/css"
href=
"style.css"
/>
<script
type=
"text/javascript"
src=
"lib/litegraph.core.js"
></script>
<script
type=
"text/javascript"
src=
"lib/litegraph.core.js"
></script>
<script
type=
"module"
>
<script
type=
"module"
>
import
{
app
}
from
"
/scripts/app.js
"
;
import
{
app
}
from
"
/scripts/app.js
"
;
await
app
.
setup
();
await
app
.
setup
();
...
...
web/jsconfig.json
0 → 100644
View file @
a49b5659
{
"compilerOptions"
:
{
"baseUrl"
:
"."
,
"paths"
:
{
"/*"
:
[
"./*"
]
}
},
"include"
:
[
"."
]
}
web/lib/litegraph.core.js
View file @
a49b5659
...
@@ -108,7 +108,7 @@
...
@@ -108,7 +108,7 @@
node_box_coloured_when_on
:
false
,
// [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback
node_box_coloured_when_on
:
false
,
// [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback
node_box_coloured_by_mode
:
false
,
// [true!] nodebox based on node mode, visual feedback
node_box_coloured_by_mode
:
false
,
// [true!] nodebox based on node mode, visual feedback
dialog_close_on_mouse_leave
:
tru
e
,
// [false on mobile] better true if not touch device, TODO add an helper/listener to close if false
dialog_close_on_mouse_leave
:
fals
e
,
// [false on mobile] better true if not touch device, TODO add an helper/listener to close if false
dialog_close_on_mouse_leave_delay
:
500
,
dialog_close_on_mouse_leave_delay
:
500
,
shift_click_do_break_link_from
:
false
,
// [false!] prefer false if results too easy to break links - implement with ALT or TODO custom keys
shift_click_do_break_link_from
:
false
,
// [false!] prefer false if results too easy to break links - implement with ALT or TODO custom keys
...
@@ -138,7 +138,7 @@
...
@@ -138,7 +138,7 @@
release_link_on_empty_shows_menu
:
false
,
//[true!] dragging a link to empty space will open a menu, add from list, search or defaults
release_link_on_empty_shows_menu
:
false
,
//[true!] dragging a link to empty space will open a menu, add from list, search or defaults
pointerevents_method
:
"
mouse
"
,
// "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now)
pointerevents_method
:
"
pointer
"
,
// "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now)
// TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary)
// TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary)
/**
/**
...
@@ -5801,7 +5801,7 @@ LGraphNode.prototype.executeAction = function(action)
...
@@ -5801,7 +5801,7 @@ LGraphNode.prototype.executeAction = function(action)
var
skip_action
=
false
;
var
skip_action
=
false
;
var
now
=
LiteGraph
.
getTime
();
var
now
=
LiteGraph
.
getTime
();
var
is_primary
=
(
e
.
isPrimary
===
undefined
||
!
e
.
isPrimary
);
var
is_primary
=
(
e
.
isPrimary
===
undefined
||
!
e
.
isPrimary
);
var
is_double_click
=
(
now
-
this
.
last_mouseclick
<
300
)
&&
is_primary
;
var
is_double_click
=
(
now
-
this
.
last_mouseclick
<
300
);
this
.
mouse
[
0
]
=
e
.
clientX
;
this
.
mouse
[
0
]
=
e
.
clientX
;
this
.
mouse
[
1
]
=
e
.
clientY
;
this
.
mouse
[
1
]
=
e
.
clientY
;
this
.
graph_mouse
[
0
]
=
e
.
canvasX
;
this
.
graph_mouse
[
0
]
=
e
.
canvasX
;
...
...
web/scripts/app.js
View file @
a49b5659
...
@@ -486,6 +486,27 @@ class ComfyApp {
...
@@ -486,6 +486,27 @@ class ComfyApp {
}
}
}
}
/**
* Setup slot colors for types
*/
setupSlotColors
()
{
let
colors
=
{
"
CLIP
"
:
"
#FFD500
"
,
// bright yellow
"
CLIP_VISION
"
:
"
#A8DADC
"
,
// light blue-gray
"
CLIP_VISION_OUTPUT
"
:
"
#ad7452
"
,
// rusty brown-orange
"
CONDITIONING
"
:
"
#FFA931
"
,
// vibrant orange-yellow
"
CONTROL_NET
"
:
"
#6EE7B7
"
,
// soft mint green
"
IMAGE
"
:
"
#64B5F6
"
,
// bright sky blue
"
LATENT
"
:
"
#FF9CF9
"
,
// light pink-purple
"
MASK
"
:
"
#81C784
"
,
// muted green
"
MODEL
"
:
"
#B39DDB
"
,
// light lavender-purple
"
STYLE_MODEL
"
:
"
#C2FFAE
"
,
// light green-yellow
"
VAE
"
:
"
#FF6E6E
"
,
// bright red
};
Object
.
assign
(
this
.
canvas
.
default_connection_color_byType
,
colors
);
}
/**
/**
* Set up the app on the page
* Set up the app on the page
*/
*/
...
@@ -494,13 +515,15 @@ class ComfyApp {
...
@@ -494,13 +515,15 @@ class ComfyApp {
// Create and mount the LiteGraph in the DOM
// Create and mount the LiteGraph in the DOM
const
canvasEl
=
(
this
.
canvasEl
=
Object
.
assign
(
document
.
createElement
(
"
canvas
"
),
{
id
:
"
graph-canvas
"
}));
const
canvasEl
=
(
this
.
canvasEl
=
Object
.
assign
(
document
.
createElement
(
"
canvas
"
),
{
id
:
"
graph-canvas
"
}));
canvasEl
.
tabIndex
=
"
1
"
canvasEl
.
tabIndex
=
"
1
"
;
document
.
body
.
prepend
(
canvasEl
);
document
.
body
.
prepend
(
canvasEl
);
this
.
graph
=
new
LGraph
();
this
.
graph
=
new
LGraph
();
const
canvas
=
(
this
.
canvas
=
new
LGraphCanvas
(
canvasEl
,
this
.
graph
));
const
canvas
=
(
this
.
canvas
=
new
LGraphCanvas
(
canvasEl
,
this
.
graph
));
this
.
ctx
=
canvasEl
.
getContext
(
"
2d
"
);
this
.
ctx
=
canvasEl
.
getContext
(
"
2d
"
);
this
.
setupSlotColors
();
this
.
graph
.
start
();
this
.
graph
.
start
();
function
resizeCanvas
()
{
function
resizeCanvas
()
{
...
@@ -525,7 +548,9 @@ class ComfyApp {
...
@@ -525,7 +548,9 @@ class ComfyApp {
this
.
loadGraphData
(
workflow
);
this
.
loadGraphData
(
workflow
);
restored
=
true
;
restored
=
true
;
}
}
}
catch
(
err
)
{}
}
catch
(
err
)
{
console
.
error
(
"
Error loading previous workflow
"
,
err
);
}
// We failed to restore a workflow so load the default
// We failed to restore a workflow so load the default
if
(
!
restored
)
{
if
(
!
restored
)
{
...
@@ -572,12 +597,8 @@ class ComfyApp {
...
@@ -572,12 +597,8 @@ class ComfyApp {
const
type
=
inputData
[
0
];
const
type
=
inputData
[
0
];
if
(
Array
.
isArray
(
type
))
{
if
(
Array
.
isArray
(
type
))
{
// Enums e.g. latent rotation
// Enums
let
defaultValue
=
type
[
0
];
Object
.
assign
(
config
,
widgets
.
COMBO
(
this
,
inputName
,
inputData
,
app
)
||
{});
if
(
inputData
[
1
]
&&
inputData
[
1
].
default
)
{
defaultValue
=
inputData
[
1
].
default
;
}
this
.
addWidget
(
"
combo
"
,
inputName
,
defaultValue
,
()
=>
{},
{
values
:
type
});
}
else
if
(
`
${
type
}
:
${
inputName
}
`
in
widgets
)
{
}
else
if
(
`
${
type
}
:
${
inputName
}
`
in
widgets
)
{
// Support custom widgets by Type:Name
// Support custom widgets by Type:Name
Object
.
assign
(
config
,
widgets
[
`
${
type
}
:
${
inputName
}
`
](
this
,
inputName
,
inputData
,
app
)
||
{});
Object
.
assign
(
config
,
widgets
[
`
${
type
}
:
${
inputName
}
`
](
this
,
inputName
,
inputData
,
app
)
||
{});
...
@@ -667,11 +688,15 @@ class ComfyApp {
...
@@ -667,11 +688,15 @@ class ComfyApp {
async
graphToPrompt
()
{
async
graphToPrompt
()
{
const
workflow
=
this
.
graph
.
serialize
();
const
workflow
=
this
.
graph
.
serialize
();
const
output
=
{};
const
output
=
{};
for
(
const
n
of
workflow
.
nodes
)
{
// Process nodes in order of execution
const
node
=
this
.
graph
.
getNodeById
(
n
.
id
);
for
(
const
node
of
this
.
graph
.
computeExecutionOrder
(
false
))
{
const
n
=
workflow
.
nodes
.
find
((
n
)
=>
n
.
id
===
node
.
id
);
if
(
node
.
isVirtualNode
)
{
if
(
node
.
isVirtualNode
)
{
// Don't serialize frontend only nodes
// Don't serialize frontend only nodes but let them make changes
if
(
node
.
applyToGraph
)
{
node
.
applyToGraph
(
workflow
);
}
continue
;
continue
;
}
}
...
@@ -695,7 +720,11 @@ class ComfyApp {
...
@@ -695,7 +720,11 @@ class ComfyApp {
let
link
=
node
.
getInputLink
(
i
);
let
link
=
node
.
getInputLink
(
i
);
while
(
parent
&&
parent
.
isVirtualNode
)
{
while
(
parent
&&
parent
.
isVirtualNode
)
{
link
=
parent
.
getInputLink
(
link
.
origin_slot
);
link
=
parent
.
getInputLink
(
link
.
origin_slot
);
parent
=
parent
.
getInputNode
(
link
.
origin_slot
);
if
(
link
)
{
parent
=
parent
.
getInputNode
(
link
.
origin_slot
);
}
else
{
parent
=
null
;
}
}
}
if
(
link
)
{
if
(
link
)
{
...
...
web/scripts/ui.js
View file @
a49b5659
...
@@ -35,6 +35,54 @@ function $el(tag, propsOrChildren, children) {
...
@@ -35,6 +35,54 @@ function $el(tag, propsOrChildren, children) {
return
element
;
return
element
;
}
}
function
dragElement
(
dragEl
)
{
var
posDiffX
=
0
,
posDiffY
=
0
,
posStartX
=
0
,
posStartY
=
0
,
newPosX
=
0
,
newPosY
=
0
;
if
(
dragEl
.
getElementsByClassName
(
'
drag-handle
'
)[
0
])
{
// if present, the handle is where you move the DIV from:
dragEl
.
getElementsByClassName
(
'
drag-handle
'
)[
0
].
onmousedown
=
dragMouseDown
;
}
else
{
// otherwise, move the DIV from anywhere inside the DIV:
dragEl
.
onmousedown
=
dragMouseDown
;
}
function
dragMouseDown
(
e
)
{
e
=
e
||
window
.
event
;
e
.
preventDefault
();
// get the mouse cursor position at startup:
posStartX
=
e
.
clientX
;
posStartY
=
e
.
clientY
;
document
.
onmouseup
=
closeDragElement
;
// call a function whenever the cursor moves:
document
.
onmousemove
=
elementDrag
;
}
function
elementDrag
(
e
)
{
e
=
e
||
window
.
event
;
e
.
preventDefault
();
// calculate the new cursor position:
posDiffX
=
e
.
clientX
-
posStartX
;
posDiffY
=
e
.
clientY
-
posStartY
;
posStartX
=
e
.
clientX
;
posStartY
=
e
.
clientY
;
newPosX
=
Math
.
min
((
document
.
body
.
clientWidth
-
dragEl
.
clientWidth
),
Math
.
max
(
0
,
(
dragEl
.
offsetLeft
+
posDiffX
)));
newPosY
=
Math
.
min
((
document
.
body
.
clientHeight
-
dragEl
.
clientHeight
),
Math
.
max
(
0
,
(
dragEl
.
offsetTop
+
posDiffY
)));
// set the element's new position:
dragEl
.
style
.
top
=
newPosY
+
"
px
"
;
dragEl
.
style
.
left
=
newPosX
+
"
px
"
;
}
function
closeDragElement
()
{
// stop moving when mouse button is released:
document
.
onmouseup
=
null
;
document
.
onmousemove
=
null
;
}
}
class
ComfyDialog
{
class
ComfyDialog
{
constructor
()
{
constructor
()
{
this
.
element
=
$el
(
"
div.comfy-modal
"
,
{
parent
:
document
.
body
},
[
this
.
element
=
$el
(
"
div.comfy-modal
"
,
{
parent
:
document
.
body
},
[
...
@@ -253,6 +301,7 @@ export class ComfyUI {
...
@@ -253,6 +301,7 @@ export class ComfyUI {
this
.
menuContainer
=
$el
(
"
div.comfy-menu
"
,
{
parent
:
document
.
body
},
[
this
.
menuContainer
=
$el
(
"
div.comfy-menu
"
,
{
parent
:
document
.
body
},
[
$el
(
"
div
"
,
{
style
:
{
overflow
:
"
hidden
"
,
position
:
"
relative
"
,
width
:
"
100%
"
}
},
[
$el
(
"
div
"
,
{
style
:
{
overflow
:
"
hidden
"
,
position
:
"
relative
"
,
width
:
"
100%
"
}
},
[
$el
(
"
span.drag-handle
"
),
$el
(
"
span
"
,
{
$
:
(
q
)
=>
(
this
.
queueSize
=
q
)
}),
$el
(
"
span
"
,
{
$
:
(
q
)
=>
(
this
.
queueSize
=
q
)
}),
$el
(
"
button.comfy-settings-btn
"
,
{
textContent
:
"
⚙️
"
,
onclick
:
()
=>
this
.
settings
.
show
()
}),
$el
(
"
button.comfy-settings-btn
"
,
{
textContent
:
"
⚙️
"
,
onclick
:
()
=>
this
.
settings
.
show
()
}),
]),
]),
...
@@ -331,6 +380,8 @@ export class ComfyUI {
...
@@ -331,6 +380,8 @@ export class ComfyUI {
$el
(
"
button
"
,
{
textContent
:
"
Load Default
"
,
onclick
:
()
=>
app
.
loadGraphData
()
}),
$el
(
"
button
"
,
{
textContent
:
"
Load Default
"
,
onclick
:
()
=>
app
.
loadGraphData
()
}),
]);
]);
dragElement
(
this
.
menuContainer
);
this
.
setStatus
({
exec_info
:
{
queue_remaining
:
"
X
"
}
});
this
.
setStatus
({
exec_info
:
{
queue_remaining
:
"
X
"
}
});
}
}
...
...
web/scripts/widgets.js
View file @
a49b5659
...
@@ -10,9 +10,8 @@ function getNumberDefaults(inputData, defaultStep) {
...
@@ -10,9 +10,8 @@ function getNumberDefaults(inputData, defaultStep) {
return
{
val
:
defaultVal
,
config
:
{
min
,
max
,
step
:
10.0
*
step
}
};
return
{
val
:
defaultVal
,
config
:
{
min
,
max
,
step
:
10.0
*
step
}
};
}
}
function
seedWidget
(
node
,
inputName
,
inputData
)
{
export
function
addRandomizeWidget
(
node
,
targetWidget
,
name
,
defaultValue
=
false
)
{
const
seed
=
ComfyWidgets
.
INT
(
node
,
inputName
,
inputData
);
const
randomize
=
node
.
addWidget
(
"
toggle
"
,
name
,
defaultValue
,
function
(
v
)
{},
{
const
randomize
=
node
.
addWidget
(
"
toggle
"
,
"
Random seed after every gen
"
,
true
,
function
(
v
)
{},
{
on
:
"
enabled
"
,
on
:
"
enabled
"
,
off
:
"
disabled
"
,
off
:
"
disabled
"
,
serialize
:
false
,
// Don't include this in prompt.
serialize
:
false
,
// Don't include this in prompt.
...
@@ -20,14 +19,32 @@ function seedWidget(node, inputName, inputData) {
...
@@ -20,14 +19,32 @@ function seedWidget(node, inputName, inputData) {
randomize
.
afterQueued
=
()
=>
{
randomize
.
afterQueued
=
()
=>
{
if
(
randomize
.
value
)
{
if
(
randomize
.
value
)
{
seed
.
widget
.
value
=
Math
.
floor
(
Math
.
random
()
*
1125899906842624
);
const
min
=
targetWidget
.
options
?.
min
;
let
max
=
targetWidget
.
options
?.
max
;
if
(
min
!=
null
||
max
!=
null
)
{
if
(
max
)
{
// limit max to something that javascript can handle
max
=
Math
.
min
(
1125899906842624
,
max
);
}
targetWidget
.
value
=
Math
.
floor
(
Math
.
random
()
*
((
max
??
9999999999
)
-
(
min
??
0
)
+
1
)
+
(
min
??
0
));
}
else
{
targetWidget
.
value
=
Math
.
floor
(
Math
.
random
()
*
1125899906842624
);
}
}
}
};
};
return
randomize
;
}
function
seedWidget
(
node
,
inputName
,
inputData
)
{
const
seed
=
ComfyWidgets
.
INT
(
node
,
inputName
,
inputData
);
const
randomize
=
addRandomizeWidget
(
node
,
seed
.
widget
,
"
Random seed after every gen
"
,
true
);
seed
.
widget
.
linkedWidgets
=
[
randomize
];
return
{
widget
:
seed
,
randomize
};
return
{
widget
:
seed
,
randomize
};
}
}
const
MultilineSymbol
=
Symbol
();
const
MultilineSymbol
=
Symbol
();
const
MultilineResizeSymbol
=
Symbol
();
function
addMultilineWidget
(
node
,
name
,
opts
,
app
)
{
function
addMultilineWidget
(
node
,
name
,
opts
,
app
)
{
const
MIN_SIZE
=
50
;
const
MIN_SIZE
=
50
;
...
@@ -95,7 +112,7 @@ function addMultilineWidget(node, name, opts, app) {
...
@@ -95,7 +112,7 @@ function addMultilineWidget(node, name, opts, app) {
// Calculate it here instead
// Calculate it here instead
computeSize
(
node
.
size
);
computeSize
(
node
.
size
);
}
}
const
visible
=
app
.
canvas
.
ds
.
scale
>
0.5
;
const
visible
=
app
.
canvas
.
ds
.
scale
>
0.5
&&
this
.
type
===
"
customtext
"
;
const
t
=
ctx
.
getTransform
();
const
t
=
ctx
.
getTransform
();
const
margin
=
10
;
const
margin
=
10
;
Object
.
assign
(
this
.
inputEl
.
style
,
{
Object
.
assign
(
this
.
inputEl
.
style
,
{
...
@@ -149,9 +166,22 @@ function addMultilineWidget(node, name, opts, app) {
...
@@ -149,9 +166,22 @@ function addMultilineWidget(node, name, opts, app) {
}
}
};
};
if
(
!
(
MultilineSymbol
in
node
))
{
widget
.
onRemove
=
()
=>
{
node
[
MultilineSymbol
]
=
true
;
widget
.
inputEl
?.
remove
();
const
onResize
=
node
.
onResize
;
// Restore original size handler if we are the last
if
(
!--
node
[
MultilineSymbol
])
{
node
.
onResize
=
node
[
MultilineResizeSymbol
];
delete
node
[
MultilineSymbol
];
delete
node
[
MultilineResizeSymbol
];
}
};
if
(
node
[
MultilineSymbol
])
{
node
[
MultilineSymbol
]
++
;
}
else
{
node
[
MultilineSymbol
]
=
1
;
const
onResize
=
(
node
[
MultilineResizeSymbol
]
=
node
.
onResize
);
node
.
onResize
=
function
(
size
)
{
node
.
onResize
=
function
(
size
)
{
computeSize
(
size
);
computeSize
(
size
);
...
@@ -199,6 +229,14 @@ export const ComfyWidgets = {
...
@@ -199,6 +229,14 @@ export const ComfyWidgets = {
return
{
widget
:
node
.
addWidget
(
"
text
"
,
inputName
,
defaultVal
,
()
=>
{},
{})
};
return
{
widget
:
node
.
addWidget
(
"
text
"
,
inputName
,
defaultVal
,
()
=>
{},
{})
};
}
}
},
},
COMBO
(
node
,
inputName
,
inputData
)
{
const
type
=
inputData
[
0
];
let
defaultValue
=
type
[
0
];
if
(
inputData
[
1
]
&&
inputData
[
1
].
default
)
{
defaultValue
=
inputData
[
1
].
default
;
}
return
{
widget
:
node
.
addWidget
(
"
combo
"
,
inputName
,
defaultValue
,
()
=>
{},
{
values
:
type
})
};
},
IMAGEUPLOAD
(
node
,
inputName
,
inputData
,
app
)
{
IMAGEUPLOAD
(
node
,
inputName
,
inputData
,
app
)
{
const
imageWidget
=
node
.
widgets
.
find
((
w
)
=>
w
.
name
===
"
image
"
);
const
imageWidget
=
node
.
widgets
.
find
((
w
)
=>
w
.
name
===
"
image
"
);
let
uploadWidget
;
let
uploadWidget
;
...
...
web/style.css
View file @
a49b5659
...
@@ -111,6 +111,31 @@ body {
...
@@ -111,6 +111,31 @@ body {
width
:
50%
;
width
:
50%
;
}
}
.comfy-menu
span
.drag-handle
{
width
:
10px
;
height
:
20px
;
display
:
inline-block
;
overflow
:
hidden
;
line-height
:
5px
;
padding
:
3px
4px
;
cursor
:
move
;
vertical-align
:
middle
;
margin-top
:
-.4em
;
margin-left
:
-.2em
;
font-size
:
12px
;
font-family
:
sans-serif
;
letter-spacing
:
2px
;
color
:
#cccccc
;
text-shadow
:
1px
0
1px
black
;
position
:
absolute
;
top
:
0
;
left
:
0
;
}
.comfy-menu
span
.drag-handle
::after
{
content
:
'.. .. ..'
;
}
.comfy-queue-btn
{
.comfy-queue-btn
{
width
:
100%
;
width
:
100%
;
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment