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
1cfb2a73
Commit
1cfb2a73
authored
May 27, 2023
by
comfyanonymous
Browse files
Merge branch 'error-improvements' of
https://github.com/space-nuko/ComfyUI
parents
00646b08
03f2d0a7
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
460 additions
and
101 deletions
+460
-101
execution.py
execution.py
+362
-94
web/scripts/api.js
web/scripts/api.js
+6
-0
web/scripts/app.js
web/scripts/app.js
+92
-7
No files found.
execution.py
View file @
1cfb2a73
...
@@ -102,13 +102,21 @@ def get_output_data(obj, input_data_all):
...
@@ -102,13 +102,21 @@ def get_output_data(obj, input_data_all):
ui
=
{
k
:
[
y
for
x
in
uis
for
y
in
x
[
k
]]
for
k
in
uis
[
0
].
keys
()}
ui
=
{
k
:
[
y
for
x
in
uis
for
y
in
x
[
k
]]
for
k
in
uis
[
0
].
keys
()}
return
output
,
ui
return
output
,
ui
def
format_value
(
x
):
if
x
is
None
:
return
None
elif
isinstance
(
x
,
(
int
,
float
,
bool
,
str
)):
return
x
else
:
return
str
(
x
)
def
recursive_execute
(
server
,
prompt
,
outputs
,
current_item
,
extra_data
,
executed
,
prompt_id
,
outputs_ui
):
def
recursive_execute
(
server
,
prompt
,
outputs
,
current_item
,
extra_data
,
executed
,
prompt_id
,
outputs_ui
):
unique_id
=
current_item
unique_id
=
current_item
inputs
=
prompt
[
unique_id
][
'inputs'
]
inputs
=
prompt
[
unique_id
][
'inputs'
]
class_type
=
prompt
[
unique_id
][
'class_type'
]
class_type
=
prompt
[
unique_id
][
'class_type'
]
class_def
=
nodes
.
NODE_CLASS_MAPPINGS
[
class_type
]
class_def
=
nodes
.
NODE_CLASS_MAPPINGS
[
class_type
]
if
unique_id
in
outputs
:
if
unique_id
in
outputs
:
return
return
(
True
,
None
,
None
)
for
x
in
inputs
:
for
x
in
inputs
:
input_data
=
inputs
[
x
]
input_data
=
inputs
[
x
]
...
@@ -117,8 +125,13 @@ def recursive_execute(server, prompt, outputs, current_item, extra_data, execute
...
@@ -117,8 +125,13 @@ def recursive_execute(server, prompt, outputs, current_item, extra_data, execute
input_unique_id
=
input_data
[
0
]
input_unique_id
=
input_data
[
0
]
output_index
=
input_data
[
1
]
output_index
=
input_data
[
1
]
if
input_unique_id
not
in
outputs
:
if
input_unique_id
not
in
outputs
:
recursive_execute
(
server
,
prompt
,
outputs
,
input_unique_id
,
extra_data
,
executed
,
prompt_id
,
outputs_ui
)
result
=
recursive_execute
(
server
,
prompt
,
outputs
,
input_unique_id
,
extra_data
,
executed
,
prompt_id
,
outputs_ui
)
if
result
[
0
]
is
not
True
:
# Another node failed further upstream
return
result
input_data_all
=
None
try
:
input_data_all
=
get_input_data
(
inputs
,
class_def
,
unique_id
,
outputs
,
prompt
,
extra_data
)
input_data_all
=
get_input_data
(
inputs
,
class_def
,
unique_id
,
outputs
,
prompt
,
extra_data
)
if
server
.
client_id
is
not
None
:
if
server
.
client_id
is
not
None
:
server
.
last_node_id
=
unique_id
server
.
last_node_id
=
unique_id
...
@@ -131,8 +144,45 @@ def recursive_execute(server, prompt, outputs, current_item, extra_data, execute
...
@@ -131,8 +144,45 @@ def recursive_execute(server, prompt, outputs, current_item, extra_data, execute
outputs_ui
[
unique_id
]
=
output_ui
outputs_ui
[
unique_id
]
=
output_ui
if
server
.
client_id
is
not
None
:
if
server
.
client_id
is
not
None
:
server
.
send_sync
(
"executed"
,
{
"node"
:
unique_id
,
"output"
:
output_ui
,
"prompt_id"
:
prompt_id
},
server
.
client_id
)
server
.
send_sync
(
"executed"
,
{
"node"
:
unique_id
,
"output"
:
output_ui
,
"prompt_id"
:
prompt_id
},
server
.
client_id
)
except
comfy
.
model_management
.
InterruptProcessingException
as
iex
:
print
(
"Processing interrupted"
)
# skip formatting inputs/outputs
error_details
=
{
"node_id"
:
unique_id
,
}
return
(
False
,
error_details
,
iex
)
except
Exception
as
ex
:
typ
,
_
,
tb
=
sys
.
exc_info
()
exception_type
=
full_type_name
(
typ
)
input_data_formatted
=
{}
if
input_data_all
is
not
None
:
input_data_formatted
=
{}
for
name
,
inputs
in
input_data_all
.
items
():
input_data_formatted
[
name
]
=
[
format_value
(
x
)
for
x
in
inputs
]
output_data_formatted
=
{}
for
node_id
,
node_outputs
in
outputs
.
items
():
output_data_formatted
[
node_id
]
=
[[
format_value
(
x
)
for
x
in
l
]
for
l
in
node_outputs
]
print
(
"!!! Exception during processing !!!"
)
print
(
traceback
.
format_exc
())
error_details
=
{
"node_id"
:
unique_id
,
"exception_message"
:
str
(
ex
),
"exception_type"
:
exception_type
,
"traceback"
:
traceback
.
format_tb
(
tb
),
"current_inputs"
:
input_data_formatted
,
"current_outputs"
:
output_data_formatted
}
return
(
False
,
error_details
,
ex
)
executed
.
add
(
unique_id
)
executed
.
add
(
unique_id
)
return
(
True
,
None
,
None
)
def
recursive_will_execute
(
prompt
,
outputs
,
current_item
):
def
recursive_will_execute
(
prompt
,
outputs
,
current_item
):
unique_id
=
current_item
unique_id
=
current_item
inputs
=
prompt
[
unique_id
][
'inputs'
]
inputs
=
prompt
[
unique_id
][
'inputs'
]
...
@@ -210,6 +260,48 @@ class PromptExecutor:
...
@@ -210,6 +260,48 @@ class PromptExecutor:
self
.
old_prompt
=
{}
self
.
old_prompt
=
{}
self
.
server
=
server
self
.
server
=
server
def
handle_execution_error
(
self
,
prompt_id
,
prompt
,
current_outputs
,
executed
,
error
,
ex
):
node_id
=
error
[
"node_id"
]
class_type
=
prompt
[
node_id
][
"class_type"
]
# First, send back the status to the frontend depending
# on the exception type
if
isinstance
(
ex
,
comfy
.
model_management
.
InterruptProcessingException
):
mes
=
{
"prompt_id"
:
prompt_id
,
"node_id"
:
node_id
,
"node_type"
:
class_type
,
"executed"
:
list
(
executed
),
}
self
.
server
.
send_sync
(
"execution_interrupted"
,
mes
,
self
.
server
.
client_id
)
else
:
if
self
.
server
.
client_id
is
not
None
:
mes
=
{
"prompt_id"
:
prompt_id
,
"node_id"
:
node_id
,
"node_type"
:
class_type
,
"executed"
:
list
(
executed
),
"exception_message"
:
error
[
"exception_message"
],
"exception_type"
:
error
[
"exception_type"
],
"traceback"
:
error
[
"traceback"
],
"current_inputs"
:
error
[
"current_inputs"
],
"current_outputs"
:
error
[
"current_outputs"
],
}
self
.
server
.
send_sync
(
"execution_error"
,
mes
,
self
.
server
.
client_id
)
# Next, remove the subsequent outputs since they will not be executed
to_delete
=
[]
for
o
in
self
.
outputs
:
if
(
o
not
in
current_outputs
)
and
(
o
not
in
executed
):
to_delete
+=
[
o
]
if
o
in
self
.
old_prompt
:
d
=
self
.
old_prompt
.
pop
(
o
)
del
d
for
o
in
to_delete
:
d
=
self
.
outputs
.
pop
(
o
)
del
d
def
execute
(
self
,
prompt
,
prompt_id
,
extra_data
=
{},
execute_outputs
=
[]):
def
execute
(
self
,
prompt
,
prompt_id
,
extra_data
=
{},
execute_outputs
=
[]):
nodes
.
interrupt_processing
(
False
)
nodes
.
interrupt_processing
(
False
)
...
@@ -244,37 +336,24 @@ class PromptExecutor:
...
@@ -244,37 +336,24 @@ class PromptExecutor:
if
self
.
server
.
client_id
is
not
None
:
if
self
.
server
.
client_id
is
not
None
:
self
.
server
.
send_sync
(
"execution_cached"
,
{
"nodes"
:
list
(
current_outputs
)
,
"prompt_id"
:
prompt_id
},
self
.
server
.
client_id
)
self
.
server
.
send_sync
(
"execution_cached"
,
{
"nodes"
:
list
(
current_outputs
)
,
"prompt_id"
:
prompt_id
},
self
.
server
.
client_id
)
executed
=
set
()
executed
=
set
()
try
:
output_node_id
=
None
to_execute
=
[]
to_execute
=
[]
for
x
in
list
(
execute_outputs
):
to_execute
+=
[(
0
,
x
)]
for
node_id
in
list
(
execute_outputs
):
to_execute
+=
[(
0
,
node_id
)]
while
len
(
to_execute
)
>
0
:
while
len
(
to_execute
)
>
0
:
#always execute the output that depends on the least amount of unexecuted nodes first
#always execute the output that depends on the least amount of unexecuted nodes first
to_execute
=
sorted
(
list
(
map
(
lambda
a
:
(
len
(
recursive_will_execute
(
prompt
,
self
.
outputs
,
a
[
-
1
])),
a
[
-
1
]),
to_execute
)))
to_execute
=
sorted
(
list
(
map
(
lambda
a
:
(
len
(
recursive_will_execute
(
prompt
,
self
.
outputs
,
a
[
-
1
])),
a
[
-
1
]),
to_execute
)))
x
=
to_execute
.
pop
(
0
)[
-
1
]
output_node_id
=
to_execute
.
pop
(
0
)[
-
1
]
recursive_execute
(
self
.
server
,
prompt
,
self
.
outputs
,
x
,
extra_data
,
executed
,
prompt_id
,
self
.
outputs_ui
)
# This call shouldn't raise anything if there's an error deep in
except
Exception
as
e
:
# the actual SD code, instead it will report the node where the
if
isinstance
(
e
,
comfy
.
model_management
.
InterruptProcessingException
):
# error was raised
print
(
"Processing interrupted"
)
success
,
error
,
ex
=
recursive_execute
(
self
.
server
,
prompt
,
self
.
outputs
,
output_node_id
,
extra_data
,
executed
,
prompt_id
,
self
.
outputs_ui
)
else
:
if
success
is
not
True
:
message
=
str
(
traceback
.
format_exc
())
self
.
handle_execution_error
(
prompt_id
,
prompt
,
current_outputs
,
executed
,
error
,
ex
)
print
(
message
)
if
self
.
server
.
client_id
is
not
None
:
self
.
server
.
send_sync
(
"execution_error"
,
{
"message"
:
message
,
"prompt_id"
:
prompt_id
},
self
.
server
.
client_id
)
to_delete
=
[]
for
o
in
self
.
outputs
:
if
(
o
not
in
current_outputs
)
and
(
o
not
in
executed
):
to_delete
+=
[
o
]
if
o
in
self
.
old_prompt
:
d
=
self
.
old_prompt
.
pop
(
o
)
del
d
for
o
in
to_delete
:
d
=
self
.
outputs
.
pop
(
o
)
del
d
finally
:
for
x
in
executed
:
for
x
in
executed
:
self
.
old_prompt
[
x
]
=
copy
.
deepcopy
(
prompt
[
x
])
self
.
old_prompt
[
x
]
=
copy
.
deepcopy
(
prompt
[
x
])
self
.
server
.
last_node_id
=
None
self
.
server
.
last_node_id
=
None
...
@@ -297,25 +376,87 @@ def validate_inputs(prompt, item, validated):
...
@@ -297,25 +376,87 @@ def validate_inputs(prompt, item, validated):
class_inputs
=
obj_class
.
INPUT_TYPES
()
class_inputs
=
obj_class
.
INPUT_TYPES
()
required_inputs
=
class_inputs
[
'required'
]
required_inputs
=
class_inputs
[
'required'
]
errors
=
[]
valid
=
True
for
x
in
required_inputs
:
for
x
in
required_inputs
:
if
x
not
in
inputs
:
if
x
not
in
inputs
:
return
(
False
,
"Required input is missing. {}, {}"
.
format
(
class_type
,
x
),
unique_id
)
error
=
{
"type"
:
"required_input_missing"
,
"message"
:
"Required input is missing"
,
"details"
:
f
"
{
x
}
"
,
"extra_info"
:
{
"input_name"
:
x
}
}
errors
.
append
(
error
)
continue
val
=
inputs
[
x
]
val
=
inputs
[
x
]
info
=
required_inputs
[
x
]
info
=
required_inputs
[
x
]
type_input
=
info
[
0
]
type_input
=
info
[
0
]
if
isinstance
(
val
,
list
):
if
isinstance
(
val
,
list
):
if
len
(
val
)
!=
2
:
if
len
(
val
)
!=
2
:
return
(
False
,
"Bad Input. {}, {}"
.
format
(
class_type
,
x
),
unique_id
)
error
=
{
"type"
:
"bad_linked_input"
,
"message"
:
"Bad linked input, must be a length-2 list of [node_id, slot_index]"
,
"details"
:
f
"
{
x
}
"
,
"extra_info"
:
{
"input_name"
:
x
,
"input_config"
:
info
,
"received_value"
:
val
}
}
errors
.
append
(
error
)
continue
o_id
=
val
[
0
]
o_id
=
val
[
0
]
o_class_type
=
prompt
[
o_id
][
'class_type'
]
o_class_type
=
prompt
[
o_id
][
'class_type'
]
r
=
nodes
.
NODE_CLASS_MAPPINGS
[
o_class_type
].
RETURN_TYPES
r
=
nodes
.
NODE_CLASS_MAPPINGS
[
o_class_type
].
RETURN_TYPES
if
r
[
val
[
1
]]
!=
type_input
:
if
r
[
val
[
1
]]
!=
type_input
:
return
(
False
,
"Return type mismatch. {}, {}, {} != {}"
.
format
(
class_type
,
x
,
r
[
val
[
1
]],
type_input
),
unique_id
)
received_type
=
r
[
val
[
1
]]
details
=
f
"
{
x
}
,
{
received_type
}
!=
{
type_input
}
"
error
=
{
"type"
:
"return_type_mismatch"
,
"message"
:
"Return type mismatch between linked nodes"
,
"details"
:
details
,
"extra_info"
:
{
"input_name"
:
x
,
"input_config"
:
info
,
"received_type"
:
received_type
,
"linked_node"
:
val
}
}
errors
.
append
(
error
)
continue
try
:
r
=
validate_inputs
(
prompt
,
o_id
,
validated
)
r
=
validate_inputs
(
prompt
,
o_id
,
validated
)
if
r
[
0
]
==
False
:
if
r
[
0
]
is
False
:
validated
[
o_id
]
=
r
# `r` will be set in `validated[o_id]` already
return
r
valid
=
False
continue
except
Exception
as
ex
:
typ
,
_
,
tb
=
sys
.
exc_info
()
valid
=
False
exception_type
=
full_type_name
(
typ
)
reasons
=
[{
"type"
:
"exception_during_inner_validation"
,
"message"
:
"Exception when validating inner node"
,
"details"
:
str
(
ex
),
"extra_info"
:
{
"input_name"
:
x
,
"input_config"
:
info
,
"exception_message"
:
str
(
ex
),
"exception_type"
:
exception_type
,
"traceback"
:
traceback
.
format_tb
(
tb
),
"linked_node"
:
val
}
}]
validated
[
o_id
]
=
(
False
,
reasons
,
o_id
)
continue
else
:
else
:
try
:
if
type_input
==
"INT"
:
if
type_input
==
"INT"
:
val
=
int
(
val
)
val
=
int
(
val
)
inputs
[
x
]
=
val
inputs
[
x
]
=
val
...
@@ -325,29 +466,112 @@ def validate_inputs(prompt, item, validated):
...
@@ -325,29 +466,112 @@ def validate_inputs(prompt, item, validated):
if
type_input
==
"STRING"
:
if
type_input
==
"STRING"
:
val
=
str
(
val
)
val
=
str
(
val
)
inputs
[
x
]
=
val
inputs
[
x
]
=
val
except
Exception
as
ex
:
error
=
{
"type"
:
"invalid_input_type"
,
"message"
:
f
"Failed to convert an input value to a
{
type_input
}
value"
,
"details"
:
f
"
{
x
}
,
{
val
}
,
{
ex
}
"
,
"extra_info"
:
{
"input_name"
:
x
,
"input_config"
:
info
,
"received_value"
:
val
,
"exception_message"
:
str
(
ex
)
}
}
errors
.
append
(
error
)
continue
if
len
(
info
)
>
1
:
if
len
(
info
)
>
1
:
if
"min"
in
info
[
1
]
and
val
<
info
[
1
][
"min"
]:
if
"min"
in
info
[
1
]
and
val
<
info
[
1
][
"min"
]:
return
(
False
,
"Value {} smaller than min of {}. {}, {}"
.
format
(
val
,
info
[
1
][
"min"
],
class_type
,
x
),
unique_id
)
error
=
{
"type"
:
"value_smaller_than_min"
,
"message"
:
"Value {} smaller than min of {}"
.
format
(
val
,
info
[
1
][
"min"
]),
"details"
:
f
"
{
x
}
"
,
"extra_info"
:
{
"input_name"
:
x
,
"input_config"
:
info
,
"received_value"
:
val
,
}
}
errors
.
append
(
error
)
continue
if
"max"
in
info
[
1
]
and
val
>
info
[
1
][
"max"
]:
if
"max"
in
info
[
1
]
and
val
>
info
[
1
][
"max"
]:
return
(
False
,
"Value {} bigger than max of {}. {}, {}"
.
format
(
val
,
info
[
1
][
"max"
],
class_type
,
x
),
unique_id
)
error
=
{
"type"
:
"value_bigger_than_max"
,
"message"
:
"Value {} bigger than max of {}"
.
format
(
val
,
info
[
1
][
"max"
]),
"details"
:
f
"
{
x
}
"
,
"extra_info"
:
{
"input_name"
:
x
,
"input_config"
:
info
,
"received_value"
:
val
,
}
}
errors
.
append
(
error
)
continue
if
hasattr
(
obj_class
,
"VALIDATE_INPUTS"
):
if
hasattr
(
obj_class
,
"VALIDATE_INPUTS"
):
input_data_all
=
get_input_data
(
inputs
,
obj_class
,
unique_id
)
input_data_all
=
get_input_data
(
inputs
,
obj_class
,
unique_id
)
#ret = obj_class.VALIDATE_INPUTS(**input_data_all)
#ret = obj_class.VALIDATE_INPUTS(**input_data_all)
ret
=
map_node_over_list
(
obj_class
,
input_data_all
,
"VALIDATE_INPUTS"
)
ret
=
map_node_over_list
(
obj_class
,
input_data_all
,
"VALIDATE_INPUTS"
)
for
r
in
ret
:
for
i
,
r
in
enumerate
(
ret
):
if
r
!=
True
:
if
r
is
not
True
:
return
(
False
,
"{}, {}"
.
format
(
class_type
,
r
),
unique_id
)
details
=
f
"
{
x
}
"
if
r
is
not
False
:
details
+=
f
" -
{
str
(
r
)
}
"
error
=
{
"type"
:
"custom_validation_failed"
,
"message"
:
"Custom validation failed for node"
,
"details"
:
details
,
"extra_info"
:
{
"input_name"
:
x
,
"input_config"
:
info
,
"received_value"
:
val
,
}
}
errors
.
append
(
error
)
continue
else
:
else
:
if
isinstance
(
type_input
,
list
):
if
isinstance
(
type_input
,
list
):
if
val
not
in
type_input
:
if
val
not
in
type_input
:
return
(
False
,
"Value not in list. {}, {}: {} not in {}"
.
format
(
class_type
,
x
,
val
,
type_input
),
unique_id
)
input_config
=
info
list_info
=
""
# Don't send back gigantic lists like if they're lots of
# scanned model filepaths
if
len
(
type_input
)
>
20
:
list_info
=
f
"(list of length
{
len
(
type_input
)
}
)"
input_config
=
None
else
:
list_info
=
str
(
type_input
)
error
=
{
"type"
:
"value_not_in_list"
,
"message"
:
"Value not in list"
,
"details"
:
f
"
{
x
}
: '
{
val
}
' not in
{
list_info
}
"
,
"extra_info"
:
{
"input_name"
:
x
,
"input_config"
:
input_config
,
"received_value"
:
val
,
}
}
errors
.
append
(
error
)
continue
if
len
(
errors
)
>
0
or
valid
is
not
True
:
ret
=
(
False
,
errors
,
unique_id
)
else
:
ret
=
(
True
,
[],
unique_id
)
ret
=
(
True
,
""
,
unique_id
)
validated
[
unique_id
]
=
ret
validated
[
unique_id
]
=
ret
return
ret
return
ret
def
full_type_name
(
klass
):
module
=
klass
.
__module__
if
module
==
'builtins'
:
return
klass
.
__qualname__
return
module
+
'.'
+
klass
.
__qualname__
def
validate_prompt
(
prompt
):
def
validate_prompt
(
prompt
):
outputs
=
set
()
outputs
=
set
()
for
x
in
prompt
:
for
x
in
prompt
:
...
@@ -356,7 +580,13 @@ def validate_prompt(prompt):
...
@@ -356,7 +580,13 @@ def validate_prompt(prompt):
outputs
.
add
(
x
)
outputs
.
add
(
x
)
if
len
(
outputs
)
==
0
:
if
len
(
outputs
)
==
0
:
return
(
False
,
"Prompt has no outputs"
,
[],
[])
error
=
{
"type"
:
"prompt_no_outputs"
,
"message"
:
"Prompt has no outputs"
,
"details"
:
""
,
"extra_info"
:
{}
}
return
(
False
,
error
,
[],
[])
good_outputs
=
set
()
good_outputs
=
set
()
errors
=
[]
errors
=
[]
...
@@ -364,34 +594,72 @@ def validate_prompt(prompt):
...
@@ -364,34 +594,72 @@ def validate_prompt(prompt):
validated
=
{}
validated
=
{}
for
o
in
outputs
:
for
o
in
outputs
:
valid
=
False
valid
=
False
reason
=
""
reason
s
=
[]
try
:
try
:
m
=
validate_inputs
(
prompt
,
o
,
validated
)
m
=
validate_inputs
(
prompt
,
o
,
validated
)
valid
=
m
[
0
]
valid
=
m
[
0
]
reason
=
m
[
1
]
reasons
=
m
[
1
]
node_id
=
m
[
2
]
except
Exception
as
ex
:
except
Exception
as
e
:
typ
,
_
,
tb
=
sys
.
exc_info
()
print
(
traceback
.
format_exc
())
valid
=
False
valid
=
False
reason
=
"Parsing error"
exception_type
=
full_type_name
(
typ
)
node_id
=
None
reasons
=
[{
"type"
:
"exception_during_validation"
,
if
valid
==
True
:
"message"
:
"Exception when validating node"
,
"details"
:
str
(
ex
),
"extra_info"
:
{
"exception_type"
:
exception_type
,
"traceback"
:
traceback
.
format_tb
(
tb
)
}
}]
validated
[
o
]
=
(
False
,
reasons
,
o
)
if
valid
is
True
:
good_outputs
.
add
(
o
)
good_outputs
.
add
(
o
)
else
:
else
:
print
(
"Failed to validate prompt for output {} {}"
.
format
(
o
,
reason
))
print
(
f
"Failed to validate prompt for output
{
o
}
:"
)
print
(
"output will be ignored"
)
if
len
(
reasons
)
>
0
:
errors
+=
[(
o
,
reason
)]
print
(
"* (prompt):"
)
if
node_id
is
not
None
:
for
reason
in
reasons
:
print
(
f
" -
{
reason
[
'message'
]
}
:
{
reason
[
'details'
]
}
"
)
errors
+=
[(
o
,
reasons
)]
for
node_id
,
result
in
validated
.
items
():
valid
=
result
[
0
]
reasons
=
result
[
1
]
# If a node upstream has errors, the nodes downstream will also
# be reported as invalid, but there will be no errors attached.
# So don't return those nodes as having errors in the response.
if
valid
is
not
True
and
len
(
reasons
)
>
0
:
if
node_id
not
in
node_errors
:
if
node_id
not
in
node_errors
:
node_errors
[
node_id
]
=
{
"message"
:
reason
,
"dependent_outputs"
:
[]}
class_type
=
prompt
[
node_id
][
'class_type'
]
node_errors
[
node_id
]
=
{
"errors"
:
reasons
,
"dependent_outputs"
:
[],
"class_type"
:
class_type
}
print
(
f
"*
{
class_type
}
{
node_id
}
:"
)
for
reason
in
reasons
:
print
(
f
" -
{
reason
[
'message'
]
}
:
{
reason
[
'details'
]
}
"
)
node_errors
[
node_id
][
"dependent_outputs"
].
append
(
o
)
node_errors
[
node_id
][
"dependent_outputs"
].
append
(
o
)
print
(
"Output will be ignored"
)
if
len
(
good_outputs
)
==
0
:
if
len
(
good_outputs
)
==
0
:
errors_list
=
"
\n
"
.
join
(
set
(
map
(
lambda
a
:
"{}"
.
format
(
a
[
1
]),
errors
)))
errors_list
=
[]
return
(
False
,
"Prompt has no properly connected outputs
\n
{}"
.
format
(
errors_list
),
list
(
good_outputs
),
node_errors
)
for
o
,
errors
in
errors
:
for
error
in
errors
:
return
(
True
,
""
,
list
(
good_outputs
),
node_errors
)
errors_list
.
append
(
f
"
{
error
[
'message'
]
}
:
{
error
[
'details'
]
}
"
)
errors_list
=
"
\n
"
.
join
(
errors_list
)
error
=
{
"type"
:
"prompt_outputs_failed_validation"
,
"message"
:
"Prompt outputs failed validation"
,
"details"
:
errors_list
,
"extra_info"
:
{}
}
return
(
False
,
error
,
list
(
good_outputs
),
node_errors
)
return
(
True
,
None
,
list
(
good_outputs
),
node_errors
)
class
PromptQueue
:
class
PromptQueue
:
...
...
web/scripts/api.js
View file @
1cfb2a73
...
@@ -88,6 +88,12 @@ class ComfyApi extends EventTarget {
...
@@ -88,6 +88,12 @@ class ComfyApi extends EventTarget {
case
"
executed
"
:
case
"
executed
"
:
this
.
dispatchEvent
(
new
CustomEvent
(
"
executed
"
,
{
detail
:
msg
.
data
}));
this
.
dispatchEvent
(
new
CustomEvent
(
"
executed
"
,
{
detail
:
msg
.
data
}));
break
;
break
;
case
"
execution_start
"
:
this
.
dispatchEvent
(
new
CustomEvent
(
"
execution_start
"
,
{
detail
:
msg
.
data
}));
break
;
case
"
execution_error
"
:
this
.
dispatchEvent
(
new
CustomEvent
(
"
execution_error
"
,
{
detail
:
msg
.
data
}));
break
;
default
:
default
:
if
(
this
.
#
registered
.
has
(
msg
.
type
))
{
if
(
this
.
#
registered
.
has
(
msg
.
type
))
{
this
.
dispatchEvent
(
new
CustomEvent
(
msg
.
type
,
{
detail
:
msg
.
data
}));
this
.
dispatchEvent
(
new
CustomEvent
(
msg
.
type
,
{
detail
:
msg
.
data
}));
...
...
web/scripts/app.js
View file @
1cfb2a73
...
@@ -771,16 +771,27 @@ export class ComfyApp {
...
@@ -771,16 +771,27 @@ export class ComfyApp {
LGraphCanvas
.
prototype
.
drawNodeShape
=
function
(
node
,
ctx
,
size
,
fgcolor
,
bgcolor
,
selected
,
mouse_over
)
{
LGraphCanvas
.
prototype
.
drawNodeShape
=
function
(
node
,
ctx
,
size
,
fgcolor
,
bgcolor
,
selected
,
mouse_over
)
{
const
res
=
origDrawNodeShape
.
apply
(
this
,
arguments
);
const
res
=
origDrawNodeShape
.
apply
(
this
,
arguments
);
const
nodeErrors
=
self
.
lastPromptError
?.
node_errors
[
node
.
id
];
let
color
=
null
;
let
color
=
null
;
let
lineWidth
=
1
;
if
(
node
.
id
===
+
self
.
runningNodeId
)
{
if
(
node
.
id
===
+
self
.
runningNodeId
)
{
color
=
"
#0f0
"
;
color
=
"
#0f0
"
;
}
else
if
(
self
.
dragOverNode
&&
node
.
id
===
self
.
dragOverNode
.
id
)
{
}
else
if
(
self
.
dragOverNode
&&
node
.
id
===
self
.
dragOverNode
.
id
)
{
color
=
"
dodgerblue
"
;
color
=
"
dodgerblue
"
;
}
}
else
if
(
self
.
lastPromptError
!=
null
&&
nodeErrors
?.
errors
)
{
color
=
"
red
"
;
lineWidth
=
2
;
}
else
if
(
self
.
lastExecutionError
&&
+
self
.
lastExecutionError
.
node_id
===
node
.
id
)
{
color
=
"
#f0f
"
;
lineWidth
=
2
;
}
if
(
color
)
{
if
(
color
)
{
const
shape
=
node
.
_shape
||
node
.
constructor
.
shape
||
LiteGraph
.
ROUND_SHAPE
;
const
shape
=
node
.
_shape
||
node
.
constructor
.
shape
||
LiteGraph
.
ROUND_SHAPE
;
ctx
.
lineWidth
=
1
;
ctx
.
lineWidth
=
lineWidth
;
ctx
.
globalAlpha
=
0.8
;
ctx
.
globalAlpha
=
0.8
;
ctx
.
beginPath
();
ctx
.
beginPath
();
if
(
shape
==
LiteGraph
.
BOX_SHAPE
)
if
(
shape
==
LiteGraph
.
BOX_SHAPE
)
...
@@ -807,12 +818,29 @@ export class ComfyApp {
...
@@ -807,12 +818,29 @@ export class ComfyApp {
ctx
.
stroke
();
ctx
.
stroke
();
ctx
.
strokeStyle
=
fgcolor
;
ctx
.
strokeStyle
=
fgcolor
;
ctx
.
globalAlpha
=
1
;
ctx
.
globalAlpha
=
1
;
}
if
(
self
.
progress
)
{
if
(
self
.
progress
&&
node
.
id
===
+
self
.
runningNodeId
)
{
ctx
.
fillStyle
=
"
green
"
;
ctx
.
fillStyle
=
"
green
"
;
ctx
.
fillRect
(
0
,
0
,
size
[
0
]
*
(
self
.
progress
.
value
/
self
.
progress
.
max
),
6
);
ctx
.
fillRect
(
0
,
0
,
size
[
0
]
*
(
self
.
progress
.
value
/
self
.
progress
.
max
),
6
);
ctx
.
fillStyle
=
bgcolor
;
ctx
.
fillStyle
=
bgcolor
;
}
}
// Highlight inputs that failed validation
if
(
nodeErrors
)
{
ctx
.
lineWidth
=
2
;
ctx
.
strokeStyle
=
"
red
"
;
for
(
const
error
of
nodeErrors
.
errors
)
{
if
(
error
.
extra_info
&&
error
.
extra_info
.
input_name
)
{
const
inputIndex
=
node
.
findInputSlot
(
error
.
extra_info
.
input_name
)
if
(
inputIndex
!==
-
1
)
{
let
pos
=
node
.
getConnectionPos
(
true
,
inputIndex
);
ctx
.
beginPath
();
ctx
.
arc
(
pos
[
0
]
-
node
.
pos
[
0
],
pos
[
1
]
-
node
.
pos
[
1
],
12
,
0
,
2
*
Math
.
PI
,
false
)
ctx
.
stroke
();
}
}
}
}
}
return
res
;
return
res
;
...
@@ -869,6 +897,17 @@ export class ComfyApp {
...
@@ -869,6 +897,17 @@ export class ComfyApp {
}
}
});
});
api
.
addEventListener
(
"
execution_start
"
,
({
detail
})
=>
{
this
.
lastExecutionError
=
null
});
api
.
addEventListener
(
"
execution_error
"
,
({
detail
})
=>
{
this
.
lastExecutionError
=
detail
;
const
formattedError
=
this
.
#
formatExecutionError
(
detail
);
this
.
ui
.
dialog
.
show
(
formattedError
);
this
.
canvas
.
draw
(
true
,
true
);
});
api
.
init
();
api
.
init
();
}
}
...
@@ -1243,6 +1282,43 @@ export class ComfyApp {
...
@@ -1243,6 +1282,43 @@ export class ComfyApp {
return
{
workflow
,
output
};
return
{
workflow
,
output
};
}
}
#
formatPromptError
(
error
)
{
if
(
error
==
null
)
{
return
"
(unknown error)
"
}
else
if
(
typeof
error
===
"
string
"
)
{
return
error
;
}
else
if
(
error
.
stack
&&
error
.
message
)
{
return
error
.
toString
()
}
else
if
(
error
.
response
)
{
let
message
=
error
.
response
.
error
.
message
;
if
(
error
.
response
.
error
.
details
)
message
+=
"
:
"
+
error
.
response
.
error
.
details
;
for
(
const
[
nodeID
,
nodeError
]
of
Object
.
entries
(
error
.
response
.
node_errors
))
{
message
+=
"
\n
"
+
nodeError
.
class_type
+
"
:
"
for
(
const
errorReason
of
nodeError
.
errors
)
{
message
+=
"
\n
-
"
+
errorReason
.
message
+
"
:
"
+
errorReason
.
details
}
}
return
message
}
return
"
(unknown error)
"
}
#
formatExecutionError
(
error
)
{
if
(
error
==
null
)
{
return
"
(unknown error)
"
}
const
traceback
=
error
.
traceback
.
join
(
""
)
const
nodeId
=
error
.
node_id
const
nodeType
=
error
.
node_type
return
`Error occurred when executing
${
nodeType
}
:\n\n
${
error
.
message
}
\n\n
${
traceback
}
`
}
async
queuePrompt
(
number
,
batchCount
=
1
)
{
async
queuePrompt
(
number
,
batchCount
=
1
)
{
this
.
#
queueItems
.
push
({
number
,
batchCount
});
this
.
#
queueItems
.
push
({
number
,
batchCount
});
...
@@ -1252,6 +1328,8 @@ export class ComfyApp {
...
@@ -1252,6 +1328,8 @@ export class ComfyApp {
}
}
this
.
#
processingQueue
=
true
;
this
.
#
processingQueue
=
true
;
this
.
lastPromptError
=
null
;
try
{
try
{
while
(
this
.
#
queueItems
.
length
)
{
while
(
this
.
#
queueItems
.
length
)
{
({
number
,
batchCount
}
=
this
.
#
queueItems
.
pop
());
({
number
,
batchCount
}
=
this
.
#
queueItems
.
pop
());
...
@@ -1262,7 +1340,12 @@ export class ComfyApp {
...
@@ -1262,7 +1340,12 @@ export class ComfyApp {
try
{
try
{
await
api
.
queuePrompt
(
number
,
p
);
await
api
.
queuePrompt
(
number
,
p
);
}
catch
(
error
)
{
}
catch
(
error
)
{
this
.
ui
.
dialog
.
show
(
error
.
response
.
error
||
error
.
toString
());
const
formattedError
=
this
.
#
formatPromptError
(
error
)
this
.
ui
.
dialog
.
show
(
formattedError
);
if
(
error
.
response
)
{
this
.
lastPromptError
=
error
.
response
;
this
.
canvas
.
draw
(
true
,
true
);
}
break
;
break
;
}
}
...
@@ -1360,6 +1443,8 @@ export class ComfyApp {
...
@@ -1360,6 +1443,8 @@ export class ComfyApp {
*/
*/
clean
()
{
clean
()
{
this
.
nodeOutputs
=
{};
this
.
nodeOutputs
=
{};
this
.
lastPromptError
=
null
;
this
.
lastExecutionError
=
null
;
}
}
}
}
...
...
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