Compare commits
611 Commits
3.3.10
...
feat/add-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e4b448b91 | ||
|
|
c888ea6435 | ||
|
|
b089db79c5 | ||
|
|
7a73f5db73 | ||
|
|
a96e7b114e | ||
|
|
0148b5a3cc | ||
|
|
2120a0aa79 | ||
|
|
706b6d8317 | ||
|
|
a59e6e176e | ||
|
|
1d575fb654 | ||
|
|
98af8dc849 | ||
|
|
4d89c69109 | ||
|
|
b73dc6121f | ||
|
|
b55e1404b1 | ||
|
|
0be0a2e6d7 | ||
|
|
3afafdb884 | ||
|
|
884b503728 | ||
|
|
7f1ebbe081 | ||
|
|
c8882dcb7c | ||
|
|
601f1bf452 | ||
|
|
3870abfd2d | ||
|
|
ae9fdd0255 | ||
|
|
b3874ee6fd | ||
|
|
62af4891f3 | ||
|
|
2176e0c0ad | ||
|
|
cac105b0d5 | ||
|
|
cd7c42cc23 | ||
|
|
a3fb847773 | ||
|
|
5c2f4f9e4b | ||
|
|
0a511d5b87 | ||
|
|
efe1aad5db | ||
|
|
eed4c53df0 | ||
|
|
9c08a6314b | ||
|
|
a6b2d2c722 | ||
|
|
3c6b5300e5 | ||
|
|
f084c30b20 | ||
|
|
206004fc1f | ||
|
|
d9641cbff8 | ||
|
|
13b272052a | ||
|
|
c79e0d26d8 | ||
|
|
ec4a4c2cfc | ||
|
|
9a9491bff9 | ||
|
|
5b5155819f | ||
|
|
1b941c6b29 | ||
|
|
9b9665d2e9 | ||
|
|
4cceb46641 | ||
|
|
19cf83cce6 | ||
|
|
bb60d399fc | ||
|
|
1a9f1dd0ae | ||
|
|
586c465aaa | ||
|
|
50ceb974d9 | ||
|
|
27cf40d392 | ||
|
|
bbb6005634 | ||
|
|
8dbd996558 | ||
|
|
8605345499 | ||
|
|
8303e7c043 | ||
|
|
3671ddbd4b | ||
|
|
5bc1ceacb2 | ||
|
|
47b9fa3651 | ||
|
|
6062b87771 | ||
|
|
213152aa43 | ||
|
|
ea8047344f | ||
|
|
a7bc167d53 | ||
|
|
18e78ee2c2 | ||
|
|
754236e35b | ||
|
|
2645d62991 | ||
|
|
e55d9416dc | ||
|
|
24d35eec54 | ||
|
|
ee053f50b4 | ||
|
|
3593c9ed3e | ||
|
|
93f548696d | ||
|
|
cecb952add | ||
|
|
596571bb38 | ||
|
|
85a6fb75b8 | ||
|
|
7dea42433b | ||
|
|
ec5e4af6b7 | ||
|
|
0048754fe8 | ||
|
|
5c0bd0f79c | ||
|
|
669cdffe08 | ||
|
|
3cd553301b | ||
|
|
db7ef4f253 | ||
|
|
a09704567c | ||
|
|
21fe577a2e | ||
|
|
9f258f5c9c | ||
|
|
9cd088feb0 | ||
|
|
89e3828138 | ||
|
|
731c89dc27 | ||
|
|
3d920cab4d | ||
|
|
470b8c1fb8 | ||
|
|
dbf988fd5a | ||
|
|
0031743ad4 | ||
|
|
0f2c0ab65d | ||
|
|
53244b794f | ||
|
|
416122d61d | ||
|
|
d3c625e791 | ||
|
|
ca2c41783c | ||
|
|
e2a6446585 | ||
|
|
839790b5ab | ||
|
|
58b9946936 | ||
|
|
a19ba22eaf | ||
|
|
117715aa22 | ||
|
|
891a5a85ee | ||
|
|
35464654c1 | ||
|
|
ec9d52d482 | ||
|
|
166debfabb | ||
|
|
7258a09fe5 | ||
|
|
058a436187 | ||
|
|
1950802c55 | ||
|
|
eb52a03372 | ||
|
|
f8aa428be3 | ||
|
|
ec0893f136 | ||
|
|
92b99ea963 | ||
|
|
02cd52bb65 | ||
|
|
af1ec2c87b | ||
|
|
41006c3a33 | ||
|
|
116a6d500d | ||
|
|
87d0ac807f | ||
|
|
fc943172eb | ||
|
|
9daa5a2fbd | ||
|
|
b7b2746a61 | ||
|
|
d66a4fbfc8 | ||
|
|
683a172ad8 | ||
|
|
6e12358f5a | ||
|
|
8bcf16dc90 | ||
|
|
65c0a2a1f5 | ||
|
|
115236eb9c | ||
|
|
08de942abe | ||
|
|
e9dff83290 | ||
|
|
3bc6c7584d | ||
|
|
22a2bf1584 | ||
|
|
79ece5f72c | ||
|
|
5da6fe1373 | ||
|
|
48c10d0b95 | ||
|
|
9bb56b1457 | ||
|
|
83420fd828 | ||
|
|
52f4b9506f | ||
|
|
b501e9b20b | ||
|
|
1f7ae5319a | ||
|
|
68c201239d | ||
|
|
6e4e43f612 | ||
|
|
81c3708f39 | ||
|
|
f4d2bbde34 | ||
|
|
d14b42a42c | ||
|
|
0e9c32344c | ||
|
|
30c4ea06af | ||
|
|
8211264993 | ||
|
|
67cf5b49e1 | ||
|
|
90ce448380 | ||
|
|
8e7ba18e05 | ||
|
|
8359e1063e | ||
|
|
ca078e54b9 | ||
|
|
f7e930c5a2 | ||
|
|
479d95e1c8 | ||
|
|
2b0ff08eef | ||
|
|
67a487db15 | ||
|
|
2488cb3458 | ||
|
|
157e6336fa | ||
|
|
d808a1f406 | ||
|
|
2bb4d8cd63 | ||
|
|
a8164e1631 | ||
|
|
a31d286945 | ||
|
|
12eeef4cf0 | ||
|
|
ce8e6dc36e | ||
|
|
7a32e544a7 | ||
|
|
e16e9d7a0e | ||
|
|
821f908dbc | ||
|
|
e007e6f897 | ||
|
|
94f496fd65 | ||
|
|
d2ce35d2e6 | ||
|
|
2eeebb32dc | ||
|
|
f6d636d82f | ||
|
|
56125839ac | ||
|
|
0cd397623e | ||
|
|
5978b6c9ee | ||
|
|
9e132811bc | ||
|
|
cd49799bed | ||
|
|
d547a05106 | ||
|
|
3a3b5c1f92 | ||
|
|
26be01ff82 | ||
|
|
8f6dd92374 | ||
|
|
d50b71a887 | ||
|
|
3bc9cbc767 | ||
|
|
b6f6b4fd8a | ||
|
|
a66bada8a3 | ||
|
|
db0b57a14c | ||
|
|
2048ac87a9 | ||
|
|
9adf6de850 | ||
|
|
7657c7866f | ||
|
|
d638f75117 | ||
|
|
a804f7de19 | ||
|
|
efff6b2c18 | ||
|
|
72a61a9966 | ||
|
|
b08bb658ea | ||
|
|
7b28bf608b | ||
|
|
0c46434164 | ||
|
|
0bb8947c02 | ||
|
|
b57747fdf1 | ||
|
|
0735271b10 | ||
|
|
770cd0f9f5 | ||
|
|
32b6266dd9 | ||
|
|
2a8412a2bf | ||
|
|
0c4d289002 | ||
|
|
cee01fec25 | ||
|
|
f00686f3f2 | ||
|
|
bd33f7726e | ||
|
|
22ab526b0c | ||
|
|
af269d198d | ||
|
|
995ef6356e | ||
|
|
aa3bf77c28 | ||
|
|
15667c1259 | ||
|
|
c7b6b565da | ||
|
|
3214ab52c6 | ||
|
|
e3062ff613 | ||
|
|
036b63efe7 | ||
|
|
09e8e8798c | ||
|
|
abfd85602e | ||
|
|
1816bb748e | ||
|
|
8d3e1d60d0 | ||
|
|
59876452f4 | ||
|
|
04972ad87f | ||
|
|
c7e69f4e26 | ||
|
|
7a59b6d0d9 | ||
|
|
d227ad97a4 | ||
|
|
b93a474dae | ||
|
|
a5fe075bf3 | ||
|
|
05ceab68f8 | ||
|
|
46a37907e6 | ||
|
|
7fc8ba587e | ||
|
|
7a35bd9d9a | ||
|
|
17e5c3d2f5 | ||
|
|
27bfc539f7 | ||
|
|
a76ef49d2d | ||
|
|
bb0fcf6ea6 | ||
|
|
539e0a1534 | ||
|
|
aaae6ce304 | ||
|
|
821fded09d | ||
|
|
ec4a2aa873 | ||
|
|
d6b2d54f3f | ||
|
|
97ae67bb9a | ||
|
|
765514a33f | ||
|
|
e2cdcc96c4 | ||
|
|
3c413840d7 | ||
|
|
29ca93fcb4 | ||
|
|
9dc8e630a0 | ||
|
|
10105ad737 | ||
|
|
5738ea861a | ||
|
|
dbd25b0f0a | ||
|
|
0de9d36c28 | ||
|
|
05f1a8ab0d | ||
|
|
5ce170b7ce | ||
|
|
2b47aad076 | ||
|
|
b4dc59331d | ||
|
|
81e84fad78 | ||
|
|
42e8a959dd | ||
|
|
208ca31836 | ||
|
|
0738b2a73f | ||
|
|
98db79910e | ||
|
|
0b21a05aac | ||
|
|
4b71db54aa | ||
|
|
a6bc890f36 | ||
|
|
76903c39e1 | ||
|
|
cf9ed1c631 | ||
|
|
50fc1389b0 | ||
|
|
c70cb2868b | ||
|
|
12fa571aa2 | ||
|
|
4a3018760f | ||
|
|
d005d06cf8 | ||
|
|
a87e3f9ee9 | ||
|
|
52b9a3f3a0 | ||
|
|
c01a7e41d0 | ||
|
|
fe301bb91a | ||
|
|
a42953e3be | ||
|
|
1899255a69 | ||
|
|
908a1009d2 | ||
|
|
fb9c68fc32 | ||
|
|
d54ec0eb05 | ||
|
|
a386948fd1 | ||
|
|
007b812ede | ||
|
|
0ddb0cec03 | ||
|
|
e687f83fbf | ||
|
|
458c9de70f | ||
|
|
a128baf894 | ||
|
|
57b847eebf | ||
|
|
149257e4f1 | ||
|
|
212b8e7ed2 | ||
|
|
87a652d038 | ||
|
|
d889df4c89 | ||
|
|
a2e72d26aa | ||
|
|
a4fdc874e7 | ||
|
|
dfbe382d60 | ||
|
|
0d56ebb1bf | ||
|
|
01ac9c895a | ||
|
|
ebcb14e6aa | ||
|
|
9e66da174e | ||
|
|
55fcb00168 | ||
|
|
68aa534e1d | ||
|
|
7fd94a401b | ||
|
|
2b9cec50ce | ||
|
|
d1a80cf082 | ||
|
|
fb445aa510 | ||
|
|
4b904934ef | ||
|
|
d6295a00e6 | ||
|
|
3b01673829 | ||
|
|
a5e83a807f | ||
|
|
ddd766ce58 | ||
|
|
a6d2fd36fb | ||
|
|
9156d6bdba | ||
|
|
d18a3ffeff | ||
|
|
e933eaa2b0 | ||
|
|
5393653ddc | ||
|
|
1f3274d3f5 | ||
|
|
39eaa76b8a | ||
|
|
e5396713ce | ||
|
|
79943de808 | ||
|
|
e05f329602 | ||
|
|
eed0e8ebea | ||
|
|
731eb4fcbe | ||
|
|
44a63e4b6d | ||
|
|
7651e5e48b | ||
|
|
2449636d32 | ||
|
|
f9990ca8eb | ||
|
|
c3eed981c0 | ||
|
|
bbb54d4a08 | ||
|
|
4566c585db | ||
|
|
a946338a18 | ||
|
|
0a60a44478 | ||
|
|
cef0ad6707 | ||
|
|
7176f0837a | ||
|
|
6b1f2b2d9d | ||
|
|
38a1a9b320 | ||
|
|
402e2c384f | ||
|
|
9d5faa096c | ||
|
|
97d0dc20f1 | ||
|
|
8d99ff07b6 | ||
|
|
04fa540a8c | ||
|
|
eb41867e04 | ||
|
|
eee5d7d9e8 | ||
|
|
e983f9ed35 | ||
|
|
8b16ef641b | ||
|
|
e87d616b7a | ||
|
|
2220f325fc | ||
|
|
b53ed47ccb | ||
|
|
39df2743fe | ||
|
|
3f729aaf03 | ||
|
|
b7324621e4 | ||
|
|
e8c782c8e1 | ||
|
|
9136505565 | ||
|
|
f406d728cc | ||
|
|
d649ca47c6 | ||
|
|
e8111527b4 | ||
|
|
2af66d7efc | ||
|
|
27706f37f6 | ||
|
|
3de17b2fa6 | ||
|
|
22ecb5de95 | ||
|
|
992b8b3cb5 | ||
|
|
bebc16d5a6 | ||
|
|
ddb719f866 | ||
|
|
0bd1bf2605 | ||
|
|
fd32ba4035 | ||
|
|
22f723b920 | ||
|
|
1248bd0413 | ||
|
|
c150eec2b6 | ||
|
|
c7248c2d47 | ||
|
|
e71e68e298 | ||
|
|
6969557693 | ||
|
|
f6be5ad839 | ||
|
|
cebe3664fd | ||
|
|
cdab465c90 | ||
|
|
144384655c | ||
|
|
0e213d6dab | ||
|
|
21294a4e4a | ||
|
|
3ba4d44d9e | ||
|
|
1f86ef5a37 | ||
|
|
fac60da333 | ||
|
|
5a5a37dfee | ||
|
|
0d487bc14f | ||
|
|
a52b4eb5ed | ||
|
|
f1b7f5f52f | ||
|
|
5ef58652bf | ||
|
|
e26a9e75c6 | ||
|
|
b0035ff4a7 | ||
|
|
94b6f9b2fe | ||
|
|
cad1482b3f | ||
|
|
ea7aafb3e6 | ||
|
|
42b15ad4a5 | ||
|
|
d3d613cca9 | ||
|
|
86893d999a | ||
|
|
4fd17b0bf5 | ||
|
|
76d2206058 | ||
|
|
51e8b608dc | ||
|
|
a68330fb8f | ||
|
|
2449ad5c69 | ||
|
|
064c812df3 | ||
|
|
48d5ec9e66 | ||
|
|
914419fd1e | ||
|
|
f005fc8ca0 | ||
|
|
43b7960de2 | ||
|
|
2ed1e58032 | ||
|
|
c63b212700 | ||
|
|
e9df78c0e7 | ||
|
|
b0daf81185 | ||
|
|
cee4fdcbb0 | ||
|
|
df3cdfccb0 | ||
|
|
894042cd0e | ||
|
|
8123287952 | ||
|
|
bc677705d8 | ||
|
|
5dd8ea8aab | ||
|
|
41172be796 | ||
|
|
ad1b4a9a86 | ||
|
|
e0e3ec02b3 | ||
|
|
a6cc392473 | ||
|
|
36f48b8656 | ||
|
|
3d883ca37d | ||
|
|
34ed81ca64 | ||
|
|
a9e0880572 | ||
|
|
9500e1c3c4 | ||
|
|
d81aa9cbbc | ||
|
|
21d4b25c2d | ||
|
|
0080783a11 | ||
|
|
2c3f44a3f8 | ||
|
|
3ddf414097 | ||
|
|
59fb63f1f7 | ||
|
|
fa1b514440 | ||
|
|
1579c58876 | ||
|
|
153d044331 | ||
|
|
f2496f7054 | ||
|
|
99022f4f3d | ||
|
|
60a5e4f261 | ||
|
|
661586d3b6 | ||
|
|
abc26cf906 | ||
|
|
12351bada7 | ||
|
|
a6816d53d7 | ||
|
|
3b0709f5f2 | ||
|
|
d7af7e2917 | ||
|
|
6516e62d33 | ||
|
|
6b832edd2f | ||
|
|
eebace1652 | ||
|
|
6ff6e05408 | ||
|
|
aaf569ca8c | ||
|
|
31eef6222e | ||
|
|
9963afa558 | ||
|
|
5b2e2fcf9d | ||
|
|
cc746e59a1 | ||
|
|
2cdb1c519d | ||
|
|
426074ded9 | ||
|
|
772a096615 | ||
|
|
e113e011cb | ||
|
|
22266484bd | ||
|
|
559c011420 | ||
|
|
411c0633a3 | ||
|
|
488f023bdf | ||
|
|
22878f4ef8 | ||
|
|
e732a39fea | ||
|
|
62b4bf7af4 | ||
|
|
47a525ddb4 | ||
|
|
f4360725e0 | ||
|
|
b86607cd41 | ||
|
|
bf57de85c3 | ||
|
|
2dd6118ff4 | ||
|
|
816a53a7b1 | ||
|
|
ced93b0525 | ||
|
|
524ff9a4a6 | ||
|
|
f15032f905 | ||
|
|
d7d31a19e5 | ||
|
|
df2a7ddca4 | ||
|
|
ba9c71ffa4 | ||
|
|
21b6c6569c | ||
|
|
92aba9565a | ||
|
|
6ea0aebb0b | ||
|
|
b5cdcb75b4 | ||
|
|
bd9aae40b8 | ||
|
|
33f931c0a4 | ||
|
|
ede8279c17 | ||
|
|
268b84a2b6 | ||
|
|
0a67145d80 | ||
|
|
2e55bc470c | ||
|
|
cf0d038978 | ||
|
|
92e7db1082 | ||
|
|
c45c47f935 | ||
|
|
341e27f9a3 | ||
|
|
ab167175c9 | ||
|
|
3c2933338f | ||
|
|
829784fa50 | ||
|
|
3c45f8dc91 | ||
|
|
f8ebf7c6ad | ||
|
|
510c364607 | ||
|
|
a3d6fcccb7 | ||
|
|
42c8082edd | ||
|
|
1a7edf7f0e | ||
|
|
4760deaf9c | ||
|
|
0f7b9d02a0 | ||
|
|
adc86c7945 | ||
|
|
12969eda07 | ||
|
|
e07952455f | ||
|
|
4494230854 | ||
|
|
e8dd21c0c3 | ||
|
|
36ef1b2fd6 | ||
|
|
c3d2bd8ed1 | ||
|
|
da2b4be539 | ||
|
|
b5e11f85d5 | ||
|
|
9e1b2f8912 | ||
|
|
1e5f4b0267 | ||
|
|
4fedd03074 | ||
|
|
f6feaeea85 | ||
|
|
c8743c0ab7 | ||
|
|
3d80ed95ca | ||
|
|
0a28bfa9c2 | ||
|
|
6d771f77e6 | ||
|
|
717ca1bb18 | ||
|
|
4f3c48cb4f | ||
|
|
b1b02dc8e5 | ||
|
|
a060ff52ad | ||
|
|
42d73fe25d | ||
|
|
b5946344dc | ||
|
|
dd46e45aba | ||
|
|
61ee4549e1 | ||
|
|
9767f6244f | ||
|
|
0038d74b86 | ||
|
|
6b2163c61f | ||
|
|
56f976c6b5 | ||
|
|
3ee0bfe1ea | ||
|
|
cd9f003da1 | ||
|
|
c452524e3e | ||
|
|
13f98ddbd6 | ||
|
|
9a5c7c10de | ||
|
|
41998565db | ||
|
|
3c64a8eb18 | ||
|
|
962ba0b358 | ||
|
|
16780f91a3 | ||
|
|
5e5a06b0ff | ||
|
|
859e0f20b8 | ||
|
|
9a15d5ce4e | ||
|
|
e4bba28579 | ||
|
|
f3efddd849 | ||
|
|
39190f97d4 | ||
|
|
3b037c5011 | ||
|
|
79387d5396 | ||
|
|
62e747b74a | ||
|
|
9643aed8f8 | ||
|
|
f4fb9e3ab4 | ||
|
|
30487e6108 | ||
|
|
fd2d285af5 | ||
|
|
87bbf59d87 | ||
|
|
37e954626d | ||
|
|
829c7d8be6 | ||
|
|
3274885803 | ||
|
|
c6153ea67d | ||
|
|
191bffedcb | ||
|
|
9ddda81372 | ||
|
|
ddb3c4e3ce | ||
|
|
c87d27630b | ||
|
|
c1d0bb830e | ||
|
|
93dde4c985 | ||
|
|
0eb1cbce43 | ||
|
|
a935c8bb35 | ||
|
|
03eea8ce15 | ||
|
|
76b1adebc4 | ||
|
|
3be8f685bd | ||
|
|
4a392395ab | ||
|
|
fd9755e4a0 | ||
|
|
34151b03ef | ||
|
|
f63205f86c | ||
|
|
5e5867528d | ||
|
|
05623b0e13 | ||
|
|
12602da16c | ||
|
|
2b6dee9949 | ||
|
|
11fa305508 | ||
|
|
b532a3e784 | ||
|
|
f37f5b0ae2 | ||
|
|
c779573204 | ||
|
|
897debb106 | ||
|
|
0b43716c56 | ||
|
|
4ad1c8a238 | ||
|
|
9578ce0820 | ||
|
|
c5d8a1b3ad | ||
|
|
99049db807 | ||
|
|
70de42ccea | ||
|
|
fb74f39793 | ||
|
|
ef130b23ef | ||
|
|
2549dc7d20 | ||
|
|
85a03e6249 | ||
|
|
0903f28b0c | ||
|
|
c663907e37 | ||
|
|
830be27eb2 | ||
|
|
041f4e4bb5 | ||
|
|
d3fa87fd94 | ||
|
|
4dffb5d593 | ||
|
|
b114672e03 | ||
|
|
3c3713553e | ||
|
|
fd164862f3 | ||
|
|
ac8804ca6a | ||
|
|
429e13bf4d | ||
|
|
5d9578d231 | ||
|
|
f4e0ce2ad4 | ||
|
|
aff6785e0b | ||
|
|
2656fae9c9 | ||
|
|
3ed10e304d | ||
|
|
7d17ef0da1 | ||
|
|
0202cf07d5 | ||
|
|
ad9c35e44b | ||
|
|
65286803a5 | ||
|
|
16bd58667c | ||
|
|
939d556c7e | ||
|
|
823d5459af | ||
|
|
10bd619712 | ||
|
|
9f5b0c9ddd | ||
|
|
87eadb4825 | ||
|
|
5b91e4214c | ||
|
|
fd5c120d36 | ||
|
|
3075764402 | ||
|
|
bdad599f36 |
1
.env.example
Normal file
1
.env.example
Normal file
@@ -0,0 +1 @@
|
|||||||
|
PYPI_TOKEN=your-pypi-token
|
||||||
70
.github/workflows/ci.yml
vendored
Normal file
70
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ main, feat/*, fix/* ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ main ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-openapi:
|
||||||
|
name: Validate OpenAPI Specification
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Check if OpenAPI changed
|
||||||
|
id: openapi-changed
|
||||||
|
uses: tj-actions/changed-files@v44
|
||||||
|
with:
|
||||||
|
files: openapi.yaml
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
if: steps.openapi-changed.outputs.any_changed == 'true'
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '18'
|
||||||
|
|
||||||
|
- name: Install Redoc CLI
|
||||||
|
if: steps.openapi-changed.outputs.any_changed == 'true'
|
||||||
|
run: |
|
||||||
|
npm install -g @redocly/cli
|
||||||
|
|
||||||
|
- name: Validate OpenAPI specification
|
||||||
|
if: steps.openapi-changed.outputs.any_changed == 'true'
|
||||||
|
run: |
|
||||||
|
redocly lint openapi.yaml
|
||||||
|
|
||||||
|
code-quality:
|
||||||
|
name: Code Quality Checks
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # Fetch all history for proper diff
|
||||||
|
|
||||||
|
- name: Get changed Python files
|
||||||
|
id: changed-py-files
|
||||||
|
uses: tj-actions/changed-files@v44
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
**/*.py
|
||||||
|
files_ignore: |
|
||||||
|
comfyui_manager/legacy/**
|
||||||
|
|
||||||
|
- name: Setup Python
|
||||||
|
if: steps.changed-py-files.outputs.any_changed == 'true'
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
if: steps.changed-py-files.outputs.any_changed == 'true'
|
||||||
|
run: |
|
||||||
|
pip install ruff
|
||||||
|
|
||||||
|
- name: Run ruff linting on changed files
|
||||||
|
if: steps.changed-py-files.outputs.any_changed == 'true'
|
||||||
|
run: |
|
||||||
|
echo "Changed files: ${{ steps.changed-py-files.outputs.all_changed_files }}"
|
||||||
|
echo "${{ steps.changed-py-files.outputs.all_changed_files }}" | xargs -r ruff check
|
||||||
58
.github/workflows/publish-to-pypi.yml
vendored
Normal file
58
.github/workflows/publish-to-pypi.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
name: Publish to PyPI
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- "pyproject.toml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.repository_owner == 'ltdrdata' || github.repository_owner == 'Comfy-Org' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
|
||||||
|
- name: Install build dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install build twine
|
||||||
|
|
||||||
|
- name: Get current version
|
||||||
|
id: current_version
|
||||||
|
run: |
|
||||||
|
CURRENT_VERSION=$(grep -oP 'version = "\K[^"]+' pyproject.toml)
|
||||||
|
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "Current version: $CURRENT_VERSION"
|
||||||
|
|
||||||
|
- name: Build package
|
||||||
|
run: python -m build
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
id: create_release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
files: dist/*
|
||||||
|
tag_name: v${{ steps.current_version.outputs.version }}
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
generate_release_notes: true
|
||||||
|
|
||||||
|
- name: Publish to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
password: ${{ secrets.PYPI_TOKEN }}
|
||||||
|
skip-existing: true
|
||||||
|
verbose: true
|
||||||
6
.github/workflows/publish.yml
vendored
6
.github/workflows/publish.yml
vendored
@@ -7,15 +7,19 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-node:
|
publish-node:
|
||||||
name: Publish Custom Node to registry
|
name: Publish Custom Node to registry
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.repository_owner == 'ltdrdata' || github.repository_owner == 'Comfy-Org' }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code
|
- name: Check out code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Publish Custom Node
|
- name: Publish Custom Node
|
||||||
uses: Comfy-Org/publish-node-action@main
|
uses: Comfy-Org/publish-node-action@v1
|
||||||
with:
|
with:
|
||||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
## Add your own personal access token to your Github Repository secrets and reference it here.
|
||||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -18,3 +18,7 @@ pip_overrides.json
|
|||||||
*.json
|
*.json
|
||||||
check2.sh
|
check2.sh
|
||||||
/venv/
|
/venv/
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
*.egg-info
|
||||||
|
.env
|
||||||
47
CONTRIBUTING.md
Normal file
47
CONTRIBUTING.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
## Testing Changes
|
||||||
|
|
||||||
|
1. Activate the ComfyUI environment.
|
||||||
|
|
||||||
|
2. Build package locally after making changes.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# from inside the ComfyUI-Manager directory, with the ComfyUI environment activated
|
||||||
|
python -m build
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Install the package locally in the ComfyUI environment.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Uninstall existing package
|
||||||
|
pip uninstall comfyui-manager
|
||||||
|
|
||||||
|
# Install the locale package
|
||||||
|
pip install dist/comfyui-manager-*.whl
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Start ComfyUI.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# after navigating to the ComfyUI directory
|
||||||
|
python main.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manually Publish Test Version to PyPi
|
||||||
|
|
||||||
|
1. Set the `PYPI_TOKEN` environment variable in env file.
|
||||||
|
|
||||||
|
2. If manually publishing, you likely want to use a release candidate version, so set the version in [pyproject.toml](pyproject.toml) to something like `0.0.1rc1`.
|
||||||
|
|
||||||
|
3. Build the package.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m build
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Upload the package to PyPi.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m twine upload dist/* --username __token__ --password $PYPI_TOKEN
|
||||||
|
```
|
||||||
|
|
||||||
|
5. View at https://pypi.org/project/comfyui-manager/
|
||||||
14
MANIFEST.in
Normal file
14
MANIFEST.in
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
include comfyui_manager/js/*
|
||||||
|
include comfyui_manager/*.json
|
||||||
|
include comfyui_manager/glob/*
|
||||||
|
include LICENSE.txt
|
||||||
|
include README.md
|
||||||
|
include requirements.txt
|
||||||
|
include pyproject.toml
|
||||||
|
include custom-node-list.json
|
||||||
|
include extension-node-list.json
|
||||||
|
include extras.json
|
||||||
|
include github-stats.json
|
||||||
|
include model-list.json
|
||||||
|
include alter-list.json
|
||||||
|
include comfyui_manager/channels.list.template
|
||||||
173
README.md
173
README.md
@@ -2,83 +2,38 @@
|
|||||||
|
|
||||||
**ComfyUI-Manager** is an extension designed to enhance the usability of [ComfyUI](https://github.com/comfyanonymous/ComfyUI). It offers management functions to **install, remove, disable, and enable** various custom nodes of ComfyUI. Furthermore, this extension provides a hub feature and convenience functions to access a wide range of information within ComfyUI.
|
**ComfyUI-Manager** is an extension designed to enhance the usability of [ComfyUI](https://github.com/comfyanonymous/ComfyUI). It offers management functions to **install, remove, disable, and enable** various custom nodes of ComfyUI. Furthermore, this extension provides a hub feature and convenience functions to access a wide range of information within ComfyUI.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## NOTICE
|
## NOTICE
|
||||||
* V3.3.2: Overhauled. Officially supports [https://comfyregistry.org/](https://comfyregistry.org/).
|
* V4.0: Modify the structure to be installable via pip instead of using git clone.
|
||||||
|
* V3.16: Support for `uv` has been added. Set `use_uv` in `config.ini`.
|
||||||
|
* V3.10: `double-click feature` is removed
|
||||||
|
* This feature has been moved to https://github.com/ltdrdata/comfyui-connection-helper
|
||||||
|
* V3.3.2: Overhauled. Officially supports [https://registry.comfy.org/](https://registry.comfy.org/).
|
||||||
* You can see whole nodes info on [ComfyUI Nodes Info](https://ltdrdata.github.io/) page.
|
* You can see whole nodes info on [ComfyUI Nodes Info](https://ltdrdata.github.io/) page.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Installation[method1] (General installation method: ComfyUI-Manager only)
|
* When installing the latest ComfyUI, it will be automatically installed as a dependency, so manual installation is no longer necessary.
|
||||||
|
|
||||||
To install ComfyUI-Manager in addition to an existing installation of ComfyUI, you can follow the following steps:
|
* Manual installation of the nightly version:
|
||||||
|
* Clone to a temporary directory (**Note:** Do **not** clone into `ComfyUI/custom_nodes`.)
|
||||||
|
```
|
||||||
|
git clone https://github.com/Comfy-Org/ComfyUI-Manager
|
||||||
|
```
|
||||||
|
* Install via pip
|
||||||
|
```
|
||||||
|
cd ComfyUI-Manager
|
||||||
|
pip install .
|
||||||
|
```
|
||||||
|
|
||||||
1. goto `ComfyUI/custom_nodes` dir in terminal(cmd)
|
* See also: https://github.com/Comfy-Org/comfy-cli
|
||||||
2. `git clone https://github.com/ltdrdata/ComfyUI-Manager comfyui-manager`
|
|
||||||
3. Restart ComfyUI
|
|
||||||
|
|
||||||
|
|
||||||
### Installation[method2] (Installation for portable ComfyUI version: ComfyUI-Manager only)
|
## Front-end
|
||||||
1. install git
|
|
||||||
- https://git-scm.com/download/win
|
|
||||||
- standalone version
|
|
||||||
- select option: use windows default console window
|
|
||||||
2. Download [scripts/install-manager-for-portable-version.bat](https://github.com/ltdrdata/ComfyUI-Manager/raw/main/scripts/install-manager-for-portable-version.bat) into installed `"ComfyUI_windows_portable"` directory
|
|
||||||
3. double click `install-manager-for-portable-version.bat` batch file
|
|
||||||
|
|
||||||

|
* The built-in front-end of ComfyUI-Manager is the legacy front-end. The front-end for ComfyUI-Manager is now provided via [ComfyUI Frontend](https://github.com/Comfy-Org/ComfyUI_frontend).
|
||||||
|
* To enable the legacy front-end, set the environment variable `ENABLE_LEGACY_COMFYUI_MANAGER_FRONT` to `true` before running.
|
||||||
|
|
||||||
### Installation[method3] (Installation through comfy-cli: install ComfyUI and ComfyUI-Manager at once.)
|
|
||||||
> RECOMMENDED: comfy-cli provides various features to manage ComfyUI from the CLI.
|
|
||||||
|
|
||||||
* **prerequisite: python 3, git**
|
|
||||||
|
|
||||||
Windows:
|
|
||||||
```commandline
|
|
||||||
python -m venv venv
|
|
||||||
venv\Scripts\activate
|
|
||||||
pip install comfy-cli
|
|
||||||
comfy install
|
|
||||||
```
|
|
||||||
|
|
||||||
Linux/OSX:
|
|
||||||
```commandline
|
|
||||||
python -m venv venv
|
|
||||||
. venv/bin/activate
|
|
||||||
pip install comfy-cli
|
|
||||||
comfy install
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Installation[method4] (Installation for linux+venv: ComfyUI + ComfyUI-Manager)
|
|
||||||
|
|
||||||
To install ComfyUI with ComfyUI-Manager on Linux using a venv environment, you can follow these steps:
|
|
||||||
* **prerequisite: python-is-python3, python3-venv, git**
|
|
||||||
|
|
||||||
1. Download [scripts/install-comfyui-venv-linux.sh](https://github.com/ltdrdata/ComfyUI-Manager/raw/main/scripts/install-comfyui-venv-linux.sh) into empty install directory
|
|
||||||
- ComfyUI will be installed in the subdirectory of the specified directory, and the directory will contain the generated executable script.
|
|
||||||
2. `chmod +x install-comfyui-venv-linux.sh`
|
|
||||||
3. `./install-comfyui-venv-linux.sh`
|
|
||||||
|
|
||||||
### Installation Precautions
|
|
||||||
* **DO**: `ComfyUI-Manager` files must be accurately located in the path `ComfyUI/custom_nodes/comfyui-manager`
|
|
||||||
* Installing in a compressed file format is not recommended.
|
|
||||||
* **DON'T**: Decompress directly into the `ComfyUI/custom_nodes` location, resulting in the Manager contents like `__init__.py` being placed directly in that directory.
|
|
||||||
* You have to remove all ComfyUI-Manager files from `ComfyUI/custom_nodes`
|
|
||||||
* **DON'T**: In a form where decompression occurs in a path such as `ComfyUI/custom_nodes/ComfyUI-Manager/ComfyUI-Manager`.
|
|
||||||
* **DON'T**: In a form where decompression occurs in a path such as `ComfyUI/custom_nodes/ComfyUI-Manager-main`.
|
|
||||||
* In such cases, `ComfyUI-Manager` may operate, but it won't be recognized within `ComfyUI-Manager`, and updates cannot be performed. It also poses the risk of duplicate installations. Remove it and install properly via `git clone` method.
|
|
||||||
|
|
||||||
|
|
||||||
You can execute ComfyUI by running either `./run_gpu.sh` or `./run_cpu.sh` depending on your system configuration.
|
|
||||||
|
|
||||||
## Colab Notebook
|
|
||||||
This repository provides Colab notebooks that allow you to install and use ComfyUI, including ComfyUI-Manager. To use ComfyUI, [click on this link](https://colab.research.google.com/github/ltdrdata/ComfyUI-Manager/blob/main/notebooks/comfyui_colab_with_manager.ipynb).
|
|
||||||
* Support for installing ComfyUI
|
|
||||||
* Support for basic installation of ComfyUI-Manager
|
|
||||||
* Support for automatically installing dependencies of custom nodes upon restarting Colab notebooks.
|
|
||||||
|
|
||||||
|
|
||||||
## How To Use
|
## How To Use
|
||||||
@@ -90,7 +45,7 @@ This repository provides Colab notebooks that allow you to install and use Comfy
|
|||||||
|
|
||||||
2. If you click on 'Install Custom Nodes' or 'Install Models', an installer dialog will open.
|
2. If you click on 'Install Custom Nodes' or 'Install Models', an installer dialog will open.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
* There are three DB modes: `DB: Channel (1day cache)`, `DB: Local`, and `DB: Channel (remote)`.
|
* There are three DB modes: `DB: Channel (1day cache)`, `DB: Local`, and `DB: Channel (remote)`.
|
||||||
* `Channel (1day cache)` utilizes Channel cache information with a validity period of one day to quickly display the list.
|
* `Channel (1day cache)` utilizes Channel cache information with a validity period of one day to quickly display the list.
|
||||||
@@ -143,11 +98,21 @@ In `ComfyUI-Manager` V3.0 and later, configuration files and dynamically generat
|
|||||||
* Basic config files: `<USER_DIRECTORY>/default/ComfyUI-Manager/config.ini`
|
* Basic config files: `<USER_DIRECTORY>/default/ComfyUI-Manager/config.ini`
|
||||||
* Configurable channel lists: `<USER_DIRECTORY>/default/ComfyUI-Manager/channels.ini`
|
* Configurable channel lists: `<USER_DIRECTORY>/default/ComfyUI-Manager/channels.ini`
|
||||||
* Configurable pip overrides: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_overrides.json`
|
* Configurable pip overrides: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_overrides.json`
|
||||||
|
* Configurable pip blacklist: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_blacklist.list`
|
||||||
|
* Configurable pip auto fix: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_auto_fix.list`
|
||||||
* Saved snapshot files: `<USER_DIRECTORY>/default/ComfyUI-Manager/snapshots`
|
* Saved snapshot files: `<USER_DIRECTORY>/default/ComfyUI-Manager/snapshots`
|
||||||
* Startup script files: `<USER_DIRECTORY>/default/ComfyUI-Manager/startup-scripts`
|
* Startup script files: `<USER_DIRECTORY>/default/ComfyUI-Manager/startup-scripts`
|
||||||
* Component files: `<USER_DIRECTORY>/default/ComfyUI-Manager/components`
|
* Component files: `<USER_DIRECTORY>/default/ComfyUI-Manager/components`
|
||||||
|
|
||||||
|
|
||||||
|
## `extra_model_paths.yaml` Configuration
|
||||||
|
The following settings are applied based on the section marked as `is_default`.
|
||||||
|
|
||||||
|
* `custom_nodes`: Path for installing custom nodes
|
||||||
|
* Importing does not need to adhere to the path set as `is_default`, but this is the path where custom nodes are installed by the `ComfyUI Nodes Manager`.
|
||||||
|
* `download_model_base`: Path for downloading models
|
||||||
|
|
||||||
|
|
||||||
## Snapshot-Manager
|
## Snapshot-Manager
|
||||||
* When you press `Save snapshot` or use `Update All` on `Manager Menu`, the current installation status snapshot is saved.
|
* When you press `Save snapshot` or use `Update All` on `Manager Menu`, the current installation status snapshot is saved.
|
||||||
* Snapshot file dir: `<USER_DIRECTORY>/default/ComfyUI-Manager/snapshots`
|
* Snapshot file dir: `<USER_DIRECTORY>/default/ComfyUI-Manager/snapshots`
|
||||||
@@ -174,17 +139,18 @@ In `ComfyUI-Manager` V3.0 and later, configuration files and dynamically generat
|
|||||||
|
|
||||||
## Custom node support guide
|
## Custom node support guide
|
||||||
|
|
||||||
|
* **NOTICE:**
|
||||||
|
- You should no longer assume that the GitHub repository name will match the subdirectory name under `custom_nodes`. The name of the subdirectory under `custom_nodes` will now use the normalized name from the `name` field in `pyproject.toml`.
|
||||||
|
- Avoid relying on directory names for imports whenever possible.
|
||||||
|
|
||||||
* https://docs.comfy.org/registry/overview
|
* https://docs.comfy.org/registry/overview
|
||||||
|
* https://github.com/Comfy-Org/rfcs
|
||||||
|
|
||||||
|
**Special purpose files** (optional)
|
||||||
* **Special purpose files** (optional)
|
* `pyproject.toml` - Spec file for comfyregistry.
|
||||||
* `node_list.json` - When your custom nodes pattern of NODE_CLASS_MAPPINGS is not conventional, it is used to manually provide a list of nodes for reference. ([example](https://github.com/melMass/comfy_mtb/raw/main/node_list.json))
|
* `node_list.json` - When your custom nodes pattern of NODE_CLASS_MAPPINGS is not conventional, it is used to manually provide a list of nodes for reference. ([example](https://github.com/melMass/comfy_mtb/raw/main/node_list.json))
|
||||||
* `requirements.txt` - When installing, this pip requirements will be installed automatically
|
* `requirements.txt` - When installing, this pip requirements will be installed automatically
|
||||||
* `install.py` - When installing, it is automatically called
|
* `install.py` - When installing, it is automatically called
|
||||||
* `uninstall.py` - When uninstalling, it is automatically called
|
|
||||||
* `disable.py` - When disabled, it is automatically called
|
|
||||||
* When installing a custom node setup `.js` file, it is recommended to write this script for disabling.
|
|
||||||
* `enable.py` - When enabled, it is automatically called
|
|
||||||
* **All scripts are executed from the root path of the corresponding custom node.**
|
* **All scripts are executed from the root path of the corresponding custom node.**
|
||||||
|
|
||||||
|
|
||||||
@@ -225,13 +191,39 @@ In `ComfyUI-Manager` V3.0 and later, configuration files and dynamically generat
|
|||||||
|
|
||||||
## Support of missing nodes installation
|
## Support of missing nodes installation
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
* When you click on the ```Install Missing Custom Nodes``` button in the menu, it displays a list of extension nodes that contain nodes not currently present in the workflow.
|
* When you click on the ```Install Missing Custom Nodes``` button in the menu, it displays a list of extension nodes that contain nodes not currently present in the workflow.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
# Config
|
||||||
|
* You can modify the `config.ini` file to apply the settings for ComfyUI-Manager.
|
||||||
|
* The path to the `config.ini` used by ComfyUI-Manager is displayed in the startup log messages.
|
||||||
|
* See also: [https://github.com/ltdrdata/ComfyUI-Manager#paths]
|
||||||
|
* Configuration options:
|
||||||
|
```
|
||||||
|
[default]
|
||||||
|
git_exe = <Manually specify the path to the git executable. If left empty, the default git executable path will be used.>
|
||||||
|
use_uv = <Use uv instead of pip for dependency installation.>
|
||||||
|
default_cache_as_channel_url = <Determines whether to retrieve the DB designated as channel_url at startup>
|
||||||
|
bypass_ssl = <Set to True if SSL errors occur to disable SSL.>
|
||||||
|
file_logging = <Configure whether to create a log file used by ComfyUI-Manager.>
|
||||||
|
windows_selector_event_loop_policy = <If an event loop error occurs on Windows, set this to True.>
|
||||||
|
model_download_by_agent = <When downloading models, use an agent instead of torchvision_download_url.>
|
||||||
|
downgrade_blacklist = <Set a list of packages to prevent downgrades. List them separated by commas.>
|
||||||
|
security_level = <Set the security level => strong|normal|normal-|weak>
|
||||||
|
always_lazy_install = <Whether to perform dependency installation on restart even in environments other than Windows.>
|
||||||
|
network_mode = <Set the network mode => public|private|offline>
|
||||||
|
```
|
||||||
|
|
||||||
|
* network_mode:
|
||||||
|
- public: An environment that uses a typical public network.
|
||||||
|
- private: An environment that uses a closed network, where a private node DB is configured via `channel_url`. (Uses cache if available)
|
||||||
|
- offline: An environment that does not use any external connections when using an offline network. (Uses cache if available)
|
||||||
|
|
||||||
|
|
||||||
## Additional Feature
|
## Additional Feature
|
||||||
* Logging to file feature
|
* Logging to file feature
|
||||||
* This feature is enabled by default and can be disabled by setting `file_logging = False` in the `config.ini`.
|
* This feature is enabled by default and can be disabled by setting `file_logging = False` in the `config.ini`.
|
||||||
@@ -261,11 +253,39 @@ In `ComfyUI-Manager` V3.0 and later, configuration files and dynamically generat
|
|||||||
* When you create the `pip_overrides.json` file, it changes the installation of specific pip packages to installations defined by the user.
|
* When you create the `pip_overrides.json` file, it changes the installation of specific pip packages to installations defined by the user.
|
||||||
* Please refer to the `pip_overrides.json.template` file.
|
* Please refer to the `pip_overrides.json.template` file.
|
||||||
|
|
||||||
|
* Prevent the installation of specific pip packages
|
||||||
|
* List the package names one per line in the `pip_blacklist.list` file.
|
||||||
|
|
||||||
|
* Automatically Restoring pip Installation
|
||||||
|
* If you list pip spec requirements in `pip_auto_fix.list`, similar to `requirements.txt`, it will automatically restore the specified versions when starting ComfyUI or when versions get mismatched during various custom node installations.
|
||||||
|
* `--index-url` can be used.
|
||||||
|
|
||||||
* Use `aria2` as downloader
|
* Use `aria2` as downloader
|
||||||
* [howto](docs/en/use_aria2.md)
|
* [howto](docs/en/use_aria2.md)
|
||||||
|
|
||||||
* If you add the item `skip_migration_check = True` to `config.ini`, it will not check whether there are nodes that can be migrated at startup.
|
|
||||||
* This option can be used if performance issues occur in a Colab+GDrive environment.
|
## Environment Variables
|
||||||
|
|
||||||
|
The following features can be configured using environment variables:
|
||||||
|
|
||||||
|
* **COMFYUI_PATH**: The installation path of ComfyUI
|
||||||
|
* **GITHUB_ENDPOINT**: Reverse proxy configuration for environments with limited access to GitHub
|
||||||
|
* **HF_ENDPOINT**: Reverse proxy configuration for environments with limited access to Hugging Face
|
||||||
|
|
||||||
|
|
||||||
|
### Example 1:
|
||||||
|
Redirecting `https://github.com/ltdrdata/ComfyUI-Impact-Pack` to `https://mirror.ghproxy.com/https://github.com/ltdrdata/ComfyUI-Impact-Pack`
|
||||||
|
|
||||||
|
```
|
||||||
|
GITHUB_ENDPOINT=https://mirror.ghproxy.com/https://github.com
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Example 2:
|
||||||
|
Changing `https://huggingface.co/path/to/somewhere` to `https://some-hf-mirror.com/path/to/somewhere`
|
||||||
|
|
||||||
|
```
|
||||||
|
HF_ENDPOINT=https://some-hf-mirror.com
|
||||||
|
```
|
||||||
|
|
||||||
## Scanner
|
## Scanner
|
||||||
When you run the `scan.sh` script:
|
When you run the `scan.sh` script:
|
||||||
@@ -290,6 +310,7 @@ When you run the `scan.sh` script:
|
|||||||
* if `SSL: CERTIFICATE_VERIFY_FAILED` error is occured.
|
* if `SSL: CERTIFICATE_VERIFY_FAILED` error is occured.
|
||||||
* Edit `config.ini` file: add `bypass_ssl = True`
|
* Edit `config.ini` file: add `bypass_ssl = True`
|
||||||
|
|
||||||
|
|
||||||
## Security policy
|
## Security policy
|
||||||
* Edit `config.ini` file: add `security_level = <LEVEL>`
|
* Edit `config.ini` file: add `security_level = <LEVEL>`
|
||||||
* `strong`
|
* `strong`
|
||||||
|
|||||||
18
__init__.py
18
__init__.py
@@ -1,18 +0,0 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
cli_mode_flag = os.path.join(os.path.dirname(__file__), '.enable-cli-only-mode')
|
|
||||||
|
|
||||||
if not os.path.exists(cli_mode_flag):
|
|
||||||
sys.path.append(os.path.join(os.path.dirname(__file__), "glob"))
|
|
||||||
import manager_server # noqa: F401
|
|
||||||
import share_3rdparty # noqa: F401
|
|
||||||
WEB_DIRECTORY = "js"
|
|
||||||
else:
|
|
||||||
print("\n[ComfyUI-Manager] !! cli-only-mode is enabled !!\n")
|
|
||||||
|
|
||||||
NODE_CLASS_MAPPINGS = {}
|
|
||||||
__all__ = ['NODE_CLASS_MAPPINGS']
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
default::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main
|
|
||||||
recent::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/new
|
|
||||||
legacy::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/legacy
|
|
||||||
forked::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/forked
|
|
||||||
dev::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/dev
|
|
||||||
tutorial::https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/node_db/tutorial
|
|
||||||
49
comfyui_manager/README.md
Normal file
49
comfyui_manager/README.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# ComfyUI-Manager: Core Backend (glob)
|
||||||
|
|
||||||
|
This directory contains the Python backend modules that power ComfyUI-Manager, handling the core functionality of node management, downloading, security, and server operations.
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
- **glob/** - code for new cacheless ComfyUI-Manager
|
||||||
|
- **legacy/** - code for legacy ComfyUI-Manager
|
||||||
|
|
||||||
|
## Core Modules
|
||||||
|
- **manager_core.py**: The central implementation of management functions, handling configuration, installation, updates, and node management.
|
||||||
|
- **manager_server.py**: Implements server functionality and API endpoints for the web interface to interact with the backend.
|
||||||
|
|
||||||
|
## Specialized Modules
|
||||||
|
|
||||||
|
- **share_3rdparty.py**: Manages integration with third-party sharing platforms.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The backend follows a modular design pattern with clear separation of concerns:
|
||||||
|
|
||||||
|
1. **Core Layer**: Manager modules provide the primary API and business logic
|
||||||
|
2. **Utility Layer**: Helper modules provide specialized functionality
|
||||||
|
3. **Integration Layer**: Modules that connect to external systems
|
||||||
|
|
||||||
|
## Security Model
|
||||||
|
|
||||||
|
The system implements a comprehensive security framework with multiple levels:
|
||||||
|
|
||||||
|
- **Block**: Highest security - blocks most remote operations
|
||||||
|
- **High**: Allows only specific trusted operations
|
||||||
|
- **Middle**: Standard security for most users
|
||||||
|
- **Normal-**: More permissive for advanced users
|
||||||
|
- **Weak**: Lowest security for development environments
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
- The backend is designed to work seamlessly with ComfyUI
|
||||||
|
- Asynchronous task queuing is implemented for background operations
|
||||||
|
- The system supports multiple installation modes
|
||||||
|
- Error handling and risk assessment are integrated throughout the codebase
|
||||||
|
|
||||||
|
## API Integration
|
||||||
|
|
||||||
|
The backend exposes a REST API via `manager_server.py` that enables:
|
||||||
|
- Custom node management (install, update, disable, remove)
|
||||||
|
- Model downloading and organization
|
||||||
|
- System configuration
|
||||||
|
- Snapshot management
|
||||||
|
- Workflow component handling
|
||||||
45
comfyui_manager/__init__.py
Normal file
45
comfyui_manager/__init__.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
def prestartup():
|
||||||
|
from . import prestartup_script # noqa: F401
|
||||||
|
logging.info('[PRE] ComfyUI-Manager')
|
||||||
|
|
||||||
|
|
||||||
|
def start():
|
||||||
|
from comfy.cli_args import args
|
||||||
|
|
||||||
|
logging.info('[START] ComfyUI-Manager')
|
||||||
|
from .common import cm_global # noqa: F401
|
||||||
|
|
||||||
|
if not args.disable_manager:
|
||||||
|
if args.enable_manager_legacy_ui:
|
||||||
|
try:
|
||||||
|
from .legacy import manager_server # noqa: F401
|
||||||
|
from .legacy import share_3rdparty # noqa: F401
|
||||||
|
import nodes
|
||||||
|
|
||||||
|
logging.info("[ComfyUI-Manager] Legacy UI is enabled.")
|
||||||
|
nodes.EXTENSION_WEB_DIRS['comfyui-manager-legacy'] = os.path.join(os.path.dirname(__file__), 'js')
|
||||||
|
except Exception as e:
|
||||||
|
print("Error enabling legacy ComfyUI Manager frontend:", e)
|
||||||
|
else:
|
||||||
|
from .glob import manager_server # noqa: F401
|
||||||
|
from .glob import share_3rdparty # noqa: F401
|
||||||
|
|
||||||
|
|
||||||
|
def should_be_disabled(fullpath:str) -> bool:
|
||||||
|
"""
|
||||||
|
1. Disables the legacy ComfyUI-Manager.
|
||||||
|
2. The blocklist can be expanded later based on policies.
|
||||||
|
"""
|
||||||
|
from comfy.cli_args import args
|
||||||
|
|
||||||
|
if not args.disable_manager:
|
||||||
|
# In cases where installation is done via a zip archive, the directory name may not be comfyui-manager, and it may not contain a git repository.
|
||||||
|
# It is assumed that any installed legacy ComfyUI-Manager will have at least 'comfyui-manager' in its directory name.
|
||||||
|
dir_name = os.path.basename(fullpath).lower()
|
||||||
|
if 'comfyui-manager' in dir_name:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
6
comfyui_manager/channels.list.template
Normal file
6
comfyui_manager/channels.list.template
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
default::https://raw.githubusercontent.com/Comfy-Org/ComfyUI-Manager/main
|
||||||
|
recent::https://raw.githubusercontent.com/Comfy-Org/ComfyUI-Manager/main/node_db/new
|
||||||
|
legacy::https://raw.githubusercontent.com/Comfy-Org/ComfyUI-Manager/main/node_db/legacy
|
||||||
|
forked::https://raw.githubusercontent.com/Comfy-Org/ComfyUI-Manager/main/node_db/forked
|
||||||
|
dev::https://raw.githubusercontent.com/Comfy-Org/ComfyUI-Manager/main/node_db/dev
|
||||||
|
tutorial::https://raw.githubusercontent.com/Comfy-Org/ComfyUI-Manager/main/node_db/tutorial
|
||||||
@@ -15,54 +15,67 @@ import git
|
|||||||
import importlib
|
import importlib
|
||||||
|
|
||||||
|
|
||||||
sys.path.append(os.path.dirname(__file__))
|
from ..common import manager_util
|
||||||
sys.path.append(os.path.join(os.path.dirname(__file__), "glob"))
|
|
||||||
|
|
||||||
import manager_util
|
# read env vars
|
||||||
|
# COMFYUI_FOLDERS_BASE_PATH is not required in cm-cli.py
|
||||||
|
# `comfy_path` should be resolved before importing manager_core
|
||||||
|
|
||||||
comfy_path = os.environ.get('COMFYUI_PATH')
|
comfy_path = os.environ.get('COMFYUI_PATH')
|
||||||
|
|
||||||
if comfy_path is None:
|
if comfy_path is None:
|
||||||
try:
|
print("[bold red]cm-cli: environment variable 'COMFYUI_PATH' is not specified.[/bold red]")
|
||||||
import folder_paths
|
exit(-1)
|
||||||
comfy_path = os.path.join(os.path.dirname(folder_paths.__file__))
|
|
||||||
except:
|
|
||||||
comfy_path = os.path.abspath(os.path.join(manager_util.comfyui_manager_path, '..', '..'))
|
|
||||||
|
|
||||||
sys.path.append(comfy_path)
|
sys.path.append(comfy_path)
|
||||||
|
|
||||||
|
if not os.path.exists(os.path.join(comfy_path, 'folder_paths.py')):
|
||||||
|
print("[bold red]cm-cli: '{comfy_path}' is not a valid 'COMFYUI_PATH' location.[/bold red]")
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
|
||||||
import utils.extra_config
|
import utils.extra_config
|
||||||
import cm_global
|
from ..common import cm_global
|
||||||
import manager_core as core
|
from ..legacy import manager_core as core
|
||||||
from manager_core import unified_manager
|
from ..common import context
|
||||||
import cnr_utils
|
from ..legacy.manager_core import unified_manager
|
||||||
|
from ..common import cnr_utils
|
||||||
|
|
||||||
|
|
||||||
comfyui_manager_path = os.path.abspath(os.path.dirname(__file__))
|
comfyui_manager_path = os.path.abspath(os.path.dirname(__file__))
|
||||||
comfy_path = os.environ.get('COMFYUI_PATH')
|
|
||||||
|
|
||||||
if comfy_path is None:
|
cm_global.pip_blacklist = {'torch', 'torchaudio', 'torchsde', 'torchvision'}
|
||||||
print("\n[bold yellow]WARN: The `COMFYUI_PATH` environment variable is not set. Assuming `custom_nodes/ComfyUI-Manager/../../` as the ComfyUI path.[/bold yellow]", file=sys.stderr)
|
cm_global.pip_downgrade_blacklist = ['torch', 'torchaudio', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
|
||||||
comfy_path = os.path.abspath(os.path.join(comfyui_manager_path, '..', '..'))
|
|
||||||
|
|
||||||
|
if sys.version_info < (3, 13):
|
||||||
cm_global.pip_blacklist = ['torch', 'torchsde', 'torchvision']
|
cm_global.pip_overrides = {'numpy': 'numpy<2'}
|
||||||
cm_global.pip_downgrade_blacklist = ['torch', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
|
else:
|
||||||
cm_global.pip_overrides = {'numpy': 'numpy<2'}
|
cm_global.pip_overrides = {}
|
||||||
|
|
||||||
if os.path.exists(os.path.join(manager_util.comfyui_manager_path, "pip_overrides.json")):
|
if os.path.exists(os.path.join(manager_util.comfyui_manager_path, "pip_overrides.json")):
|
||||||
with open(os.path.join(manager_util.comfyui_manager_path, "pip_overrides.json"), 'r', encoding="UTF-8", errors="ignore") as json_file:
|
with open(os.path.join(manager_util.comfyui_manager_path, "pip_overrides.json"), 'r', encoding="UTF-8", errors="ignore") as json_file:
|
||||||
cm_global.pip_overrides = json.load(json_file)
|
cm_global.pip_overrides = json.load(json_file)
|
||||||
|
|
||||||
|
|
||||||
|
if os.path.exists(os.path.join(manager_util.comfyui_manager_path, "pip_blacklist.list")):
|
||||||
|
with open(os.path.join(manager_util.comfyui_manager_path, "pip_blacklist.list"), 'r', encoding="UTF-8", errors="ignore") as f:
|
||||||
|
for x in f.readlines():
|
||||||
|
y = x.strip()
|
||||||
|
if y != '':
|
||||||
|
cm_global.pip_blacklist.add(y)
|
||||||
|
|
||||||
|
|
||||||
def check_comfyui_hash():
|
def check_comfyui_hash():
|
||||||
repo = git.Repo(comfy_path)
|
try:
|
||||||
core.comfy_ui_revision = len(list(repo.iter_commits('HEAD')))
|
repo = git.Repo(comfy_path)
|
||||||
|
core.comfy_ui_revision = len(list(repo.iter_commits('HEAD')))
|
||||||
|
core.comfy_ui_commit_datetime = repo.head.commit.committed_datetime
|
||||||
|
except Exception:
|
||||||
|
print('[bold yellow]INFO: Frozen ComfyUI mode.[/bold yellow]')
|
||||||
|
core.comfy_ui_revision = 0
|
||||||
|
core.comfy_ui_commit_datetime = 0
|
||||||
|
|
||||||
cm_global.variables['comfyui.revision'] = core.comfy_ui_revision
|
cm_global.variables['comfyui.revision'] = core.comfy_ui_revision
|
||||||
|
|
||||||
core.comfy_ui_commit_datetime = repo.head.commit.committed_datetime
|
|
||||||
|
|
||||||
|
|
||||||
check_comfyui_hash() # This is a preparation step for manager_core
|
check_comfyui_hash() # This is a preparation step for manager_core
|
||||||
core.check_invalid_nodes()
|
core.check_invalid_nodes()
|
||||||
@@ -71,9 +84,8 @@ core.check_invalid_nodes()
|
|||||||
def read_downgrade_blacklist():
|
def read_downgrade_blacklist():
|
||||||
try:
|
try:
|
||||||
import configparser
|
import configparser
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "config.ini")
|
config = configparser.ConfigParser(strict=False)
|
||||||
config = configparser.ConfigParser()
|
config.read(context.manager_config_path)
|
||||||
config.read(config_path)
|
|
||||||
default_conf = config['default']
|
default_conf = config['default']
|
||||||
|
|
||||||
if 'downgrade_blacklist' in default_conf:
|
if 'downgrade_blacklist' in default_conf:
|
||||||
@@ -81,7 +93,7 @@ def read_downgrade_blacklist():
|
|||||||
items = [x.strip() for x in items if x != '']
|
items = [x.strip() for x in items if x != '']
|
||||||
cm_global.pip_downgrade_blacklist += items
|
cm_global.pip_downgrade_blacklist += items
|
||||||
cm_global.pip_downgrade_blacklist = list(set(cm_global.pip_downgrade_blacklist))
|
cm_global.pip_downgrade_blacklist = list(set(cm_global.pip_downgrade_blacklist))
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -96,7 +108,8 @@ class Ctx:
|
|||||||
self.no_deps = False
|
self.no_deps = False
|
||||||
self.mode = 'cache'
|
self.mode = 'cache'
|
||||||
self.user_directory = None
|
self.user_directory = None
|
||||||
self.custom_nodes_paths = [os.path.join(core.comfy_path, 'custom_nodes')]
|
self.custom_nodes_paths = [os.path.join(context.comfy_base_path, 'custom_nodes')]
|
||||||
|
self.manager_files_directory = os.path.dirname(__file__)
|
||||||
|
|
||||||
if Ctx.folder_paths is None:
|
if Ctx.folder_paths is None:
|
||||||
try:
|
try:
|
||||||
@@ -119,7 +132,7 @@ class Ctx:
|
|||||||
if channel is not None:
|
if channel is not None:
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
|
|
||||||
asyncio.run(unified_manager.reload(cache_mode=self.mode == 'cache'))
|
asyncio.run(unified_manager.reload(cache_mode=self.mode, dont_wait=False))
|
||||||
asyncio.run(unified_manager.load_nightly(self.channel, self.mode))
|
asyncio.run(unified_manager.load_nightly(self.channel, self.mode))
|
||||||
|
|
||||||
def set_no_deps(self, no_deps):
|
def set_no_deps(self, no_deps):
|
||||||
@@ -133,24 +146,38 @@ class Ctx:
|
|||||||
if os.path.exists(extra_model_paths_yaml):
|
if os.path.exists(extra_model_paths_yaml):
|
||||||
utils.extra_config.load_extra_path_config(extra_model_paths_yaml)
|
utils.extra_config.load_extra_path_config(extra_model_paths_yaml)
|
||||||
|
|
||||||
core.update_user_directory(user_directory)
|
context.update_user_directory(user_directory)
|
||||||
|
|
||||||
if os.path.exists(core.manager_pip_overrides_path):
|
if os.path.exists(context.manager_pip_overrides_path):
|
||||||
cm_global.pip_overrides = {'numpy': 'numpy<2'}
|
with open(context.manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file:
|
||||||
with open(core.manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file:
|
|
||||||
cm_global.pip_overrides = json.load(json_file)
|
cm_global.pip_overrides = json.load(json_file)
|
||||||
|
|
||||||
|
if sys.version_info < (3, 13):
|
||||||
|
cm_global.pip_overrides = {'numpy': 'numpy<2'}
|
||||||
|
|
||||||
|
if os.path.exists(context.manager_pip_blacklist_path):
|
||||||
|
with open(context.manager_pip_blacklist_path, 'r', encoding="UTF-8", errors="ignore") as f:
|
||||||
|
for x in f.readlines():
|
||||||
|
y = x.strip()
|
||||||
|
if y != '':
|
||||||
|
cm_global.pip_blacklist.add(y)
|
||||||
|
|
||||||
|
def update_custom_nodes_dir(self, target_dir):
|
||||||
|
import folder_paths
|
||||||
|
a, b = folder_paths.folder_names_and_paths['custom_nodes']
|
||||||
|
folder_paths.folder_names_and_paths['custom_nodes'] = [os.path.abspath(target_dir)], set()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_startup_scripts_path():
|
def get_startup_scripts_path():
|
||||||
return os.path.join(core.manager_startup_script_path, "install-scripts.txt")
|
return os.path.join(context.manager_startup_script_path, "install-scripts.txt")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_restore_snapshot_path():
|
def get_restore_snapshot_path():
|
||||||
return os.path.join(core.manager_startup_script_path, "restore-snapshot.json")
|
return os.path.join(context.manager_startup_script_path, "restore-snapshot.json")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_snapshot_path():
|
def get_snapshot_path():
|
||||||
return core.manager_snapshot_path
|
return context.manager_snapshot_path
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_custom_nodes_paths():
|
def get_custom_nodes_paths():
|
||||||
@@ -163,13 +190,18 @@ class Ctx:
|
|||||||
cmd_ctx = Ctx()
|
cmd_ctx = Ctx()
|
||||||
|
|
||||||
|
|
||||||
def install_node(node_spec_str, is_all=False, cnt_msg=''):
|
def install_node(node_spec_str, is_all=False, cnt_msg='', **kwargs):
|
||||||
|
exit_on_fail = kwargs.get('exit_on_fail', False)
|
||||||
|
print(f"install_node exit on fail:{exit_on_fail}...")
|
||||||
|
|
||||||
if core.is_valid_url(node_spec_str):
|
if core.is_valid_url(node_spec_str):
|
||||||
# install via urls
|
# install via urls
|
||||||
res = asyncio.run(core.gitclone_install(node_spec_str, no_deps=cmd_ctx.no_deps))
|
res = asyncio.run(core.gitclone_install(node_spec_str, no_deps=cmd_ctx.no_deps))
|
||||||
if not res.result:
|
if not res.result:
|
||||||
print(res.msg)
|
print(res.msg)
|
||||||
print(f"[bold red]ERROR: An error occurred while installing '{node_spec_str}'.[/bold red]")
|
print(f"[bold red]ERROR: An error occurred while installing '{node_spec_str}'.[/bold red]")
|
||||||
|
if exit_on_fail:
|
||||||
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
print(f"{cnt_msg} [INSTALLED] {node_spec_str:50}")
|
print(f"{cnt_msg} [INSTALLED] {node_spec_str:50}")
|
||||||
else:
|
else:
|
||||||
@@ -204,6 +236,8 @@ def install_node(node_spec_str, is_all=False, cnt_msg=''):
|
|||||||
print("")
|
print("")
|
||||||
else:
|
else:
|
||||||
print(f"[bold red]ERROR: An error occurred while installing '{node_name}'.\n{res.msg}[/bold red]")
|
print(f"[bold red]ERROR: An error occurred while installing '{node_name}'.\n{res.msg}[/bold red]")
|
||||||
|
if exit_on_fail:
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
def reinstall_node(node_spec_str, is_all=False, cnt_msg=''):
|
def reinstall_node(node_spec_str, is_all=False, cnt_msg=''):
|
||||||
@@ -233,7 +267,7 @@ def fix_node(node_spec_str, is_all=False, cnt_msg=''):
|
|||||||
res = unified_manager.unified_fix(node_name, version_spec, no_deps=cmd_ctx.no_deps)
|
res = unified_manager.unified_fix(node_name, version_spec, no_deps=cmd_ctx.no_deps)
|
||||||
|
|
||||||
if not res.result:
|
if not res.result:
|
||||||
print(f"ERROR: f{res.msg}")
|
print(f"[bold red]ERROR: f{res.msg}[/bold red]")
|
||||||
|
|
||||||
|
|
||||||
def uninstall_node(node_spec_str: str, is_all: bool = False, cnt_msg: str = ''):
|
def uninstall_node(node_spec_str: str, is_all: bool = False, cnt_msg: str = ''):
|
||||||
@@ -410,8 +444,11 @@ def show_list(kind, simple=False):
|
|||||||
flag = kind in ['all', 'cnr', 'installed', 'enabled']
|
flag = kind in ['all', 'cnr', 'installed', 'enabled']
|
||||||
for k, v in unified_manager.active_nodes.items():
|
for k, v in unified_manager.active_nodes.items():
|
||||||
if flag:
|
if flag:
|
||||||
cnr = unified_manager.cnr_map[k]
|
cnr = unified_manager.cnr_map.get(k)
|
||||||
processed[k] = "[ ENABLED ] ", cnr['name'], k, cnr['publisher']['name'], v[0]
|
if cnr:
|
||||||
|
processed[k] = "[ ENABLED ] ", cnr['name'], k, cnr['publisher']['name'], v[0]
|
||||||
|
else:
|
||||||
|
processed[k] = None
|
||||||
else:
|
else:
|
||||||
processed[k] = None
|
processed[k] = None
|
||||||
|
|
||||||
@@ -431,8 +468,11 @@ def show_list(kind, simple=False):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if flag:
|
if flag:
|
||||||
cnr = unified_manager.cnr_map[k]
|
cnr = unified_manager.cnr_map.get(k) # NOTE: can this be None if removed from CNR after installed
|
||||||
processed[k] = "[ DISABLED ] ", cnr['name'], k, cnr['publisher']['name'], ", ".join(list(v.keys()))
|
if cnr:
|
||||||
|
processed[k] = "[ DISABLED ] ", cnr['name'], k, cnr['publisher']['name'], ", ".join(list(v.keys()))
|
||||||
|
else:
|
||||||
|
processed[k] = None
|
||||||
else:
|
else:
|
||||||
processed[k] = None
|
processed[k] = None
|
||||||
|
|
||||||
@@ -441,8 +481,11 @@ def show_list(kind, simple=False):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if flag:
|
if flag:
|
||||||
cnr = unified_manager.cnr_map[k]
|
cnr = unified_manager.cnr_map.get(k)
|
||||||
processed[k] = "[ DISABLED ] ", cnr['name'], k, cnr['publisher']['name'], 'nightly'
|
if cnr:
|
||||||
|
processed[k] = "[ DISABLED ] ", cnr['name'], k, cnr['publisher']['name'], 'nightly'
|
||||||
|
else:
|
||||||
|
processed[k] = None
|
||||||
else:
|
else:
|
||||||
processed[k] = None
|
processed[k] = None
|
||||||
|
|
||||||
@@ -462,9 +505,12 @@ def show_list(kind, simple=False):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if flag:
|
if flag:
|
||||||
cnr = unified_manager.cnr_map[k]
|
cnr = unified_manager.cnr_map.get(k)
|
||||||
ver_spec = v['latest_version']['version'] if 'latest_version' in v else '0.0.0'
|
if cnr:
|
||||||
processed[k] = "[ NOT INSTALLED ] ", cnr['name'], k, cnr['publisher']['name'], ver_spec
|
ver_spec = v['latest_version']['version'] if 'latest_version' in v else '0.0.0'
|
||||||
|
processed[k] = "[ NOT INSTALLED ] ", cnr['name'], k, cnr['publisher']['name'], ver_spec
|
||||||
|
else:
|
||||||
|
processed[k] = None
|
||||||
else:
|
else:
|
||||||
processed[k] = None
|
processed[k] = None
|
||||||
|
|
||||||
@@ -538,7 +584,7 @@ def get_all_installed_node_specs():
|
|||||||
res.append(node_spec_str)
|
res.append(node_spec_str)
|
||||||
processed.add(k)
|
processed.add(k)
|
||||||
|
|
||||||
for k, _ in unified_manager.cnr_inactive_nodes.keys():
|
for k in unified_manager.cnr_inactive_nodes.keys():
|
||||||
if k in processed:
|
if k in processed:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -547,7 +593,7 @@ def get_all_installed_node_specs():
|
|||||||
node_spec_str = f"{k}@{str(latest[0])}"
|
node_spec_str = f"{k}@{str(latest[0])}"
|
||||||
res.append(node_spec_str)
|
res.append(node_spec_str)
|
||||||
|
|
||||||
for k, _ in unified_manager.nightly_inactive_nodes.keys():
|
for k in unified_manager.nightly_inactive_nodes.keys():
|
||||||
if k in processed:
|
if k in processed:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -565,7 +611,7 @@ def get_all_installed_node_specs():
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
def for_each_nodes(nodes, act, allow_all=True):
|
def for_each_nodes(nodes, act, allow_all=True, **kwargs):
|
||||||
is_all = False
|
is_all = False
|
||||||
if allow_all and 'all' in nodes:
|
if allow_all and 'all' in nodes:
|
||||||
is_all = True
|
is_all = True
|
||||||
@@ -577,7 +623,7 @@ def for_each_nodes(nodes, act, allow_all=True):
|
|||||||
i = 1
|
i = 1
|
||||||
for x in nodes:
|
for x in nodes:
|
||||||
try:
|
try:
|
||||||
act(x, is_all=is_all, cnt_msg=f'{i}/{total}')
|
act(x, is_all=is_all, cnt_msg=f'{i}/{total}', **kwargs)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"ERROR: {e}")
|
print(f"ERROR: {e}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@@ -621,11 +667,18 @@ def install(
|
|||||||
None,
|
None,
|
||||||
help="user directory"
|
help="user directory"
|
||||||
),
|
),
|
||||||
|
exit_on_fail: bool = typer.Option(
|
||||||
|
False,
|
||||||
|
help="Exit on failure"
|
||||||
|
)
|
||||||
):
|
):
|
||||||
cmd_ctx.set_user_directory(user_directory)
|
cmd_ctx.set_user_directory(user_directory)
|
||||||
cmd_ctx.set_channel_mode(channel, mode)
|
cmd_ctx.set_channel_mode(channel, mode)
|
||||||
cmd_ctx.set_no_deps(no_deps)
|
cmd_ctx.set_no_deps(no_deps)
|
||||||
for_each_nodes(nodes, act=install_node)
|
|
||||||
|
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, context.manager_files_path)
|
||||||
|
for_each_nodes(nodes, act=install_node, exit_on_fail=exit_on_fail)
|
||||||
|
pip_fixer.fix_broken()
|
||||||
|
|
||||||
|
|
||||||
@app.command(help="Reinstall custom nodes")
|
@app.command(help="Reinstall custom nodes")
|
||||||
@@ -660,7 +713,10 @@ def reinstall(
|
|||||||
cmd_ctx.set_user_directory(user_directory)
|
cmd_ctx.set_user_directory(user_directory)
|
||||||
cmd_ctx.set_channel_mode(channel, mode)
|
cmd_ctx.set_channel_mode(channel, mode)
|
||||||
cmd_ctx.set_no_deps(no_deps)
|
cmd_ctx.set_no_deps(no_deps)
|
||||||
|
|
||||||
|
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, context.manager_files_path)
|
||||||
for_each_nodes(nodes, act=reinstall_node)
|
for_each_nodes(nodes, act=reinstall_node)
|
||||||
|
pip_fixer.fix_broken()
|
||||||
|
|
||||||
|
|
||||||
@app.command(help="Uninstall custom nodes")
|
@app.command(help="Uninstall custom nodes")
|
||||||
@@ -684,7 +740,7 @@ def uninstall(
|
|||||||
for_each_nodes(nodes, act=uninstall_node)
|
for_each_nodes(nodes, act=uninstall_node)
|
||||||
|
|
||||||
|
|
||||||
@app.command(help="Disable custom nodes")
|
@app.command(help="Update custom nodes")
|
||||||
def update(
|
def update(
|
||||||
nodes: List[str] = typer.Argument(
|
nodes: List[str] = typer.Argument(
|
||||||
...,
|
...,
|
||||||
@@ -712,12 +768,15 @@ def update(
|
|||||||
if 'all' in nodes:
|
if 'all' in nodes:
|
||||||
asyncio.run(auto_save_snapshot())
|
asyncio.run(auto_save_snapshot())
|
||||||
|
|
||||||
|
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, context.manager_files_path)
|
||||||
|
|
||||||
for x in nodes:
|
for x in nodes:
|
||||||
if x.lower() in ['comfyui', 'comfy', 'all']:
|
if x.lower() in ['comfyui', 'comfy', 'all']:
|
||||||
update_comfyui()
|
update_comfyui()
|
||||||
break
|
break
|
||||||
|
|
||||||
update_parallel(nodes)
|
update_parallel(nodes)
|
||||||
|
pip_fixer.fix_broken()
|
||||||
|
|
||||||
|
|
||||||
@app.command(help="Disable custom nodes")
|
@app.command(help="Disable custom nodes")
|
||||||
@@ -810,7 +869,9 @@ def fix(
|
|||||||
if 'all' in nodes:
|
if 'all' in nodes:
|
||||||
asyncio.run(auto_save_snapshot())
|
asyncio.run(auto_save_snapshot())
|
||||||
|
|
||||||
|
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, context.manager_files_path)
|
||||||
for_each_nodes(nodes, fix_node, allow_all=True)
|
for_each_nodes(nodes, fix_node, allow_all=True)
|
||||||
|
pip_fixer.fix_broken()
|
||||||
|
|
||||||
|
|
||||||
@app.command("show-versions", help="Show all available versions of the node")
|
@app.command("show-versions", help="Show all available versions of the node")
|
||||||
@@ -1005,11 +1066,28 @@ def save_snapshot(
|
|||||||
user_directory: str = typer.Option(
|
user_directory: str = typer.Option(
|
||||||
None,
|
None,
|
||||||
help="user directory"
|
help="user directory"
|
||||||
)
|
),
|
||||||
|
full_snapshot: Annotated[
|
||||||
|
bool,
|
||||||
|
typer.Option(
|
||||||
|
show_default=False, help="If the snapshot should include custom node, ComfyUI version and pip versions (default), or only custom node details"
|
||||||
|
),
|
||||||
|
] = True,
|
||||||
):
|
):
|
||||||
cmd_ctx.set_user_directory(user_directory)
|
cmd_ctx.set_user_directory(user_directory)
|
||||||
|
|
||||||
path = asyncio.run(core.save_snapshot_with_postfix('snapshot', output))
|
if output is not None:
|
||||||
|
if(not output.endswith('.json') and not output.endswith('.yaml')):
|
||||||
|
print("[bold red]ERROR: output path should be either '.json' or '.yaml' file.[/bold red]")
|
||||||
|
raise typer.Exit(code=1)
|
||||||
|
|
||||||
|
dir_path = os.path.dirname(output)
|
||||||
|
|
||||||
|
if(dir_path != '' and not os.path.exists(dir_path)):
|
||||||
|
print(f"[bold red]ERROR: {output} path not exists.[/bold red]")
|
||||||
|
raise typer.Exit(code=1)
|
||||||
|
|
||||||
|
path = asyncio.run(core.save_snapshot_with_postfix('snapshot', output, not full_snapshot))
|
||||||
print(f"Current snapshot is saved as `{path}`")
|
print(f"Current snapshot is saved as `{path}`")
|
||||||
|
|
||||||
|
|
||||||
@@ -1037,10 +1115,17 @@ def restore_snapshot(
|
|||||||
user_directory: str = typer.Option(
|
user_directory: str = typer.Option(
|
||||||
None,
|
None,
|
||||||
help="user directory"
|
help="user directory"
|
||||||
|
),
|
||||||
|
restore_to: Optional[str] = typer.Option(
|
||||||
|
None,
|
||||||
|
help="Manually specify the installation path for the custom node. Ignore user directory."
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
cmd_ctx.set_user_directory(user_directory)
|
cmd_ctx.set_user_directory(user_directory)
|
||||||
|
|
||||||
|
if restore_to:
|
||||||
|
cmd_ctx.update_custom_nodes_dir(restore_to)
|
||||||
|
|
||||||
extras = []
|
extras = []
|
||||||
if pip_non_url:
|
if pip_non_url:
|
||||||
extras.append('--pip-non-url')
|
extras.append('--pip-non-url')
|
||||||
@@ -1061,12 +1146,14 @@ def restore_snapshot(
|
|||||||
print(f"[bold red]ERROR: `{snapshot_path}` is not exists.[/bold red]")
|
print(f"[bold red]ERROR: `{snapshot_path}` is not exists.[/bold red]")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, context.manager_files_path)
|
||||||
try:
|
try:
|
||||||
asyncio.run(core.restore_snapshot(snapshot_path, extras))
|
asyncio.run(core.restore_snapshot(snapshot_path, extras))
|
||||||
except Exception:
|
except Exception:
|
||||||
print("[bold red]ERROR: Failed to restore snapshot.[/bold red]")
|
print("[bold red]ERROR: Failed to restore snapshot.[/bold red]")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
raise typer.Exit(code=1)
|
raise typer.Exit(code=1)
|
||||||
|
pip_fixer.fix_broken()
|
||||||
|
|
||||||
|
|
||||||
@app.command(
|
@app.command(
|
||||||
@@ -1090,11 +1177,14 @@ def restore_dependencies(
|
|||||||
|
|
||||||
total = len(node_paths)
|
total = len(node_paths)
|
||||||
i = 1
|
i = 1
|
||||||
|
|
||||||
|
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, context.manager_files_path)
|
||||||
for x in node_paths:
|
for x in node_paths:
|
||||||
print("----------------------------------------------------------------------------------------------------")
|
print("----------------------------------------------------------------------------------------------------")
|
||||||
print(f"Restoring [{i}/{total}]: {x}")
|
print(f"Restoring [{i}/{total}]: {x}")
|
||||||
unified_manager.execute_install_script('', x, instant_execution=True)
|
unified_manager.execute_install_script('', x, instant_execution=True)
|
||||||
i += 1
|
i += 1
|
||||||
|
pip_fixer.fix_broken()
|
||||||
|
|
||||||
|
|
||||||
@app.command(
|
@app.command(
|
||||||
@@ -1106,7 +1196,10 @@ def post_install(
|
|||||||
)
|
)
|
||||||
):
|
):
|
||||||
path = os.path.expanduser(path)
|
path = os.path.expanduser(path)
|
||||||
|
|
||||||
|
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, context.manager_files_path)
|
||||||
unified_manager.execute_install_script('', path, instant_execution=True)
|
unified_manager.execute_install_script('', path, instant_execution=True)
|
||||||
|
pip_fixer.fix_broken()
|
||||||
|
|
||||||
|
|
||||||
@app.command(
|
@app.command(
|
||||||
@@ -1144,10 +1237,11 @@ def install_deps(
|
|||||||
with open(deps, 'r', encoding="UTF-8", errors="ignore") as json_file:
|
with open(deps, 'r', encoding="UTF-8", errors="ignore") as json_file:
|
||||||
try:
|
try:
|
||||||
json_obj = json.load(json_file)
|
json_obj = json.load(json_file)
|
||||||
except:
|
except Exception:
|
||||||
print(f"[bold red]Invalid json file: {deps}[/bold red]")
|
print(f"[bold red]Invalid json file: {deps}[/bold red]")
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
|
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, context.manager_files_path)
|
||||||
for k in json_obj['custom_nodes'].keys():
|
for k in json_obj['custom_nodes'].keys():
|
||||||
state = core.simple_check_custom_node(k)
|
state = core.simple_check_custom_node(k)
|
||||||
if state == 'installed':
|
if state == 'installed':
|
||||||
@@ -1156,6 +1250,7 @@ def install_deps(
|
|||||||
asyncio.run(core.gitclone_install(k, instant_execution=True))
|
asyncio.run(core.gitclone_install(k, instant_execution=True))
|
||||||
else: # disabled
|
else: # disabled
|
||||||
core.gitclone_set_active([k], False)
|
core.gitclone_set_active([k], False)
|
||||||
|
pip_fixer.fix_broken()
|
||||||
|
|
||||||
print("Dependency installation and activation complete.")
|
print("Dependency installation and activation complete.")
|
||||||
|
|
||||||
@@ -1203,18 +1298,8 @@ def export_custom_node_ids(
|
|||||||
print(f"{x['id']}@unknown", file=output_file)
|
print(f"{x['id']}@unknown", file=output_file)
|
||||||
|
|
||||||
|
|
||||||
@app.command(
|
def main():
|
||||||
"migrate",
|
app()
|
||||||
help="Migrate legacy node system to new node system",
|
|
||||||
)
|
|
||||||
def migrate(
|
|
||||||
user_directory: str = typer.Option(
|
|
||||||
None,
|
|
||||||
help="user directory"
|
|
||||||
)
|
|
||||||
):
|
|
||||||
cmd_ctx.set_user_directory(user_directory)
|
|
||||||
asyncio.run(unified_manager.migrate_unmanaged_nodes())
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
16
comfyui_manager/common/README.md
Normal file
16
comfyui_manager/common/README.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# ComfyUI-Manager: Core Backend (glob)
|
||||||
|
|
||||||
|
This directory contains the Python backend modules that power ComfyUI-Manager, handling the core functionality of node management, downloading, security, and server operations.
|
||||||
|
|
||||||
|
## Core Modules
|
||||||
|
|
||||||
|
- **manager_downloader.py**: Handles downloading operations for models, extensions, and other resources.
|
||||||
|
- **manager_util.py**: Provides utility functions used throughout the system.
|
||||||
|
|
||||||
|
## Specialized Modules
|
||||||
|
|
||||||
|
- **cm_global.py**: Maintains global variables and state management across the system.
|
||||||
|
- **cnr_utils.py**: Helper utilities for interacting with the custom node registry (CNR).
|
||||||
|
- **git_utils.py**: Git-specific utilities for repository operations.
|
||||||
|
- **node_package.py**: Handles the packaging and installation of node extensions.
|
||||||
|
- **security_check.py**: Implements the multi-level security system for installation safety.
|
||||||
0
comfyui_manager/common/__init__.py
Normal file
0
comfyui_manager/common/__init__.py
Normal file
@@ -110,3 +110,8 @@ def add_on_revision_detected(k, f):
|
|||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
else:
|
else:
|
||||||
variables['cm.on_revision_detected_handler'].append((k, f))
|
variables['cm.on_revision_detected_handler'].append((k, f))
|
||||||
|
|
||||||
|
|
||||||
|
error_dict = {}
|
||||||
|
|
||||||
|
disable_front = False
|
||||||
254
comfyui_manager/common/cnr_utils.py
Normal file
254
comfyui_manager/common/cnr_utils.py
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
import asyncio
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import time
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from . import context
|
||||||
|
from . import manager_util
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import toml
|
||||||
|
|
||||||
|
base_url = "https://api.comfy.org"
|
||||||
|
|
||||||
|
|
||||||
|
lock = asyncio.Lock()
|
||||||
|
|
||||||
|
is_cache_loading = False
|
||||||
|
|
||||||
|
async def get_cnr_data(cache_mode=True, dont_wait=True):
|
||||||
|
try:
|
||||||
|
return await _get_cnr_data(cache_mode, dont_wait)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
print("A timeout occurred during the fetch process from ComfyRegistry.")
|
||||||
|
return await _get_cnr_data(cache_mode=True, dont_wait=True) # timeout fallback
|
||||||
|
|
||||||
|
async def _get_cnr_data(cache_mode=True, dont_wait=True):
|
||||||
|
global is_cache_loading
|
||||||
|
|
||||||
|
uri = f'{base_url}/nodes'
|
||||||
|
|
||||||
|
async def fetch_all():
|
||||||
|
remained = True
|
||||||
|
page = 1
|
||||||
|
|
||||||
|
full_nodes = {}
|
||||||
|
|
||||||
|
|
||||||
|
# Determine form factor based on environment and platform
|
||||||
|
is_desktop = bool(os.environ.get('__COMFYUI_DESKTOP_VERSION__'))
|
||||||
|
system = platform.system().lower()
|
||||||
|
is_windows = system == 'windows'
|
||||||
|
is_mac = system == 'darwin'
|
||||||
|
is_linux = system == 'linux'
|
||||||
|
|
||||||
|
# Get ComfyUI version tag
|
||||||
|
if is_desktop:
|
||||||
|
# extract version from pyproject.toml instead of git tag
|
||||||
|
comfyui_ver = context.get_current_comfyui_ver() or 'unknown'
|
||||||
|
else:
|
||||||
|
comfyui_ver = context.get_comfyui_tag() or 'unknown'
|
||||||
|
|
||||||
|
if is_desktop:
|
||||||
|
if is_windows:
|
||||||
|
form_factor = 'desktop-win'
|
||||||
|
elif is_mac:
|
||||||
|
form_factor = 'desktop-mac'
|
||||||
|
else:
|
||||||
|
form_factor = 'other'
|
||||||
|
else:
|
||||||
|
if is_windows:
|
||||||
|
form_factor = 'git-windows'
|
||||||
|
elif is_mac:
|
||||||
|
form_factor = 'git-mac'
|
||||||
|
elif is_linux:
|
||||||
|
form_factor = 'git-linux'
|
||||||
|
else:
|
||||||
|
form_factor = 'other'
|
||||||
|
|
||||||
|
while remained:
|
||||||
|
# Add comfyui_version and form_factor to the API request
|
||||||
|
sub_uri = f'{base_url}/nodes?page={page}&limit=30&comfyui_version={comfyui_ver}&form_factor={form_factor}'
|
||||||
|
sub_json_obj = await asyncio.wait_for(manager_util.get_data_with_cache(sub_uri, cache_mode=False, silent=True, dont_cache=True), timeout=30)
|
||||||
|
remained = page < sub_json_obj['totalPages']
|
||||||
|
|
||||||
|
for x in sub_json_obj['nodes']:
|
||||||
|
full_nodes[x['id']] = x
|
||||||
|
|
||||||
|
if page % 5 == 0:
|
||||||
|
print(f"FETCH ComfyRegistry Data: {page}/{sub_json_obj['totalPages']}")
|
||||||
|
|
||||||
|
page += 1
|
||||||
|
time.sleep(0.5)
|
||||||
|
|
||||||
|
print("FETCH ComfyRegistry Data [DONE]")
|
||||||
|
|
||||||
|
for v in full_nodes.values():
|
||||||
|
if 'latest_version' not in v:
|
||||||
|
v['latest_version'] = dict(version='nightly')
|
||||||
|
|
||||||
|
return {'nodes': list(full_nodes.values())}
|
||||||
|
|
||||||
|
if cache_mode:
|
||||||
|
is_cache_loading = True
|
||||||
|
cache_state = manager_util.get_cache_state(uri)
|
||||||
|
|
||||||
|
if dont_wait:
|
||||||
|
if cache_state == 'not-cached':
|
||||||
|
return {}
|
||||||
|
else:
|
||||||
|
print("[ComfyUI-Manager] The ComfyRegistry cache update is still in progress, so an outdated cache is being used.")
|
||||||
|
with open(manager_util.get_cache_path(uri), 'r', encoding="UTF-8", errors="ignore") as json_file:
|
||||||
|
return json.load(json_file)['nodes']
|
||||||
|
|
||||||
|
if cache_state == 'cached':
|
||||||
|
with open(manager_util.get_cache_path(uri), 'r', encoding="UTF-8", errors="ignore") as json_file:
|
||||||
|
return json.load(json_file)['nodes']
|
||||||
|
|
||||||
|
try:
|
||||||
|
json_obj = await fetch_all()
|
||||||
|
manager_util.save_to_cache(uri, json_obj)
|
||||||
|
return json_obj['nodes']
|
||||||
|
except Exception:
|
||||||
|
res = {}
|
||||||
|
print("Cannot connect to comfyregistry.")
|
||||||
|
finally:
|
||||||
|
if cache_mode:
|
||||||
|
is_cache_loading = False
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class NodeVersion:
|
||||||
|
changelog: str
|
||||||
|
dependencies: List[str]
|
||||||
|
deprecated: bool
|
||||||
|
id: str
|
||||||
|
version: str
|
||||||
|
download_url: str
|
||||||
|
|
||||||
|
|
||||||
|
def map_node_version(api_node_version):
|
||||||
|
"""
|
||||||
|
Maps node version data from API response to NodeVersion dataclass.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
api_data (dict): The 'node_version' part of the API response.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
NodeVersion: An instance of NodeVersion dataclass populated with data from the API.
|
||||||
|
"""
|
||||||
|
return NodeVersion(
|
||||||
|
changelog=api_node_version.get(
|
||||||
|
"changelog", ""
|
||||||
|
), # Provide a default value if 'changelog' is missing
|
||||||
|
dependencies=api_node_version.get(
|
||||||
|
"dependencies", []
|
||||||
|
), # Provide a default empty list if 'dependencies' is missing
|
||||||
|
deprecated=api_node_version.get(
|
||||||
|
"deprecated", False
|
||||||
|
), # Assume False if 'deprecated' is not specified
|
||||||
|
id=api_node_version[
|
||||||
|
"id"
|
||||||
|
], # 'id' should be mandatory; raise KeyError if missing
|
||||||
|
version=api_node_version[
|
||||||
|
"version"
|
||||||
|
], # 'version' should be mandatory; raise KeyError if missing
|
||||||
|
download_url=api_node_version.get(
|
||||||
|
"downloadUrl", ""
|
||||||
|
), # Provide a default value if 'downloadUrl' is missing
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def install_node(node_id, version=None):
|
||||||
|
"""
|
||||||
|
Retrieves the node version for installation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node_id (str): The unique identifier of the node.
|
||||||
|
version (str, optional): Specific version of the node to retrieve. If omitted, the latest version is returned.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
NodeVersion: Node version data or error message.
|
||||||
|
"""
|
||||||
|
if version is None:
|
||||||
|
url = f"{base_url}/nodes/{node_id}/install"
|
||||||
|
else:
|
||||||
|
url = f"{base_url}/nodes/{node_id}/install?version={version}"
|
||||||
|
|
||||||
|
response = requests.get(url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
# Convert the API response to a NodeVersion object
|
||||||
|
return map_node_version(response.json())
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def all_versions_of_node(node_id):
|
||||||
|
url = f"{base_url}/nodes/{node_id}/versions?statuses=NodeVersionStatusActive&statuses=NodeVersionStatusPending"
|
||||||
|
|
||||||
|
response = requests.get(url)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def read_cnr_info(fullpath):
|
||||||
|
try:
|
||||||
|
toml_path = os.path.join(fullpath, 'pyproject.toml')
|
||||||
|
tracking_path = os.path.join(fullpath, '.tracking')
|
||||||
|
|
||||||
|
if not os.path.exists(toml_path) or not os.path.exists(tracking_path):
|
||||||
|
return None # not valid CNR node pack
|
||||||
|
|
||||||
|
with open(toml_path, "r", encoding="utf-8") as f:
|
||||||
|
data = toml.load(f)
|
||||||
|
|
||||||
|
project = data.get('project', {})
|
||||||
|
name = project.get('name').strip().lower()
|
||||||
|
|
||||||
|
# normalize version
|
||||||
|
# for example: 2.5 -> 2.5.0
|
||||||
|
version = str(manager_util.StrictVersion(project.get('version')))
|
||||||
|
|
||||||
|
urls = project.get('urls', {})
|
||||||
|
repository = urls.get('Repository')
|
||||||
|
|
||||||
|
if name and version: # repository is optional
|
||||||
|
return {
|
||||||
|
"id": name,
|
||||||
|
"version": version,
|
||||||
|
"url": repository
|
||||||
|
}
|
||||||
|
|
||||||
|
return None
|
||||||
|
except Exception:
|
||||||
|
return None # not valid CNR node pack
|
||||||
|
|
||||||
|
|
||||||
|
def generate_cnr_id(fullpath, cnr_id):
|
||||||
|
cnr_id_path = os.path.join(fullpath, '.git', '.cnr-id')
|
||||||
|
try:
|
||||||
|
if not os.path.exists(cnr_id_path):
|
||||||
|
with open(cnr_id_path, "w") as f:
|
||||||
|
return f.write(cnr_id)
|
||||||
|
except Exception:
|
||||||
|
print(f"[ComfyUI Manager] unable to create file: {cnr_id_path}")
|
||||||
|
|
||||||
|
|
||||||
|
def read_cnr_id(fullpath):
|
||||||
|
cnr_id_path = os.path.join(fullpath, '.git', '.cnr-id')
|
||||||
|
try:
|
||||||
|
if os.path.exists(cnr_id_path):
|
||||||
|
with open(cnr_id_path) as f:
|
||||||
|
return f.read().strip()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
109
comfyui_manager/common/context.py
Normal file
109
comfyui_manager/common/context.py
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
from . import manager_util
|
||||||
|
import toml
|
||||||
|
import git
|
||||||
|
|
||||||
|
|
||||||
|
# read env vars
|
||||||
|
comfy_path: str = os.environ.get('COMFYUI_PATH')
|
||||||
|
comfy_base_path = os.environ.get('COMFYUI_FOLDERS_BASE_PATH')
|
||||||
|
|
||||||
|
if comfy_path is None:
|
||||||
|
try:
|
||||||
|
comfy_path = os.path.abspath(os.path.dirname(sys.modules['__main__'].__file__))
|
||||||
|
os.environ['COMFYUI_PATH'] = comfy_path
|
||||||
|
except Exception:
|
||||||
|
logging.error("[ComfyUI-Manager] environment variable 'COMFYUI_PATH' is not specified.")
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
|
if comfy_base_path is None:
|
||||||
|
comfy_base_path = comfy_path
|
||||||
|
|
||||||
|
channel_list_template_path = os.path.join(manager_util.comfyui_manager_path, 'channels.list.template')
|
||||||
|
git_script_path = os.path.join(manager_util.comfyui_manager_path, "git_helper.py")
|
||||||
|
|
||||||
|
manager_files_path = None
|
||||||
|
manager_config_path = None
|
||||||
|
manager_channel_list_path = None
|
||||||
|
manager_startup_script_path:str = None
|
||||||
|
manager_snapshot_path = None
|
||||||
|
manager_pip_overrides_path = None
|
||||||
|
manager_pip_blacklist_path = None
|
||||||
|
manager_components_path = None
|
||||||
|
manager_batch_history_path = None
|
||||||
|
|
||||||
|
def update_user_directory(user_dir):
|
||||||
|
global manager_files_path
|
||||||
|
global manager_config_path
|
||||||
|
global manager_channel_list_path
|
||||||
|
global manager_startup_script_path
|
||||||
|
global manager_snapshot_path
|
||||||
|
global manager_pip_overrides_path
|
||||||
|
global manager_pip_blacklist_path
|
||||||
|
global manager_components_path
|
||||||
|
global manager_batch_history_path
|
||||||
|
|
||||||
|
manager_files_path = os.path.abspath(os.path.join(user_dir, 'default', 'ComfyUI-Manager'))
|
||||||
|
if not os.path.exists(manager_files_path):
|
||||||
|
os.makedirs(manager_files_path)
|
||||||
|
|
||||||
|
manager_snapshot_path = os.path.join(manager_files_path, "snapshots")
|
||||||
|
if not os.path.exists(manager_snapshot_path):
|
||||||
|
os.makedirs(manager_snapshot_path)
|
||||||
|
|
||||||
|
manager_startup_script_path = os.path.join(manager_files_path, "startup-scripts")
|
||||||
|
if not os.path.exists(manager_startup_script_path):
|
||||||
|
os.makedirs(manager_startup_script_path)
|
||||||
|
|
||||||
|
manager_config_path = os.path.join(manager_files_path, 'config.ini')
|
||||||
|
manager_channel_list_path = os.path.join(manager_files_path, 'channels.list')
|
||||||
|
manager_pip_overrides_path = os.path.join(manager_files_path, "pip_overrides.json")
|
||||||
|
manager_pip_blacklist_path = os.path.join(manager_files_path, "pip_blacklist.list")
|
||||||
|
manager_components_path = os.path.join(manager_files_path, "components")
|
||||||
|
manager_util.cache_dir = os.path.join(manager_files_path, "cache")
|
||||||
|
manager_batch_history_path = os.path.join(manager_files_path, "batch_history")
|
||||||
|
|
||||||
|
if not os.path.exists(manager_util.cache_dir):
|
||||||
|
os.makedirs(manager_util.cache_dir)
|
||||||
|
|
||||||
|
if not os.path.exists(manager_batch_history_path):
|
||||||
|
os.makedirs(manager_batch_history_path)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import folder_paths
|
||||||
|
update_user_directory(folder_paths.get_user_directory())
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
# fallback:
|
||||||
|
# This case is only possible when running with cm-cli, and in practice, this case is not actually used.
|
||||||
|
update_user_directory(os.path.abspath(manager_util.comfyui_manager_path))
|
||||||
|
|
||||||
|
|
||||||
|
def get_current_comfyui_ver():
|
||||||
|
"""
|
||||||
|
Extract version from pyproject.toml
|
||||||
|
"""
|
||||||
|
toml_path = os.path.join(comfy_path, 'pyproject.toml')
|
||||||
|
if not os.path.exists(toml_path):
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
with open(toml_path, "r", encoding="utf-8") as f:
|
||||||
|
data = toml.load(f)
|
||||||
|
|
||||||
|
project = data.get('project', {})
|
||||||
|
return project.get('version')
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_comfyui_tag():
|
||||||
|
try:
|
||||||
|
with git.Repo(comfy_path) as repo:
|
||||||
|
return repo.git.describe('--tags')
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
17
comfyui_manager/common/enums.py
Normal file
17
comfyui_manager/common/enums.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import enum
|
||||||
|
|
||||||
|
class NetworkMode(enum.Enum):
|
||||||
|
PUBLIC = "public"
|
||||||
|
PRIVATE = "private"
|
||||||
|
OFFLINE = "offline"
|
||||||
|
|
||||||
|
class SecurityLevel(enum.Enum):
|
||||||
|
STRONG = "strong"
|
||||||
|
NORMAL = "normal"
|
||||||
|
NORMAL_MINUS = "normal-minus"
|
||||||
|
WEAK = "weak"
|
||||||
|
|
||||||
|
class DBMode(enum.Enum):
|
||||||
|
LOCAL = "local"
|
||||||
|
CACHE = "cache"
|
||||||
|
REMOTE = "remote"
|
||||||
@@ -4,7 +4,6 @@ import os
|
|||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
import git
|
import git
|
||||||
import configparser
|
|
||||||
import json
|
import json
|
||||||
import yaml
|
import yaml
|
||||||
import requests
|
import requests
|
||||||
@@ -13,12 +12,15 @@ from git.remote import RemoteProgress
|
|||||||
|
|
||||||
|
|
||||||
comfy_path = os.environ.get('COMFYUI_PATH')
|
comfy_path = os.environ.get('COMFYUI_PATH')
|
||||||
|
git_exe_path = os.environ.get('GIT_EXE_PATH')
|
||||||
|
|
||||||
if comfy_path is None:
|
if comfy_path is None:
|
||||||
print("\n[bold yellow]WARN: The `COMFYUI_PATH` environment variable is not set. Assuming `custom_nodes/ComfyUI-Manager/../../` as the ComfyUI path.[/bold yellow]", file=sys.stderr)
|
print("git_helper: environment variable 'COMFYUI_PATH' is not specified.")
|
||||||
comfy_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
|
exit(-1)
|
||||||
|
|
||||||
|
|
||||||
|
if not os.path.exists(os.path.join(comfy_path, 'folder_paths.py')):
|
||||||
|
print("git_helper: '{comfy_path}' is not a valid 'COMFYUI_PATH' location.")
|
||||||
|
exit(-1)
|
||||||
|
|
||||||
def download_url(url, dest_folder, filename=None):
|
def download_url(url, dest_folder, filename=None):
|
||||||
# Ensure the destination folder exists
|
# Ensure the destination folder exists
|
||||||
@@ -43,7 +45,6 @@ def download_url(url, dest_folder, filename=None):
|
|||||||
print(f"Failed to download file from {url}")
|
print(f"Failed to download file from {url}")
|
||||||
|
|
||||||
|
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "config.ini")
|
|
||||||
nodelist_path = os.path.join(os.path.dirname(__file__), "custom-node-list.json")
|
nodelist_path = os.path.join(os.path.dirname(__file__), "custom-node-list.json")
|
||||||
working_directory = os.getcwd()
|
working_directory = os.getcwd()
|
||||||
|
|
||||||
@@ -126,18 +127,60 @@ def gitcheck(path, do_fetch=False):
|
|||||||
print("CUSTOM NODE CHECK: Error")
|
print("CUSTOM NODE CHECK: Error")
|
||||||
|
|
||||||
|
|
||||||
|
def get_remote_name(repo):
|
||||||
|
available_remotes = [remote.name for remote in repo.remotes]
|
||||||
|
if 'origin' in available_remotes:
|
||||||
|
return 'origin'
|
||||||
|
elif 'upstream' in available_remotes:
|
||||||
|
return 'upstream'
|
||||||
|
elif len(available_remotes) > 0:
|
||||||
|
return available_remotes[0]
|
||||||
|
|
||||||
|
if not available_remotes:
|
||||||
|
print(f"[ComfyUI-Manager] No remotes are configured for this repository: {repo.working_dir}")
|
||||||
|
else:
|
||||||
|
print(f"[ComfyUI-Manager] Available remotes in '{repo.working_dir}': ")
|
||||||
|
for remote in available_remotes:
|
||||||
|
print(f"- {remote}")
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def switch_to_default_branch(repo):
|
def switch_to_default_branch(repo):
|
||||||
|
remote_name = get_remote_name(repo)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
default_branch = repo.git.symbolic_ref('refs/remotes/origin/HEAD').replace('refs/remotes/origin/', '')
|
if remote_name is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
default_branch = repo.git.symbolic_ref(f'refs/remotes/{remote_name}/HEAD').replace(f'refs/remotes/{remote_name}/', '')
|
||||||
repo.git.checkout(default_branch)
|
repo.git.checkout(default_branch)
|
||||||
except:
|
return True
|
||||||
|
except Exception:
|
||||||
|
# try checkout master
|
||||||
|
# try checkout main if failed
|
||||||
try:
|
try:
|
||||||
repo.git.checkout(repo.heads.master)
|
repo.git.checkout(repo.heads.master)
|
||||||
except:
|
return True
|
||||||
|
except Exception:
|
||||||
try:
|
try:
|
||||||
repo.git.checkout(repo.heads.main)
|
if remote_name is not None:
|
||||||
except:
|
repo.git.checkout('-b', 'master', f'{remote_name}/master')
|
||||||
print("[ComfyUI Manager] Failed to switch to the default branch (master or main)")
|
return True
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
repo.git.checkout(repo.heads.main)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
if remote_name is not None:
|
||||||
|
repo.git.checkout('-b', 'main', f'{remote_name}/main')
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
print("[ComfyUI Manager] Failed to switch to the default branch")
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def gitpull(path):
|
def gitpull(path):
|
||||||
@@ -168,7 +211,11 @@ def gitpull(path):
|
|||||||
branch_name = current_branch.name
|
branch_name = current_branch.name
|
||||||
|
|
||||||
remote.fetch()
|
remote.fetch()
|
||||||
remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha
|
if f'{remote_name}/{branch_name}' in repo.refs:
|
||||||
|
remote_commit_hash = repo.refs[f'{remote_name}/{branch_name}'].object.hexsha
|
||||||
|
else:
|
||||||
|
print("CUSTOM NODE PULL: Fail") # update fail
|
||||||
|
return
|
||||||
|
|
||||||
if commit_hash == remote_commit_hash:
|
if commit_hash == remote_commit_hash:
|
||||||
print("CUSTOM NODE PULL: None") # there is no update
|
print("CUSTOM NODE PULL: None") # there is no update
|
||||||
@@ -358,12 +405,13 @@ def apply_snapshot(path):
|
|||||||
git_custom_node_infos = info['git_custom_nodes']
|
git_custom_node_infos = info['git_custom_nodes']
|
||||||
file_custom_node_infos = info['file_custom_nodes']
|
file_custom_node_infos = info['file_custom_nodes']
|
||||||
|
|
||||||
checkout_comfyui_hash(comfyui_hash)
|
if comfyui_hash:
|
||||||
|
checkout_comfyui_hash(comfyui_hash)
|
||||||
checkout_custom_node_hash(git_custom_node_infos)
|
checkout_custom_node_hash(git_custom_node_infos)
|
||||||
invalidate_custom_node_file(file_custom_node_infos)
|
invalidate_custom_node_file(file_custom_node_infos)
|
||||||
|
|
||||||
print("APPLY SNAPSHOT: True")
|
print("APPLY SNAPSHOT: True")
|
||||||
if 'pips' in info:
|
if 'pips' in info and info['pips']:
|
||||||
return info['pips']
|
return info['pips']
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
@@ -399,7 +447,7 @@ def restore_pip_snapshot(pips, options):
|
|||||||
res = 1
|
res = 1
|
||||||
try:
|
try:
|
||||||
res = subprocess.check_call([sys.executable, '-m', 'pip', 'install'] + non_url)
|
res = subprocess.check_call([sys.executable, '-m', 'pip', 'install'] + non_url)
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# fallback
|
# fallback
|
||||||
@@ -408,7 +456,7 @@ def restore_pip_snapshot(pips, options):
|
|||||||
res = 1
|
res = 1
|
||||||
try:
|
try:
|
||||||
res = subprocess.check_call([sys.executable, '-m', 'pip', 'install', x])
|
res = subprocess.check_call([sys.executable, '-m', 'pip', 'install', x])
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if res != 0:
|
if res != 0:
|
||||||
@@ -419,7 +467,7 @@ def restore_pip_snapshot(pips, options):
|
|||||||
res = 1
|
res = 1
|
||||||
try:
|
try:
|
||||||
res = subprocess.check_call([sys.executable, '-m', 'pip', 'install', x])
|
res = subprocess.check_call([sys.executable, '-m', 'pip', 'install', x])
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if res != 0:
|
if res != 0:
|
||||||
@@ -430,7 +478,7 @@ def restore_pip_snapshot(pips, options):
|
|||||||
res = 1
|
res = 1
|
||||||
try:
|
try:
|
||||||
res = subprocess.check_call([sys.executable, '-m', 'pip', 'install', x])
|
res = subprocess.check_call([sys.executable, '-m', 'pip', 'install', x])
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if res != 0:
|
if res != 0:
|
||||||
@@ -440,10 +488,8 @@ def restore_pip_snapshot(pips, options):
|
|||||||
|
|
||||||
|
|
||||||
def setup_environment():
|
def setup_environment():
|
||||||
config = configparser.ConfigParser()
|
if git_exe_path is not None:
|
||||||
config.read(config_path)
|
git.Git().update_environment(GIT_PYTHON_GIT_EXECUTABLE=git_exe_path)
|
||||||
if 'default' in config and 'git_exe' in config['default'] and config['default']['git_exe'] != '':
|
|
||||||
git.Git().update_environment(GIT_PYTHON_GIT_EXECUTABLE=config['default']['git_exe'])
|
|
||||||
|
|
||||||
|
|
||||||
setup_environment()
|
setup_environment()
|
||||||
@@ -2,6 +2,9 @@ import os
|
|||||||
import configparser
|
import configparser
|
||||||
|
|
||||||
|
|
||||||
|
GITHUB_ENDPOINT = os.getenv('GITHUB_ENDPOINT')
|
||||||
|
|
||||||
|
|
||||||
def is_git_repo(path: str) -> bool:
|
def is_git_repo(path: str) -> bool:
|
||||||
""" Check if the path is a git repository. """
|
""" Check if the path is a git repository. """
|
||||||
# NOTE: Checking it through `git.Repo` must be avoided.
|
# NOTE: Checking it through `git.Repo` must be avoided.
|
||||||
@@ -37,25 +40,48 @@ def git_url(fullpath):
|
|||||||
if not os.path.exists(git_config_path):
|
if not os.path.exists(git_config_path):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
config = configparser.ConfigParser()
|
# Set `strict=False` to allow duplicate `vscode-merge-base` sections, addressing <https://github.com/ltdrdata/ComfyUI-Manager/issues/1529>
|
||||||
|
config = configparser.ConfigParser(strict=False)
|
||||||
config.read(git_config_path)
|
config.read(git_config_path)
|
||||||
|
|
||||||
for k, v in config.items():
|
for k, v in config.items():
|
||||||
if k.startswith('remote ') and 'url' in v:
|
if k.startswith('remote ') and 'url' in v:
|
||||||
|
if 'Comfy-Org/ComfyUI-Manager' in v['url']:
|
||||||
|
return "https://github.com/ltdrdata/ComfyUI-Manager"
|
||||||
return v['url']
|
return v['url']
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def normalize_url(url) -> str:
|
def normalize_url(url) -> str:
|
||||||
url = url.replace("git@github.com:", "https://github.com/")
|
github_id = normalize_to_github_id(url)
|
||||||
if url.endswith('.git'):
|
if github_id is not None:
|
||||||
url = url[:-4]
|
url = f"https://github.com/{github_id}"
|
||||||
|
|
||||||
return url
|
return url
|
||||||
|
|
||||||
def normalize_url_http(url) -> str:
|
|
||||||
url = url.replace("https://github.com/", "git@github.com:")
|
def normalize_to_github_id(url) -> str:
|
||||||
if url.endswith('.git'):
|
if 'github' in url or (GITHUB_ENDPOINT is not None and GITHUB_ENDPOINT in url):
|
||||||
url = url[:-4]
|
author = os.path.basename(os.path.dirname(url))
|
||||||
|
|
||||||
|
if author.startswith('git@github.com:'):
|
||||||
|
author = author.split(':')[1]
|
||||||
|
|
||||||
|
repo_name = os.path.basename(url)
|
||||||
|
if repo_name.endswith('.git'):
|
||||||
|
repo_name = repo_name[:-4]
|
||||||
|
|
||||||
|
return f"{author}/{repo_name}"
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_url_for_clone(url):
|
||||||
|
url = normalize_url(url)
|
||||||
|
|
||||||
|
if GITHUB_ENDPOINT is not None and url.startswith('https://github.com/'):
|
||||||
|
url = GITHUB_ENDPOINT + url[18:] # url[18:] -> remove `https://github.com`
|
||||||
|
|
||||||
return url
|
return url
|
||||||
|
|
||||||
@@ -2,10 +2,16 @@ import os
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
import urllib
|
import urllib
|
||||||
import sys
|
import sys
|
||||||
|
import logging
|
||||||
|
import requests
|
||||||
|
from huggingface_hub import HfApi
|
||||||
|
from tqdm.auto import tqdm
|
||||||
|
|
||||||
|
|
||||||
aria2 = os.getenv('COMFYUI_MANAGER_ARIA2_SERVER')
|
aria2 = os.getenv('COMFYUI_MANAGER_ARIA2_SERVER')
|
||||||
HF_ENDPOINT = os.getenv('HF_ENDPOINT')
|
HF_ENDPOINT = os.getenv('HF_ENDPOINT')
|
||||||
|
|
||||||
|
|
||||||
if aria2 is not None:
|
if aria2 is not None:
|
||||||
secret = os.getenv('COMFYUI_MANAGER_ARIA2_SECRET')
|
secret = os.getenv('COMFYUI_MANAGER_ARIA2_SECRET')
|
||||||
url = urlparse(aria2)
|
url = urlparse(aria2)
|
||||||
@@ -16,7 +22,11 @@ if aria2 is not None:
|
|||||||
aria2 = aria2p.API(aria2p.Client(host=host, port=port, secret=secret))
|
aria2 = aria2p.API(aria2p.Client(host=host, port=port, secret=secret))
|
||||||
|
|
||||||
|
|
||||||
def basic_download_url(url, dest_folder, filename):
|
def basic_download_url(url, dest_folder: str, filename: str):
|
||||||
|
'''
|
||||||
|
Download file from url to dest_folder with filename
|
||||||
|
using requests library.
|
||||||
|
'''
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
# Ensure the destination folder exists
|
# Ensure the destination folder exists
|
||||||
@@ -40,6 +50,7 @@ def basic_download_url(url, dest_folder, filename):
|
|||||||
def download_url(model_url: str, model_dir: str, filename: str):
|
def download_url(model_url: str, model_dir: str, filename: str):
|
||||||
if HF_ENDPOINT:
|
if HF_ENDPOINT:
|
||||||
model_url = model_url.replace('https://huggingface.co', HF_ENDPOINT)
|
model_url = model_url.replace('https://huggingface.co', HF_ENDPOINT)
|
||||||
|
logging.info(f"model_url replaced by HF_ENDPOINT, new = {model_url}")
|
||||||
if aria2:
|
if aria2:
|
||||||
return aria2_download_url(model_url, model_dir, filename)
|
return aria2_download_url(model_url, model_dir, filename)
|
||||||
else:
|
else:
|
||||||
@@ -112,3 +123,37 @@ def download_url_with_agent(url, save_path):
|
|||||||
|
|
||||||
print("Installation was successful.")
|
print("Installation was successful.")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
# NOTE: snapshot_download doesn't provide file size tqdm.
|
||||||
|
def download_repo_in_bytes(repo_id, local_dir):
|
||||||
|
api = HfApi()
|
||||||
|
repo_info = api.repo_info(repo_id=repo_id, files_metadata=True)
|
||||||
|
|
||||||
|
os.makedirs(local_dir, exist_ok=True)
|
||||||
|
|
||||||
|
total_size = 0
|
||||||
|
for file_info in repo_info.siblings:
|
||||||
|
if file_info.size is not None:
|
||||||
|
total_size += file_info.size
|
||||||
|
|
||||||
|
pbar = tqdm(total=total_size, unit="B", unit_scale=True, desc="Downloading")
|
||||||
|
|
||||||
|
for file_info in repo_info.siblings:
|
||||||
|
out_path = os.path.join(local_dir, file_info.rfilename)
|
||||||
|
os.makedirs(os.path.dirname(out_path), exist_ok=True)
|
||||||
|
|
||||||
|
if file_info.size is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
download_url = f"https://huggingface.co/{repo_id}/resolve/main/{file_info.rfilename}"
|
||||||
|
|
||||||
|
with requests.get(download_url, stream=True) as r, open(out_path, "wb") as f:
|
||||||
|
r.raise_for_status()
|
||||||
|
for chunk in r.iter_content(chunk_size=65536):
|
||||||
|
if chunk:
|
||||||
|
f.write(chunk)
|
||||||
|
pbar.update(len(chunk))
|
||||||
|
|
||||||
|
pbar.close()
|
||||||
|
|
||||||
|
|
||||||
542
comfyui_manager/common/manager_util.py
Normal file
542
comfyui_manager/common/manager_util.py
Normal file
@@ -0,0 +1,542 @@
|
|||||||
|
"""
|
||||||
|
description:
|
||||||
|
`manager_util` is the lightest module shared across the prestartup_script, main code, and cm-cli of ComfyUI-Manager.
|
||||||
|
"""
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
import json
|
||||||
|
import threading
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
import logging
|
||||||
|
import platform
|
||||||
|
import shlex
|
||||||
|
from . import cm_global
|
||||||
|
|
||||||
|
|
||||||
|
cache_lock = threading.Lock()
|
||||||
|
session_lock = threading.Lock()
|
||||||
|
|
||||||
|
comfyui_manager_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
||||||
|
cache_dir = os.path.join(comfyui_manager_path, '.cache') # This path is also updated together in **manager_core.update_user_directory**.
|
||||||
|
|
||||||
|
use_uv = False
|
||||||
|
|
||||||
|
def is_manager_pip_package():
|
||||||
|
return not os.path.exists(os.path.join(comfyui_manager_path, '..', 'custom_nodes'))
|
||||||
|
|
||||||
|
def add_python_path_to_env():
|
||||||
|
if platform.system() != "Windows":
|
||||||
|
sep = ':'
|
||||||
|
else:
|
||||||
|
sep = ';'
|
||||||
|
|
||||||
|
os.environ['PATH'] = os.path.dirname(sys.executable)+sep+os.environ['PATH']
|
||||||
|
|
||||||
|
|
||||||
|
def make_pip_cmd(cmd):
|
||||||
|
if 'python_embeded' in sys.executable:
|
||||||
|
if use_uv:
|
||||||
|
return [sys.executable, '-s', '-m', 'uv', 'pip'] + cmd
|
||||||
|
else:
|
||||||
|
return [sys.executable, '-s', '-m', 'pip'] + cmd
|
||||||
|
else:
|
||||||
|
# FIXED: https://github.com/ltdrdata/ComfyUI-Manager/issues/1667
|
||||||
|
if use_uv:
|
||||||
|
return [sys.executable, '-m', 'uv', 'pip'] + cmd
|
||||||
|
else:
|
||||||
|
return [sys.executable, '-m', 'pip'] + cmd
|
||||||
|
|
||||||
|
# DON'T USE StrictVersion - cannot handle pre_release version
|
||||||
|
# try:
|
||||||
|
# from distutils.version import StrictVersion
|
||||||
|
# except Exception:
|
||||||
|
# print(f"[ComfyUI-Manager] 'distutils' package not found. Activating fallback mode for compatibility.")
|
||||||
|
class StrictVersion:
|
||||||
|
def __init__(self, version_string):
|
||||||
|
self.version_string = version_string
|
||||||
|
self.major = 0
|
||||||
|
self.minor = 0
|
||||||
|
self.patch = 0
|
||||||
|
self.pre_release = None
|
||||||
|
self.parse_version_string()
|
||||||
|
|
||||||
|
def parse_version_string(self):
|
||||||
|
parts = self.version_string.split('.')
|
||||||
|
if not parts:
|
||||||
|
raise ValueError("Version string must not be empty")
|
||||||
|
|
||||||
|
self.major = int(parts[0])
|
||||||
|
self.minor = int(parts[1]) if len(parts) > 1 else 0
|
||||||
|
self.patch = int(parts[2]) if len(parts) > 2 else 0
|
||||||
|
|
||||||
|
# Handling pre-release versions if present
|
||||||
|
if len(parts) > 3:
|
||||||
|
self.pre_release = parts[3]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
version = f"{self.major}.{self.minor}.{self.patch}"
|
||||||
|
if self.pre_release:
|
||||||
|
version += f"-{self.pre_release}"
|
||||||
|
return version
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return (self.major, self.minor, self.patch, self.pre_release) == \
|
||||||
|
(other.major, other.minor, other.patch, other.pre_release)
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch):
|
||||||
|
return self.pre_release_compare(self.pre_release, other.pre_release) < 0
|
||||||
|
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def pre_release_compare(pre1, pre2):
|
||||||
|
if pre1 == pre2:
|
||||||
|
return 0
|
||||||
|
if pre1 is None:
|
||||||
|
return 1
|
||||||
|
if pre2 is None:
|
||||||
|
return -1
|
||||||
|
return -1 if pre1 < pre2 else 1
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
return self == other or self < other
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
return not self <= other
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
return not self < other
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self == other
|
||||||
|
|
||||||
|
|
||||||
|
def simple_hash(input_string):
|
||||||
|
hash_value = 0
|
||||||
|
for char in input_string:
|
||||||
|
hash_value = (hash_value * 31 + ord(char)) % (2**32)
|
||||||
|
|
||||||
|
return hash_value
|
||||||
|
|
||||||
|
|
||||||
|
def is_file_created_within_one_day(file_path):
|
||||||
|
if not os.path.exists(file_path):
|
||||||
|
return False
|
||||||
|
|
||||||
|
file_creation_time = os.path.getctime(file_path)
|
||||||
|
current_time = datetime.now().timestamp()
|
||||||
|
time_difference = current_time - file_creation_time
|
||||||
|
|
||||||
|
return time_difference <= 86400
|
||||||
|
|
||||||
|
|
||||||
|
async def get_data(uri, silent=False):
|
||||||
|
if not silent:
|
||||||
|
print(f"FETCH DATA from: {uri}", end="")
|
||||||
|
|
||||||
|
if uri.startswith("http"):
|
||||||
|
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
|
||||||
|
headers = {
|
||||||
|
'Cache-Control': 'no-cache',
|
||||||
|
'Pragma': 'no-cache',
|
||||||
|
'Expires': '0'
|
||||||
|
}
|
||||||
|
async with session.get(uri, headers=headers) as resp:
|
||||||
|
json_text = await resp.text()
|
||||||
|
else:
|
||||||
|
with cache_lock:
|
||||||
|
with open(uri, "r", encoding="utf-8") as f:
|
||||||
|
json_text = f.read()
|
||||||
|
|
||||||
|
try:
|
||||||
|
json_obj = json.loads(json_text)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"[ComfyUI-Manager] An error occurred while fetching '{uri}': {e}")
|
||||||
|
|
||||||
|
return {}
|
||||||
|
|
||||||
|
if not silent:
|
||||||
|
print(" [DONE]")
|
||||||
|
|
||||||
|
return json_obj
|
||||||
|
|
||||||
|
|
||||||
|
def get_cache_path(uri):
|
||||||
|
cache_uri = str(simple_hash(uri)) + '_' + os.path.basename(uri).replace('&', "_").replace('?', "_").replace('=', "_")
|
||||||
|
return os.path.join(cache_dir, cache_uri+'.json')
|
||||||
|
|
||||||
|
|
||||||
|
def get_cache_state(uri):
|
||||||
|
cache_uri = get_cache_path(uri)
|
||||||
|
|
||||||
|
if not os.path.exists(cache_uri):
|
||||||
|
return "not-cached"
|
||||||
|
elif is_file_created_within_one_day(cache_uri):
|
||||||
|
return "cached"
|
||||||
|
|
||||||
|
return "expired"
|
||||||
|
|
||||||
|
|
||||||
|
def save_to_cache(uri, json_obj, silent=False):
|
||||||
|
cache_uri = get_cache_path(uri)
|
||||||
|
|
||||||
|
with cache_lock:
|
||||||
|
with open(cache_uri, "w", encoding='utf-8') as file:
|
||||||
|
json.dump(json_obj, file, indent=4, sort_keys=True)
|
||||||
|
if not silent:
|
||||||
|
logging.info(f"[ComfyUI-Manager] default cache updated: {uri}")
|
||||||
|
|
||||||
|
|
||||||
|
async def get_data_with_cache(uri, silent=False, cache_mode=True, dont_wait=False, dont_cache=False):
|
||||||
|
cache_uri = get_cache_path(uri)
|
||||||
|
|
||||||
|
if cache_mode and dont_wait:
|
||||||
|
# NOTE: return the cache if possible, even if it is expired, so do not cache
|
||||||
|
if not os.path.exists(cache_uri):
|
||||||
|
logging.error(f"[ComfyUI-Manager] The network connection is unstable, so it is operating in fallback mode: {uri}")
|
||||||
|
|
||||||
|
return {}
|
||||||
|
else:
|
||||||
|
if not is_file_created_within_one_day(cache_uri):
|
||||||
|
logging.error(f"[ComfyUI-Manager] The network connection is unstable, so it is operating in outdated cache mode: {uri}")
|
||||||
|
|
||||||
|
return await get_data(cache_uri, silent=silent)
|
||||||
|
|
||||||
|
if cache_mode and is_file_created_within_one_day(cache_uri):
|
||||||
|
json_obj = await get_data(cache_uri, silent=silent)
|
||||||
|
else:
|
||||||
|
json_obj = await get_data(uri, silent=silent)
|
||||||
|
if not dont_cache:
|
||||||
|
with cache_lock:
|
||||||
|
with open(cache_uri, "w", encoding='utf-8') as file:
|
||||||
|
json.dump(json_obj, file, indent=4, sort_keys=True)
|
||||||
|
if not silent:
|
||||||
|
logging.info(f"[ComfyUI-Manager] default cache updated: {uri}")
|
||||||
|
|
||||||
|
return json_obj
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_tag(x):
|
||||||
|
return x.replace('<', '<').replace('>', '>')
|
||||||
|
|
||||||
|
|
||||||
|
def extract_package_as_zip(file_path, extract_path):
|
||||||
|
import zipfile
|
||||||
|
try:
|
||||||
|
with zipfile.ZipFile(file_path, "r") as zip_ref:
|
||||||
|
zip_ref.extractall(extract_path)
|
||||||
|
extracted_files = zip_ref.namelist()
|
||||||
|
logging.info(f"Extracted zip file to {extract_path}")
|
||||||
|
return extracted_files
|
||||||
|
except zipfile.BadZipFile:
|
||||||
|
logging.error(f"File '{file_path}' is not a zip or is corrupted.")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
pip_map = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_installed_packages(renew=False):
|
||||||
|
global pip_map
|
||||||
|
|
||||||
|
if renew or pip_map is None:
|
||||||
|
try:
|
||||||
|
result = subprocess.check_output(make_pip_cmd(['list']), universal_newlines=True)
|
||||||
|
|
||||||
|
pip_map = {}
|
||||||
|
for line in result.split('\n'):
|
||||||
|
x = line.strip()
|
||||||
|
if x:
|
||||||
|
y = line.split()
|
||||||
|
if y[0] == 'Package' or y[0].startswith('-'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
normalized_name = y[0].lower().replace('-', '_')
|
||||||
|
pip_map[normalized_name] = y[1]
|
||||||
|
except subprocess.CalledProcessError:
|
||||||
|
logging.error("[ComfyUI-Manager] Failed to retrieve the information of installed pip packages.")
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return pip_map
|
||||||
|
|
||||||
|
|
||||||
|
def clear_pip_cache():
|
||||||
|
global pip_map
|
||||||
|
pip_map = None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_requirement_line(line):
|
||||||
|
tokens = shlex.split(line)
|
||||||
|
if not tokens:
|
||||||
|
return None
|
||||||
|
|
||||||
|
package_spec = tokens[0]
|
||||||
|
|
||||||
|
pattern = re.compile(
|
||||||
|
r'^(?P<package>[A-Za-z0-9_.+-]+)'
|
||||||
|
r'(?P<operator>==|>=|<=|!=|~=|>|<)?'
|
||||||
|
r'(?P<version>[A-Za-z0-9_.+-]*)$'
|
||||||
|
)
|
||||||
|
m = pattern.match(package_spec)
|
||||||
|
if not m:
|
||||||
|
return None
|
||||||
|
|
||||||
|
package = m.group('package')
|
||||||
|
operator = m.group('operator') or None
|
||||||
|
version = m.group('version') or None
|
||||||
|
|
||||||
|
index_url = None
|
||||||
|
if '--index-url' in tokens:
|
||||||
|
idx = tokens.index('--index-url')
|
||||||
|
if idx + 1 < len(tokens):
|
||||||
|
index_url = tokens[idx + 1]
|
||||||
|
|
||||||
|
res = {'package': package}
|
||||||
|
|
||||||
|
if operator is not None:
|
||||||
|
res['operator'] = operator
|
||||||
|
|
||||||
|
if version is not None:
|
||||||
|
res['version'] = StrictVersion(version)
|
||||||
|
|
||||||
|
if index_url is not None:
|
||||||
|
res['index_url'] = index_url
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
torch_torchvision_torchaudio_version_map = {
|
||||||
|
'2.7.0': ('0.22.0', '2.7.0'),
|
||||||
|
'2.6.0': ('0.21.0', '2.6.0'),
|
||||||
|
'2.5.1': ('0.20.0', '2.5.0'),
|
||||||
|
'2.5.0': ('0.20.0', '2.5.0'),
|
||||||
|
'2.4.1': ('0.19.1', '2.4.1'),
|
||||||
|
'2.4.0': ('0.19.0', '2.4.0'),
|
||||||
|
'2.3.1': ('0.18.1', '2.3.1'),
|
||||||
|
'2.3.0': ('0.18.0', '2.3.0'),
|
||||||
|
'2.2.2': ('0.17.2', '2.2.2'),
|
||||||
|
'2.2.1': ('0.17.1', '2.2.1'),
|
||||||
|
'2.2.0': ('0.17.0', '2.2.0'),
|
||||||
|
'2.1.2': ('0.16.2', '2.1.2'),
|
||||||
|
'2.1.1': ('0.16.1', '2.1.1'),
|
||||||
|
'2.1.0': ('0.16.0', '2.1.0'),
|
||||||
|
'2.0.1': ('0.15.2', '2.0.1'),
|
||||||
|
'2.0.0': ('0.15.1', '2.0.0'),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class PIPFixer:
|
||||||
|
def __init__(self, prev_pip_versions, comfyui_path, manager_files_path):
|
||||||
|
self.prev_pip_versions = { **prev_pip_versions }
|
||||||
|
self.comfyui_path = comfyui_path
|
||||||
|
self.manager_files_path = manager_files_path
|
||||||
|
|
||||||
|
def torch_rollback(self):
|
||||||
|
spec = self.prev_pip_versions['torch'].split('+')
|
||||||
|
if len(spec) > 0:
|
||||||
|
platform = spec[1]
|
||||||
|
else:
|
||||||
|
cmd = make_pip_cmd(['install', '--force', 'torch', 'torchvision', 'torchaudio'])
|
||||||
|
subprocess.check_output(cmd, universal_newlines=True)
|
||||||
|
logging.error(cmd)
|
||||||
|
return
|
||||||
|
|
||||||
|
torch_ver = StrictVersion(spec[0])
|
||||||
|
torch_ver = f"{torch_ver.major}.{torch_ver.minor}.{torch_ver.patch}"
|
||||||
|
torch_torchvision_torchaudio_ver = torch_torchvision_torchaudio_version_map.get(torch_ver)
|
||||||
|
|
||||||
|
if torch_torchvision_torchaudio_ver is None:
|
||||||
|
cmd = make_pip_cmd(['install', '--pre', 'torch', 'torchvision', 'torchaudio',
|
||||||
|
'--index-url', f"https://download.pytorch.org/whl/nightly/{platform}"])
|
||||||
|
logging.info("[ComfyUI-Manager] restore PyTorch to nightly version")
|
||||||
|
else:
|
||||||
|
torchvision_ver, torchaudio_ver = torch_torchvision_torchaudio_ver
|
||||||
|
cmd = make_pip_cmd(['install', f'torch=={torch_ver}', f'torchvision=={torchvision_ver}', f"torchaudio=={torchaudio_ver}",
|
||||||
|
'--index-url', f"https://download.pytorch.org/whl/{platform}"])
|
||||||
|
logging.info(f"[ComfyUI-Manager] restore PyTorch to {torch_ver}+{platform}")
|
||||||
|
|
||||||
|
subprocess.check_output(cmd, universal_newlines=True)
|
||||||
|
|
||||||
|
def fix_broken(self):
|
||||||
|
new_pip_versions = get_installed_packages(True)
|
||||||
|
|
||||||
|
# remove `comfy` python package
|
||||||
|
try:
|
||||||
|
if 'comfy' in new_pip_versions:
|
||||||
|
cmd = make_pip_cmd(['uninstall', 'comfy'])
|
||||||
|
subprocess.check_output(cmd, universal_newlines=True)
|
||||||
|
|
||||||
|
logging.warning("[ComfyUI-Manager] 'comfy' python package is uninstalled.\nWARN: The 'comfy' package is completely unrelated to ComfyUI and should never be installed as it causes conflicts with ComfyUI.")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("[ComfyUI-Manager] Failed to uninstall `comfy` python package")
|
||||||
|
logging.error(e)
|
||||||
|
|
||||||
|
# fix torch - reinstall torch packages if version is changed
|
||||||
|
try:
|
||||||
|
if 'torch' not in self.prev_pip_versions or 'torchvision' not in self.prev_pip_versions or 'torchaudio' not in self.prev_pip_versions:
|
||||||
|
logging.error("[ComfyUI-Manager] PyTorch is not installed")
|
||||||
|
elif self.prev_pip_versions['torch'] != new_pip_versions['torch'] \
|
||||||
|
or self.prev_pip_versions['torchvision'] != new_pip_versions['torchvision'] \
|
||||||
|
or self.prev_pip_versions['torchaudio'] != new_pip_versions['torchaudio']:
|
||||||
|
self.torch_rollback()
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("[ComfyUI-Manager] Failed to restore PyTorch")
|
||||||
|
logging.error(e)
|
||||||
|
|
||||||
|
# fix opencv
|
||||||
|
try:
|
||||||
|
ocp = new_pip_versions.get('opencv-contrib-python')
|
||||||
|
ocph = new_pip_versions.get('opencv-contrib-python-headless')
|
||||||
|
op = new_pip_versions.get('opencv-python')
|
||||||
|
oph = new_pip_versions.get('opencv-python-headless')
|
||||||
|
|
||||||
|
versions = [ocp, ocph, op, oph]
|
||||||
|
versions = [StrictVersion(x) for x in versions if x is not None]
|
||||||
|
versions.sort(reverse=True)
|
||||||
|
|
||||||
|
if len(versions) > 0:
|
||||||
|
# upgrade to maximum version
|
||||||
|
targets = []
|
||||||
|
cur = versions[0]
|
||||||
|
if ocp is not None and StrictVersion(ocp) != cur:
|
||||||
|
targets.append('opencv-contrib-python')
|
||||||
|
if ocph is not None and StrictVersion(ocph) != cur:
|
||||||
|
targets.append('opencv-contrib-python-headless')
|
||||||
|
if op is not None and StrictVersion(op) != cur:
|
||||||
|
targets.append('opencv-python')
|
||||||
|
if oph is not None and StrictVersion(oph) != cur:
|
||||||
|
targets.append('opencv-python-headless')
|
||||||
|
|
||||||
|
if len(targets) > 0:
|
||||||
|
for x in targets:
|
||||||
|
if sys.version_info < (3, 13):
|
||||||
|
cmd = make_pip_cmd(['install', f"{x}=={versions[0].version_string}", "numpy<2"])
|
||||||
|
subprocess.check_output(cmd, universal_newlines=True)
|
||||||
|
|
||||||
|
logging.info(f"[ComfyUI-Manager] 'opencv' dependencies were fixed: {targets}")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("[ComfyUI-Manager] Failed to restore opencv")
|
||||||
|
logging.error(e)
|
||||||
|
|
||||||
|
# fix numpy
|
||||||
|
if sys.version_info >= (3, 13):
|
||||||
|
logging.info("[ComfyUI-Manager] In Python 3.13 and above, PIP Fixer does not downgrade `numpy` below version 2.0. If you need to force a downgrade of `numpy`, please use `pip_auto_fix.list`.")
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
np = new_pip_versions.get('numpy')
|
||||||
|
if cm_global.pip_overrides.get('numpy') == 'numpy<2':
|
||||||
|
if np is not None:
|
||||||
|
if StrictVersion(np) >= StrictVersion('2'):
|
||||||
|
cmd = make_pip_cmd(['install', "numpy<2"])
|
||||||
|
subprocess.check_output(cmd , universal_newlines=True)
|
||||||
|
|
||||||
|
logging.info("[ComfyUI-Manager] 'numpy' dependency were fixed")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("[ComfyUI-Manager] Failed to restore numpy")
|
||||||
|
logging.error(e)
|
||||||
|
|
||||||
|
# fix missing frontend
|
||||||
|
try:
|
||||||
|
# NOTE: package name in requirements is 'comfyui-frontend-package'
|
||||||
|
# but, package name from `pip freeze` is 'comfyui_frontend_package'
|
||||||
|
# but, package name from `uv pip freeze` is 'comfyui-frontend-package'
|
||||||
|
#
|
||||||
|
# get_installed_packages returns normalized name (i.e. comfyui_frontend_package)
|
||||||
|
if 'comfyui_frontend_package' not in new_pip_versions:
|
||||||
|
requirements_path = os.path.join(self.comfyui_path, 'requirements.txt')
|
||||||
|
|
||||||
|
with open(requirements_path, 'r') as file:
|
||||||
|
lines = file.readlines()
|
||||||
|
|
||||||
|
front_line = next((line.strip() for line in lines if line.startswith('comfyui-frontend-package')), None)
|
||||||
|
if front_line is None:
|
||||||
|
logging.info("[ComfyUI-Manager] Skipped fixing the 'comfyui-frontend-package' dependency because the ComfyUI is outdated.")
|
||||||
|
else:
|
||||||
|
cmd = make_pip_cmd(['install', front_line])
|
||||||
|
subprocess.check_output(cmd , universal_newlines=True)
|
||||||
|
logging.info("[ComfyUI-Manager] 'comfyui-frontend-package' dependency were fixed")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error("[ComfyUI-Manager] Failed to restore comfyui-frontend-package")
|
||||||
|
logging.error(e)
|
||||||
|
|
||||||
|
# restore based on custom list
|
||||||
|
pip_auto_fix_path = os.path.join(self.manager_files_path, "pip_auto_fix.list")
|
||||||
|
if os.path.exists(pip_auto_fix_path):
|
||||||
|
with open(pip_auto_fix_path, 'r', encoding="UTF-8", errors="ignore") as f:
|
||||||
|
fixed_list = []
|
||||||
|
|
||||||
|
for x in f.readlines():
|
||||||
|
try:
|
||||||
|
parsed = parse_requirement_line(x)
|
||||||
|
need_to_reinstall = True
|
||||||
|
|
||||||
|
normalized_name = parsed['package'].lower().replace('-', '_')
|
||||||
|
if normalized_name in new_pip_versions:
|
||||||
|
if 'version' in parsed and 'operator' in parsed:
|
||||||
|
cur = StrictVersion(new_pip_versions[normalized_name])
|
||||||
|
dest = parsed['version']
|
||||||
|
op = parsed['operator']
|
||||||
|
if cur == dest:
|
||||||
|
if op in ['==', '>=', '<=']:
|
||||||
|
need_to_reinstall = False
|
||||||
|
elif cur < dest:
|
||||||
|
if op in ['<=', '<', '~=', '!=']:
|
||||||
|
need_to_reinstall = False
|
||||||
|
elif cur > dest:
|
||||||
|
if op in ['>=', '>', '~=', '!=']:
|
||||||
|
need_to_reinstall = False
|
||||||
|
|
||||||
|
if need_to_reinstall:
|
||||||
|
cmd_args = ['install']
|
||||||
|
if 'version' in parsed and 'operator' in parsed:
|
||||||
|
cmd_args.append(parsed['package']+parsed['operator']+parsed['version'].version_string)
|
||||||
|
|
||||||
|
if 'index_url' in parsed:
|
||||||
|
cmd_args.append('--index-url')
|
||||||
|
cmd_args.append(parsed['index_url'])
|
||||||
|
|
||||||
|
cmd = make_pip_cmd(cmd_args)
|
||||||
|
subprocess.check_output(cmd, universal_newlines=True)
|
||||||
|
|
||||||
|
fixed_list.append(parsed['package'])
|
||||||
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
|
logging.error(f"[ComfyUI-Manager] Failed to restore '{x}'")
|
||||||
|
logging.error(e)
|
||||||
|
|
||||||
|
if len(fixed_list) > 0:
|
||||||
|
logging.info(f"[ComfyUI-Manager] dependencies in pip_auto_fix.json were fixed: {fixed_list}")
|
||||||
|
|
||||||
|
def sanitize(data):
|
||||||
|
return data.replace("<", "<").replace(">", ">")
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_filename(input_string):
|
||||||
|
result_string = re.sub(r'[^a-zA-Z0-9_]', '_', input_string)
|
||||||
|
return result_string
|
||||||
|
|
||||||
|
|
||||||
|
def robust_readlines(fullpath):
|
||||||
|
import chardet
|
||||||
|
try:
|
||||||
|
with open(fullpath, "r") as f:
|
||||||
|
return f.readlines()
|
||||||
|
except Exception:
|
||||||
|
encoding = None
|
||||||
|
with open(fullpath, "rb") as f:
|
||||||
|
raw_data = f.read()
|
||||||
|
result = chardet.detect(raw_data)
|
||||||
|
encoding = result['encoding']
|
||||||
|
|
||||||
|
if encoding is not None:
|
||||||
|
with open(fullpath, "r", encoding=encoding) as f:
|
||||||
|
return f.readlines()
|
||||||
|
|
||||||
|
print(f"[ComfyUI-Manager] Failed to recognize encoding for: {fullpath}")
|
||||||
|
return []
|
||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from git_utils import get_commit_hash
|
from .git_utils import get_commit_hash
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
67
comfyui_manager/data_models/README.md
Normal file
67
comfyui_manager/data_models/README.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# Data Models
|
||||||
|
|
||||||
|
This directory contains Pydantic models for ComfyUI Manager, providing type safety, validation, and serialization for the API and internal data structures.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
- `generated_models.py` - All models auto-generated from OpenAPI spec
|
||||||
|
- `__init__.py` - Package exports for all models
|
||||||
|
|
||||||
|
**Note**: All models are now auto-generated from the OpenAPI specification. Manual model files (`task_queue.py`, `state_management.py`) have been deprecated in favor of a single source of truth.
|
||||||
|
|
||||||
|
## Generating Types from OpenAPI
|
||||||
|
|
||||||
|
The state management models are automatically generated from the OpenAPI specification using `datamodel-codegen`. This ensures type safety and consistency between the API specification and the Python code.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
Install the code generator:
|
||||||
|
```bash
|
||||||
|
pipx install datamodel-code-generator
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generation Command
|
||||||
|
|
||||||
|
To regenerate all models after updating the OpenAPI spec:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
datamodel-codegen \
|
||||||
|
--use-subclass-enum \
|
||||||
|
--field-constraints \
|
||||||
|
--strict-types bytes \
|
||||||
|
--input openapi.yaml \
|
||||||
|
--output comfyui_manager/data_models/generated_models.py \
|
||||||
|
--output-model-type pydantic_v2.BaseModel
|
||||||
|
```
|
||||||
|
|
||||||
|
### When to Regenerate
|
||||||
|
|
||||||
|
You should regenerate the models when:
|
||||||
|
|
||||||
|
1. **Adding new API endpoints** that return new data structures
|
||||||
|
2. **Modifying existing schemas** in the OpenAPI specification
|
||||||
|
3. **Adding new state management features** that require new models
|
||||||
|
|
||||||
|
### Important Notes
|
||||||
|
|
||||||
|
- **Single source of truth**: All models are now generated from `openapi.yaml`
|
||||||
|
- **No manual models**: All previously manual models have been migrated to the OpenAPI spec
|
||||||
|
- **OpenAPI requirements**: New schemas must be referenced in API paths to be generated by datamodel-codegen
|
||||||
|
- **Validation**: Always validate the OpenAPI spec before generation:
|
||||||
|
```bash
|
||||||
|
python3 -c "import yaml; yaml.safe_load(open('openapi.yaml'))"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example: Adding New State Models
|
||||||
|
|
||||||
|
1. Add your schema to `openapi.yaml` under `components/schemas/`
|
||||||
|
2. Reference the schema in an API endpoint response
|
||||||
|
3. Run the generation command above
|
||||||
|
4. Update `__init__.py` to export the new models
|
||||||
|
5. Import and use the models in your code
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
- **Models not generated**: Ensure schemas are under `components/schemas/` (not `parameters/`)
|
||||||
|
- **Missing models**: Verify schemas are referenced in at least one API path
|
||||||
|
- **Import errors**: Check that new models are added to `__init__.py` exports
|
||||||
119
comfyui_manager/data_models/__init__.py
Normal file
119
comfyui_manager/data_models/__init__.py
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
"""
|
||||||
|
Data models for ComfyUI Manager.
|
||||||
|
|
||||||
|
This package contains Pydantic models used throughout the ComfyUI Manager
|
||||||
|
for data validation, serialization, and type safety.
|
||||||
|
|
||||||
|
All models are auto-generated from the OpenAPI specification to ensure
|
||||||
|
consistency between the API and implementation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .generated_models import (
|
||||||
|
# Core Task Queue Models
|
||||||
|
QueueTaskItem,
|
||||||
|
TaskHistoryItem,
|
||||||
|
TaskStateMessage,
|
||||||
|
TaskExecutionStatus,
|
||||||
|
|
||||||
|
# WebSocket Message Models
|
||||||
|
MessageTaskDone,
|
||||||
|
MessageTaskStarted,
|
||||||
|
MessageTaskFailed,
|
||||||
|
MessageUpdate,
|
||||||
|
ManagerMessageName,
|
||||||
|
|
||||||
|
# State Management Models
|
||||||
|
BatchExecutionRecord,
|
||||||
|
ComfyUISystemState,
|
||||||
|
BatchOperation,
|
||||||
|
InstalledNodeInfo,
|
||||||
|
InstalledModelInfo,
|
||||||
|
ComfyUIVersionInfo,
|
||||||
|
|
||||||
|
# Other models
|
||||||
|
Kind,
|
||||||
|
StatusStr,
|
||||||
|
ManagerPackInfo,
|
||||||
|
ManagerPackInstalled,
|
||||||
|
SelectedVersion,
|
||||||
|
ManagerChannel,
|
||||||
|
ManagerDatabaseSource,
|
||||||
|
ManagerPackState,
|
||||||
|
ManagerPackInstallType,
|
||||||
|
ManagerPack,
|
||||||
|
InstallPackParams,
|
||||||
|
UpdatePackParams,
|
||||||
|
UpdateAllPacksParams,
|
||||||
|
UpdateComfyUIParams,
|
||||||
|
FixPackParams,
|
||||||
|
UninstallPackParams,
|
||||||
|
DisablePackParams,
|
||||||
|
EnablePackParams,
|
||||||
|
QueueStatus,
|
||||||
|
ManagerMappings,
|
||||||
|
ModelMetadata,
|
||||||
|
NodePackageMetadata,
|
||||||
|
SnapshotItem,
|
||||||
|
Error,
|
||||||
|
InstalledPacksResponse,
|
||||||
|
HistoryResponse,
|
||||||
|
HistoryListResponse,
|
||||||
|
InstallType,
|
||||||
|
OperationType,
|
||||||
|
Result,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
# Core Task Queue Models
|
||||||
|
"QueueTaskItem",
|
||||||
|
"TaskHistoryItem",
|
||||||
|
"TaskStateMessage",
|
||||||
|
"TaskExecutionStatus",
|
||||||
|
|
||||||
|
# WebSocket Message Models
|
||||||
|
"MessageTaskDone",
|
||||||
|
"MessageTaskStarted",
|
||||||
|
"MessageTaskFailed",
|
||||||
|
"MessageUpdate",
|
||||||
|
"ManagerMessageName",
|
||||||
|
|
||||||
|
# State Management Models
|
||||||
|
"BatchExecutionRecord",
|
||||||
|
"ComfyUISystemState",
|
||||||
|
"BatchOperation",
|
||||||
|
"InstalledNodeInfo",
|
||||||
|
"InstalledModelInfo",
|
||||||
|
"ComfyUIVersionInfo",
|
||||||
|
|
||||||
|
# Other models
|
||||||
|
"Kind",
|
||||||
|
"StatusStr",
|
||||||
|
"ManagerPackInfo",
|
||||||
|
"ManagerPackInstalled",
|
||||||
|
"SelectedVersion",
|
||||||
|
"ManagerChannel",
|
||||||
|
"ManagerDatabaseSource",
|
||||||
|
"ManagerPackState",
|
||||||
|
"ManagerPackInstallType",
|
||||||
|
"ManagerPack",
|
||||||
|
"InstallPackParams",
|
||||||
|
"UpdatePackParams",
|
||||||
|
"UpdateAllPacksParams",
|
||||||
|
"UpdateComfyUIParams",
|
||||||
|
"FixPackParams",
|
||||||
|
"UninstallPackParams",
|
||||||
|
"DisablePackParams",
|
||||||
|
"EnablePackParams",
|
||||||
|
"QueueStatus",
|
||||||
|
"ManagerMappings",
|
||||||
|
"ModelMetadata",
|
||||||
|
"NodePackageMetadata",
|
||||||
|
"SnapshotItem",
|
||||||
|
"Error",
|
||||||
|
"InstalledPacksResponse",
|
||||||
|
"HistoryResponse",
|
||||||
|
"HistoryListResponse",
|
||||||
|
"InstallType",
|
||||||
|
"OperationType",
|
||||||
|
"Result",
|
||||||
|
]
|
||||||
476
comfyui_manager/data_models/generated_models.py
Normal file
476
comfyui_manager/data_models/generated_models.py
Normal file
@@ -0,0 +1,476 @@
|
|||||||
|
# generated by datamodel-codegen:
|
||||||
|
# filename: openapi.yaml
|
||||||
|
# timestamp: 2025-06-14T01:44:21+00:00
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Any, Dict, List, Optional, Union
|
||||||
|
|
||||||
|
from pydantic import BaseModel, Field, RootModel
|
||||||
|
|
||||||
|
|
||||||
|
class Kind(str, Enum):
|
||||||
|
install = 'install'
|
||||||
|
uninstall = 'uninstall'
|
||||||
|
update = 'update'
|
||||||
|
update_all = 'update-all'
|
||||||
|
update_comfyui = 'update-comfyui'
|
||||||
|
fix = 'fix'
|
||||||
|
disable = 'disable'
|
||||||
|
enable = 'enable'
|
||||||
|
install_model = 'install-model'
|
||||||
|
|
||||||
|
|
||||||
|
class StatusStr(str, Enum):
|
||||||
|
success = 'success'
|
||||||
|
error = 'error'
|
||||||
|
skip = 'skip'
|
||||||
|
|
||||||
|
|
||||||
|
class TaskExecutionStatus(BaseModel):
|
||||||
|
status_str: StatusStr = Field(..., description='Overall task execution status')
|
||||||
|
completed: bool = Field(..., description='Whether the task completed')
|
||||||
|
messages: List[str] = Field(..., description='Additional status messages')
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerMessageName(str, Enum):
|
||||||
|
cm_task_completed = 'cm-task-completed'
|
||||||
|
cm_task_started = 'cm-task-started'
|
||||||
|
cm_queue_status = 'cm-queue-status'
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerPackInfo(BaseModel):
|
||||||
|
id: str = Field(
|
||||||
|
...,
|
||||||
|
description='Either github-author/github-repo or name of pack from the registry',
|
||||||
|
)
|
||||||
|
version: str = Field(..., description='Semantic version or Git commit hash')
|
||||||
|
ui_id: Optional[str] = Field(None, description='Task ID - generated internally')
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerPackInstalled(BaseModel):
|
||||||
|
ver: str = Field(
|
||||||
|
...,
|
||||||
|
description='The version of the pack that is installed (Git commit hash or semantic version)',
|
||||||
|
)
|
||||||
|
cnr_id: Optional[str] = Field(
|
||||||
|
None, description='The name of the pack if installed from the registry'
|
||||||
|
)
|
||||||
|
aux_id: Optional[str] = Field(
|
||||||
|
None,
|
||||||
|
description='The name of the pack if installed from github (author/repo-name format)',
|
||||||
|
)
|
||||||
|
enabled: bool = Field(..., description='Whether the pack is enabled')
|
||||||
|
|
||||||
|
|
||||||
|
class SelectedVersion(str, Enum):
|
||||||
|
latest = 'latest'
|
||||||
|
nightly = 'nightly'
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerChannel(str, Enum):
|
||||||
|
default = 'default'
|
||||||
|
recent = 'recent'
|
||||||
|
legacy = 'legacy'
|
||||||
|
forked = 'forked'
|
||||||
|
dev = 'dev'
|
||||||
|
tutorial = 'tutorial'
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerDatabaseSource(str, Enum):
|
||||||
|
remote = 'remote'
|
||||||
|
local = 'local'
|
||||||
|
cache = 'cache'
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerPackState(str, Enum):
|
||||||
|
installed = 'installed'
|
||||||
|
disabled = 'disabled'
|
||||||
|
not_installed = 'not_installed'
|
||||||
|
import_failed = 'import_failed'
|
||||||
|
needs_update = 'needs_update'
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerPackInstallType(str, Enum):
|
||||||
|
git_clone = 'git-clone'
|
||||||
|
copy = 'copy'
|
||||||
|
cnr = 'cnr'
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateState(Enum):
|
||||||
|
false = 'false'
|
||||||
|
true = 'true'
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerPack(ManagerPackInfo):
|
||||||
|
author: Optional[str] = Field(
|
||||||
|
None, description="Pack author name or 'Unclaimed' if added via GitHub crawl"
|
||||||
|
)
|
||||||
|
files: Optional[List[str]] = Field(None, description='Files included in the pack')
|
||||||
|
reference: Optional[str] = Field(
|
||||||
|
None, description='The type of installation reference'
|
||||||
|
)
|
||||||
|
title: Optional[str] = Field(None, description='The display name of the pack')
|
||||||
|
cnr_latest: Optional[SelectedVersion] = None
|
||||||
|
repository: Optional[str] = Field(None, description='GitHub repository URL')
|
||||||
|
state: Optional[ManagerPackState] = None
|
||||||
|
update_state: Optional[UpdateState] = Field(
|
||||||
|
None, alias='update-state', description='Update availability status'
|
||||||
|
)
|
||||||
|
stars: Optional[int] = Field(None, description='GitHub stars count')
|
||||||
|
last_update: Optional[datetime] = Field(None, description='Last update timestamp')
|
||||||
|
health: Optional[str] = Field(None, description='Health status of the pack')
|
||||||
|
description: Optional[str] = Field(None, description='Pack description')
|
||||||
|
trust: Optional[bool] = Field(None, description='Whether the pack is trusted')
|
||||||
|
install_type: Optional[ManagerPackInstallType] = None
|
||||||
|
|
||||||
|
|
||||||
|
class InstallPackParams(ManagerPackInfo):
|
||||||
|
selected_version: Union[str, SelectedVersion] = Field(
|
||||||
|
..., description='Semantic version, Git commit hash, latest, or nightly'
|
||||||
|
)
|
||||||
|
repository: Optional[str] = Field(
|
||||||
|
None,
|
||||||
|
description='GitHub repository URL (required if selected_version is nightly)',
|
||||||
|
)
|
||||||
|
pip: Optional[List[str]] = Field(None, description='PyPi dependency names')
|
||||||
|
mode: ManagerDatabaseSource
|
||||||
|
channel: ManagerChannel
|
||||||
|
skip_post_install: Optional[bool] = Field(
|
||||||
|
None, description='Whether to skip post-installation steps'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateAllPacksParams(BaseModel):
|
||||||
|
mode: Optional[ManagerDatabaseSource] = None
|
||||||
|
ui_id: Optional[str] = Field(None, description='Task ID - generated internally')
|
||||||
|
|
||||||
|
|
||||||
|
class UpdatePackParams(BaseModel):
|
||||||
|
node_name: str = Field(..., description='Name of the node package to update')
|
||||||
|
node_ver: Optional[str] = Field(
|
||||||
|
None, description='Current version of the node package'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateComfyUIParams(BaseModel):
|
||||||
|
is_stable: Optional[bool] = Field(
|
||||||
|
True,
|
||||||
|
description='Whether to update to stable version (true) or nightly (false)',
|
||||||
|
)
|
||||||
|
target_version: Optional[str] = Field(
|
||||||
|
None,
|
||||||
|
description='Specific version to switch to (for version switching operations)',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class FixPackParams(BaseModel):
|
||||||
|
node_name: str = Field(..., description='Name of the node package to fix')
|
||||||
|
node_ver: str = Field(..., description='Version of the node package')
|
||||||
|
|
||||||
|
|
||||||
|
class UninstallPackParams(BaseModel):
|
||||||
|
node_name: str = Field(..., description='Name of the node package to uninstall')
|
||||||
|
is_unknown: Optional[bool] = Field(
|
||||||
|
False, description='Whether this is an unknown/unregistered package'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DisablePackParams(BaseModel):
|
||||||
|
node_name: str = Field(..., description='Name of the node package to disable')
|
||||||
|
is_unknown: Optional[bool] = Field(
|
||||||
|
False, description='Whether this is an unknown/unregistered package'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class EnablePackParams(BaseModel):
|
||||||
|
cnr_id: str = Field(
|
||||||
|
..., description='ComfyUI Node Registry ID of the package to enable'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class QueueStatus(BaseModel):
|
||||||
|
total_count: int = Field(
|
||||||
|
..., description='Total number of tasks (pending + running)'
|
||||||
|
)
|
||||||
|
done_count: int = Field(..., description='Number of completed tasks')
|
||||||
|
in_progress_count: int = Field(..., description='Number of tasks currently running')
|
||||||
|
pending_count: Optional[int] = Field(
|
||||||
|
None, description='Number of tasks waiting to be executed'
|
||||||
|
)
|
||||||
|
is_processing: bool = Field(..., description='Whether the task worker is active')
|
||||||
|
client_id: Optional[str] = Field(
|
||||||
|
None, description='Client ID (when filtered by client)'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerMappings1(BaseModel):
|
||||||
|
title_aux: Optional[str] = Field(None, description='The display name of the pack')
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerMappings(
|
||||||
|
RootModel[Optional[Dict[str, List[Union[List[str], ManagerMappings1]]]]]
|
||||||
|
):
|
||||||
|
root: Optional[Dict[str, List[Union[List[str], ManagerMappings1]]]] = Field(
|
||||||
|
None, description='Tuple of [node_names, metadata]'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ModelMetadata(BaseModel):
|
||||||
|
name: str = Field(..., description='Name of the model')
|
||||||
|
type: str = Field(..., description='Type of model')
|
||||||
|
base: Optional[str] = Field(None, description='Base model type')
|
||||||
|
save_path: Optional[str] = Field(None, description='Path for saving the model')
|
||||||
|
url: str = Field(..., description='Download URL')
|
||||||
|
filename: str = Field(..., description='Target filename')
|
||||||
|
ui_id: Optional[str] = Field(None, description='ID for UI reference')
|
||||||
|
|
||||||
|
|
||||||
|
class InstallType(str, Enum):
|
||||||
|
git = 'git'
|
||||||
|
copy = 'copy'
|
||||||
|
pip = 'pip'
|
||||||
|
|
||||||
|
|
||||||
|
class NodePackageMetadata(BaseModel):
|
||||||
|
title: Optional[str] = Field(None, description='Display name of the node package')
|
||||||
|
name: Optional[str] = Field(None, description='Repository/package name')
|
||||||
|
files: Optional[List[str]] = Field(None, description='Source URLs for the package')
|
||||||
|
description: Optional[str] = Field(
|
||||||
|
None, description='Description of the node package functionality'
|
||||||
|
)
|
||||||
|
install_type: Optional[InstallType] = Field(None, description='Installation method')
|
||||||
|
version: Optional[str] = Field(None, description='Version identifier')
|
||||||
|
id: Optional[str] = Field(
|
||||||
|
None, description='Unique identifier for the node package'
|
||||||
|
)
|
||||||
|
ui_id: Optional[str] = Field(None, description='ID for UI reference')
|
||||||
|
channel: Optional[str] = Field(None, description='Source channel')
|
||||||
|
mode: Optional[str] = Field(None, description='Source mode')
|
||||||
|
|
||||||
|
|
||||||
|
class SnapshotItem(RootModel[str]):
|
||||||
|
root: str = Field(..., description='Name of the snapshot')
|
||||||
|
|
||||||
|
|
||||||
|
class Error(BaseModel):
|
||||||
|
error: str = Field(..., description='Error message')
|
||||||
|
|
||||||
|
|
||||||
|
class InstalledPacksResponse(RootModel[Optional[Dict[str, ManagerPackInstalled]]]):
|
||||||
|
root: Optional[Dict[str, ManagerPackInstalled]] = None
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryListResponse(BaseModel):
|
||||||
|
ids: Optional[List[str]] = Field(
|
||||||
|
None, description='List of available batch history IDs'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InstalledNodeInfo(BaseModel):
|
||||||
|
name: str = Field(..., description='Node package name')
|
||||||
|
version: str = Field(..., description='Installed version')
|
||||||
|
repository_url: Optional[str] = Field(None, description='Git repository URL')
|
||||||
|
install_method: str = Field(
|
||||||
|
..., description='Installation method (cnr, git, pip, etc.)'
|
||||||
|
)
|
||||||
|
enabled: Optional[bool] = Field(
|
||||||
|
True, description='Whether the node is currently enabled'
|
||||||
|
)
|
||||||
|
install_date: Optional[datetime] = Field(
|
||||||
|
None, description='ISO timestamp of installation'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InstalledModelInfo(BaseModel):
|
||||||
|
name: str = Field(..., description='Model filename')
|
||||||
|
path: str = Field(..., description='Full path to model file')
|
||||||
|
type: str = Field(..., description='Model type (checkpoint, lora, vae, etc.)')
|
||||||
|
size_bytes: Optional[int] = Field(None, description='File size in bytes', ge=0)
|
||||||
|
hash: Optional[str] = Field(None, description='Model file hash for verification')
|
||||||
|
install_date: Optional[datetime] = Field(
|
||||||
|
None, description='ISO timestamp when added'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ComfyUIVersionInfo(BaseModel):
|
||||||
|
version: str = Field(..., description='ComfyUI version string')
|
||||||
|
commit_hash: Optional[str] = Field(None, description='Git commit hash')
|
||||||
|
branch: Optional[str] = Field(None, description='Git branch name')
|
||||||
|
is_stable: Optional[bool] = Field(
|
||||||
|
False, description='Whether this is a stable release'
|
||||||
|
)
|
||||||
|
last_updated: Optional[datetime] = Field(
|
||||||
|
None, description='ISO timestamp of last update'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OperationType(str, Enum):
|
||||||
|
install = 'install'
|
||||||
|
update = 'update'
|
||||||
|
uninstall = 'uninstall'
|
||||||
|
fix = 'fix'
|
||||||
|
disable = 'disable'
|
||||||
|
enable = 'enable'
|
||||||
|
install_model = 'install-model'
|
||||||
|
|
||||||
|
|
||||||
|
class Result(str, Enum):
|
||||||
|
success = 'success'
|
||||||
|
failed = 'failed'
|
||||||
|
skipped = 'skipped'
|
||||||
|
|
||||||
|
|
||||||
|
class BatchOperation(BaseModel):
|
||||||
|
operation_id: str = Field(..., description='Unique operation identifier')
|
||||||
|
operation_type: OperationType = Field(..., description='Type of operation')
|
||||||
|
target: str = Field(
|
||||||
|
..., description='Target of the operation (node name, model name, etc.)'
|
||||||
|
)
|
||||||
|
target_version: Optional[str] = Field(
|
||||||
|
None, description='Target version for the operation'
|
||||||
|
)
|
||||||
|
result: Result = Field(..., description='Operation result')
|
||||||
|
error_message: Optional[str] = Field(
|
||||||
|
None, description='Error message if operation failed'
|
||||||
|
)
|
||||||
|
start_time: datetime = Field(
|
||||||
|
..., description='ISO timestamp when operation started'
|
||||||
|
)
|
||||||
|
end_time: Optional[datetime] = Field(
|
||||||
|
None, description='ISO timestamp when operation completed'
|
||||||
|
)
|
||||||
|
client_id: Optional[str] = Field(
|
||||||
|
None, description='Client that initiated the operation'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ComfyUISystemState(BaseModel):
|
||||||
|
snapshot_time: datetime = Field(
|
||||||
|
..., description='ISO timestamp when snapshot was taken'
|
||||||
|
)
|
||||||
|
comfyui_version: ComfyUIVersionInfo
|
||||||
|
frontend_version: Optional[str] = Field(
|
||||||
|
None, description='ComfyUI frontend version if available'
|
||||||
|
)
|
||||||
|
python_version: str = Field(..., description='Python interpreter version')
|
||||||
|
platform_info: str = Field(
|
||||||
|
..., description='Operating system and platform information'
|
||||||
|
)
|
||||||
|
installed_nodes: Optional[Dict[str, InstalledNodeInfo]] = Field(
|
||||||
|
None, description='Map of installed node packages by name'
|
||||||
|
)
|
||||||
|
installed_models: Optional[Dict[str, InstalledModelInfo]] = Field(
|
||||||
|
None, description='Map of installed models by name'
|
||||||
|
)
|
||||||
|
manager_config: Optional[Dict[str, Any]] = Field(
|
||||||
|
None, description='ComfyUI Manager configuration settings'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BatchExecutionRecord(BaseModel):
|
||||||
|
batch_id: str = Field(..., description='Unique batch identifier')
|
||||||
|
start_time: datetime = Field(..., description='ISO timestamp when batch started')
|
||||||
|
end_time: Optional[datetime] = Field(
|
||||||
|
None, description='ISO timestamp when batch completed'
|
||||||
|
)
|
||||||
|
state_before: ComfyUISystemState
|
||||||
|
state_after: Optional[ComfyUISystemState] = Field(
|
||||||
|
None, description='System state after batch execution'
|
||||||
|
)
|
||||||
|
operations: Optional[List[BatchOperation]] = Field(
|
||||||
|
None, description='List of operations performed in this batch'
|
||||||
|
)
|
||||||
|
total_operations: Optional[int] = Field(
|
||||||
|
0, description='Total number of operations in batch', ge=0
|
||||||
|
)
|
||||||
|
successful_operations: Optional[int] = Field(
|
||||||
|
0, description='Number of successful operations', ge=0
|
||||||
|
)
|
||||||
|
failed_operations: Optional[int] = Field(
|
||||||
|
0, description='Number of failed operations', ge=0
|
||||||
|
)
|
||||||
|
skipped_operations: Optional[int] = Field(
|
||||||
|
0, description='Number of skipped operations', ge=0
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class QueueTaskItem(BaseModel):
|
||||||
|
ui_id: str = Field(..., description='Unique identifier for the task')
|
||||||
|
client_id: str = Field(..., description='Client identifier that initiated the task')
|
||||||
|
kind: Kind = Field(..., description='Type of task being performed')
|
||||||
|
params: Union[
|
||||||
|
InstallPackParams,
|
||||||
|
UpdatePackParams,
|
||||||
|
UpdateAllPacksParams,
|
||||||
|
UpdateComfyUIParams,
|
||||||
|
FixPackParams,
|
||||||
|
UninstallPackParams,
|
||||||
|
DisablePackParams,
|
||||||
|
EnablePackParams,
|
||||||
|
ModelMetadata,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class TaskHistoryItem(BaseModel):
|
||||||
|
ui_id: str = Field(..., description='Unique identifier for the task')
|
||||||
|
client_id: str = Field(..., description='Client identifier that initiated the task')
|
||||||
|
kind: str = Field(..., description='Type of task that was performed')
|
||||||
|
timestamp: datetime = Field(..., description='ISO timestamp when task completed')
|
||||||
|
result: str = Field(..., description='Task result message or details')
|
||||||
|
status: Optional[TaskExecutionStatus] = None
|
||||||
|
|
||||||
|
|
||||||
|
class TaskStateMessage(BaseModel):
|
||||||
|
history: Dict[str, TaskHistoryItem] = Field(
|
||||||
|
..., description='Map of task IDs to their history items'
|
||||||
|
)
|
||||||
|
running_queue: List[QueueTaskItem] = Field(
|
||||||
|
..., description='Currently executing tasks'
|
||||||
|
)
|
||||||
|
pending_queue: List[QueueTaskItem] = Field(
|
||||||
|
..., description='Tasks waiting to be executed'
|
||||||
|
)
|
||||||
|
installed_packs: Dict[str, ManagerPackInstalled] = Field(
|
||||||
|
..., description='Map of currently installed node packages by name'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class MessageTaskDone(BaseModel):
|
||||||
|
ui_id: str = Field(..., description='Task identifier')
|
||||||
|
result: str = Field(..., description='Task result message')
|
||||||
|
kind: str = Field(..., description='Type of task')
|
||||||
|
status: Optional[TaskExecutionStatus] = None
|
||||||
|
timestamp: datetime = Field(..., description='ISO timestamp when task completed')
|
||||||
|
state: TaskStateMessage
|
||||||
|
|
||||||
|
|
||||||
|
class MessageTaskStarted(BaseModel):
|
||||||
|
ui_id: str = Field(..., description='Task identifier')
|
||||||
|
kind: str = Field(..., description='Type of task')
|
||||||
|
timestamp: datetime = Field(..., description='ISO timestamp when task started')
|
||||||
|
state: TaskStateMessage
|
||||||
|
|
||||||
|
|
||||||
|
class MessageTaskFailed(BaseModel):
|
||||||
|
ui_id: str = Field(..., description='Task identifier')
|
||||||
|
error: str = Field(..., description='Error message')
|
||||||
|
kind: str = Field(..., description='Type of task')
|
||||||
|
timestamp: datetime = Field(..., description='ISO timestamp when task failed')
|
||||||
|
state: TaskStateMessage
|
||||||
|
|
||||||
|
|
||||||
|
class MessageUpdate(
|
||||||
|
RootModel[Union[MessageTaskDone, MessageTaskStarted, MessageTaskFailed]]
|
||||||
|
):
|
||||||
|
root: Union[MessageTaskDone, MessageTaskStarted, MessageTaskFailed] = Field(
|
||||||
|
..., description='Union type for all possible WebSocket message updates'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryResponse(BaseModel):
|
||||||
|
history: Optional[Dict[str, TaskHistoryItem]] = Field(
|
||||||
|
None, description='Map of task IDs to their history items'
|
||||||
|
)
|
||||||
10
comfyui_manager/glob/CLAUDE.md
Normal file
10
comfyui_manager/glob/CLAUDE.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
- Anytime you make a change to the data being sent or received, you should follow this process:
|
||||||
|
1. Adjust the openapi.yaml file first
|
||||||
|
2. Verify the syntax of the openapi.yaml file using `yaml.safe_load`
|
||||||
|
3. Regenerate the types following the instructions in the `data_models/README.md` file
|
||||||
|
4. Verify the new data model is generated
|
||||||
|
5. Verify the syntax of the generated types files
|
||||||
|
6. Run formatting and linting on the generated types files
|
||||||
|
7. Adjust the `__init__.py` files in the `data_models` directory to match/export the new data model
|
||||||
|
8. Only then, make the changes to the rest of the codebase
|
||||||
|
9. Run the CI tests to verify that the changes are working
|
||||||
0
comfyui_manager/glob/__init__.py
Normal file
0
comfyui_manager/glob/__init__.py
Normal file
39
comfyui_manager/glob/constants.py
Normal file
39
comfyui_manager/glob/constants.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from comfy.cli_args import args
|
||||||
|
|
||||||
|
SECURITY_MESSAGE_MIDDLE_OR_BELOW = "ERROR: To use this action, a security_level of `middle or below` is required. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
|
||||||
|
SECURITY_MESSAGE_NORMAL_MINUS = "ERROR: To use this feature, you must either set '--listen' to a local IP and set the security level to 'normal-' or lower, or set the security level to 'middle' or 'weak'. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
|
||||||
|
SECURITY_MESSAGE_GENERAL = "ERROR: This installation is not allowed in this security_level. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
|
||||||
|
SECURITY_MESSAGE_NORMAL_MINUS_MODEL = "ERROR: Downloading models that are not in '.safetensors' format is only allowed for models registered in the 'default' channel at this security level. If you want to download this model, set the security level to 'normal-' or lower."
|
||||||
|
|
||||||
|
|
||||||
|
def is_loopback(address):
|
||||||
|
import ipaddress
|
||||||
|
|
||||||
|
try:
|
||||||
|
return ipaddress.ip_address(address).is_loopback
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
is_local_mode = is_loopback(args.listen)
|
||||||
|
|
||||||
|
|
||||||
|
model_dir_name_map = {
|
||||||
|
"checkpoints": "checkpoints",
|
||||||
|
"checkpoint": "checkpoints",
|
||||||
|
"unclip": "checkpoints",
|
||||||
|
"text_encoders": "text_encoders",
|
||||||
|
"clip": "text_encoders",
|
||||||
|
"vae": "vae",
|
||||||
|
"lora": "loras",
|
||||||
|
"t2i-adapter": "controlnet",
|
||||||
|
"t2i-style": "controlnet",
|
||||||
|
"controlnet": "controlnet",
|
||||||
|
"clip_vision": "clip_vision",
|
||||||
|
"gligen": "gligen",
|
||||||
|
"upscale": "upscale_models",
|
||||||
|
"embedding": "embeddings",
|
||||||
|
"embeddings": "embeddings",
|
||||||
|
"unet": "diffusion_models",
|
||||||
|
"diffusion_model": "diffusion_models",
|
||||||
|
}
|
||||||
3263
comfyui_manager/glob/manager_core.py
Normal file
3263
comfyui_manager/glob/manager_core.py
Normal file
File diff suppressed because it is too large
Load Diff
1745
comfyui_manager/glob/manager_server.py
Normal file
1745
comfyui_manager/glob/manager_server.py
Normal file
File diff suppressed because it is too large
Load Diff
386
comfyui_manager/glob/share_3rdparty.py
Normal file
386
comfyui_manager/glob/share_3rdparty.py
Normal file
@@ -0,0 +1,386 @@
|
|||||||
|
import mimetypes
|
||||||
|
from ..common import context
|
||||||
|
from . import manager_core as core
|
||||||
|
|
||||||
|
import os
|
||||||
|
from aiohttp import web
|
||||||
|
import aiohttp
|
||||||
|
import json
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
import folder_paths
|
||||||
|
from server import PromptServer
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
def extract_model_file_names(json_data):
|
||||||
|
"""Extract unique file names from the input JSON data."""
|
||||||
|
file_names = set()
|
||||||
|
model_filename_extensions = {'.safetensors', '.ckpt', '.pt', '.pth', '.bin'}
|
||||||
|
|
||||||
|
# Recursively search for file names in the JSON data
|
||||||
|
def recursive_search(data):
|
||||||
|
if isinstance(data, dict):
|
||||||
|
for value in data.values():
|
||||||
|
recursive_search(value)
|
||||||
|
elif isinstance(data, list):
|
||||||
|
for item in data:
|
||||||
|
recursive_search(item)
|
||||||
|
elif isinstance(data, str) and '.' in data:
|
||||||
|
file_names.add(os.path.basename(data)) # file_names.add(data)
|
||||||
|
|
||||||
|
recursive_search(json_data)
|
||||||
|
return [f for f in list(file_names) if os.path.splitext(f)[1] in model_filename_extensions]
|
||||||
|
|
||||||
|
|
||||||
|
def find_file_paths(base_dir, file_names):
|
||||||
|
"""Find the paths of the files in the base directory."""
|
||||||
|
file_paths = {}
|
||||||
|
|
||||||
|
for root, dirs, files in os.walk(base_dir):
|
||||||
|
# Exclude certain directories
|
||||||
|
dirs[:] = [d for d in dirs if d not in ['.git']]
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
if file in file_names:
|
||||||
|
file_paths[file] = os.path.join(root, file)
|
||||||
|
return file_paths
|
||||||
|
|
||||||
|
|
||||||
|
def compute_sha256_checksum(filepath):
|
||||||
|
"""Compute the SHA256 checksum of a file, in chunks"""
|
||||||
|
sha256 = hashlib.sha256()
|
||||||
|
with open(filepath, 'rb') as f:
|
||||||
|
for chunk in iter(lambda: f.read(4096), b''):
|
||||||
|
sha256.update(chunk)
|
||||||
|
return sha256.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/v2/manager/share_option")
|
||||||
|
async def share_option(request):
|
||||||
|
if "value" in request.rel_url.query:
|
||||||
|
core.get_config()['share_option'] = request.rel_url.query['value']
|
||||||
|
core.write_config()
|
||||||
|
else:
|
||||||
|
return web.Response(text=core.get_config()['share_option'], status=200)
|
||||||
|
|
||||||
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
def get_openart_auth():
|
||||||
|
if not os.path.exists(os.path.join(context.manager_files_path, ".openart_key")):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(os.path.join(context.manager_files_path, ".openart_key"), "r") as f:
|
||||||
|
openart_key = f.read().strip()
|
||||||
|
return openart_key if openart_key else None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_matrix_auth():
|
||||||
|
if not os.path.exists(os.path.join(context.manager_files_path, "matrix_auth")):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(os.path.join(context.manager_files_path, "matrix_auth"), "r") as f:
|
||||||
|
matrix_auth = f.read()
|
||||||
|
homeserver, username, password = matrix_auth.strip().split("\n")
|
||||||
|
if not homeserver or not username or not password:
|
||||||
|
return None
|
||||||
|
return {
|
||||||
|
"homeserver": homeserver,
|
||||||
|
"username": username,
|
||||||
|
"password": password,
|
||||||
|
}
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_comfyworkflows_auth():
|
||||||
|
if not os.path.exists(os.path.join(context.manager_files_path, "comfyworkflows_sharekey")):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(os.path.join(context.manager_files_path, "comfyworkflows_sharekey"), "r") as f:
|
||||||
|
share_key = f.read()
|
||||||
|
if not share_key.strip():
|
||||||
|
return None
|
||||||
|
return share_key
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def get_youml_settings():
|
||||||
|
if not os.path.exists(os.path.join(context.manager_files_path, ".youml")):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
with open(os.path.join(context.manager_files_path, ".youml"), "r") as f:
|
||||||
|
youml_settings = f.read().strip()
|
||||||
|
return youml_settings if youml_settings else None
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def set_youml_settings(settings):
|
||||||
|
with open(os.path.join(context.manager_files_path, ".youml"), "w") as f:
|
||||||
|
f.write(settings)
|
||||||
|
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/v2/manager/get_openart_auth")
|
||||||
|
async def api_get_openart_auth(request):
|
||||||
|
# print("Getting stored Matrix credentials...")
|
||||||
|
openart_key = get_openart_auth()
|
||||||
|
if not openart_key:
|
||||||
|
return web.Response(status=404)
|
||||||
|
return web.json_response({"openart_key": openart_key})
|
||||||
|
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.post("/v2/manager/set_openart_auth")
|
||||||
|
async def api_set_openart_auth(request):
|
||||||
|
json_data = await request.json()
|
||||||
|
openart_key = json_data['openart_key']
|
||||||
|
with open(os.path.join(context.manager_files_path, ".openart_key"), "w") as f:
|
||||||
|
f.write(openart_key)
|
||||||
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/v2/manager/get_matrix_auth")
|
||||||
|
async def api_get_matrix_auth(request):
|
||||||
|
# print("Getting stored Matrix credentials...")
|
||||||
|
matrix_auth = get_matrix_auth()
|
||||||
|
if not matrix_auth:
|
||||||
|
return web.Response(status=404)
|
||||||
|
return web.json_response(matrix_auth)
|
||||||
|
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/v2/manager/youml/settings")
|
||||||
|
async def api_get_youml_settings(request):
|
||||||
|
youml_settings = get_youml_settings()
|
||||||
|
if not youml_settings:
|
||||||
|
return web.Response(status=404)
|
||||||
|
return web.json_response(json.loads(youml_settings))
|
||||||
|
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.post("/v2/manager/youml/settings")
|
||||||
|
async def api_set_youml_settings(request):
|
||||||
|
json_data = await request.json()
|
||||||
|
set_youml_settings(json.dumps(json_data))
|
||||||
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/v2/manager/get_comfyworkflows_auth")
|
||||||
|
async def api_get_comfyworkflows_auth(request):
|
||||||
|
# Check if the user has provided Matrix credentials in a file called 'matrix_accesstoken'
|
||||||
|
# in the same directory as the ComfyUI base folder
|
||||||
|
# print("Getting stored Comfyworkflows.com auth...")
|
||||||
|
comfyworkflows_auth = get_comfyworkflows_auth()
|
||||||
|
if not comfyworkflows_auth:
|
||||||
|
return web.Response(status=404)
|
||||||
|
return web.json_response({"comfyworkflows_sharekey": comfyworkflows_auth})
|
||||||
|
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.post("/v2/manager/set_esheep_workflow_and_images")
|
||||||
|
async def set_esheep_workflow_and_images(request):
|
||||||
|
json_data = await request.json()
|
||||||
|
with open(os.path.join(context.manager_files_path, "esheep_share_message.json"), "w", encoding='utf-8') as file:
|
||||||
|
json.dump(json_data, file, indent=4)
|
||||||
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/v2/manager/get_esheep_workflow_and_images")
|
||||||
|
async def get_esheep_workflow_and_images(request):
|
||||||
|
with open(os.path.join(context.manager_files_path, "esheep_share_message.json"), 'r', encoding='utf-8') as file:
|
||||||
|
data = json.load(file)
|
||||||
|
return web.Response(status=200, text=json.dumps(data))
|
||||||
|
|
||||||
|
|
||||||
|
def set_matrix_auth(json_data):
|
||||||
|
homeserver = json_data['homeserver']
|
||||||
|
username = json_data['username']
|
||||||
|
password = json_data['password']
|
||||||
|
with open(os.path.join(context.manager_files_path, "matrix_auth"), "w") as f:
|
||||||
|
f.write("\n".join([homeserver, username, password]))
|
||||||
|
|
||||||
|
|
||||||
|
def set_comfyworkflows_auth(comfyworkflows_sharekey):
|
||||||
|
with open(os.path.join(context.manager_files_path, "comfyworkflows_sharekey"), "w") as f:
|
||||||
|
f.write(comfyworkflows_sharekey)
|
||||||
|
|
||||||
|
|
||||||
|
def has_provided_matrix_auth(matrix_auth):
|
||||||
|
return matrix_auth['homeserver'].strip() and matrix_auth['username'].strip() and matrix_auth['password'].strip()
|
||||||
|
|
||||||
|
|
||||||
|
def has_provided_comfyworkflows_auth(comfyworkflows_sharekey):
|
||||||
|
return comfyworkflows_sharekey.strip()
|
||||||
|
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.post("/v2/manager/share")
|
||||||
|
async def share_art(request):
|
||||||
|
# get json data
|
||||||
|
json_data = await request.json()
|
||||||
|
|
||||||
|
matrix_auth = json_data['matrix_auth']
|
||||||
|
comfyworkflows_sharekey = json_data['cw_auth']['cw_sharekey']
|
||||||
|
|
||||||
|
set_matrix_auth(matrix_auth)
|
||||||
|
set_comfyworkflows_auth(comfyworkflows_sharekey)
|
||||||
|
|
||||||
|
share_destinations = json_data['share_destinations']
|
||||||
|
credits = json_data['credits']
|
||||||
|
title = json_data['title']
|
||||||
|
description = json_data['description']
|
||||||
|
is_nsfw = json_data['is_nsfw']
|
||||||
|
prompt = json_data['prompt']
|
||||||
|
potential_outputs = json_data['potential_outputs']
|
||||||
|
selected_output_index = json_data['selected_output_index']
|
||||||
|
|
||||||
|
try:
|
||||||
|
output_to_share = potential_outputs[int(selected_output_index)]
|
||||||
|
except Exception:
|
||||||
|
# for now, pick the first output
|
||||||
|
output_to_share = potential_outputs[0]
|
||||||
|
|
||||||
|
assert output_to_share['type'] in ('image', 'output')
|
||||||
|
output_dir = folder_paths.get_output_directory()
|
||||||
|
|
||||||
|
if output_to_share['type'] == 'image':
|
||||||
|
asset_filename = output_to_share['image']['filename']
|
||||||
|
asset_subfolder = output_to_share['image']['subfolder']
|
||||||
|
|
||||||
|
if output_to_share['image']['type'] == 'temp':
|
||||||
|
output_dir = folder_paths.get_temp_directory()
|
||||||
|
else:
|
||||||
|
asset_filename = output_to_share['output']['filename']
|
||||||
|
asset_subfolder = output_to_share['output']['subfolder']
|
||||||
|
|
||||||
|
if asset_subfolder:
|
||||||
|
asset_filepath = os.path.join(output_dir, asset_subfolder, asset_filename)
|
||||||
|
else:
|
||||||
|
asset_filepath = os.path.join(output_dir, asset_filename)
|
||||||
|
|
||||||
|
# get the mime type of the asset
|
||||||
|
assetFileType = mimetypes.guess_type(asset_filepath)[0]
|
||||||
|
|
||||||
|
share_website_host = "UNKNOWN"
|
||||||
|
if "comfyworkflows" in share_destinations:
|
||||||
|
share_website_host = "https://comfyworkflows.com"
|
||||||
|
share_endpoint = f"{share_website_host}/api"
|
||||||
|
|
||||||
|
# get presigned urls
|
||||||
|
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
|
||||||
|
async with session.post(
|
||||||
|
f"{share_endpoint}/get_presigned_urls",
|
||||||
|
json={
|
||||||
|
"assetFileName": asset_filename,
|
||||||
|
"assetFileType": assetFileType,
|
||||||
|
"workflowJsonFileName": 'workflow.json',
|
||||||
|
"workflowJsonFileType": 'application/json',
|
||||||
|
},
|
||||||
|
) as resp:
|
||||||
|
assert resp.status == 200
|
||||||
|
presigned_urls_json = await resp.json()
|
||||||
|
assetFilePresignedUrl = presigned_urls_json["assetFilePresignedUrl"]
|
||||||
|
assetFileKey = presigned_urls_json["assetFileKey"]
|
||||||
|
workflowJsonFilePresignedUrl = presigned_urls_json["workflowJsonFilePresignedUrl"]
|
||||||
|
workflowJsonFileKey = presigned_urls_json["workflowJsonFileKey"]
|
||||||
|
|
||||||
|
# upload asset
|
||||||
|
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
|
||||||
|
async with session.put(assetFilePresignedUrl, data=open(asset_filepath, "rb")) as resp:
|
||||||
|
assert resp.status == 200
|
||||||
|
|
||||||
|
# upload workflow json
|
||||||
|
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
|
||||||
|
async with session.put(workflowJsonFilePresignedUrl, data=json.dumps(prompt['workflow']).encode('utf-8')) as resp:
|
||||||
|
assert resp.status == 200
|
||||||
|
|
||||||
|
model_filenames = extract_model_file_names(prompt['workflow'])
|
||||||
|
model_file_paths = find_file_paths(folder_paths.base_path, model_filenames)
|
||||||
|
|
||||||
|
models_info = {}
|
||||||
|
for filename, filepath in model_file_paths.items():
|
||||||
|
models_info[filename] = {
|
||||||
|
"filename": filename,
|
||||||
|
"sha256_checksum": compute_sha256_checksum(filepath),
|
||||||
|
"relative_path": os.path.relpath(filepath, folder_paths.base_path),
|
||||||
|
}
|
||||||
|
|
||||||
|
# make a POST request to /api/upload_workflow with form data key values
|
||||||
|
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
|
||||||
|
form = aiohttp.FormData()
|
||||||
|
if comfyworkflows_sharekey:
|
||||||
|
form.add_field("shareKey", comfyworkflows_sharekey)
|
||||||
|
form.add_field("source", "comfyui_manager")
|
||||||
|
form.add_field("assetFileKey", assetFileKey)
|
||||||
|
form.add_field("assetFileType", assetFileType)
|
||||||
|
form.add_field("workflowJsonFileKey", workflowJsonFileKey)
|
||||||
|
form.add_field("sharedWorkflowWorkflowJsonString", json.dumps(prompt['workflow']))
|
||||||
|
form.add_field("sharedWorkflowPromptJsonString", json.dumps(prompt['output']))
|
||||||
|
form.add_field("shareWorkflowCredits", credits)
|
||||||
|
form.add_field("shareWorkflowTitle", title)
|
||||||
|
form.add_field("shareWorkflowDescription", description)
|
||||||
|
form.add_field("shareWorkflowIsNSFW", str(is_nsfw).lower())
|
||||||
|
form.add_field("currentSnapshot", json.dumps(await core.get_current_snapshot()))
|
||||||
|
form.add_field("modelsInfo", json.dumps(models_info))
|
||||||
|
|
||||||
|
async with session.post(
|
||||||
|
f"{share_endpoint}/upload_workflow",
|
||||||
|
data=form,
|
||||||
|
) as resp:
|
||||||
|
assert resp.status == 200
|
||||||
|
upload_workflow_json = await resp.json()
|
||||||
|
workflowId = upload_workflow_json["workflowId"]
|
||||||
|
|
||||||
|
# check if the user has provided Matrix credentials
|
||||||
|
if "matrix" in share_destinations:
|
||||||
|
comfyui_share_room_id = '!LGYSoacpJPhIfBqVfb:matrix.org'
|
||||||
|
filename = os.path.basename(asset_filepath)
|
||||||
|
content_type = assetFileType
|
||||||
|
|
||||||
|
try:
|
||||||
|
from matrix_client.api import MatrixHttpApi
|
||||||
|
from matrix_client.client import MatrixClient
|
||||||
|
|
||||||
|
homeserver = 'matrix.org'
|
||||||
|
if matrix_auth:
|
||||||
|
homeserver = matrix_auth.get('homeserver', 'matrix.org')
|
||||||
|
homeserver = homeserver.replace("http://", "https://")
|
||||||
|
if not homeserver.startswith("https://"):
|
||||||
|
homeserver = "https://" + homeserver
|
||||||
|
|
||||||
|
client = MatrixClient(homeserver)
|
||||||
|
try:
|
||||||
|
token = client.login(username=matrix_auth['username'], password=matrix_auth['password'])
|
||||||
|
if not token:
|
||||||
|
return web.json_response({"error": "Invalid Matrix credentials."}, content_type='application/json', status=400)
|
||||||
|
except Exception:
|
||||||
|
return web.json_response({"error": "Invalid Matrix credentials."}, content_type='application/json', status=400)
|
||||||
|
|
||||||
|
matrix = MatrixHttpApi(homeserver, token=token)
|
||||||
|
with open(asset_filepath, 'rb') as f:
|
||||||
|
mxc_url = matrix.media_upload(f.read(), content_type, filename=filename)['content_uri']
|
||||||
|
|
||||||
|
workflow_json_mxc_url = matrix.media_upload(prompt['workflow'], 'application/json', filename='workflow.json')['content_uri']
|
||||||
|
|
||||||
|
text_content = ""
|
||||||
|
if title:
|
||||||
|
text_content += f"{title}\n"
|
||||||
|
if description:
|
||||||
|
text_content += f"{description}\n"
|
||||||
|
if credits:
|
||||||
|
text_content += f"\ncredits: {credits}\n"
|
||||||
|
matrix.send_message(comfyui_share_room_id, text_content)
|
||||||
|
matrix.send_content(comfyui_share_room_id, mxc_url, filename, 'm.image')
|
||||||
|
matrix.send_content(comfyui_share_room_id, workflow_json_mxc_url, 'workflow.json', 'm.file')
|
||||||
|
except Exception:
|
||||||
|
logging.exception("An error occurred")
|
||||||
|
return web.json_response({"error": "An error occurred when sharing your art to Matrix."}, content_type='application/json', status=500)
|
||||||
|
|
||||||
|
return web.json_response({
|
||||||
|
"comfyworkflows": {
|
||||||
|
"url": None if "comfyworkflows" not in share_destinations else f"{share_website_host}/workflows/{workflowId}",
|
||||||
|
},
|
||||||
|
"matrix": {
|
||||||
|
"success": None if "matrix" not in share_destinations else True
|
||||||
|
}
|
||||||
|
}, content_type='application/json', status=200)
|
||||||
0
comfyui_manager/glob/utils/__init__.py
Normal file
0
comfyui_manager/glob/utils/__init__.py
Normal file
142
comfyui_manager/glob/utils/environment_utils.py
Normal file
142
comfyui_manager/glob/utils/environment_utils.py
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
import os
|
||||||
|
import git
|
||||||
|
import logging
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
from comfyui_manager.common import context
|
||||||
|
import folder_paths
|
||||||
|
from comfy.cli_args import args
|
||||||
|
import latent_preview
|
||||||
|
|
||||||
|
from comfyui_manager.glob import manager_core as core
|
||||||
|
from comfyui_manager.common import cm_global
|
||||||
|
|
||||||
|
|
||||||
|
comfy_ui_hash = "-"
|
||||||
|
comfyui_tag = None
|
||||||
|
|
||||||
|
|
||||||
|
def print_comfyui_version():
|
||||||
|
global comfy_ui_hash
|
||||||
|
global comfyui_tag
|
||||||
|
|
||||||
|
is_detached = False
|
||||||
|
try:
|
||||||
|
repo = git.Repo(os.path.dirname(folder_paths.__file__))
|
||||||
|
core.comfy_ui_revision = len(list(repo.iter_commits("HEAD")))
|
||||||
|
|
||||||
|
comfy_ui_hash = repo.head.commit.hexsha
|
||||||
|
cm_global.variables["comfyui.revision"] = core.comfy_ui_revision
|
||||||
|
|
||||||
|
core.comfy_ui_commit_datetime = repo.head.commit.committed_datetime
|
||||||
|
cm_global.variables["comfyui.commit_datetime"] = core.comfy_ui_commit_datetime
|
||||||
|
|
||||||
|
is_detached = repo.head.is_detached
|
||||||
|
current_branch = repo.active_branch.name
|
||||||
|
|
||||||
|
comfyui_tag = context.get_comfyui_tag()
|
||||||
|
|
||||||
|
try:
|
||||||
|
if (
|
||||||
|
not os.environ.get("__COMFYUI_DESKTOP_VERSION__")
|
||||||
|
and core.comfy_ui_commit_datetime.date()
|
||||||
|
< core.comfy_ui_required_commit_datetime.date()
|
||||||
|
):
|
||||||
|
logging.warning(
|
||||||
|
f"\n\n## [WARN] ComfyUI-Manager: Your ComfyUI version ({core.comfy_ui_revision})[{core.comfy_ui_commit_datetime.date()}] is too old. Please update to the latest version. ##\n\n"
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# process on_revision_detected -->
|
||||||
|
if "cm.on_revision_detected_handler" in cm_global.variables:
|
||||||
|
for k, f in cm_global.variables["cm.on_revision_detected_handler"]:
|
||||||
|
try:
|
||||||
|
f(core.comfy_ui_revision)
|
||||||
|
except Exception:
|
||||||
|
logging.error(f"[ERROR] '{k}' on_revision_detected_handler")
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
del cm_global.variables["cm.on_revision_detected_handler"]
|
||||||
|
else:
|
||||||
|
logging.warning(
|
||||||
|
"[ComfyUI-Manager] Some features are restricted due to your ComfyUI being outdated."
|
||||||
|
)
|
||||||
|
# <--
|
||||||
|
|
||||||
|
if current_branch == "master":
|
||||||
|
if comfyui_tag:
|
||||||
|
logging.info(
|
||||||
|
f"### ComfyUI Version: {comfyui_tag} | Released on '{core.comfy_ui_commit_datetime.date()}'"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logging.info(
|
||||||
|
f"### ComfyUI Revision: {core.comfy_ui_revision} [{comfy_ui_hash[:8]}] | Released on '{core.comfy_ui_commit_datetime.date()}'"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if comfyui_tag:
|
||||||
|
logging.info(
|
||||||
|
f"### ComfyUI Version: {comfyui_tag} on '{current_branch}' | Released on '{core.comfy_ui_commit_datetime.date()}'"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logging.info(
|
||||||
|
f"### ComfyUI Revision: {core.comfy_ui_revision} on '{current_branch}' [{comfy_ui_hash[:8]}] | Released on '{core.comfy_ui_commit_datetime.date()}'"
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
if is_detached:
|
||||||
|
logging.info(
|
||||||
|
f"### ComfyUI Revision: {core.comfy_ui_revision} [{comfy_ui_hash[:8]}] *DETACHED | Released on '{core.comfy_ui_commit_datetime.date()}'"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logging.info(
|
||||||
|
"### ComfyUI Revision: UNKNOWN (The currently installed ComfyUI is not a Git repository)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def set_preview_method(method):
|
||||||
|
if method == "auto":
|
||||||
|
args.preview_method = latent_preview.LatentPreviewMethod.Auto
|
||||||
|
elif method == "latent2rgb":
|
||||||
|
args.preview_method = latent_preview.LatentPreviewMethod.Latent2RGB
|
||||||
|
elif method == "taesd":
|
||||||
|
args.preview_method = latent_preview.LatentPreviewMethod.TAESD
|
||||||
|
else:
|
||||||
|
args.preview_method = latent_preview.LatentPreviewMethod.NoPreviews
|
||||||
|
|
||||||
|
core.get_config()["preview_method"] = method
|
||||||
|
|
||||||
|
|
||||||
|
def set_update_policy(mode):
|
||||||
|
core.get_config()["update_policy"] = mode
|
||||||
|
|
||||||
|
|
||||||
|
def set_db_mode(mode):
|
||||||
|
core.get_config()["db_mode"] = mode
|
||||||
|
|
||||||
|
|
||||||
|
def setup_environment():
|
||||||
|
git_exe = core.get_config()["git_exe"]
|
||||||
|
|
||||||
|
if git_exe != "":
|
||||||
|
git.Git().update_environment(GIT_PYTHON_GIT_EXECUTABLE=git_exe)
|
||||||
|
|
||||||
|
|
||||||
|
def initialize_environment():
|
||||||
|
context.comfy_path = os.path.dirname(folder_paths.__file__)
|
||||||
|
core.js_path = os.path.join(context.comfy_path, "web", "extensions")
|
||||||
|
|
||||||
|
# Legacy database paths - kept for potential future use
|
||||||
|
# local_db_model = os.path.join(manager_util.comfyui_manager_path, "model-list.json")
|
||||||
|
# local_db_alter = os.path.join(manager_util.comfyui_manager_path, "alter-list.json")
|
||||||
|
# local_db_custom_node_list = os.path.join(
|
||||||
|
# manager_util.comfyui_manager_path, "custom-node-list.json"
|
||||||
|
# )
|
||||||
|
# local_db_extension_node_mappings = os.path.join(
|
||||||
|
# manager_util.comfyui_manager_path, "extension-node-map.json"
|
||||||
|
# )
|
||||||
|
|
||||||
|
set_preview_method(core.get_config()["preview_method"])
|
||||||
|
print_comfyui_version()
|
||||||
|
setup_environment()
|
||||||
|
|
||||||
|
core.check_invalid_nodes()
|
||||||
60
comfyui_manager/glob/utils/formatting_utils.py
Normal file
60
comfyui_manager/glob/utils/formatting_utils.py
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import locale
|
||||||
|
import sys
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def handle_stream(stream, prefix):
|
||||||
|
stream.reconfigure(encoding=locale.getpreferredencoding(), errors="replace")
|
||||||
|
for msg in stream:
|
||||||
|
if (
|
||||||
|
prefix == "[!]"
|
||||||
|
and ("it/s]" in msg or "s/it]" in msg)
|
||||||
|
and ("%|" in msg or "it [" in msg)
|
||||||
|
):
|
||||||
|
if msg.startswith("100%"):
|
||||||
|
print("\r" + msg, end="", file=sys.stderr),
|
||||||
|
else:
|
||||||
|
print("\r" + msg[:-1], end="", file=sys.stderr),
|
||||||
|
else:
|
||||||
|
if prefix == "[!]":
|
||||||
|
print(prefix, msg, end="", file=sys.stderr)
|
||||||
|
else:
|
||||||
|
print(prefix, msg, end="")
|
||||||
|
|
||||||
|
|
||||||
|
def convert_markdown_to_html(input_text):
|
||||||
|
pattern_a = re.compile(r"\[a/([^]]+)]\(([^)]+)\)")
|
||||||
|
pattern_w = re.compile(r"\[w/([^]]+)]")
|
||||||
|
pattern_i = re.compile(r"\[i/([^]]+)]")
|
||||||
|
pattern_bold = re.compile(r"\*\*([^*]+)\*\*")
|
||||||
|
pattern_white = re.compile(r"%%([^*]+)%%")
|
||||||
|
|
||||||
|
def replace_a(match):
|
||||||
|
return f"<a href='{match.group(2)}' target='blank'>{match.group(1)}</a>"
|
||||||
|
|
||||||
|
def replace_w(match):
|
||||||
|
return f"<p class='cm-warn-note'>{match.group(1)}</p>"
|
||||||
|
|
||||||
|
def replace_i(match):
|
||||||
|
return f"<p class='cm-info-note'>{match.group(1)}</p>"
|
||||||
|
|
||||||
|
def replace_bold(match):
|
||||||
|
return f"<B>{match.group(1)}</B>"
|
||||||
|
|
||||||
|
def replace_white(match):
|
||||||
|
return f"<font color='white'>{match.group(1)}</font>"
|
||||||
|
|
||||||
|
input_text = (
|
||||||
|
input_text.replace("\\[", "[")
|
||||||
|
.replace("\\]", "]")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">")
|
||||||
|
)
|
||||||
|
|
||||||
|
result_text = re.sub(pattern_a, replace_a, input_text)
|
||||||
|
result_text = re.sub(pattern_w, replace_w, result_text)
|
||||||
|
result_text = re.sub(pattern_i, replace_i, result_text)
|
||||||
|
result_text = re.sub(pattern_bold, replace_bold, result_text)
|
||||||
|
result_text = re.sub(pattern_white, replace_white, result_text)
|
||||||
|
|
||||||
|
return result_text.replace("\n", "<BR>")
|
||||||
74
comfyui_manager/glob/utils/model_utils.py
Normal file
74
comfyui_manager/glob/utils/model_utils.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import folder_paths
|
||||||
|
|
||||||
|
from comfyui_manager.glob import manager_core as core
|
||||||
|
from comfyui_manager.glob.constants import model_dir_name_map
|
||||||
|
|
||||||
|
|
||||||
|
def get_model_dir(data, show_log=False):
|
||||||
|
if "download_model_base" in folder_paths.folder_names_and_paths:
|
||||||
|
models_base = folder_paths.folder_names_and_paths["download_model_base"][0][0]
|
||||||
|
else:
|
||||||
|
models_base = folder_paths.models_dir
|
||||||
|
|
||||||
|
# NOTE: Validate to prevent path traversal.
|
||||||
|
if any(char in data["filename"] for char in {"/", "\\", ":"}):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def resolve_custom_node(save_path):
|
||||||
|
save_path = save_path[13:] # remove 'custom_nodes/'
|
||||||
|
|
||||||
|
# NOTE: Validate to prevent path traversal.
|
||||||
|
if save_path.startswith(os.path.sep) or ":" in save_path:
|
||||||
|
return None
|
||||||
|
|
||||||
|
repo_name = save_path.replace("\\", "/").split("/")[
|
||||||
|
0
|
||||||
|
] # get custom node repo name
|
||||||
|
|
||||||
|
# NOTE: The creation of files within the custom node path should be removed in the future.
|
||||||
|
repo_path = core.lookup_installed_custom_nodes_legacy(repo_name)
|
||||||
|
if repo_path is not None and repo_path[0]:
|
||||||
|
# Returns the retargeted path based on the actually installed repository
|
||||||
|
return os.path.join(os.path.dirname(repo_path[1]), save_path)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if data["save_path"] != "default":
|
||||||
|
if ".." in data["save_path"] or data["save_path"].startswith("/"):
|
||||||
|
if show_log:
|
||||||
|
logging.info(
|
||||||
|
f"[WARN] '{data['save_path']}' is not allowed path. So it will be saved into 'models/etc'."
|
||||||
|
)
|
||||||
|
base_model = os.path.join(models_base, "etc")
|
||||||
|
else:
|
||||||
|
if data["save_path"].startswith("custom_nodes"):
|
||||||
|
base_model = resolve_custom_node(data["save_path"])
|
||||||
|
if base_model is None:
|
||||||
|
if show_log:
|
||||||
|
logging.info(
|
||||||
|
f"[ComfyUI-Manager] The target custom node for model download is not installed: {data['save_path']}"
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
base_model = os.path.join(models_base, data["save_path"])
|
||||||
|
else:
|
||||||
|
model_dir_name = model_dir_name_map.get(data["type"].lower())
|
||||||
|
if model_dir_name is not None:
|
||||||
|
base_model = folder_paths.folder_names_and_paths[model_dir_name][0][0]
|
||||||
|
else:
|
||||||
|
base_model = os.path.join(models_base, "etc")
|
||||||
|
|
||||||
|
return base_model
|
||||||
|
|
||||||
|
|
||||||
|
def get_model_path(data, show_log=False):
|
||||||
|
base_model = get_model_dir(data, show_log)
|
||||||
|
if base_model is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
if data["filename"] == "<huggingface>":
|
||||||
|
return os.path.join(base_model, os.path.basename(data["url"]))
|
||||||
|
else:
|
||||||
|
return os.path.join(base_model, data["filename"])
|
||||||
65
comfyui_manager/glob/utils/node_pack_utils.py
Normal file
65
comfyui_manager/glob/utils/node_pack_utils.py
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import concurrent.futures
|
||||||
|
|
||||||
|
from comfyui_manager.glob import manager_core as core
|
||||||
|
|
||||||
|
|
||||||
|
def check_state_of_git_node_pack(
|
||||||
|
node_packs, do_fetch=False, do_update_check=True, do_update=False
|
||||||
|
):
|
||||||
|
if do_fetch:
|
||||||
|
print("Start fetching...", end="")
|
||||||
|
elif do_update:
|
||||||
|
print("Start updating...", end="")
|
||||||
|
elif do_update_check:
|
||||||
|
print("Start update check...", end="")
|
||||||
|
|
||||||
|
def process_custom_node(item):
|
||||||
|
core.check_state_of_git_node_pack_single(
|
||||||
|
item, do_fetch, do_update_check, do_update
|
||||||
|
)
|
||||||
|
|
||||||
|
with concurrent.futures.ThreadPoolExecutor(4) as executor:
|
||||||
|
for k, v in node_packs.items():
|
||||||
|
if v.get("active_version") in ["unknown", "nightly"]:
|
||||||
|
executor.submit(process_custom_node, v)
|
||||||
|
|
||||||
|
if do_fetch:
|
||||||
|
print("\x1b[2K\rFetching done.")
|
||||||
|
elif do_update:
|
||||||
|
update_exists = any(
|
||||||
|
item.get("updatable", False) for item in node_packs.values()
|
||||||
|
)
|
||||||
|
if update_exists:
|
||||||
|
print("\x1b[2K\rUpdate done.")
|
||||||
|
else:
|
||||||
|
print("\x1b[2K\rAll extensions are already up-to-date.")
|
||||||
|
elif do_update_check:
|
||||||
|
print("\x1b[2K\rUpdate check done.")
|
||||||
|
|
||||||
|
|
||||||
|
def nickname_filter(json_obj):
|
||||||
|
preemptions_map = {}
|
||||||
|
|
||||||
|
for k, x in json_obj.items():
|
||||||
|
if "preemptions" in x[1]:
|
||||||
|
for y in x[1]["preemptions"]:
|
||||||
|
preemptions_map[y] = k
|
||||||
|
elif k.endswith("/ComfyUI"):
|
||||||
|
for y in x[0]:
|
||||||
|
preemptions_map[y] = k
|
||||||
|
|
||||||
|
updates = {}
|
||||||
|
for k, x in json_obj.items():
|
||||||
|
removes = set()
|
||||||
|
for y in x[0]:
|
||||||
|
k2 = preemptions_map.get(y)
|
||||||
|
if k2 is not None and k != k2:
|
||||||
|
removes.add(y)
|
||||||
|
|
||||||
|
if len(removes) > 0:
|
||||||
|
updates[k] = [y for y in x[0] if y not in removes]
|
||||||
|
|
||||||
|
for k, v in updates.items():
|
||||||
|
json_obj[k][0] = v
|
||||||
|
|
||||||
|
return json_obj
|
||||||
53
comfyui_manager/glob/utils/security_utils.py
Normal file
53
comfyui_manager/glob/utils/security_utils.py
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
from comfyui_manager.glob import manager_core as core
|
||||||
|
from comfy.cli_args import args
|
||||||
|
|
||||||
|
|
||||||
|
def is_loopback(address):
|
||||||
|
import ipaddress
|
||||||
|
try:
|
||||||
|
return ipaddress.ip_address(address).is_loopback
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def is_allowed_security_level(level):
|
||||||
|
is_local_mode = is_loopback(args.listen)
|
||||||
|
|
||||||
|
if level == "block":
|
||||||
|
return False
|
||||||
|
elif level == "high":
|
||||||
|
if is_local_mode:
|
||||||
|
return core.get_config()["security_level"] in ["weak", "normal-"]
|
||||||
|
else:
|
||||||
|
return core.get_config()["security_level"] == "weak"
|
||||||
|
elif level == "middle":
|
||||||
|
return core.get_config()["security_level"] in ["weak", "normal", "normal-"]
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def get_risky_level(files, pip_packages):
|
||||||
|
json_data1 = await core.get_data_by_mode("local", "custom-node-list.json")
|
||||||
|
json_data2 = await core.get_data_by_mode(
|
||||||
|
"cache",
|
||||||
|
"custom-node-list.json",
|
||||||
|
channel_url="https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main",
|
||||||
|
)
|
||||||
|
|
||||||
|
all_urls = set()
|
||||||
|
for x in json_data1["custom_nodes"] + json_data2["custom_nodes"]:
|
||||||
|
all_urls.update(x.get("files", []))
|
||||||
|
|
||||||
|
for x in files:
|
||||||
|
if x not in all_urls:
|
||||||
|
return "high"
|
||||||
|
|
||||||
|
all_pip_packages = set()
|
||||||
|
for x in json_data1["custom_nodes"] + json_data2["custom_nodes"]:
|
||||||
|
all_pip_packages.update(x.get("pip", []))
|
||||||
|
|
||||||
|
for p in pip_packages:
|
||||||
|
if p not in all_pip_packages:
|
||||||
|
return "block"
|
||||||
|
|
||||||
|
return "middle"
|
||||||
50
comfyui_manager/js/README.md
Normal file
50
comfyui_manager/js/README.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# ComfyUI-Manager: Frontend (js)
|
||||||
|
|
||||||
|
This directory contains the JavaScript frontend implementation for ComfyUI-Manager, providing the user interface components that interact with the backend API.
|
||||||
|
|
||||||
|
## Core Components
|
||||||
|
|
||||||
|
- **comfyui-manager.js**: Main entry point that initializes the manager UI and integrates with ComfyUI.
|
||||||
|
- **custom-nodes-manager.js**: Implements the UI for browsing, installing, and managing custom nodes.
|
||||||
|
- **model-manager.js**: Handles the model management interface for downloading and organizing AI models.
|
||||||
|
- **components-manager.js**: Manages reusable workflow components system.
|
||||||
|
- **snapshot.js**: Implements the snapshot system for backing up and restoring installations.
|
||||||
|
|
||||||
|
## Sharing Components
|
||||||
|
|
||||||
|
- **comfyui-share-common.js**: Base functionality for workflow sharing features.
|
||||||
|
- **comfyui-share-copus.js**: Integration with the ComfyUI Opus sharing platform.
|
||||||
|
- **comfyui-share-openart.js**: Integration with the OpenArt sharing platform.
|
||||||
|
- **comfyui-share-youml.js**: Integration with the YouML sharing platform.
|
||||||
|
|
||||||
|
## Utility Components
|
||||||
|
|
||||||
|
- **cm-api.js**: Client-side API wrapper for communication with the backend.
|
||||||
|
- **common.js**: Shared utilities and helper functions used across the frontend.
|
||||||
|
- **node_fixer.js**: Utilities for fixing disconnected links and repairing malformed nodes by recreating them while preserving connections.
|
||||||
|
- **popover-helper.js**: UI component for popup tooltips and contextual information.
|
||||||
|
- **turbogrid.esm.js**: Grid component library - https://github.com/cenfun/turbogrid
|
||||||
|
- **workflow-metadata.js**: Handles workflow metadata parsing, validation and cross-repository compatibility including versioning, dependencies tracking, and resource management.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The frontend follows a modular component-based architecture:
|
||||||
|
|
||||||
|
1. **Integration Layer**: Connects with ComfyUI's existing UI system
|
||||||
|
2. **Manager Components**: Individual functional UI components (node manager, model manager, etc.)
|
||||||
|
3. **Sharing Components**: Platform-specific sharing implementations
|
||||||
|
4. **Utility Layer**: Reusable UI components and helpers
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
- The frontend integrates directly with ComfyUI's UI system through `app.js`
|
||||||
|
- Dialog-based UI for most manager functions to avoid cluttering the main interface
|
||||||
|
- Asynchronous API calls to handle backend operations without blocking the UI
|
||||||
|
|
||||||
|
## Styling
|
||||||
|
|
||||||
|
CSS files are included for specific components:
|
||||||
|
- **custom-nodes-manager.css**: Styling for the node management UI
|
||||||
|
- **model-manager.css**: Styling for the model management UI
|
||||||
|
|
||||||
|
This frontend implementation provides a comprehensive yet user-friendly interface for managing the ComfyUI ecosystem.
|
||||||
@@ -25,7 +25,7 @@ async function tryInstallCustomNode(event) {
|
|||||||
const res = await customConfirm(msg);
|
const res = await customConfirm(msg);
|
||||||
if(res) {
|
if(res) {
|
||||||
if(event.detail.target.installed == 'Disabled') {
|
if(event.detail.target.installed == 'Disabled') {
|
||||||
const response = await api.fetchApi(`/customnode/toggle_active`, {
|
const response = await api.fetchApi(`/v2/customnode/toggle_active`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(event.detail.target)
|
body: JSON.stringify(event.detail.target)
|
||||||
@@ -35,7 +35,7 @@ async function tryInstallCustomNode(event) {
|
|||||||
await sleep(300);
|
await sleep(300);
|
||||||
app.ui.dialog.show(`Installing... '${event.detail.target.title}'`);
|
app.ui.dialog.show(`Installing... '${event.detail.target.title}'`);
|
||||||
|
|
||||||
const response = await api.fetchApi(`/customnode/install`, {
|
const response = await api.fetchApi(`/v2/customnode/install`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(event.detail.target)
|
body: JSON.stringify(event.detail.target)
|
||||||
@@ -45,9 +45,14 @@ async function tryInstallCustomNode(event) {
|
|||||||
show_message('This action is not allowed with this security level configuration.');
|
show_message('This action is not allowed with this security level configuration.');
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
else if(response.status == 400) {
|
||||||
|
let msg = await res.text();
|
||||||
|
show_message(msg);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let response = await api.fetchApi("/manager/reboot");
|
let response = await api.fetchApi("/v2/manager/reboot");
|
||||||
if(response.status == 403) {
|
if(response.status == 403) {
|
||||||
show_message('This action is not allowed with this security level configuration.');
|
show_message('This action is not allowed with this security level configuration.');
|
||||||
return false;
|
return false;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -172,7 +172,7 @@ export const shareToEsheep= () => {
|
|||||||
const nodes = app.graph._nodes
|
const nodes = app.graph._nodes
|
||||||
const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes);
|
const { potential_outputs, potential_output_nodes } = getPotentialOutputsAndOutputNodes(nodes);
|
||||||
const workflow = prompt['workflow']
|
const workflow = prompt['workflow']
|
||||||
api.fetchApi(`/manager/set_esheep_workflow_and_images`, {
|
api.fetchApi(`/v2/manager/set_esheep_workflow_and_images`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -812,7 +812,7 @@ export class ShareDialog extends ComfyDialog {
|
|||||||
// get the user's existing matrix auth and share key
|
// get the user's existing matrix auth and share key
|
||||||
ShareDialog.matrix_auth = { homeserver: "matrix.org", username: "", password: "" };
|
ShareDialog.matrix_auth = { homeserver: "matrix.org", username: "", password: "" };
|
||||||
try {
|
try {
|
||||||
api.fetchApi(`/manager/get_matrix_auth`)
|
api.fetchApi(`/v2/manager/get_matrix_auth`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
ShareDialog.matrix_auth = data;
|
ShareDialog.matrix_auth = data;
|
||||||
@@ -831,7 +831,7 @@ export class ShareDialog extends ComfyDialog {
|
|||||||
ShareDialog.cw_sharekey = "";
|
ShareDialog.cw_sharekey = "";
|
||||||
try {
|
try {
|
||||||
// console.log("Fetching comfyworkflows share key")
|
// console.log("Fetching comfyworkflows share key")
|
||||||
api.fetchApi(`/manager/get_comfyworkflows_auth`)
|
api.fetchApi(`/v2/manager/get_comfyworkflows_auth`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
ShareDialog.cw_sharekey = data.comfyworkflows_sharekey;
|
ShareDialog.cw_sharekey = data.comfyworkflows_sharekey;
|
||||||
@@ -891,7 +891,7 @@ export class ShareDialog extends ComfyDialog {
|
|||||||
// Change the text of the share button to "Sharing..." to indicate that the share process has started
|
// Change the text of the share button to "Sharing..." to indicate that the share process has started
|
||||||
this.share_button.textContent = "Sharing...";
|
this.share_button.textContent = "Sharing...";
|
||||||
|
|
||||||
const response = await api.fetchApi(`/manager/share`, {
|
const response = await api.fetchApi(`/v2/manager/share`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -67,7 +67,7 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
async readKey() {
|
async readKey() {
|
||||||
let key = ""
|
let key = ""
|
||||||
try {
|
try {
|
||||||
key = await api.fetchApi(`/manager/get_openart_auth`)
|
key = await api.fetchApi(`/v2/manager/get_openart_auth`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
return data.openart_key;
|
return data.openart_key;
|
||||||
@@ -82,7 +82,7 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async saveKey(value) {
|
async saveKey(value) {
|
||||||
await api.fetchApi(`/manager/set_openart_auth`, {
|
await api.fetchApi(`/v2/manager/set_openart_auth`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -399,7 +399,7 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
form.append("file", uploadFile);
|
form.append("file", uploadFile);
|
||||||
try {
|
try {
|
||||||
const res = await this.fetchApi(
|
const res = await this.fetchApi(
|
||||||
`/workflows/upload_thumbnail`,
|
`/v2/workflows/upload_thumbnail`,
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: form,
|
body: form,
|
||||||
@@ -459,7 +459,7 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
throw new Error("Title is required");
|
throw new Error("Title is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
const current_snapshot = await api.fetchApi(`/snapshot/get_current`)
|
const current_snapshot = await api.fetchApi(`/v2/snapshot/get_current`)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
// console.log(error);
|
// console.log(error);
|
||||||
@@ -489,7 +489,7 @@ export class OpenArtShareDialog extends ComfyDialog {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await this.fetchApi(
|
const response = await this.fetchApi(
|
||||||
"/workflows/publish",
|
"/v2/workflows/publish",
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {"Content-Type": "application/json"},
|
headers: {"Content-Type": "application/json"},
|
||||||
@@ -179,7 +179,7 @@ export class YouMLShareDialog extends ComfyDialog {
|
|||||||
async loadToken() {
|
async loadToken() {
|
||||||
let key = ""
|
let key = ""
|
||||||
try {
|
try {
|
||||||
const response = await api.fetchApi(`/manager/youml/settings`)
|
const response = await api.fetchApi(`/v2/manager/youml/settings`)
|
||||||
const settings = await response.json()
|
const settings = await response.json()
|
||||||
return settings.token
|
return settings.token
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -188,7 +188,7 @@ export class YouMLShareDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async saveToken(value) {
|
async saveToken(value) {
|
||||||
await api.fetchApi(`/manager/youml/settings`, {
|
await api.fetchApi(`/v2/manager/youml/settings`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
@@ -380,7 +380,7 @@ export class YouMLShareDialog extends ComfyDialog {
|
|||||||
try {
|
try {
|
||||||
let snapshotData = null;
|
let snapshotData = null;
|
||||||
try {
|
try {
|
||||||
const snapshot = await api.fetchApi(`/snapshot/get_current`)
|
const snapshot = await api.fetchApi(`/v2/snapshot/get_current`)
|
||||||
snapshotData = await snapshot.json()
|
snapshotData = await snapshot.json()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("Failed to get snapshot", e)
|
console.error("Failed to get snapshot", e)
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { api } from "../../scripts/api.js";
|
import { api } from "../../scripts/api.js";
|
||||||
import { $el, ComfyDialog } from "../../scripts/ui.js";
|
import { $el, ComfyDialog } from "../../scripts/ui.js";
|
||||||
|
import { getBestPosition, getPositionStyle, getRect } from './popover-helper.js';
|
||||||
|
|
||||||
|
|
||||||
function internalCustomConfirm(message, confirmMessage, cancelMessage) {
|
function internalCustomConfirm(message, confirmMessage, cancelMessage) {
|
||||||
@@ -96,7 +97,7 @@ function internalCustomConfirm(message, confirmMessage, cancelMessage) {
|
|||||||
|
|
||||||
export function show_message(msg) {
|
export function show_message(msg) {
|
||||||
app.ui.dialog.show(msg);
|
app.ui.dialog.show(msg);
|
||||||
app.ui.dialog.element.style.zIndex = 1099;
|
app.ui.dialog.element.style.zIndex = 1100;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sleep(ms) {
|
export async function sleep(ms) {
|
||||||
@@ -130,6 +131,20 @@ export function customAlert(message) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function infoToast(summary, message) {
|
||||||
|
try {
|
||||||
|
app.extensionManager.toast.add({
|
||||||
|
severity: 'info',
|
||||||
|
summary: summary,
|
||||||
|
detail: message,
|
||||||
|
life: 3000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function customPrompt(title, message) {
|
export async function customPrompt(title, message) {
|
||||||
try {
|
try {
|
||||||
@@ -157,7 +172,7 @@ export function rebootAPI() {
|
|||||||
customConfirm("Are you sure you'd like to reboot the server?").then((isConfirmed) => {
|
customConfirm("Are you sure you'd like to reboot the server?").then((isConfirmed) => {
|
||||||
if (isConfirmed) {
|
if (isConfirmed) {
|
||||||
try {
|
try {
|
||||||
api.fetchApi("/manager/reboot");
|
api.fetchApi("/v2/manager/reboot");
|
||||||
}
|
}
|
||||||
catch(exception) {}
|
catch(exception) {}
|
||||||
}
|
}
|
||||||
@@ -167,23 +182,6 @@ export function rebootAPI() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export async function migrateAPI() {
|
|
||||||
let confirmed = await customConfirm("When performing a migration, existing installed custom nodes will be renamed and the server will be restarted. Are you sure you want to apply this?\n\n(If you don't perform the migration, ComfyUI-Manager's start-up time will be longer each time due to re-checking during startup.)")
|
|
||||||
if (confirmed) {
|
|
||||||
try {
|
|
||||||
await api.fetchApi("/manager/migrate_unmanaged_nodes");
|
|
||||||
api.fetchApi("/manager/reboot");
|
|
||||||
}
|
|
||||||
catch(exception) {
|
|
||||||
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export var manager_instance = null;
|
export var manager_instance = null;
|
||||||
|
|
||||||
export function setManagerInstance(obj) {
|
export function setManagerInstance(obj) {
|
||||||
@@ -212,7 +210,7 @@ export async function install_pip(packages) {
|
|||||||
if(packages.includes('&'))
|
if(packages.includes('&'))
|
||||||
app.ui.dialog.show(`Invalid PIP package enumeration: '${packages}'`);
|
app.ui.dialog.show(`Invalid PIP package enumeration: '${packages}'`);
|
||||||
|
|
||||||
const res = await api.fetchApi("/customnode/install/pip", {
|
const res = await api.fetchApi("/v2/customnode/install/pip", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: packages,
|
body: packages,
|
||||||
});
|
});
|
||||||
@@ -247,7 +245,7 @@ export async function install_via_git_url(url, manager_dialog) {
|
|||||||
|
|
||||||
show_message(`Wait...<BR><BR>Installing '${url}'`);
|
show_message(`Wait...<BR><BR>Installing '${url}'`);
|
||||||
|
|
||||||
const res = await api.fetchApi("/customnode/install/git_url", {
|
const res = await api.fetchApi("/v2/customnode/install/git_url", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: url,
|
body: url,
|
||||||
});
|
});
|
||||||
@@ -390,10 +388,275 @@ export async function fetchData(route, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://cenfun.github.io/open-icons/
|
||||||
export const icons = {
|
export const icons = {
|
||||||
search: '<svg viewBox="0 0 24 24" width="100%" height="100%" pointer-events="none" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m21 21-4.486-4.494M19 10.5a8.5 8.5 0 1 1-17 0 8.5 8.5 0 0 1 17 0"/></svg>',
|
search: '<svg viewBox="0 0 24 24" width="100%" height="100%" pointer-events="none" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m21 21-4.486-4.494M19 10.5a8.5 8.5 0 1 1-17 0 8.5 8.5 0 0 1 17 0"/></svg>',
|
||||||
extensions: '<svg viewBox="64 64 896 896" width="100%" height="100%" pointer-events="none" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="M843.5 737.4c-12.4-75.2-79.2-129.1-155.3-125.4S550.9 676 546 752c-153.5-4.8-208-40.7-199.1-113.7 3.3-27.3 19.8-41.9 50.1-49 18.4-4.3 38.8-4.9 57.3-3.2 1.7.2 3.5.3 5.2.5 11.3 2.7 22.8 5 34.3 6.8 34.1 5.6 68.8 8.4 101.8 6.6 92.8-5 156-45.9 159.2-132.7 3.1-84.1-54.7-143.7-147.9-183.6-29.9-12.8-61.6-22.7-93.3-30.2-14.3-3.4-26.3-5.7-35.2-7.2-7.9-75.9-71.5-133.8-147.8-134.4S189.7 168 180.5 243.8s40 146.3 114.2 163.9 149.9-23.3 175.7-95.1c9.4 1.7 18.7 3.6 28 5.8 28.2 6.6 56.4 15.4 82.4 26.6 70.7 30.2 109.3 70.1 107.5 119.9-1.6 44.6-33.6 65.2-96.2 68.6-27.5 1.5-57.6-.9-87.3-5.8-8.3-1.4-15.9-2.8-22.6-4.3-3.9-.8-6.6-1.5-7.8-1.8l-3.1-.6c-2.2-.3-5.9-.8-10.7-1.3-25-2.3-52.1-1.5-78.5 4.6-55.2 12.9-93.9 47.2-101.1 105.8-15.7 126.2 78.6 184.7 276 188.9 29.1 70.4 106.4 107.9 179.6 87 73.3-20.9 119.3-93.4 106.9-168.6M329.1 345.2a83.3 83.3 0 1 1 .01-166.61 83.3 83.3 0 0 1-.01 166.61M695.6 845a83.3 83.3 0 1 1 .01-166.61A83.3 83.3 0 0 1 695.6 845"/></svg>',
|
|
||||||
conflicts: '<svg viewBox="0 0 400 400" width="100%" height="100%" pointer-events="none" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="m397.2 350.4.2-.2-180-320-.2.2C213.8 24.2 207.4 20 200 20s-13.8 4.2-17.2 10.4l-.2-.2-180 320 .2.2c-1.6 2.8-2.8 6-2.8 9.6 0 11 9 20 20 20h360c11 0 20-9 20-20 0-3.6-1.2-6.8-2.8-9.6M220 340h-40v-40h40zm0-60h-40V120h40z"/></svg>',
|
conflicts: '<svg viewBox="0 0 400 400" width="100%" height="100%" pointer-events="none" xmlns="http://www.w3.org/2000/svg"><path fill="currentColor" d="m397.2 350.4.2-.2-180-320-.2.2C213.8 24.2 207.4 20 200 20s-13.8 4.2-17.2 10.4l-.2-.2-180 320 .2.2c-1.6 2.8-2.8 6-2.8 9.6 0 11 9 20 20 20h360c11 0 20-9 20-20 0-3.6-1.2-6.8-2.8-9.6M220 340h-40v-40h40zm0-60h-40V120h40z"/></svg>',
|
||||||
passed: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 426.667 426.667"><path fill="#6AC259" d="M213.333,0C95.518,0,0,95.514,0,213.333s95.518,213.333,213.333,213.333c117.828,0,213.333-95.514,213.333-213.333S331.157,0,213.333,0z M174.199,322.918l-93.935-93.931l31.309-31.309l62.626,62.622l140.894-140.898l31.309,31.309L174.199,322.918z"/></svg>',
|
passed: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 426.667 426.667"><path fill="#6AC259" d="M213.333,0C95.518,0,0,95.514,0,213.333s95.518,213.333,213.333,213.333c117.828,0,213.333-95.514,213.333-213.333S331.157,0,213.333,0z M174.199,322.918l-93.935-93.931l31.309-31.309l62.626,62.622l140.894-140.898l31.309,31.309L174.199,322.918z"/></svg>',
|
||||||
download: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" width="100%" height="100%" viewBox="0 0 32 32"><path fill="currentColor" d="M26 24v4H6v-4H4v4a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-4zm0-10l-1.41-1.41L17 20.17V2h-2v18.17l-7.59-7.58L6 14l10 10l10-10z"></path></svg>'
|
download: '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" width="100%" height="100%" viewBox="0 0 32 32"><path fill="currentColor" d="M26 24v4H6v-4H4v4a2 2 0 0 0 2 2h20a2 2 0 0 0 2-2v-4zm0-10l-1.41-1.41L17 20.17V2h-2v18.17l-7.59-7.58L6 14l10 10l10-10z"></path></svg>',
|
||||||
|
close: '<svg xmlns="http://www.w3.org/2000/svg" pointer-events="none" width="100%" height="100%" viewBox="0 0 16 16"><g fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="m7.116 8-4.558 4.558.884.884L8 8.884l4.558 4.558.884-.884L8.884 8l4.558-4.558-.884-.884L8 7.116 3.442 2.558l-.884.884L7.116 8z"/></g></svg>',
|
||||||
|
arrowRight: '<svg xmlns="http://www.w3.org/2000/svg" pointer-events="none" width="100%" height="100%" viewBox="0 0 20 20"><path fill="currentColor" fill-rule="evenodd" d="m2.542 2.154 7.254 7.26c.136.14.204.302.204.483a.73.73 0 0 1-.204.5l-7.575 7.398c-.383.317-.724.317-1.022 0-.299-.317-.299-.643 0-.98l7.08-6.918-6.754-6.763c-.237-.343-.215-.654.066-.935.281-.28.598-.295.951-.045Zm9 0 7.254 7.26c.136.14.204.302.204.483a.73.73 0 0 1-.204.5l-7.575 7.398c-.383.317-.724.317-1.022 0-.299-.317-.299-.643 0-.98l7.08-6.918-6.754-6.763c-.237-.343-.215-.654.066-.935.281-.28.598-.295.951-.045Z"/></svg>'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function sanitizeHTML(str) {
|
||||||
|
return str
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """)
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function showTerminal() {
|
||||||
|
try {
|
||||||
|
const panel = app.extensionManager.bottomPanel;
|
||||||
|
const isTerminalVisible = panel.bottomPanelVisible && panel.activeBottomPanelTab.id === 'logs-terminal';
|
||||||
|
if (!isTerminalVisible)
|
||||||
|
panel.toggleBottomPanelTab('logs-terminal');
|
||||||
|
}
|
||||||
|
catch(exception) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let need_restart = false;
|
||||||
|
|
||||||
|
export function setNeedRestart(value) {
|
||||||
|
need_restart = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onReconnected(event) {
|
||||||
|
if(need_restart) {
|
||||||
|
setNeedRestart(false);
|
||||||
|
|
||||||
|
const confirmed = await customConfirm("To apply the changes to the node pack's installation status, you need to refresh the browser. Would you like to refresh?");
|
||||||
|
if (!confirmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.reload(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.addEventListener('reconnected', onReconnected);
|
||||||
|
|
||||||
|
const storeId = "comfyui-manager-grid";
|
||||||
|
let timeId;
|
||||||
|
export function storeColumnWidth(gridId, columnItem) {
|
||||||
|
clearTimeout(timeId);
|
||||||
|
timeId = setTimeout(() => {
|
||||||
|
let data = {};
|
||||||
|
const dataStr = localStorage.getItem(storeId);
|
||||||
|
if (dataStr) {
|
||||||
|
try {
|
||||||
|
data = JSON.parse(dataStr);
|
||||||
|
} catch (e) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data[gridId]) {
|
||||||
|
data[gridId] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
data[gridId][columnItem.id] = columnItem.width;
|
||||||
|
|
||||||
|
localStorage.setItem(storeId, JSON.stringify(data));
|
||||||
|
|
||||||
|
}, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function restoreColumnWidth(gridId, columns) {
|
||||||
|
const dataStr = localStorage.getItem(storeId);
|
||||||
|
if (!dataStr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let data;
|
||||||
|
try {
|
||||||
|
data = JSON.parse(dataStr);
|
||||||
|
} catch (e) {}
|
||||||
|
if(!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const widthMap = data[gridId];
|
||||||
|
if (!widthMap) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
columns.forEach(columnItem => {
|
||||||
|
const w = widthMap[columnItem.id];
|
||||||
|
if (w) {
|
||||||
|
columnItem.width = w;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTimeAgo(dateStr) {
|
||||||
|
const date = new Date(dateStr);
|
||||||
|
|
||||||
|
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const units = [
|
||||||
|
{ max: 2760000, value: 60000, name: 'minute', past: 'a minute ago', future: 'in a minute' },
|
||||||
|
{ max: 72000000, value: 3600000, name: 'hour', past: 'an hour ago', future: 'in an hour' },
|
||||||
|
{ max: 518400000, value: 86400000, name: 'day', past: 'yesterday', future: 'tomorrow' },
|
||||||
|
{ max: 2419200000, value: 604800000, name: 'week', past: 'last week', future: 'in a week' },
|
||||||
|
{ max: 28512000000, value: 2592000000, name: 'month', past: 'last month', future: 'in a month' }
|
||||||
|
];
|
||||||
|
const diff = Date.now() - date.getTime();
|
||||||
|
// less than a minute
|
||||||
|
if (Math.abs(diff) < 60000)
|
||||||
|
return 'just now';
|
||||||
|
for (let i = 0; i < units.length; i++) {
|
||||||
|
if (Math.abs(diff) < units[i].max) {
|
||||||
|
return format(diff, units[i].value, units[i].name, units[i].past, units[i].future, diff < 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function format(diff, divisor, unit, past, future, isInTheFuture) {
|
||||||
|
const val = Math.round(Math.abs(diff) / divisor);
|
||||||
|
if (isInTheFuture)
|
||||||
|
return val <= 1 ? future : 'in ' + val + ' ' + unit + 's';
|
||||||
|
return val <= 1 ? past : val + ' ' + unit + 's ago';
|
||||||
|
}
|
||||||
|
return format(diff, 31536000000, 'year', 'last year', 'in a year', diff < 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadCss = (cssFile) => {
|
||||||
|
const cssPath = import.meta.resolve(cssFile);
|
||||||
|
//console.log(cssPath);
|
||||||
|
const $link = document.createElement("link");
|
||||||
|
$link.setAttribute("rel", 'stylesheet');
|
||||||
|
$link.setAttribute("href", cssPath);
|
||||||
|
document.head.appendChild($link);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const copyText = (text) => {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let err;
|
||||||
|
try {
|
||||||
|
navigator.clipboard.writeText(text);
|
||||||
|
} catch (e) {
|
||||||
|
err = e;
|
||||||
|
}
|
||||||
|
if (err) {
|
||||||
|
resolve(false);
|
||||||
|
} else {
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
function renderPopover($elem, target, options = {}) {
|
||||||
|
// async microtask
|
||||||
|
queueMicrotask(() => {
|
||||||
|
|
||||||
|
const containerRect = getRect(window);
|
||||||
|
const targetRect = getRect(target);
|
||||||
|
const elemRect = getRect($elem);
|
||||||
|
|
||||||
|
const positionInfo = getBestPosition(
|
||||||
|
containerRect,
|
||||||
|
targetRect,
|
||||||
|
elemRect,
|
||||||
|
options.positions
|
||||||
|
);
|
||||||
|
const style = getPositionStyle(positionInfo, {
|
||||||
|
bgColor: options.bgColor,
|
||||||
|
borderColor: options.borderColor,
|
||||||
|
borderRadius: options.borderRadius
|
||||||
|
});
|
||||||
|
|
||||||
|
$elem.style.top = positionInfo.top + "px";
|
||||||
|
$elem.style.left = positionInfo.left + "px";
|
||||||
|
$elem.style.background = style.background;
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let $popover;
|
||||||
|
export function hidePopover() {
|
||||||
|
if ($popover) {
|
||||||
|
$popover.remove();
|
||||||
|
$popover = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function showPopover(target, text, className, options) {
|
||||||
|
hidePopover();
|
||||||
|
$popover = document.createElement("div");
|
||||||
|
$popover.className = ['cn-popover', className].filter(it => it).join(" ");
|
||||||
|
document.body.appendChild($popover);
|
||||||
|
$popover.innerHTML = text;
|
||||||
|
$popover.style.display = "block";
|
||||||
|
renderPopover($popover, target, {
|
||||||
|
borderRadius: 10,
|
||||||
|
... options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let $tooltip;
|
||||||
|
export function hideTooltip(target) {
|
||||||
|
if ($tooltip) {
|
||||||
|
$tooltip.style.display = "none";
|
||||||
|
$tooltip.innerHTML = "";
|
||||||
|
$tooltip.style.top = "0px";
|
||||||
|
$tooltip.style.left = "0px";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function showTooltip(target, text, className = 'cn-tooltip', styleMap = {}) {
|
||||||
|
if (!$tooltip) {
|
||||||
|
$tooltip = document.createElement("div");
|
||||||
|
$tooltip.className = className;
|
||||||
|
$tooltip.style.cssText = `
|
||||||
|
pointer-events: none;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 10001;
|
||||||
|
padding: 20px;
|
||||||
|
color: #1e1e1e;
|
||||||
|
max-width: 350px;
|
||||||
|
filter: drop-shadow(1px 5px 5px rgb(0 0 0 / 30%));
|
||||||
|
${Object.keys(styleMap).map(k=>k+":"+styleMap[k]+";").join("")}
|
||||||
|
`;
|
||||||
|
document.body.appendChild($tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
$tooltip.innerHTML = text;
|
||||||
|
$tooltip.style.display = "block";
|
||||||
|
renderPopover($tooltip, target, {
|
||||||
|
positions: ['top', 'bottom', 'right', 'center'],
|
||||||
|
bgColor: "#ffffff",
|
||||||
|
borderColor: "#cccccc",
|
||||||
|
borderRadius: 5
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateUUID() {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||||
|
const r = Math.random() * 16 | 0;
|
||||||
|
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initTooltip () {
|
||||||
|
const mouseenterHandler = (e) => {
|
||||||
|
const target = e.target;
|
||||||
|
const text = target.getAttribute('tooltip');
|
||||||
|
if (text) {
|
||||||
|
showTooltip(target, text);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const mouseleaveHandler = (e) => {
|
||||||
|
const target = e.target;
|
||||||
|
const text = target.getAttribute('tooltip');
|
||||||
|
if (text) {
|
||||||
|
hideTooltip(target);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
document.body.removeEventListener('mouseenter', mouseenterHandler, true);
|
||||||
|
document.body.removeEventListener('mouseleave', mouseleaveHandler, true);
|
||||||
|
document.body.addEventListener('mouseenter', mouseenterHandler, true);
|
||||||
|
document.body.addEventListener('mouseleave', mouseleaveHandler, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
initTooltip();
|
||||||
@@ -64,7 +64,7 @@ function storeGroupNode(name, data, register=true) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function load_components() {
|
export async function load_components() {
|
||||||
let data = await api.fetchApi('/manager/component/loads', {method: "POST"});
|
let data = await api.fetchApi('/v2/manager/component/loads', {method: "POST"});
|
||||||
let components = await data.json();
|
let components = await data.json();
|
||||||
|
|
||||||
let start_time = Date.now();
|
let start_time = Date.now();
|
||||||
@@ -222,7 +222,7 @@ async function save_as_component(node, version, author, prefix, nodename, packna
|
|||||||
pack_map[packname] = component_name;
|
pack_map[packname] = component_name;
|
||||||
rpack_map[component_name] = subgraph;
|
rpack_map[component_name] = subgraph;
|
||||||
|
|
||||||
const res = await api.fetchApi('/manager/component/save', {
|
const res = await api.fetchApi('/v2/manager/component/save', {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -259,7 +259,7 @@ async function import_component(component_name, component, mode) {
|
|||||||
workflow: component
|
workflow: component
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await api.fetchApi('/manager/component/save', {
|
const res = await api.fetchApi('/v2/manager/component/save', {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json", },
|
headers: { "Content-Type": "application/json", },
|
||||||
body: JSON.stringify(body)
|
body: JSON.stringify(body)
|
||||||
@@ -709,7 +709,7 @@ app.handleFile = handleFile;
|
|||||||
|
|
||||||
let current_component_policy = 'workflow';
|
let current_component_policy = 'workflow';
|
||||||
try {
|
try {
|
||||||
api.fetchApi('/manager/component/policy')
|
api.fetchApi('/v2/manager/policy/component')
|
||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
.then(data => { current_component_policy = data; });
|
.then(data => { current_component_policy = data; });
|
||||||
}
|
}
|
||||||
699
comfyui_manager/js/custom-nodes-manager.css
Normal file
699
comfyui_manager/js/custom-nodes-manager.css
Normal file
@@ -0,0 +1,699 @@
|
|||||||
|
.cn-manager {
|
||||||
|
--grid-font: -apple-system, BlinkMacSystemFont, "Segue UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||||
|
z-index: 1099;
|
||||||
|
width: 80%;
|
||||||
|
height: 80%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
color: var(--fg-color);
|
||||||
|
font-family: arial, sans-serif;
|
||||||
|
text-underline-offset: 3px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-flex-auto {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager button {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--input-text);
|
||||||
|
background-color: var(--comfy-input-bg);
|
||||||
|
border-radius: 8px;
|
||||||
|
border-color: var(--border-color);
|
||||||
|
border-style: solid;
|
||||||
|
margin: 0;
|
||||||
|
padding: 4px 8px;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager button:disabled,
|
||||||
|
.cn-manager input:disabled,
|
||||||
|
.cn-manager select:disabled {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager button:disabled {
|
||||||
|
background-color: var(--comfy-input-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-manager-restart {
|
||||||
|
display: none;
|
||||||
|
background-color: #500000;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-manager-stop {
|
||||||
|
display: none;
|
||||||
|
background-color: #500000;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-manager-back {
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.arrow-icon {
|
||||||
|
height: 1em;
|
||||||
|
width: 1em;
|
||||||
|
margin-right: 5px;
|
||||||
|
transform: translateY(2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-icon {
|
||||||
|
display: block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-icon svg {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-header {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-header label {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-filter {
|
||||||
|
height: 28px;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-keywords {
|
||||||
|
height: 28px;
|
||||||
|
line-height: 28px;
|
||||||
|
padding: 0 5px 0 26px;
|
||||||
|
background-size: 16px;
|
||||||
|
background-position: 5px center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20pointer-events%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23888%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m21%2021-4.486-4.494M19%2010.5a8.5%208.5%200%201%201-17%200%208.5%208.5%200%200%201%2017%200%22%2F%3E%3C%2Fsvg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-status {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-grid {
|
||||||
|
flex: auto;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-selection {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-message {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-footer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-grid .tg-turbogrid {
|
||||||
|
font-family: var(--grid-font);
|
||||||
|
font-size: 15px;
|
||||||
|
background: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-grid .tg-turbogrid .tg-highlight::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: #80bdff11;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-grid .cn-pack-name a {
|
||||||
|
color: skyblue;
|
||||||
|
text-decoration: none;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-grid .cn-pack-desc a {
|
||||||
|
color: #5555FF;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-grid .tg-cell a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-grid .cn-pack-version {
|
||||||
|
line-height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-grid .cn-pack-nodes {
|
||||||
|
line-height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-grid .cn-pack-nodes:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-grid .cn-pack-conflicts {
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-popover {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 10000;
|
||||||
|
padding: 20px;
|
||||||
|
color: #1e1e1e;
|
||||||
|
filter: drop-shadow(1px 5px 5px rgb(0 0 0 / 30%));
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-flyover {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 1000;
|
||||||
|
display: none;
|
||||||
|
width: 50%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: var(--comfy-menu-bg);
|
||||||
|
animation-duration: 0.2s;
|
||||||
|
animation-fill-mode: both;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-flyover::before {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
content: "";
|
||||||
|
z-index: 10;
|
||||||
|
display: block;
|
||||||
|
width: 10px;
|
||||||
|
height: 100%;
|
||||||
|
pointer-events: none;
|
||||||
|
left: -10px;
|
||||||
|
background-image: linear-gradient(to left, rgb(0 0 0 / 20%), rgb(0 0 0 / 0%));
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-flyover-header {
|
||||||
|
height: 45px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-flyover-close {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 10px;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.8;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-flyover-close:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-flyover-close svg {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-flyover-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-weight: bold;
|
||||||
|
gap: 10px;
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-flyover-body {
|
||||||
|
height: calc(100% - 45px);
|
||||||
|
overflow-y: auto;
|
||||||
|
position: relative;
|
||||||
|
background-color: var(--comfy-menu-secondary-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cn-slide-in-right {
|
||||||
|
from {
|
||||||
|
visibility: visible;
|
||||||
|
transform: translate3d(100%, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-slide-in-right {
|
||||||
|
animation-name: cn-slide-in-right;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cn-slide-out-right {
|
||||||
|
from {
|
||||||
|
transform: translate3d(0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translate3d(100%, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-slide-out-right {
|
||||||
|
animation-name: cn-slide-out-right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-nodes-list {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-nodes-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-nodes-row:nth-child(odd) {
|
||||||
|
background-color: rgb(0 0 0 / 5%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-nodes-row:hover {
|
||||||
|
background-color: rgb(0 0 0 / 10%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-nodes-sn {
|
||||||
|
text-align: right;
|
||||||
|
min-width: 35px;
|
||||||
|
color: var(--drag-text);
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 8px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-nodes-name {
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
padding: 8px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-nodes-name::after {
|
||||||
|
content: attr(action);
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
top: 50%;
|
||||||
|
left: 100%;
|
||||||
|
transform: translate(5px, -50%);
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--drag-text);
|
||||||
|
background-color: var(--comfy-input-bg);
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
padding: 3px 8px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-nodes-name.action::after {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-nodes-name:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-nodes-conflict .cn-nodes-name,
|
||||||
|
.cn-nodes-conflict .cn-icon {
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-conflicts-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-conflicts-list b {
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--descrip-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-nodes-pack {
|
||||||
|
cursor: pointer;
|
||||||
|
color: skyblue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-nodes-pack:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-pack-badge {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: normal;
|
||||||
|
background-color: var(--comfy-input-bg);
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
padding: 3px 8px;
|
||||||
|
color: var(--error-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview {
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 500px;
|
||||||
|
min-height: 120px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 12px;
|
||||||
|
pointer-events: none;
|
||||||
|
padding: 12px;
|
||||||
|
color: var(--fg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-header {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid var(--comfy-input-bg);
|
||||||
|
padding: 5px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: grey;
|
||||||
|
position: relative;
|
||||||
|
filter: drop-shadow(1px 2px 3px rgb(0 0 0 / 30%));
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-dot.cn-preview-optional::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background-color: var(--comfy-input-bg);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 3px;
|
||||||
|
height: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-dot.cn-preview-grid {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-dot.cn-preview-grid::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
border-left: 1px solid var(--comfy-input-bg);
|
||||||
|
border-right: 1px solid var(--comfy-input-bg);
|
||||||
|
width: 4px;
|
||||||
|
height: 100%;
|
||||||
|
left: 2px;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-dot.cn-preview-grid::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
border-top: 1px solid var(--comfy-input-bg);
|
||||||
|
border-bottom: 1px solid var(--comfy-input-bg);
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
left: 0;
|
||||||
|
top: 2px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-name {
|
||||||
|
flex: auto;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-io {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-column > div {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
height: 18px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-input {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-output {
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
padding: 0 10px 10px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-switch {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: var(--bg-color);
|
||||||
|
border: 2px solid var(--border-color);
|
||||||
|
border-radius: 10px;
|
||||||
|
text-wrap: nowrap;
|
||||||
|
padding: 2px 20px;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-switch::before,
|
||||||
|
.cn-preview-switch::after {
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(0, -50%);
|
||||||
|
color: var(--fg-color);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-switch::before {
|
||||||
|
content: "◀";
|
||||||
|
left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-switch::after {
|
||||||
|
content: "▶";
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-value {
|
||||||
|
color: var(--descrip-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-string {
|
||||||
|
min-height: 30px;
|
||||||
|
max-height: 300px;
|
||||||
|
background: var(--bg-color);
|
||||||
|
color: var(--descrip-text);
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 3px 5px;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-preview-description {
|
||||||
|
margin: 0px 10px 10px 10px;
|
||||||
|
padding: 6px;
|
||||||
|
background: var(--border-color);
|
||||||
|
color: var(--descrip-text);
|
||||||
|
border-radius: 5px;
|
||||||
|
font-style: italic;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-tag-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-tag-list > div {
|
||||||
|
background-color: var(--border-color);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-install-buttons {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 3px;
|
||||||
|
padding: 3px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-selected-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
align-items: center;
|
||||||
|
padding-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-btn-enable {
|
||||||
|
background-color: #333399;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-btn-disable {
|
||||||
|
background-color: #442277;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-btn-update {
|
||||||
|
background-color: #1155AA;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-btn-try-update {
|
||||||
|
background-color: Gray;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-btn-try-fix {
|
||||||
|
background-color: #6495ED;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-btn-import-failed {
|
||||||
|
background-color: #AA1111;
|
||||||
|
font-size: 10px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-btn-install {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-btn-try-install {
|
||||||
|
background-color: Gray;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-btn-uninstall {
|
||||||
|
background-color: #993333;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-btn-reinstall {
|
||||||
|
background-color: #993333;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-btn-switch {
|
||||||
|
background-color: #448833;
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cn-btn-loading-bg {
|
||||||
|
0% {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: -105px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager button.cn-btn-loading {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-color: rgb(0 119 207 / 80%);
|
||||||
|
background-color: var(--comfy-input-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager button.cn-btn-loading::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
content: "";
|
||||||
|
width: 500px;
|
||||||
|
height: 100%;
|
||||||
|
background-image: repeating-linear-gradient(
|
||||||
|
-45deg,
|
||||||
|
rgb(0 119 207 / 30%),
|
||||||
|
rgb(0 119 207 / 30%) 10px,
|
||||||
|
transparent 10px,
|
||||||
|
transparent 15px
|
||||||
|
);
|
||||||
|
animation: cn-btn-loading-bg 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-light .cn-pack-name a {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-light .cm-warn-note {
|
||||||
|
background-color: #ccc !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-light .cn-btn-install {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
213
comfyui_manager/js/model-manager.css
Normal file
213
comfyui_manager/js/model-manager.css
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
.cmm-manager {
|
||||||
|
--grid-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||||
|
z-index: 1099;
|
||||||
|
width: 80%;
|
||||||
|
height: 80%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
color: var(--fg-color);
|
||||||
|
font-family: arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager .cmm-flex-auto {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager button {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--input-text);
|
||||||
|
background-color: var(--comfy-input-bg);
|
||||||
|
border-radius: 8px;
|
||||||
|
border-color: var(--border-color);
|
||||||
|
border-style: solid;
|
||||||
|
margin: 0;
|
||||||
|
padding: 4px 8px;
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager button:disabled,
|
||||||
|
.cmm-manager input:disabled,
|
||||||
|
.cmm-manager select:disabled {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager button:disabled {
|
||||||
|
background-color: var(--comfy-input-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager .cmm-manager-refresh {
|
||||||
|
display: none;
|
||||||
|
background-color: #000080;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager .cmm-manager-stop {
|
||||||
|
display: none;
|
||||||
|
background-color: #500000;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-header {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-header label {
|
||||||
|
display: flex;
|
||||||
|
gap: 5px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-type,
|
||||||
|
.cmm-manager-base,
|
||||||
|
.cmm-manager-filter {
|
||||||
|
height: 28px;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-keywords {
|
||||||
|
height: 28px;
|
||||||
|
line-height: 28px;
|
||||||
|
padding: 0 5px 0 26px;
|
||||||
|
background-size: 16px;
|
||||||
|
background-position: 5px center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20pointer-events%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23888%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m21%2021-4.486-4.494M19%2010.5a8.5%208.5%200%201%201-17%200%208.5%208.5%200%200%201%2017%200%22%2F%3E%3C%2Fsvg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-status {
|
||||||
|
padding-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-grid {
|
||||||
|
flex: auto;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-selection {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-footer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-grid .tg-turbogrid {
|
||||||
|
font-family: var(--grid-font);
|
||||||
|
font-size: 15px;
|
||||||
|
background: var(--bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-grid .cmm-node-name a {
|
||||||
|
color: skyblue;
|
||||||
|
text-decoration: none;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-grid .cmm-node-desc a {
|
||||||
|
color: #5555FF;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-grid .tg-cell a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-icon-passed {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
position: absolute;
|
||||||
|
left: calc(50% - 10px);
|
||||||
|
top: calc(50% - 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager .cmm-btn-enable {
|
||||||
|
background-color: blue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager .cmm-btn-disable {
|
||||||
|
background-color: MediumSlateBlue;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager .cmm-btn-install {
|
||||||
|
background-color: black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-btn-download {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
position: absolute;
|
||||||
|
left: calc(50% - 10px);
|
||||||
|
top: calc(50% - 10px);
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.8;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-btn-download:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-light .cmm-btn-download {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes cmm-btn-loading-bg {
|
||||||
|
0% {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
left: -105px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager button.cmm-btn-loading {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-color: rgb(0 119 207 / 80%);
|
||||||
|
background-color: var(--comfy-input-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager button.cmm-btn-loading::after {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
content: "";
|
||||||
|
width: 500px;
|
||||||
|
height: 100%;
|
||||||
|
background-image: repeating-linear-gradient(
|
||||||
|
-45deg,
|
||||||
|
rgb(0 119 207 / 30%),
|
||||||
|
rgb(0 119 207 / 30%) 10px,
|
||||||
|
transparent 10px,
|
||||||
|
transparent 15px
|
||||||
|
);
|
||||||
|
animation: cmm-btn-loading-bg 2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-light .cmm-node-name a {
|
||||||
|
color: blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-light .cm-warn-note {
|
||||||
|
background-color: #ccc !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-light .cmm-btn-install {
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
@@ -1,220 +1,18 @@
|
|||||||
|
import { app } from "../../scripts/app.js";
|
||||||
import { $el } from "../../scripts/ui.js";
|
import { $el } from "../../scripts/ui.js";
|
||||||
import {
|
import {
|
||||||
manager_instance, rebootAPI,
|
manager_instance, rebootAPI,
|
||||||
fetchData, md5, icons
|
fetchData, md5, icons, show_message, customAlert, infoToast, showTerminal,
|
||||||
|
storeColumnWidth, restoreColumnWidth, loadCss, generateUUID
|
||||||
} from "./common.js";
|
} from "./common.js";
|
||||||
|
import { api } from "../../scripts/api.js";
|
||||||
|
|
||||||
// https://cenfun.github.io/turbogrid/api.html
|
// https://cenfun.github.io/turbogrid/api.html
|
||||||
import TG from "./turbogrid.esm.js";
|
import TG from "./turbogrid.esm.js";
|
||||||
|
|
||||||
const pageCss = `
|
loadCss("./model-manager.css");
|
||||||
.cmm-manager {
|
|
||||||
--grid-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
|
||||||
z-index: 1099;
|
|
||||||
width: 80%;
|
|
||||||
height: 80%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 10px;
|
|
||||||
color: var(--fg-color);
|
|
||||||
font-family: arial, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager .cmm-flex-auto {
|
const gridId = "model";
|
||||||
flex: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager button {
|
|
||||||
font-size: 16px;
|
|
||||||
color: var(--input-text);
|
|
||||||
background-color: var(--comfy-input-bg);
|
|
||||||
border-radius: 8px;
|
|
||||||
border-color: var(--border-color);
|
|
||||||
border-style: solid;
|
|
||||||
margin: 0;
|
|
||||||
padding: 4px 8px;
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager button:disabled,
|
|
||||||
.cmm-manager input:disabled,
|
|
||||||
.cmm-manager select:disabled {
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager button:disabled {
|
|
||||||
background-color: var(--comfy-input-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-header {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 5px;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-header label {
|
|
||||||
display: flex;
|
|
||||||
gap: 5px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-type,
|
|
||||||
.cmm-manager-base,
|
|
||||||
.cmm-manager-filter {
|
|
||||||
height: 28px;
|
|
||||||
line-height: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-keywords {
|
|
||||||
height: 28px;
|
|
||||||
line-height: 28px;
|
|
||||||
padding: 0 5px 0 26px;
|
|
||||||
background-size: 16px;
|
|
||||||
background-position: 5px center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-image: url("data:image/svg+xml;charset=utf8,${encodeURIComponent(icons.search.replace("currentColor", "#888"))}");
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-status {
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-grid {
|
|
||||||
flex: auto;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-selection {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-message {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-footer {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-grid .tg-turbogrid {
|
|
||||||
font-family: var(--grid-font);
|
|
||||||
font-size: 15px;
|
|
||||||
background: var(--bg-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-grid .cmm-node-name a {
|
|
||||||
color: skyblue;
|
|
||||||
text-decoration: none;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-grid .cmm-node-desc a {
|
|
||||||
color: #5555FF;
|
|
||||||
font-weight: bold;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-grid .tg-cell a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-icon-passed {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
position: absolute;
|
|
||||||
left: calc(50% - 10px);
|
|
||||||
top: calc(50% - 10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager .cmm-btn-enable {
|
|
||||||
background-color: blue;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager .cmm-btn-disable {
|
|
||||||
background-color: MediumSlateBlue;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager .cmm-btn-install {
|
|
||||||
background-color: black;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-btn-download {
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
position: absolute;
|
|
||||||
left: calc(50% - 10px);
|
|
||||||
top: calc(50% - 10px);
|
|
||||||
cursor: pointer;
|
|
||||||
opacity: 0.8;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-btn-download:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-light .cmm-btn-download {
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes cmm-btn-loading-bg {
|
|
||||||
0% {
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
left: -105px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager button.cmm-btn-loading {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
border-color: rgb(0 119 207 / 80%);
|
|
||||||
background-color: var(--comfy-input-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager button.cmm-btn-loading::after {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
content: "";
|
|
||||||
width: 500px;
|
|
||||||
height: 100%;
|
|
||||||
background-image: repeating-linear-gradient(
|
|
||||||
-45deg,
|
|
||||||
rgb(0 119 207 / 30%),
|
|
||||||
rgb(0 119 207 / 30%) 10px,
|
|
||||||
transparent 10px,
|
|
||||||
transparent 15px
|
|
||||||
);
|
|
||||||
animation: cmm-btn-loading-bg 2s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-light .cmm-node-name a {
|
|
||||||
color: blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-light .cm-warn-note {
|
|
||||||
background-color: #ccc !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cmm-manager-light .cmm-btn-install {
|
|
||||||
background-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
`;
|
|
||||||
|
|
||||||
const pageHtml = `
|
const pageHtml = `
|
||||||
<div class="cmm-manager-header">
|
<div class="cmm-manager-header">
|
||||||
@@ -235,7 +33,14 @@ const pageHtml = `
|
|||||||
<div class="cmm-manager-selection"></div>
|
<div class="cmm-manager-selection"></div>
|
||||||
<div class="cmm-manager-message"></div>
|
<div class="cmm-manager-message"></div>
|
||||||
<div class="cmm-manager-footer">
|
<div class="cmm-manager-footer">
|
||||||
<button class="cmm-manager-back">Back</button>
|
<button class="cmm-manager-back">
|
||||||
|
<svg class="arrow-icon" width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M2 8H18M2 8L8 2M2 8L8 14" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
<button class="cmm-manager-refresh">Refresh</button>
|
||||||
|
<button class="cmm-manager-stop">Stop</button>
|
||||||
<div class="cmm-flex-auto"></div>
|
<div class="cmm-flex-auto"></div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -254,17 +59,11 @@ export class ModelManager {
|
|||||||
this.keywords = '';
|
this.keywords = '';
|
||||||
|
|
||||||
this.init();
|
this.init();
|
||||||
|
|
||||||
|
api.addEventListener("cm-queue-status", this.onQueueStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
|
|
||||||
if (!document.querySelector(`style[context="${this.id}"]`)) {
|
|
||||||
const $style = document.createElement("style");
|
|
||||||
$style.setAttribute("context", this.id);
|
|
||||||
$style.innerHTML = pageCss;
|
|
||||||
document.head.appendChild($style);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.element = $el("div", {
|
this.element = $el("div", {
|
||||||
parent: document.body,
|
parent: document.body,
|
||||||
className: "comfy-modal cmm-manager"
|
className: "comfy-modal cmm-manager"
|
||||||
@@ -282,10 +81,13 @@ export class ModelManager {
|
|||||||
value: ""
|
value: ""
|
||||||
}, {
|
}, {
|
||||||
label: "Installed",
|
label: "Installed",
|
||||||
value: "True"
|
value: "installed"
|
||||||
}, {
|
}, {
|
||||||
label: "Not Installed",
|
label: "Not Installed",
|
||||||
value: "False"
|
value: "not_installed"
|
||||||
|
}, {
|
||||||
|
label: "In Workflow",
|
||||||
|
value: "in_workflow"
|
||||||
}];
|
}];
|
||||||
|
|
||||||
this.typeList = [{
|
this.typeList = [{
|
||||||
@@ -365,12 +167,25 @@ export class ModelManager {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
".cmm-manager-refresh": {
|
||||||
|
click: () => {
|
||||||
|
app.refreshComboInNodes();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
".cmm-manager-stop": {
|
||||||
|
click: () => {
|
||||||
|
api.fetchApi('/v2/manager/queue/reset');
|
||||||
|
infoToast('Cancel', 'Remaining tasks will stop after completing the current task.');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
".cmm-manager-back": {
|
".cmm-manager-back": {
|
||||||
click: (e) => {
|
click: (e) => {
|
||||||
this.close()
|
this.close()
|
||||||
manager_instance.show();
|
manager_instance.show();
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
Object.keys(eventsMap).forEach(selector => {
|
Object.keys(eventsMap).forEach(selector => {
|
||||||
const target = this.element.querySelector(selector);
|
const target = this.element.querySelector(selector);
|
||||||
@@ -402,6 +217,10 @@ export class ModelManager {
|
|||||||
this.renderSelected();
|
this.renderSelected();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
grid.bind("onColumnWidthChanged", (e, columnItem) => {
|
||||||
|
storeColumnWidth(gridId, columnItem)
|
||||||
|
});
|
||||||
|
|
||||||
grid.bind('onClick', (e, d) => {
|
grid.bind('onClick', (e, d) => {
|
||||||
const { rowItem } = d;
|
const { rowItem } = d;
|
||||||
const target = d.e.target;
|
const target = d.e.target;
|
||||||
@@ -438,12 +257,31 @@ export class ModelManager {
|
|||||||
rowFilter: (rowItem) => {
|
rowFilter: (rowItem) => {
|
||||||
|
|
||||||
const searchableColumns = ["name", "type", "base", "description", "filename", "save_path"];
|
const searchableColumns = ["name", "type", "base", "description", "filename", "save_path"];
|
||||||
|
const models_extensions = ['.ckpt', '.pt', '.pt2', '.bin', '.pth', '.safetensors', '.pkl', '.sft'];
|
||||||
|
|
||||||
let shouldShown = grid.highlightKeywordsFilter(rowItem, searchableColumns, this.keywords);
|
let shouldShown = grid.highlightKeywordsFilter(rowItem, searchableColumns, this.keywords);
|
||||||
|
|
||||||
if (shouldShown) {
|
if (shouldShown) {
|
||||||
if(this.filter && rowItem.installed !== this.filter) {
|
if(this.filter) {
|
||||||
return false;
|
if (this.filter == "in_workflow") {
|
||||||
|
rowItem.in_workflow = null;
|
||||||
|
if (Array.isArray(app.graph._nodes)) {
|
||||||
|
app.graph._nodes.forEach((item, i) => {
|
||||||
|
if (Array.isArray(item.widgets_values)) {
|
||||||
|
item.widgets_values.forEach((_item, i) => {
|
||||||
|
if (rowItem.in_workflow === null && _item !== null && models_extensions.includes("." + _item.toString().split('.').pop())) {
|
||||||
|
let filename = _item.match(/([^\/]+)(?=\.\w+$)/)[0];
|
||||||
|
if (grid.highlightKeywordsFilter(rowItem, searchableColumns, filename)) {
|
||||||
|
rowItem.in_workflow = "True";
|
||||||
|
grid.highlightKeywordsFilter(rowItem, searchableColumns, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ((this.filter == "installed" && rowItem.installed == "True") || (this.filter == "not_installed" && rowItem.installed == "False") || (this.filter == "in_workflow" && rowItem.in_workflow == "True"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.type && rowItem.type !== this.type) {
|
if(this.type && rowItem.type !== this.type) {
|
||||||
@@ -518,7 +356,7 @@ export class ModelManager {
|
|||||||
sortable: false,
|
sortable: false,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
formatter: (url, rowItem, columnItem) => {
|
formatter: (url, rowItem, columnItem) => {
|
||||||
return `<a class="cmm-btn-download" title="Download file" href="${url}" target="_blank">${icons.download}</a>`;
|
return `<a class="cmm-btn-download" tooltip="Download file" href="${url}" target="_blank">${icons.download}</a>`;
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
id: 'size',
|
id: 'size',
|
||||||
@@ -553,6 +391,8 @@ export class ModelManager {
|
|||||||
width: 200
|
width: 200
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
restoreColumnWidth(gridId, columns);
|
||||||
|
|
||||||
this.grid.setData({
|
this.grid.setData({
|
||||||
options,
|
options,
|
||||||
rows,
|
rows,
|
||||||
@@ -595,17 +435,19 @@ export class ModelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async installModels(list, btn) {
|
async installModels(list, btn) {
|
||||||
|
|
||||||
btn.classList.add("cmm-btn-loading");
|
btn.classList.add("cmm-btn-loading");
|
||||||
this.showLoading();
|
|
||||||
this.showError("");
|
this.showError("");
|
||||||
|
|
||||||
let needRestart = false;
|
let needRefresh = false;
|
||||||
let errorMsg = "";
|
let errorMsg = "";
|
||||||
|
|
||||||
for (const item of list) {
|
let target_items = [];
|
||||||
|
|
||||||
|
let batch = {};
|
||||||
|
|
||||||
|
for (const item of list) {
|
||||||
this.grid.scrollRowIntoView(item);
|
this.grid.scrollRowIntoView(item);
|
||||||
|
target_items.push(item);
|
||||||
|
|
||||||
if (!this.focusInstall(item)) {
|
if (!this.focusInstall(item)) {
|
||||||
this.grid.onNextUpdated(() => {
|
this.grid.onNextUpdated(() => {
|
||||||
@@ -616,48 +458,120 @@ export class ModelManager {
|
|||||||
this.showStatus(`Install ${item.name} ...`);
|
this.showStatus(`Install ${item.name} ...`);
|
||||||
|
|
||||||
const data = item.originalData;
|
const data = item.originalData;
|
||||||
const res = await fetchData('/model/install', {
|
data.ui_id = item.hash;
|
||||||
|
|
||||||
|
|
||||||
|
if(batch['install_model']) {
|
||||||
|
batch['install_model'].push(data);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
batch['install_model'] = [data];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.install_context = {btn: btn, targets: target_items};
|
||||||
|
|
||||||
|
if(errorMsg) {
|
||||||
|
this.showError(errorMsg);
|
||||||
|
show_message("[Installation Errors]\n"+errorMsg);
|
||||||
|
|
||||||
|
// reset
|
||||||
|
for(let k in target_items) {
|
||||||
|
const item = target_items[k];
|
||||||
|
this.grid.updateCell(item, "installed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.batch_id = generateUUID();
|
||||||
|
batch['batch_id'] = this.batch_id;
|
||||||
|
|
||||||
|
const res = await api.fetchApi(`/v2/manager/queue/batch`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
body: JSON.stringify(batch)
|
||||||
body: JSON.stringify(data)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let failed = await res.json();
|
||||||
|
|
||||||
if (res.error) {
|
if(failed.length > 0) {
|
||||||
errorMsg = `Install failed: ${item.name} ${res.error.message}`;
|
for(let k in failed) {
|
||||||
break;;
|
let hash = failed[k];
|
||||||
|
const item = self.grid.getRowItemBy("hash", hash);
|
||||||
|
errorMsg = `[FAIL] ${item.title}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
needRestart = true;
|
this.showStop();
|
||||||
|
showTerminal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.grid.setRowSelected(item, false);
|
async onQueueStatus(event) {
|
||||||
|
let self = ModelManager.instance;
|
||||||
|
|
||||||
|
if(event.detail.status == 'in_progress' && event.detail.ui_target == 'model_manager') {
|
||||||
|
const hash = event.detail.target;
|
||||||
|
|
||||||
|
const item = self.grid.getRowItemBy("hash", hash);
|
||||||
|
|
||||||
item.refresh = true;
|
item.refresh = true;
|
||||||
|
self.grid.setRowSelected(item, false);
|
||||||
item.selectable = false;
|
item.selectable = false;
|
||||||
this.grid.updateCell(item, "installed");
|
// self.grid.updateCell(item, "tg-column-select");
|
||||||
this.grid.updateCell(item, "tg-column-select");
|
self.grid.updateRow(item);
|
||||||
|
}
|
||||||
|
else if(event.detail.status == 'batch-done') {
|
||||||
|
self.hideStop();
|
||||||
|
self.onQueueCompleted(event.detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.showStatus(`Install ${item.name} successfully`);
|
async onQueueCompleted(info) {
|
||||||
|
let result = info.model_result;
|
||||||
|
|
||||||
|
if(result.length == 0) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hideLoading();
|
let self = ModelManager.instance;
|
||||||
|
|
||||||
|
if(!self.install_context) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let btn = self.install_context.btn;
|
||||||
|
|
||||||
|
self.hideLoading();
|
||||||
btn.classList.remove("cmm-btn-loading");
|
btn.classList.remove("cmm-btn-loading");
|
||||||
|
|
||||||
|
let errorMsg = "";
|
||||||
|
|
||||||
|
for(let hash in result){
|
||||||
|
let v = result[hash];
|
||||||
|
|
||||||
|
if(v != 'success')
|
||||||
|
errorMsg += v + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let k in self.install_context.targets) {
|
||||||
|
let item = self.install_context.targets[k];
|
||||||
|
self.grid.updateCell(item, "installed");
|
||||||
|
}
|
||||||
|
|
||||||
if (errorMsg) {
|
if (errorMsg) {
|
||||||
this.showError(errorMsg);
|
self.showError(errorMsg);
|
||||||
|
show_message("Installation Error:\n"+errorMsg);
|
||||||
} else {
|
} else {
|
||||||
this.showStatus(`Install ${list.length} models successfully`);
|
self.showStatus(`Install ${result.length} models successfully`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needRestart) {
|
self.showRefresh();
|
||||||
this.showMessage(`To apply the installed model, please click the 'Refresh' button on the main menu.`, "red")
|
self.showMessage(`To apply the installed model, please click the 'Refresh' button.`, "red")
|
||||||
}
|
|
||||||
|
|
||||||
|
infoToast('Tasks done', `[ComfyUI-Manager] All model downloading tasks in the queue have been completed.\n${info.done_count}/${info.total_count}`);
|
||||||
|
self.install_context = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
getModelList(models) {
|
getModelList(models) {
|
||||||
|
|
||||||
const typeMap = new Map();
|
const typeMap = new Map();
|
||||||
const baseMap = new Map();
|
const baseMap = new Map();
|
||||||
|
|
||||||
@@ -731,7 +645,7 @@ export class ModelManager {
|
|||||||
|
|
||||||
const mode = manager_instance.datasrc_combo.value;
|
const mode = manager_instance.datasrc_combo.value;
|
||||||
|
|
||||||
const res = await fetchData(`/externalmodel/getlist?mode=${mode}`);
|
const res = await fetchData(`/v2/externalmodel/getlist?mode=${mode}`);
|
||||||
if (res.error) {
|
if (res.error) {
|
||||||
this.showError("Failed to get external model list.");
|
this.showError("Failed to get external model list.");
|
||||||
this.hideLoading();
|
this.hideLoading();
|
||||||
@@ -826,7 +740,7 @@ export class ModelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showLoading() {
|
showLoading() {
|
||||||
this.setDisabled(true);
|
// this.setDisabled(true);
|
||||||
if (this.grid) {
|
if (this.grid) {
|
||||||
this.grid.showLoading();
|
this.grid.showLoading();
|
||||||
this.grid.showMask({
|
this.grid.showMask({
|
||||||
@@ -836,7 +750,7 @@ export class ModelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hideLoading() {
|
hideLoading() {
|
||||||
this.setDisabled(false);
|
// this.setDisabled(false);
|
||||||
if (this.grid) {
|
if (this.grid) {
|
||||||
this.grid.hideLoading();
|
this.grid.hideLoading();
|
||||||
this.grid.hideMask();
|
this.grid.hideMask();
|
||||||
@@ -844,8 +758,9 @@ export class ModelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setDisabled(disabled) {
|
setDisabled(disabled) {
|
||||||
|
|
||||||
const $close = this.element.querySelector(".cmm-manager-close");
|
const $close = this.element.querySelector(".cmm-manager-close");
|
||||||
|
const $refresh = this.element.querySelector(".cmm-manager-refresh");
|
||||||
|
const $stop = this.element.querySelector(".cmm-manager-stop");
|
||||||
|
|
||||||
const list = [
|
const list = [
|
||||||
".cmm-manager-header input",
|
".cmm-manager-header input",
|
||||||
@@ -857,7 +772,7 @@ export class ModelManager {
|
|||||||
})
|
})
|
||||||
.flat()
|
.flat()
|
||||||
.filter(it => {
|
.filter(it => {
|
||||||
return it !== $close;
|
return it !== $close && it !== $refresh && it !== $stop;
|
||||||
});
|
});
|
||||||
|
|
||||||
list.forEach($elem => {
|
list.forEach($elem => {
|
||||||
@@ -874,6 +789,18 @@ export class ModelManager {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showRefresh() {
|
||||||
|
this.element.querySelector(".cmm-manager-refresh").style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
showStop() {
|
||||||
|
this.element.querySelector(".cmm-manager-stop").style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
hideStop() {
|
||||||
|
this.element.querySelector(".cmm-manager-stop").style.display = "none";
|
||||||
|
}
|
||||||
|
|
||||||
setKeywords(keywords = "") {
|
setKeywords(keywords = "") {
|
||||||
this.keywords = keywords;
|
this.keywords = keywords;
|
||||||
this.element.querySelector(".cmm-manager-keywords").value = keywords;
|
this.element.querySelector(".cmm-manager-keywords").value = keywords;
|
||||||
@@ -1,16 +1,6 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { api } from "../../scripts/api.js";
|
import { api } from "../../scripts/api.js";
|
||||||
|
|
||||||
let double_click_policy = "copy-all";
|
|
||||||
|
|
||||||
api.fetchApi('/manager/dbl_click/policy')
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(data => set_double_click_policy(data));
|
|
||||||
|
|
||||||
export function set_double_click_policy(mode) {
|
|
||||||
double_click_policy = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
function addMenuHandler(nodeType, cb) {
|
function addMenuHandler(nodeType, cb) {
|
||||||
const getOpts = nodeType.prototype.getExtraMenuOptions;
|
const getOpts = nodeType.prototype.getExtraMenuOptions;
|
||||||
nodeType.prototype.getExtraMenuOptions = function () {
|
nodeType.prototype.getExtraMenuOptions = function () {
|
||||||
@@ -152,63 +142,7 @@ function node_info_copy(src, dest, connect_both, copy_shape) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app.registerExtension({
|
app.registerExtension({
|
||||||
name: "Comfy.Manager.NodeFixer",
|
name: "Comfy.Legacy.Manager.NodeFixer",
|
||||||
|
|
||||||
async nodeCreated(node, app) {
|
|
||||||
let orig_dblClick = node.onDblClick;
|
|
||||||
node.onDblClick = function (e, pos, self) {
|
|
||||||
orig_dblClick?.apply?.(this, arguments);
|
|
||||||
|
|
||||||
if((!node.inputs && !node.outputs) || pos[1] > 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch(double_click_policy) {
|
|
||||||
case "copy-all":
|
|
||||||
case "copy-full":
|
|
||||||
case "copy-input":
|
|
||||||
{
|
|
||||||
if(node.inputs?.some(x => x.link != null) || node.outputs?.some(x => x.links != null && x.links.length > 0) )
|
|
||||||
return;
|
|
||||||
|
|
||||||
let src_node = lookup_nearest_nodes(node);
|
|
||||||
if(src_node)
|
|
||||||
{
|
|
||||||
let both_connection = double_click_policy != "copy-input";
|
|
||||||
let copy_shape = double_click_policy == "copy-full";
|
|
||||||
node_info_copy(src_node, node, both_connection, copy_shape);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "possible-input":
|
|
||||||
{
|
|
||||||
let nearest_inputs = lookup_nearest_inputs(node);
|
|
||||||
if(nearest_inputs)
|
|
||||||
connect_inputs(nearest_inputs, node);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "dual":
|
|
||||||
{
|
|
||||||
if(pos[0] < node.size[0]/2) {
|
|
||||||
// left: possible-input
|
|
||||||
let nearest_inputs = lookup_nearest_inputs(node);
|
|
||||||
if(nearest_inputs)
|
|
||||||
connect_inputs(nearest_inputs, node);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// right: copy-all
|
|
||||||
if(node.inputs?.some(x => x.link != null) || node.outputs?.some(x => x.links != null && x.links.length > 0) )
|
|
||||||
return;
|
|
||||||
|
|
||||||
let src_node = lookup_nearest_nodes(node);
|
|
||||||
if(src_node)
|
|
||||||
node_info_copy(src_node, node, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeRegisterNodeDef(nodeType, nodeData, app) {
|
beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||||
addMenuHandler(nodeType, function (_, options) {
|
addMenuHandler(nodeType, function (_, options) {
|
||||||
options.push({
|
options.push({
|
||||||
@@ -219,6 +153,7 @@ app.registerExtension({
|
|||||||
app.canvas.graph.add(new_node, false);
|
app.canvas.graph.add(new_node, false);
|
||||||
node_info_copy(this, new_node, true);
|
node_info_copy(this, new_node, true);
|
||||||
app.canvas.graph.remove(this);
|
app.canvas.graph.remove(this);
|
||||||
|
requestAnimationFrame(() => app.canvas.setDirty(true, true))
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
619
comfyui_manager/js/popover-helper.js
Normal file
619
comfyui_manager/js/popover-helper.js
Normal file
@@ -0,0 +1,619 @@
|
|||||||
|
const hasOwn = function(obj, key) {
|
||||||
|
return Object.prototype.hasOwnProperty.call(obj, key);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isNum = function(num) {
|
||||||
|
if (typeof num !== 'number' || isNaN(num)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const isInvalid = function(n) {
|
||||||
|
if (n === Number.MAX_VALUE || n === Number.MIN_VALUE || n === Number.NEGATIVE_INFINITY || n === Number.POSITIVE_INFINITY) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if (isInvalid(num)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const toNum = (num) => {
|
||||||
|
if (typeof (num) !== 'number') {
|
||||||
|
num = parseFloat(num);
|
||||||
|
}
|
||||||
|
if (isNaN(num)) {
|
||||||
|
num = 0;
|
||||||
|
}
|
||||||
|
num = Math.round(num);
|
||||||
|
return num;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clamp = function(value, min, max) {
|
||||||
|
return Math.max(min, Math.min(max, value));
|
||||||
|
};
|
||||||
|
|
||||||
|
const isWindow = (obj) => {
|
||||||
|
return Boolean(obj && obj === obj.window);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isDocument = (obj) => {
|
||||||
|
return Boolean(obj && obj.nodeType === 9);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isElement = (obj) => {
|
||||||
|
return Boolean(obj && obj.nodeType === 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===========================================================================================
|
||||||
|
|
||||||
|
export const toRect = (obj) => {
|
||||||
|
if (obj) {
|
||||||
|
return {
|
||||||
|
left: toNum(obj.left || obj.x),
|
||||||
|
top: toNum(obj.top || obj.y),
|
||||||
|
width: toNum(obj.width),
|
||||||
|
height: toNum(obj.height)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getElement = (selector) => {
|
||||||
|
if (typeof selector === 'string' && selector) {
|
||||||
|
if (selector.startsWith('#')) {
|
||||||
|
return document.getElementById(selector.slice(1));
|
||||||
|
}
|
||||||
|
return document.querySelector(selector);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDocument(selector)) {
|
||||||
|
return selector.body;
|
||||||
|
}
|
||||||
|
if (isElement(selector)) {
|
||||||
|
return selector;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getRect = (target, fixed) => {
|
||||||
|
if (!target) {
|
||||||
|
return toRect();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWindow(target)) {
|
||||||
|
return {
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
width: window.innerWidth,
|
||||||
|
height: window.innerHeight
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const elem = getElement(target);
|
||||||
|
if (!elem) {
|
||||||
|
return toRect(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
const br = elem.getBoundingClientRect();
|
||||||
|
const rect = toRect(br);
|
||||||
|
|
||||||
|
// fix offset
|
||||||
|
if (!fixed) {
|
||||||
|
rect.left += window.scrollX;
|
||||||
|
rect.top += window.scrollY;
|
||||||
|
}
|
||||||
|
|
||||||
|
rect.width = elem.offsetWidth;
|
||||||
|
rect.height = elem.offsetHeight;
|
||||||
|
|
||||||
|
return rect;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===========================================================================================
|
||||||
|
|
||||||
|
const calculators = {
|
||||||
|
|
||||||
|
bottom: (info, containerRect, targetRect) => {
|
||||||
|
info.space = containerRect.top + containerRect.height - targetRect.top - targetRect.height - info.height;
|
||||||
|
info.top = targetRect.top + targetRect.height;
|
||||||
|
info.left = Math.round(targetRect.left + targetRect.width * 0.5 - info.width * 0.5);
|
||||||
|
},
|
||||||
|
|
||||||
|
top: (info, containerRect, targetRect) => {
|
||||||
|
info.space = targetRect.top - info.height - containerRect.top;
|
||||||
|
info.top = targetRect.top - info.height;
|
||||||
|
info.left = Math.round(targetRect.left + targetRect.width * 0.5 - info.width * 0.5);
|
||||||
|
},
|
||||||
|
|
||||||
|
right: (info, containerRect, targetRect) => {
|
||||||
|
info.space = containerRect.left + containerRect.width - targetRect.left - targetRect.width - info.width;
|
||||||
|
info.top = Math.round(targetRect.top + targetRect.height * 0.5 - info.height * 0.5);
|
||||||
|
info.left = targetRect.left + targetRect.width;
|
||||||
|
},
|
||||||
|
|
||||||
|
left: (info, containerRect, targetRect) => {
|
||||||
|
info.space = targetRect.left - info.width - containerRect.left;
|
||||||
|
info.top = Math.round(targetRect.top + targetRect.height * 0.5 - info.height * 0.5);
|
||||||
|
info.left = targetRect.left - info.width;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// with order
|
||||||
|
export const getDefaultPositions = () => {
|
||||||
|
return Object.keys(calculators);
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateSpace = (info, containerRect, targetRect) => {
|
||||||
|
const calculator = calculators[info.position];
|
||||||
|
calculator(info, containerRect, targetRect);
|
||||||
|
if (info.space >= 0) {
|
||||||
|
info.passed += 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===========================================================================================
|
||||||
|
|
||||||
|
const calculateAlignOffset = (info, containerRect, targetRect, alignType, sizeType) => {
|
||||||
|
|
||||||
|
const popoverStart = info[alignType];
|
||||||
|
const popoverSize = info[sizeType];
|
||||||
|
|
||||||
|
const containerStart = containerRect[alignType];
|
||||||
|
const containerSize = containerRect[sizeType];
|
||||||
|
|
||||||
|
const targetStart = targetRect[alignType];
|
||||||
|
const targetSize = targetRect[sizeType];
|
||||||
|
|
||||||
|
const targetCenter = targetStart + targetSize * 0.5;
|
||||||
|
|
||||||
|
// size overflow
|
||||||
|
if (popoverSize > containerSize) {
|
||||||
|
const overflow = (popoverSize - containerSize) * 0.5;
|
||||||
|
info[alignType] = containerStart - overflow;
|
||||||
|
info.offset = targetCenter - containerStart + overflow;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const space1 = popoverStart - containerStart;
|
||||||
|
const space2 = (containerStart + containerSize) - (popoverStart + popoverSize);
|
||||||
|
|
||||||
|
// both side passed, default to center
|
||||||
|
if (space1 >= 0 && space2 >= 0) {
|
||||||
|
if (info.passed) {
|
||||||
|
info.passed += 2;
|
||||||
|
}
|
||||||
|
info.offset = popoverSize * 0.5;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// one side passed
|
||||||
|
if (info.passed) {
|
||||||
|
info.passed += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (space1 < 0) {
|
||||||
|
const min = containerStart;
|
||||||
|
info[alignType] = min;
|
||||||
|
info.offset = targetCenter - min;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// space2 < 0
|
||||||
|
const max = containerStart + containerSize - popoverSize;
|
||||||
|
info[alignType] = max;
|
||||||
|
info.offset = targetCenter - max;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateHV = (info, containerRect) => {
|
||||||
|
if (['top', 'bottom'].includes(info.position)) {
|
||||||
|
info.top = clamp(info.top, containerRect.top, containerRect.top + containerRect.height - info.height);
|
||||||
|
return ['left', 'width'];
|
||||||
|
}
|
||||||
|
info.left = clamp(info.left, containerRect.left, containerRect.left + containerRect.width - info.width);
|
||||||
|
return ['top', 'height'];
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateOffset = (info, containerRect, targetRect) => {
|
||||||
|
|
||||||
|
const [alignType, sizeType] = calculateHV(info, containerRect);
|
||||||
|
|
||||||
|
calculateAlignOffset(info, containerRect, targetRect, alignType, sizeType);
|
||||||
|
|
||||||
|
info.offset = clamp(info.offset, 0, info[sizeType]);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===========================================================================================
|
||||||
|
|
||||||
|
const calculateDistance = (info, previousPositionInfo) => {
|
||||||
|
if (!previousPositionInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// no change if position no change with previous
|
||||||
|
if (info.position === previousPositionInfo.position) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ax = info.left + info.width * 0.5;
|
||||||
|
const ay = info.top + info.height * 0.5;
|
||||||
|
const bx = previousPositionInfo.left + previousPositionInfo.width * 0.5;
|
||||||
|
const by = previousPositionInfo.top + previousPositionInfo.height * 0.5;
|
||||||
|
const dx = Math.abs(ax - bx);
|
||||||
|
const dy = Math.abs(ay - by);
|
||||||
|
info.distance = Math.round(Math.sqrt(dx * dx + dy * dy));
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===========================================================================================
|
||||||
|
|
||||||
|
const calculatePositionInfo = (info, containerRect, targetRect, previousPositionInfo) => {
|
||||||
|
calculateSpace(info, containerRect, targetRect);
|
||||||
|
calculateOffset(info, containerRect, targetRect);
|
||||||
|
calculateDistance(info, previousPositionInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===========================================================================================
|
||||||
|
|
||||||
|
const calculateBestPosition = (containerRect, targetRect, infoMap, withOrder, previousPositionInfo) => {
|
||||||
|
|
||||||
|
// position space: +1
|
||||||
|
// align space:
|
||||||
|
// two side passed: +2
|
||||||
|
// one side passed: +1
|
||||||
|
|
||||||
|
const safePassed = 3;
|
||||||
|
|
||||||
|
if (previousPositionInfo) {
|
||||||
|
const prevInfo = infoMap[previousPositionInfo.position];
|
||||||
|
if (prevInfo) {
|
||||||
|
calculatePositionInfo(prevInfo, containerRect, targetRect);
|
||||||
|
if (prevInfo.passed >= safePassed) {
|
||||||
|
return prevInfo;
|
||||||
|
}
|
||||||
|
prevInfo.calculated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const positionList = [];
|
||||||
|
Object.values(infoMap).forEach((info) => {
|
||||||
|
if (!info.calculated) {
|
||||||
|
calculatePositionInfo(info, containerRect, targetRect, previousPositionInfo);
|
||||||
|
}
|
||||||
|
positionList.push(info);
|
||||||
|
});
|
||||||
|
|
||||||
|
positionList.sort((a, b) => {
|
||||||
|
if (a.passed !== b.passed) {
|
||||||
|
return b.passed - a.passed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (withOrder && a.passed >= safePassed && b.passed >= safePassed) {
|
||||||
|
return a.index - b.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a.space !== b.space) {
|
||||||
|
return b.space - a.space;
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.index - b.index;
|
||||||
|
});
|
||||||
|
|
||||||
|
// logTable(positionList);
|
||||||
|
|
||||||
|
return positionList[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
// const logTable = (() => {
|
||||||
|
// let time_id;
|
||||||
|
// return (info) => {
|
||||||
|
// clearTimeout(time_id);
|
||||||
|
// time_id = setTimeout(() => {
|
||||||
|
// console.table(info);
|
||||||
|
// }, 10);
|
||||||
|
// };
|
||||||
|
// })();
|
||||||
|
|
||||||
|
// ===========================================================================================
|
||||||
|
|
||||||
|
const getAllowPositions = (positions, defaultAllowPositions) => {
|
||||||
|
if (!positions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (Array.isArray(positions)) {
|
||||||
|
positions = positions.join(',');
|
||||||
|
}
|
||||||
|
positions = String(positions).split(',').map((it) => it.trim().toLowerCase()).filter((it) => it);
|
||||||
|
positions = positions.filter((it) => defaultAllowPositions.includes(it));
|
||||||
|
if (!positions.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return positions;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isPositionChanged = (info, previousPositionInfo) => {
|
||||||
|
if (!previousPositionInfo) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.left !== previousPositionInfo.left) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.top !== previousPositionInfo.top) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===========================================================================================
|
||||||
|
|
||||||
|
// const log = (name, time) => {
|
||||||
|
// if (time > 0.1) {
|
||||||
|
// console.log(name, time);
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
|
||||||
|
export const getBestPosition = (containerRect, targetRect, popoverRect, positions, previousPositionInfo) => {
|
||||||
|
|
||||||
|
const defaultAllowPositions = getDefaultPositions();
|
||||||
|
let withOrder = true;
|
||||||
|
let allowPositions = getAllowPositions(positions, defaultAllowPositions);
|
||||||
|
if (!allowPositions) {
|
||||||
|
allowPositions = defaultAllowPositions;
|
||||||
|
withOrder = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log('withOrder', withOrder);
|
||||||
|
|
||||||
|
// const start_time = performance.now();
|
||||||
|
|
||||||
|
const infoMap = {};
|
||||||
|
allowPositions.forEach((k, i) => {
|
||||||
|
infoMap[k] = {
|
||||||
|
position: k,
|
||||||
|
index: i,
|
||||||
|
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: popoverRect.width,
|
||||||
|
height: popoverRect.height,
|
||||||
|
|
||||||
|
space: 0,
|
||||||
|
|
||||||
|
offset: 0,
|
||||||
|
passed: 0,
|
||||||
|
|
||||||
|
distance: 0
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// log('infoMap', performance.now() - start_time);
|
||||||
|
|
||||||
|
|
||||||
|
const bestPosition = calculateBestPosition(containerRect, targetRect, infoMap, withOrder, previousPositionInfo);
|
||||||
|
|
||||||
|
// check left/top
|
||||||
|
bestPosition.changed = isPositionChanged(bestPosition, previousPositionInfo);
|
||||||
|
|
||||||
|
return bestPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===========================================================================================
|
||||||
|
|
||||||
|
const getTemplatePath = (width, height, arrowOffset, arrowSize, borderRadius) => {
|
||||||
|
const p = (px, py) => {
|
||||||
|
return [px, py].join(',');
|
||||||
|
};
|
||||||
|
|
||||||
|
const px = function(num, alignEnd) {
|
||||||
|
const floor = Math.floor(num);
|
||||||
|
let n = num < floor + 0.5 ? floor + 0.5 : floor + 1.5;
|
||||||
|
if (alignEnd) {
|
||||||
|
n -= 1;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pxe = function(num) {
|
||||||
|
return px(num, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ls = [];
|
||||||
|
|
||||||
|
const innerLeft = px(arrowSize);
|
||||||
|
const innerRight = pxe(width - arrowSize);
|
||||||
|
arrowOffset = clamp(arrowOffset, innerLeft, innerRight);
|
||||||
|
|
||||||
|
const innerTop = px(arrowSize);
|
||||||
|
const innerBottom = pxe(height - arrowSize);
|
||||||
|
|
||||||
|
const startPoint = p(innerLeft, innerTop + borderRadius);
|
||||||
|
const arrowPoint = p(arrowOffset, 1);
|
||||||
|
|
||||||
|
const LT = p(innerLeft, innerTop);
|
||||||
|
const RT = p(innerRight, innerTop);
|
||||||
|
|
||||||
|
const AOT = p(arrowOffset - arrowSize, innerTop);
|
||||||
|
const RRT = p(innerRight - borderRadius, innerTop);
|
||||||
|
|
||||||
|
ls.push(`M${startPoint}`);
|
||||||
|
ls.push(`V${innerBottom - borderRadius}`);
|
||||||
|
ls.push(`Q${p(innerLeft, innerBottom)} ${p(innerLeft + borderRadius, innerBottom)}`);
|
||||||
|
ls.push(`H${innerRight - borderRadius}`);
|
||||||
|
ls.push(`Q${p(innerRight, innerBottom)} ${p(innerRight, innerBottom - borderRadius)}`);
|
||||||
|
ls.push(`V${innerTop + borderRadius}`);
|
||||||
|
|
||||||
|
if (arrowOffset < innerLeft + arrowSize + borderRadius) {
|
||||||
|
ls.push(`Q${RT} ${RRT}`);
|
||||||
|
ls.push(`H${arrowOffset + arrowSize}`);
|
||||||
|
ls.push(`L${arrowPoint}`);
|
||||||
|
if (arrowOffset < innerLeft + arrowSize) {
|
||||||
|
ls.push(`L${LT}`);
|
||||||
|
ls.push(`L${startPoint}`);
|
||||||
|
} else {
|
||||||
|
ls.push(`L${AOT}`);
|
||||||
|
ls.push(`Q${LT} ${startPoint}`);
|
||||||
|
}
|
||||||
|
} else if (arrowOffset > innerRight - arrowSize - borderRadius) {
|
||||||
|
if (arrowOffset > innerRight - arrowSize) {
|
||||||
|
ls.push(`L${RT}`);
|
||||||
|
} else {
|
||||||
|
ls.push(`Q${RT} ${p(arrowOffset + arrowSize, innerTop)}`);
|
||||||
|
}
|
||||||
|
ls.push(`L${arrowPoint}`);
|
||||||
|
ls.push(`L${AOT}`);
|
||||||
|
ls.push(`H${innerLeft + borderRadius}`);
|
||||||
|
ls.push(`Q${LT} ${startPoint}`);
|
||||||
|
} else {
|
||||||
|
ls.push(`Q${RT} ${RRT}`);
|
||||||
|
ls.push(`H${arrowOffset + arrowSize}`);
|
||||||
|
ls.push(`L${arrowPoint}`);
|
||||||
|
ls.push(`L${AOT}`);
|
||||||
|
ls.push(`H${innerLeft + borderRadius}`);
|
||||||
|
ls.push(`Q${LT} ${startPoint}`);
|
||||||
|
}
|
||||||
|
return ls.join('');
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPathData = function(position, width, height, arrowOffset, arrowSize, borderRadius) {
|
||||||
|
|
||||||
|
const handlers = {
|
||||||
|
|
||||||
|
bottom: () => {
|
||||||
|
const d = getTemplatePath(width, height, arrowOffset, arrowSize, borderRadius);
|
||||||
|
return {
|
||||||
|
d,
|
||||||
|
transform: ''
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
top: () => {
|
||||||
|
const d = getTemplatePath(width, height, width - arrowOffset, arrowSize, borderRadius);
|
||||||
|
return {
|
||||||
|
d,
|
||||||
|
transform: `rotate(180,${width * 0.5},${height * 0.5})`
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
left: () => {
|
||||||
|
const d = getTemplatePath(height, width, arrowOffset, arrowSize, borderRadius);
|
||||||
|
const x = (width - height) * 0.5;
|
||||||
|
const y = (height - width) * 0.5;
|
||||||
|
return {
|
||||||
|
d,
|
||||||
|
transform: `translate(${x} ${y}) rotate(90,${height * 0.5},${width * 0.5})`
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
right: () => {
|
||||||
|
const d = getTemplatePath(height, width, height - arrowOffset, arrowSize, borderRadius);
|
||||||
|
const x = (width - height) * 0.5;
|
||||||
|
const y = (height - width) * 0.5;
|
||||||
|
return {
|
||||||
|
d,
|
||||||
|
transform: `translate(${x} ${y}) rotate(-90,${height * 0.5},${width * 0.5})`
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return handlers[position]();
|
||||||
|
};
|
||||||
|
|
||||||
|
// ===========================================================================================
|
||||||
|
|
||||||
|
// position style cache
|
||||||
|
const styleCache = {
|
||||||
|
// position: '',
|
||||||
|
// top: {},
|
||||||
|
// bottom: {},
|
||||||
|
// left: {},
|
||||||
|
// right: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getPositionStyle = (info, options = {}) => {
|
||||||
|
|
||||||
|
const o = {
|
||||||
|
bgColor: '#fff',
|
||||||
|
borderColor: '#ccc',
|
||||||
|
borderRadius: 5,
|
||||||
|
arrowSize: 10
|
||||||
|
};
|
||||||
|
Object.keys(o).forEach((k) => {
|
||||||
|
|
||||||
|
if (hasOwn(options, k)) {
|
||||||
|
const d = o[k];
|
||||||
|
const v = options[k];
|
||||||
|
|
||||||
|
if (typeof d === 'string') {
|
||||||
|
// string
|
||||||
|
if (typeof v === 'string' && v) {
|
||||||
|
o[k] = v;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// number
|
||||||
|
if (isNum(v) && v >= 0) {
|
||||||
|
o[k] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = [
|
||||||
|
info.width,
|
||||||
|
info.height,
|
||||||
|
info.offset,
|
||||||
|
o.arrowSize,
|
||||||
|
o.borderRadius,
|
||||||
|
o.bgColor,
|
||||||
|
o.borderColor
|
||||||
|
].join('-');
|
||||||
|
|
||||||
|
const positionCache = styleCache[info.position];
|
||||||
|
if (positionCache && key === positionCache.key) {
|
||||||
|
const st = positionCache.style;
|
||||||
|
st.changed = styleCache.position !== info.position;
|
||||||
|
styleCache.position = info.position;
|
||||||
|
return st;
|
||||||
|
}
|
||||||
|
|
||||||
|
// console.log(options);
|
||||||
|
|
||||||
|
const data = getPathData(info.position, info.width, info.height, info.offset, o.arrowSize, o.borderRadius);
|
||||||
|
// console.log(data);
|
||||||
|
|
||||||
|
const viewBox = [0, 0, info.width, info.height].join(' ');
|
||||||
|
const svg = [
|
||||||
|
`<svg viewBox="${viewBox}" xmlns="http://www.w3.org/2000/svg">`,
|
||||||
|
`<path d="${data.d}" fill="${o.bgColor}" stroke="${o.borderColor}" transform="${data.transform}" />`,
|
||||||
|
'</svg>'
|
||||||
|
].join('');
|
||||||
|
|
||||||
|
// console.log(svg);
|
||||||
|
const backgroundImage = `url("data:image/svg+xml;charset=utf8,${encodeURIComponent(svg)}")`;
|
||||||
|
|
||||||
|
const background = `${backgroundImage} center no-repeat`;
|
||||||
|
|
||||||
|
const padding = `${o.arrowSize + o.borderRadius}px`;
|
||||||
|
|
||||||
|
const style = {
|
||||||
|
background,
|
||||||
|
backgroundImage,
|
||||||
|
padding,
|
||||||
|
changed: true
|
||||||
|
};
|
||||||
|
|
||||||
|
styleCache.position = info.position;
|
||||||
|
styleCache[info.position] = {
|
||||||
|
key,
|
||||||
|
style
|
||||||
|
};
|
||||||
|
|
||||||
|
return style;
|
||||||
|
};
|
||||||
@@ -7,7 +7,7 @@ import { manager_instance, rebootAPI, show_message } from "./common.js";
|
|||||||
async function restore_snapshot(target) {
|
async function restore_snapshot(target) {
|
||||||
if(SnapshotManager.instance) {
|
if(SnapshotManager.instance) {
|
||||||
try {
|
try {
|
||||||
const response = await api.fetchApi(`/snapshot/restore?target=${target}`, { cache: "no-store" });
|
const response = await api.fetchApi(`/v2/snapshot/restore?target=${target}`, { cache: "no-store" });
|
||||||
|
|
||||||
if(response.status == 403) {
|
if(response.status == 403) {
|
||||||
show_message('This action is not allowed with this security level configuration.');
|
show_message('This action is not allowed with this security level configuration.');
|
||||||
@@ -35,7 +35,7 @@ async function restore_snapshot(target) {
|
|||||||
async function remove_snapshot(target) {
|
async function remove_snapshot(target) {
|
||||||
if(SnapshotManager.instance) {
|
if(SnapshotManager.instance) {
|
||||||
try {
|
try {
|
||||||
const response = await api.fetchApi(`/snapshot/remove?target=${target}`, { cache: "no-store" });
|
const response = await api.fetchApi(`/v2/snapshot/remove?target=${target}`, { cache: "no-store" });
|
||||||
|
|
||||||
if(response.status == 403) {
|
if(response.status == 403) {
|
||||||
show_message('This action is not allowed with this security level configuration.');
|
show_message('This action is not allowed with this security level configuration.');
|
||||||
@@ -61,7 +61,7 @@ async function remove_snapshot(target) {
|
|||||||
|
|
||||||
async function save_current_snapshot() {
|
async function save_current_snapshot() {
|
||||||
try {
|
try {
|
||||||
const response = await api.fetchApi('/snapshot/save', { cache: "no-store" });
|
const response = await api.fetchApi('/v2/snapshot/save', { cache: "no-store" });
|
||||||
app.ui.dialog.close();
|
app.ui.dialog.close();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,7 @@ async function save_current_snapshot() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getSnapshotList() {
|
async function getSnapshotList() {
|
||||||
const response = await api.fetchApi(`/snapshot/getlist`);
|
const response = await api.fetchApi(`/v2/snapshot/getlist`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
84
comfyui_manager/js/workflow-metadata.js
Normal file
84
comfyui_manager/js/workflow-metadata.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
/**
|
||||||
|
* Attaches metadata to the workflow on save
|
||||||
|
* - custom node pack version to all custom nodes used in the workflow
|
||||||
|
*
|
||||||
|
* Example metadata:
|
||||||
|
* "nodes": {
|
||||||
|
* "1": {
|
||||||
|
* type: "CheckpointLoaderSimple",
|
||||||
|
* ...
|
||||||
|
* properties: {
|
||||||
|
* cnr_id: "comfy-core",
|
||||||
|
* version: "0.3.8",
|
||||||
|
* },
|
||||||
|
* },
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @typedef {Object} NodeInfo
|
||||||
|
* @property {string} ver - Version (git hash or semantic version)
|
||||||
|
* @property {string} cnr_id - ComfyRegistry node ID
|
||||||
|
* @property {boolean} enabled - Whether the node is enabled
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { app } from "../../scripts/app.js";
|
||||||
|
import { api } from "../../scripts/api.js";
|
||||||
|
|
||||||
|
class WorkflowMetadataExtension {
|
||||||
|
constructor() {
|
||||||
|
this.name = "Comfy.CustomNodesManager.WorkflowMetadata";
|
||||||
|
this.installedNodes = {};
|
||||||
|
this.comfyCoreVersion = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the installed nodes info
|
||||||
|
* @returns {Promise<Record<string, NodeInfo>>} The mapping from node name to its info.
|
||||||
|
* ver can either be a git commit hash or a semantic version such as "1.0.0"
|
||||||
|
* cnr_id is the id of the node in the ComfyRegistry
|
||||||
|
* enabled is true if the node is enabled, false if it is disabled
|
||||||
|
*/
|
||||||
|
async getInstalledNodes() {
|
||||||
|
const res = await api.fetchApi("/v2/customnode/installed");
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this.installedNodes = await this.getInstalledNodes();
|
||||||
|
this.comfyCoreVersion = (await api.getSystemStats()).system.comfyui_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when any node is created
|
||||||
|
* @param {LGraphNode} node The newly created node
|
||||||
|
*/
|
||||||
|
nodeCreated(node) {
|
||||||
|
try {
|
||||||
|
// nodeData doesn't exist if node is missing or node is frontend only node
|
||||||
|
if (!node?.constructor?.nodeData?.python_module) return;
|
||||||
|
|
||||||
|
const nodeProperties = (node.properties ??= {});
|
||||||
|
const modules = node.constructor.nodeData.python_module.split(".");
|
||||||
|
const moduleType = modules[0];
|
||||||
|
|
||||||
|
if (moduleType === "custom_nodes") {
|
||||||
|
const nodePackageName = modules[1];
|
||||||
|
const { cnr_id, aux_id, ver } =
|
||||||
|
this.installedNodes[nodePackageName] ??
|
||||||
|
this.installedNodes[nodePackageName.toLowerCase()] ??
|
||||||
|
{};
|
||||||
|
|
||||||
|
if (cnr_id === "comfy-core") return; // don't allow hijacking comfy-core name
|
||||||
|
if (cnr_id) nodeProperties.cnr_id = cnr_id;
|
||||||
|
else nodeProperties.aux_id = aux_id;
|
||||||
|
if (ver) nodeProperties.ver = ver.trim();
|
||||||
|
} else if (["nodes", "comfy_extras", "comfy_api_nodes"].includes(moduleType)) {
|
||||||
|
nodeProperties.cnr_id = "comfy-core";
|
||||||
|
nodeProperties.ver = this.comfyCoreVersion;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.registerExtension(new WorkflowMetadataExtension());
|
||||||
0
comfyui_manager/legacy/__init__.py
Normal file
0
comfyui_manager/legacy/__init__.py
Normal file
File diff suppressed because it is too large
Load Diff
1957
comfyui_manager/legacy/manager_server.py
Normal file
1957
comfyui_manager/legacy/manager_server.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
|||||||
import mimetypes
|
import mimetypes
|
||||||
import manager_core as core
|
from ..common import context
|
||||||
|
from . import manager_core as core
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from aiohttp import web
|
from aiohttp import web
|
||||||
import aiohttp
|
import aiohttp
|
||||||
@@ -53,7 +55,7 @@ def compute_sha256_checksum(filepath):
|
|||||||
return sha256.hexdigest()
|
return sha256.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/share_option")
|
@PromptServer.instance.routes.get("/v2/manager/share_option")
|
||||||
async def share_option(request):
|
async def share_option(request):
|
||||||
if "value" in request.rel_url.query:
|
if "value" in request.rel_url.query:
|
||||||
core.get_config()['share_option'] = request.rel_url.query['value']
|
core.get_config()['share_option'] = request.rel_url.query['value']
|
||||||
@@ -65,21 +67,21 @@ async def share_option(request):
|
|||||||
|
|
||||||
|
|
||||||
def get_openart_auth():
|
def get_openart_auth():
|
||||||
if not os.path.exists(os.path.join(core.manager_files_path, ".openart_key")):
|
if not os.path.exists(os.path.join(context.manager_files_path, ".openart_key")):
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(core.manager_files_path, ".openart_key"), "r") as f:
|
with open(os.path.join(context.manager_files_path, ".openart_key"), "r") as f:
|
||||||
openart_key = f.read().strip()
|
openart_key = f.read().strip()
|
||||||
return openart_key if openart_key else None
|
return openart_key if openart_key else None
|
||||||
except:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_matrix_auth():
|
def get_matrix_auth():
|
||||||
if not os.path.exists(os.path.join(core.manager_files_path, "matrix_auth")):
|
if not os.path.exists(os.path.join(context.manager_files_path, "matrix_auth")):
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(core.manager_files_path, "matrix_auth"), "r") as f:
|
with open(os.path.join(context.manager_files_path, "matrix_auth"), "r") as f:
|
||||||
matrix_auth = f.read()
|
matrix_auth = f.read()
|
||||||
homeserver, username, password = matrix_auth.strip().split("\n")
|
homeserver, username, password = matrix_auth.strip().split("\n")
|
||||||
if not homeserver or not username or not password:
|
if not homeserver or not username or not password:
|
||||||
@@ -89,40 +91,40 @@ def get_matrix_auth():
|
|||||||
"username": username,
|
"username": username,
|
||||||
"password": password,
|
"password": password,
|
||||||
}
|
}
|
||||||
except:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_comfyworkflows_auth():
|
def get_comfyworkflows_auth():
|
||||||
if not os.path.exists(os.path.join(core.manager_files_path, "comfyworkflows_sharekey")):
|
if not os.path.exists(os.path.join(context.manager_files_path, "comfyworkflows_sharekey")):
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(core.manager_files_path, "comfyworkflows_sharekey"), "r") as f:
|
with open(os.path.join(context.manager_files_path, "comfyworkflows_sharekey"), "r") as f:
|
||||||
share_key = f.read()
|
share_key = f.read()
|
||||||
if not share_key.strip():
|
if not share_key.strip():
|
||||||
return None
|
return None
|
||||||
return share_key
|
return share_key
|
||||||
except:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def get_youml_settings():
|
def get_youml_settings():
|
||||||
if not os.path.exists(os.path.join(core.manager_files_path, ".youml")):
|
if not os.path.exists(os.path.join(context.manager_files_path, ".youml")):
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
with open(os.path.join(core.manager_files_path, ".youml"), "r") as f:
|
with open(os.path.join(context.manager_files_path, ".youml"), "r") as f:
|
||||||
youml_settings = f.read().strip()
|
youml_settings = f.read().strip()
|
||||||
return youml_settings if youml_settings else None
|
return youml_settings if youml_settings else None
|
||||||
except:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def set_youml_settings(settings):
|
def set_youml_settings(settings):
|
||||||
with open(os.path.join(core.manager_files_path, ".youml"), "w") as f:
|
with open(os.path.join(context.manager_files_path, ".youml"), "w") as f:
|
||||||
f.write(settings)
|
f.write(settings)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/get_openart_auth")
|
@PromptServer.instance.routes.get("/v2/manager/get_openart_auth")
|
||||||
async def api_get_openart_auth(request):
|
async def api_get_openart_auth(request):
|
||||||
# print("Getting stored Matrix credentials...")
|
# print("Getting stored Matrix credentials...")
|
||||||
openart_key = get_openart_auth()
|
openart_key = get_openart_auth()
|
||||||
@@ -131,16 +133,16 @@ async def api_get_openart_auth(request):
|
|||||||
return web.json_response({"openart_key": openart_key})
|
return web.json_response({"openart_key": openart_key})
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/manager/set_openart_auth")
|
@PromptServer.instance.routes.post("/v2/manager/set_openart_auth")
|
||||||
async def api_set_openart_auth(request):
|
async def api_set_openart_auth(request):
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
openart_key = json_data['openart_key']
|
openart_key = json_data['openart_key']
|
||||||
with open(os.path.join(core.manager_files_path, ".openart_key"), "w") as f:
|
with open(os.path.join(context.manager_files_path, ".openart_key"), "w") as f:
|
||||||
f.write(openart_key)
|
f.write(openart_key)
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/get_matrix_auth")
|
@PromptServer.instance.routes.get("/v2/manager/get_matrix_auth")
|
||||||
async def api_get_matrix_auth(request):
|
async def api_get_matrix_auth(request):
|
||||||
# print("Getting stored Matrix credentials...")
|
# print("Getting stored Matrix credentials...")
|
||||||
matrix_auth = get_matrix_auth()
|
matrix_auth = get_matrix_auth()
|
||||||
@@ -149,7 +151,7 @@ async def api_get_matrix_auth(request):
|
|||||||
return web.json_response(matrix_auth)
|
return web.json_response(matrix_auth)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/youml/settings")
|
@PromptServer.instance.routes.get("/v2/manager/youml/settings")
|
||||||
async def api_get_youml_settings(request):
|
async def api_get_youml_settings(request):
|
||||||
youml_settings = get_youml_settings()
|
youml_settings = get_youml_settings()
|
||||||
if not youml_settings:
|
if not youml_settings:
|
||||||
@@ -157,14 +159,14 @@ async def api_get_youml_settings(request):
|
|||||||
return web.json_response(json.loads(youml_settings))
|
return web.json_response(json.loads(youml_settings))
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/manager/youml/settings")
|
@PromptServer.instance.routes.post("/v2/manager/youml/settings")
|
||||||
async def api_set_youml_settings(request):
|
async def api_set_youml_settings(request):
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
set_youml_settings(json.dumps(json_data))
|
set_youml_settings(json.dumps(json_data))
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/get_comfyworkflows_auth")
|
@PromptServer.instance.routes.get("/v2/manager/get_comfyworkflows_auth")
|
||||||
async def api_get_comfyworkflows_auth(request):
|
async def api_get_comfyworkflows_auth(request):
|
||||||
# Check if the user has provided Matrix credentials in a file called 'matrix_accesstoken'
|
# Check if the user has provided Matrix credentials in a file called 'matrix_accesstoken'
|
||||||
# in the same directory as the ComfyUI base folder
|
# in the same directory as the ComfyUI base folder
|
||||||
@@ -175,17 +177,17 @@ async def api_get_comfyworkflows_auth(request):
|
|||||||
return web.json_response({"comfyworkflows_sharekey": comfyworkflows_auth})
|
return web.json_response({"comfyworkflows_sharekey": comfyworkflows_auth})
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/manager/set_esheep_workflow_and_images")
|
@PromptServer.instance.routes.post("/v2/manager/set_esheep_workflow_and_images")
|
||||||
async def set_esheep_workflow_and_images(request):
|
async def set_esheep_workflow_and_images(request):
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
with open(os.path.join(core.manager_files_path, "esheep_share_message.json"), "w", encoding='utf-8') as file:
|
with open(os.path.join(context.manager_files_path, "esheep_share_message.json"), "w", encoding='utf-8') as file:
|
||||||
json.dump(json_data, file, indent=4)
|
json.dump(json_data, file, indent=4)
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.get("/manager/get_esheep_workflow_and_images")
|
@PromptServer.instance.routes.get("/v2/manager/get_esheep_workflow_and_images")
|
||||||
async def get_esheep_workflow_and_images(request):
|
async def get_esheep_workflow_and_images(request):
|
||||||
with open(os.path.join(core.manager_files_path, "esheep_share_message.json"), 'r', encoding='utf-8') as file:
|
with open(os.path.join(context.manager_files_path, "esheep_share_message.json"), 'r', encoding='utf-8') as file:
|
||||||
data = json.load(file)
|
data = json.load(file)
|
||||||
return web.Response(status=200, text=json.dumps(data))
|
return web.Response(status=200, text=json.dumps(data))
|
||||||
|
|
||||||
@@ -194,12 +196,12 @@ def set_matrix_auth(json_data):
|
|||||||
homeserver = json_data['homeserver']
|
homeserver = json_data['homeserver']
|
||||||
username = json_data['username']
|
username = json_data['username']
|
||||||
password = json_data['password']
|
password = json_data['password']
|
||||||
with open(os.path.join(core.manager_files_path, "matrix_auth"), "w") as f:
|
with open(os.path.join(context.manager_files_path, "matrix_auth"), "w") as f:
|
||||||
f.write("\n".join([homeserver, username, password]))
|
f.write("\n".join([homeserver, username, password]))
|
||||||
|
|
||||||
|
|
||||||
def set_comfyworkflows_auth(comfyworkflows_sharekey):
|
def set_comfyworkflows_auth(comfyworkflows_sharekey):
|
||||||
with open(os.path.join(core.manager_files_path, "comfyworkflows_sharekey"), "w") as f:
|
with open(os.path.join(context.manager_files_path, "comfyworkflows_sharekey"), "w") as f:
|
||||||
f.write(comfyworkflows_sharekey)
|
f.write(comfyworkflows_sharekey)
|
||||||
|
|
||||||
|
|
||||||
@@ -211,7 +213,7 @@ def has_provided_comfyworkflows_auth(comfyworkflows_sharekey):
|
|||||||
return comfyworkflows_sharekey.strip()
|
return comfyworkflows_sharekey.strip()
|
||||||
|
|
||||||
|
|
||||||
@PromptServer.instance.routes.post("/manager/share")
|
@PromptServer.instance.routes.post("/v2/manager/share")
|
||||||
async def share_art(request):
|
async def share_art(request):
|
||||||
# get json data
|
# get json data
|
||||||
json_data = await request.json()
|
json_data = await request.json()
|
||||||
@@ -233,7 +235,7 @@ async def share_art(request):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
output_to_share = potential_outputs[int(selected_output_index)]
|
output_to_share = potential_outputs[int(selected_output_index)]
|
||||||
except:
|
except Exception:
|
||||||
# for now, pick the first output
|
# for now, pick the first output
|
||||||
output_to_share = potential_outputs[0]
|
output_to_share = potential_outputs[0]
|
||||||
|
|
||||||
@@ -350,7 +352,7 @@ async def share_art(request):
|
|||||||
token = client.login(username=matrix_auth['username'], password=matrix_auth['password'])
|
token = client.login(username=matrix_auth['username'], password=matrix_auth['password'])
|
||||||
if not token:
|
if not token:
|
||||||
return web.json_response({"error": "Invalid Matrix credentials."}, content_type='application/json', status=400)
|
return web.json_response({"error": "Invalid Matrix credentials."}, content_type='application/json', status=400)
|
||||||
except:
|
except Exception:
|
||||||
return web.json_response({"error": "Invalid Matrix credentials."}, content_type='application/json', status=400)
|
return web.json_response({"error": "Invalid Matrix credentials."}, content_type='application/json', status=400)
|
||||||
|
|
||||||
matrix = MatrixHttpApi(homeserver, token=token)
|
matrix = MatrixHttpApi(homeserver, token=token)
|
||||||
@@ -369,7 +371,7 @@ async def share_art(request):
|
|||||||
matrix.send_message(comfyui_share_room_id, text_content)
|
matrix.send_message(comfyui_share_room_id, text_content)
|
||||||
matrix.send_content(comfyui_share_room_id, mxc_url, filename, 'm.image')
|
matrix.send_content(comfyui_share_room_id, mxc_url, filename, 'm.image')
|
||||||
matrix.send_content(comfyui_share_room_id, workflow_json_mxc_url, 'workflow.json', 'm.file')
|
matrix.send_content(comfyui_share_room_id, workflow_json_mxc_url, 'workflow.json', 'm.file')
|
||||||
except:
|
except Exception:
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
return web.json_response({"error": "An error occurred when sharing your art to Matrix."}, content_type='application/json', status=500)
|
return web.json_response({"error": "An error occurred when sharing your art to Matrix."}, content_type='application/json', status=500)
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import atexit
|
import atexit
|
||||||
@@ -9,21 +10,35 @@ import platform
|
|||||||
import json
|
import json
|
||||||
import ast
|
import ast
|
||||||
import logging
|
import logging
|
||||||
|
import traceback
|
||||||
|
|
||||||
glob_path = os.path.join(os.path.dirname(__file__), "glob")
|
from .common import security_check
|
||||||
sys.path.append(glob_path)
|
from .common import manager_util
|
||||||
|
from .common import cm_global
|
||||||
import security_check
|
from .common import manager_downloader
|
||||||
import manager_util
|
|
||||||
import cm_global
|
|
||||||
import manager_downloader
|
|
||||||
from datetime import datetime
|
|
||||||
import folder_paths
|
import folder_paths
|
||||||
|
|
||||||
|
manager_util.add_python_path_to_env()
|
||||||
|
|
||||||
|
import datetime as dt
|
||||||
|
|
||||||
|
if hasattr(dt, 'datetime'):
|
||||||
|
from datetime import datetime as dt_datetime
|
||||||
|
|
||||||
|
def current_timestamp():
|
||||||
|
return dt_datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
|
||||||
|
else:
|
||||||
|
# NOTE: Occurs in some Mac environments.
|
||||||
|
import time
|
||||||
|
logging.error(f"[ComfyUI-Manager] fallback timestamp mode\n datetime module is invalid: '{dt.__file__}'")
|
||||||
|
|
||||||
|
def current_timestamp():
|
||||||
|
return str(time.time()).split('.')[0]
|
||||||
|
|
||||||
security_check.security_check()
|
security_check.security_check()
|
||||||
|
|
||||||
cm_global.pip_blacklist = ['torch', 'torchsde', 'torchvision']
|
cm_global.pip_blacklist = {'torch', 'torchaudio', 'torchsde', 'torchvision'}
|
||||||
cm_global.pip_downgrade_blacklist = ['torch', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
|
cm_global.pip_downgrade_blacklist = ['torch', 'torchaudio', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
|
||||||
|
|
||||||
|
|
||||||
def skip_pip_spam(x):
|
def skip_pip_spam(x):
|
||||||
@@ -46,26 +61,16 @@ def is_import_failed_extension(name):
|
|||||||
return name in import_failed_extensions
|
return name in import_failed_extensions
|
||||||
|
|
||||||
|
|
||||||
def check_file_logging():
|
|
||||||
global enable_file_logging
|
|
||||||
try:
|
|
||||||
import configparser
|
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "config.ini")
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.read(config_path)
|
|
||||||
default_conf = config['default']
|
|
||||||
|
|
||||||
if 'file_logging' in default_conf and default_conf['file_logging'].lower() == 'false':
|
|
||||||
enable_file_logging = False
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
check_file_logging()
|
|
||||||
|
|
||||||
comfy_path = os.environ.get('COMFYUI_PATH')
|
comfy_path = os.environ.get('COMFYUI_PATH')
|
||||||
|
comfy_base_path = os.environ.get('COMFYUI_FOLDERS_BASE_PATH')
|
||||||
|
|
||||||
if comfy_path is None:
|
if comfy_path is None:
|
||||||
comfy_path = os.path.abspath(os.path.dirname(sys.modules['__main__'].__file__))
|
comfy_path = os.path.abspath(os.path.dirname(sys.modules['__main__'].__file__))
|
||||||
|
os.environ['COMFYUI_PATH'] = comfy_path
|
||||||
|
|
||||||
|
if comfy_base_path is None:
|
||||||
|
comfy_base_path = comfy_path
|
||||||
|
|
||||||
|
|
||||||
sys.__comfyui_manager_register_message_collapse = register_message_collapse
|
sys.__comfyui_manager_register_message_collapse = register_message_collapse
|
||||||
sys.__comfyui_manager_is_import_failed_extension = is_import_failed_extension
|
sys.__comfyui_manager_is_import_failed_extension = is_import_failed_extension
|
||||||
@@ -78,18 +83,55 @@ comfyui_manager_path = os.path.abspath(os.path.dirname(__file__))
|
|||||||
custom_nodes_base_path = folder_paths.get_folder_paths('custom_nodes')[0]
|
custom_nodes_base_path = folder_paths.get_folder_paths('custom_nodes')[0]
|
||||||
manager_files_path = os.path.abspath(os.path.join(folder_paths.get_user_directory(), 'default', 'ComfyUI-Manager'))
|
manager_files_path = os.path.abspath(os.path.join(folder_paths.get_user_directory(), 'default', 'ComfyUI-Manager'))
|
||||||
manager_pip_overrides_path = os.path.join(manager_files_path, "pip_overrides.json")
|
manager_pip_overrides_path = os.path.join(manager_files_path, "pip_overrides.json")
|
||||||
|
manager_pip_blacklist_path = os.path.join(manager_files_path, "pip_blacklist.list")
|
||||||
restore_snapshot_path = os.path.join(manager_files_path, "startup-scripts", "restore-snapshot.json")
|
restore_snapshot_path = os.path.join(manager_files_path, "startup-scripts", "restore-snapshot.json")
|
||||||
|
manager_config_path = os.path.join(manager_files_path, 'config.ini')
|
||||||
|
|
||||||
git_script_path = os.path.join(comfyui_manager_path, "git_helper.py")
|
default_conf = {}
|
||||||
cm_cli_path = os.path.join(comfyui_manager_path, "cm-cli.py")
|
|
||||||
|
def read_config():
|
||||||
|
global default_conf
|
||||||
|
try:
|
||||||
|
import configparser
|
||||||
|
config = configparser.ConfigParser(strict=False)
|
||||||
|
config.read(manager_config_path)
|
||||||
|
default_conf = config['default']
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def read_uv_mode():
|
||||||
|
if 'use_uv' in default_conf:
|
||||||
|
manager_util.use_uv = default_conf['use_uv'].lower() == 'true'
|
||||||
|
|
||||||
|
def check_file_logging():
|
||||||
|
global enable_file_logging
|
||||||
|
if 'file_logging' in default_conf and default_conf['file_logging'].lower() == 'false':
|
||||||
|
enable_file_logging = False
|
||||||
|
|
||||||
|
|
||||||
cm_global.pip_overrides = {}
|
read_config()
|
||||||
|
read_uv_mode()
|
||||||
|
check_file_logging()
|
||||||
|
|
||||||
|
if sys.version_info < (3, 13):
|
||||||
|
cm_global.pip_overrides = {'numpy': 'numpy<2'}
|
||||||
|
else:
|
||||||
|
cm_global.pip_overrides = {}
|
||||||
|
|
||||||
if os.path.exists(manager_pip_overrides_path):
|
if os.path.exists(manager_pip_overrides_path):
|
||||||
with open(manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file:
|
with open(manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file:
|
||||||
cm_global.pip_overrides = json.load(json_file)
|
cm_global.pip_overrides = json.load(json_file)
|
||||||
cm_global.pip_overrides['numpy'] = 'numpy<2'
|
|
||||||
cm_global.pip_overrides['ultralytics'] = 'ultralytics==8.3.40' # for security
|
if sys.version_info < (3, 13):
|
||||||
|
cm_global.pip_overrides['numpy'] = 'numpy<2'
|
||||||
|
|
||||||
|
|
||||||
|
if os.path.exists(manager_pip_blacklist_path):
|
||||||
|
with open(manager_pip_blacklist_path, 'r', encoding="UTF-8", errors="ignore") as f:
|
||||||
|
for x in f.readlines():
|
||||||
|
y = x.strip()
|
||||||
|
if y != '':
|
||||||
|
cm_global.pip_blacklist.add(y)
|
||||||
|
|
||||||
|
|
||||||
def remap_pip_package(pkg):
|
def remap_pip_package(pkg):
|
||||||
@@ -137,6 +179,48 @@ def process_wrap(cmd_str, cwd_path, handler=None, env=None):
|
|||||||
return process.wait()
|
return process.wait()
|
||||||
|
|
||||||
|
|
||||||
|
original_stdout = sys.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def try_get_custom_nodes(x):
|
||||||
|
for custom_nodes_dir in folder_paths.get_folder_paths('custom_nodes'):
|
||||||
|
if x.startswith(custom_nodes_dir):
|
||||||
|
relative_path = os.path.relpath(x, custom_nodes_dir)
|
||||||
|
next_segment = relative_path.split(os.sep)[0]
|
||||||
|
if next_segment.lower() != 'comfyui-manager':
|
||||||
|
return next_segment, os.path.join(custom_nodes_dir, next_segment)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def extract_origin_module():
|
||||||
|
stack = traceback.extract_stack()[:-2]
|
||||||
|
for frame in reversed(stack):
|
||||||
|
info = try_get_custom_nodes(frame.filename)
|
||||||
|
if info is None:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return info
|
||||||
|
return None
|
||||||
|
|
||||||
|
def extract_origin_module_from_strings(file_paths):
|
||||||
|
for filepath in file_paths:
|
||||||
|
info = try_get_custom_nodes(filepath)
|
||||||
|
if info is None:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
return info
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def finalize_startup():
|
||||||
|
res = {}
|
||||||
|
for k, v in cm_global.error_dict.items():
|
||||||
|
if v['path'] in import_failed_extensions:
|
||||||
|
res[k] = v
|
||||||
|
|
||||||
|
cm_global.error_dict = res
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if '--port' in sys.argv:
|
if '--port' in sys.argv:
|
||||||
port_index = sys.argv.index('--port')
|
port_index = sys.argv.index('--port')
|
||||||
@@ -153,6 +237,9 @@ try:
|
|||||||
if enable_file_logging:
|
if enable_file_logging:
|
||||||
log_path_base = os.path.join(folder_paths.user_directory, 'comfyui')
|
log_path_base = os.path.join(folder_paths.user_directory, 'comfyui')
|
||||||
|
|
||||||
|
if not os.path.exists(folder_paths.user_directory):
|
||||||
|
os.makedirs(folder_paths.user_directory)
|
||||||
|
|
||||||
if os.path.exists(f"{log_path_base}{postfix}.log"):
|
if os.path.exists(f"{log_path_base}{postfix}.log"):
|
||||||
if os.path.exists(f"{log_path_base}{postfix}.prev.log"):
|
if os.path.exists(f"{log_path_base}{postfix}.prev.log"):
|
||||||
if os.path.exists(f"{log_path_base}{postfix}.prev2.log"):
|
if os.path.exists(f"{log_path_base}{postfix}.prev2.log"):
|
||||||
@@ -216,8 +303,16 @@ try:
|
|||||||
if match:
|
if match:
|
||||||
import_failed_extensions.add(match.group(1).strip())
|
import_failed_extensions.add(match.group(1).strip())
|
||||||
|
|
||||||
if 'Starting server' in message:
|
if not self.is_stdout:
|
||||||
is_start_mode = False
|
origin_info = extract_origin_module()
|
||||||
|
if origin_info is not None:
|
||||||
|
name, origin_path = origin_info
|
||||||
|
|
||||||
|
if name != 'comfyui-manager':
|
||||||
|
if name not in cm_global.error_dict:
|
||||||
|
cm_global.error_dict[name] = {'name': name, 'path': origin_path, 'msg': ''}
|
||||||
|
|
||||||
|
cm_global.error_dict[name]['msg'] += message
|
||||||
|
|
||||||
if not self.is_stdout:
|
if not self.is_stdout:
|
||||||
match = re.search(pat_tqdm, message)
|
match = re.search(pat_tqdm, message)
|
||||||
@@ -236,12 +331,17 @@ try:
|
|||||||
|
|
||||||
def sync_write(self, message, file_only=False):
|
def sync_write(self, message, file_only=False):
|
||||||
with log_lock:
|
with log_lock:
|
||||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
|
timestamp = current_timestamp()
|
||||||
if self.last_char != '\n':
|
if self.last_char != '\n':
|
||||||
log_file.write(message)
|
log_file.write(message)
|
||||||
else:
|
else:
|
||||||
log_file.write(f"[{timestamp}] {message}")
|
log_file.write(f"[{timestamp}] {message}")
|
||||||
log_file.flush()
|
|
||||||
|
try:
|
||||||
|
log_file.flush()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
self.last_char = message if message == '' else message[-1]
|
self.last_char = message if message == '' else message[-1]
|
||||||
|
|
||||||
if not file_only:
|
if not file_only:
|
||||||
@@ -254,7 +354,10 @@ try:
|
|||||||
original_stderr.flush()
|
original_stderr.flush()
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
log_file.flush()
|
try:
|
||||||
|
log_file.flush()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
with std_log_lock:
|
with std_log_lock:
|
||||||
if self.is_stdout:
|
if self.is_stdout:
|
||||||
@@ -295,19 +398,45 @@ try:
|
|||||||
def emit(self, record):
|
def emit(self, record):
|
||||||
global is_start_mode
|
global is_start_mode
|
||||||
|
|
||||||
message = record.getMessage()
|
try:
|
||||||
|
message = record.getMessage()
|
||||||
|
except Exception as e:
|
||||||
|
message = f"<<logging error>>: {record} - {e}"
|
||||||
|
original_stderr.write(message)
|
||||||
|
|
||||||
if is_start_mode:
|
if is_start_mode:
|
||||||
match = re.search(pat_import_fail, message)
|
match = re.search(pat_import_fail, message)
|
||||||
if match:
|
if match:
|
||||||
import_failed_extensions.add(match.group(1).strip())
|
import_failed_extensions.add(match.group(1).strip())
|
||||||
|
|
||||||
|
if 'Traceback' in message:
|
||||||
|
file_lists = self._extract_file_paths(message)
|
||||||
|
origin_info = extract_origin_module_from_strings(file_lists)
|
||||||
|
if origin_info is not None:
|
||||||
|
name, origin_path = origin_info
|
||||||
|
|
||||||
|
if name != 'comfyui-manager':
|
||||||
|
if name not in cm_global.error_dict:
|
||||||
|
cm_global.error_dict[name] = {'name': name, 'path': origin_path, 'msg': ''}
|
||||||
|
|
||||||
|
cm_global.error_dict[name]['msg'] += message
|
||||||
|
|
||||||
if 'Starting server' in message:
|
if 'Starting server' in message:
|
||||||
is_start_mode = False
|
is_start_mode = False
|
||||||
|
finalize_startup()
|
||||||
|
|
||||||
if stderr_wrapper:
|
if stderr_wrapper:
|
||||||
stderr_wrapper.sync_write(message+'\n', file_only=True)
|
stderr_wrapper.sync_write(message+'\n', file_only=True)
|
||||||
|
|
||||||
|
def _extract_file_paths(self, msg):
|
||||||
|
file_paths = []
|
||||||
|
for line in msg.split('\n'):
|
||||||
|
match = re.findall(r'File \"(.*?)\", line \d+', line)
|
||||||
|
for x in match:
|
||||||
|
if not x.startswith('<'):
|
||||||
|
file_paths.extend(match)
|
||||||
|
return file_paths
|
||||||
|
|
||||||
|
|
||||||
logging.getLogger().addHandler(LoggingHandler())
|
logging.getLogger().addHandler(LoggingHandler())
|
||||||
|
|
||||||
@@ -316,35 +445,15 @@ except Exception as e:
|
|||||||
print(f"[ComfyUI-Manager] Logging failed: {e}")
|
print(f"[ComfyUI-Manager] Logging failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
try:
|
print("** ComfyUI startup time:", current_timestamp())
|
||||||
import git # noqa: F401
|
|
||||||
import toml # noqa: F401
|
|
||||||
except ModuleNotFoundError:
|
|
||||||
my_path = os.path.dirname(__file__)
|
|
||||||
requirements_path = os.path.join(my_path, "requirements.txt")
|
|
||||||
|
|
||||||
print("## ComfyUI-Manager: installing dependencies. (GitPython)")
|
|
||||||
try:
|
|
||||||
result = subprocess.check_output([sys.executable, '-s', '-m', 'pip', 'install', '-r', requirements_path])
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
print("## [ERROR] ComfyUI-Manager: Attempting to reinstall dependencies using an alternative method.")
|
|
||||||
try:
|
|
||||||
result = subprocess.check_output([sys.executable, '-s', '-m', 'pip', 'install', '--user', '-r', requirements_path])
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
print("## [ERROR] ComfyUI-Manager: Failed to install the GitPython package in the correct Python environment. Please install it manually in the appropriate environment. (You can seek help at https://app.element.io/#/room/%23comfyui_space%3Amatrix.org)")
|
|
||||||
|
|
||||||
try:
|
|
||||||
print("## ComfyUI-Manager: installing dependencies done.")
|
|
||||||
except:
|
|
||||||
# maybe we should sys.exit() here? there is at least two screens worth of error messages still being pumped after our error messages
|
|
||||||
print("## [ERROR] ComfyUI-Manager: GitPython package seems to be installed, but failed to load somehow. Make sure you have a working git client installed")
|
|
||||||
|
|
||||||
|
|
||||||
print("** ComfyUI startup time:", datetime.now())
|
|
||||||
print("** Platform:", platform.system())
|
print("** Platform:", platform.system())
|
||||||
print("** Python version:", sys.version)
|
print("** Python version:", sys.version)
|
||||||
print("** Python executable:", sys.executable)
|
print("** Python executable:", sys.executable)
|
||||||
print("** ComfyUI Path:", comfy_path)
|
print("** ComfyUI Path:", comfy_path)
|
||||||
|
print("** ComfyUI Base Folder Path:", comfy_base_path)
|
||||||
|
print("** User directory:", folder_paths.user_directory)
|
||||||
|
print("** ComfyUI-Manager config path:", manager_config_path)
|
||||||
|
|
||||||
|
|
||||||
if log_path_base is not None:
|
if log_path_base is not None:
|
||||||
print("** Log path:", os.path.abspath(f'{log_path_base}.log'))
|
print("** Log path:", os.path.abspath(f'{log_path_base}.log'))
|
||||||
@@ -354,18 +463,12 @@ else:
|
|||||||
|
|
||||||
def read_downgrade_blacklist():
|
def read_downgrade_blacklist():
|
||||||
try:
|
try:
|
||||||
import configparser
|
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "config.ini")
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.read(config_path)
|
|
||||||
default_conf = config['default']
|
|
||||||
|
|
||||||
if 'downgrade_blacklist' in default_conf:
|
if 'downgrade_blacklist' in default_conf:
|
||||||
items = default_conf['downgrade_blacklist'].split(',')
|
items = default_conf['downgrade_blacklist'].split(',')
|
||||||
items = [x.strip() for x in items if x != '']
|
items = [x.strip() for x in items if x != '']
|
||||||
cm_global.pip_downgrade_blacklist += items
|
cm_global.pip_downgrade_blacklist += items
|
||||||
cm_global.pip_downgrade_blacklist = list(set(cm_global.pip_downgrade_blacklist))
|
cm_global.pip_downgrade_blacklist = list(set(cm_global.pip_downgrade_blacklist))
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -374,27 +477,20 @@ read_downgrade_blacklist()
|
|||||||
|
|
||||||
def check_bypass_ssl():
|
def check_bypass_ssl():
|
||||||
try:
|
try:
|
||||||
import configparser
|
|
||||||
import ssl
|
import ssl
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "config.ini")
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.read(config_path)
|
|
||||||
default_conf = config['default']
|
|
||||||
|
|
||||||
if 'bypass_ssl' in default_conf and default_conf['bypass_ssl'].lower() == 'true':
|
if 'bypass_ssl' in default_conf and default_conf['bypass_ssl'].lower() == 'true':
|
||||||
print("[ComfyUI-Manager] WARN: Unsafe - SSL verification bypass option is Enabled. (see ComfyUI-Manager/config.ini)")
|
print(f"[ComfyUI-Manager] WARN: Unsafe - SSL verification bypass option is Enabled. (see {manager_config_path})")
|
||||||
ssl._create_default_https_context = ssl._create_unverified_context # SSL certificate error fix.
|
ssl._create_default_https_context = ssl._create_unverified_context # SSL certificate error fix.
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
check_bypass_ssl()
|
check_bypass_ssl()
|
||||||
|
|
||||||
|
|
||||||
# Perform install
|
# Perform install
|
||||||
processed_install = set()
|
processed_install = set()
|
||||||
script_list_path = os.path.join(folder_paths.user_directory, "default", "ComfyUI-Manager", "startup-scripts", "install-scripts.txt")
|
script_list_path = os.path.join(folder_paths.user_directory, "default", "ComfyUI-Manager", "startup-scripts", "install-scripts.txt")
|
||||||
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages())
|
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, manager_files_path)
|
||||||
|
|
||||||
|
|
||||||
def is_installed(name):
|
def is_installed(name):
|
||||||
@@ -403,7 +499,7 @@ def is_installed(name):
|
|||||||
if name.startswith('#'):
|
if name.startswith('#'):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
pattern = r'([^<>!=]+)([<>!=]=?)([0-9.a-zA-Z]*)'
|
pattern = r'([^<>!~=]+)([<>!~=]=?)([0-9.a-zA-Z]*)'
|
||||||
match = re.search(pattern, name)
|
match = re.search(pattern, name)
|
||||||
|
|
||||||
if match:
|
if match:
|
||||||
@@ -418,7 +514,7 @@ def is_installed(name):
|
|||||||
if match is None:
|
if match is None:
|
||||||
if name in pips:
|
if name in pips:
|
||||||
return True
|
return True
|
||||||
elif match.group(2) in ['<=', '==', '<']:
|
elif match.group(2) in ['<=', '==', '<', '~=']:
|
||||||
if name in pips:
|
if name in pips:
|
||||||
if manager_util.StrictVersion(pips[name]) >= manager_util.StrictVersion(match.group(3)):
|
if manager_util.StrictVersion(pips[name]) >= manager_util.StrictVersion(match.group(3)):
|
||||||
print(f"[ComfyUI-Manager] skip black listed pip installation: '{name}'")
|
print(f"[ComfyUI-Manager] skip black listed pip installation: '{name}'")
|
||||||
@@ -475,9 +571,13 @@ if os.path.exists(restore_snapshot_path):
|
|||||||
|
|
||||||
print("[ComfyUI-Manager] Restore snapshot.")
|
print("[ComfyUI-Manager] Restore snapshot.")
|
||||||
new_env = os.environ.copy()
|
new_env = os.environ.copy()
|
||||||
new_env["COMFYUI_PATH"] = comfy_path
|
if 'COMFYUI_FOLDERS_BASE_PATH' not in new_env:
|
||||||
|
new_env["COMFYUI_FOLDERS_BASE_PATH"] = comfy_path
|
||||||
|
|
||||||
cmd_str = [sys.executable, cm_cli_path, 'restore-snapshot', restore_snapshot_path]
|
if 'COMFYUI_PATH' not in new_env:
|
||||||
|
new_env['COMFYUI_PATH'] = os.path.dirname(folder_paths.__file__)
|
||||||
|
|
||||||
|
cmd_str = [sys.executable, '-m', 'comfyui_manager.cm_cli', 'restore-snapshot', restore_snapshot_path]
|
||||||
exit_code = process_wrap(cmd_str, custom_nodes_base_path, handler=msg_capture, env=new_env)
|
exit_code = process_wrap(cmd_str, custom_nodes_base_path, handler=msg_capture, env=new_env)
|
||||||
|
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
@@ -500,17 +600,19 @@ def execute_lazy_install_script(repo_path, executable):
|
|||||||
|
|
||||||
if os.path.exists(requirements_path):
|
if os.path.exists(requirements_path):
|
||||||
print(f"Install: pip packages for '{repo_path}'")
|
print(f"Install: pip packages for '{repo_path}'")
|
||||||
with open(requirements_path, "r") as requirements_file:
|
|
||||||
for line in requirements_file:
|
|
||||||
package_name = remap_pip_package(line.strip())
|
|
||||||
if package_name and not is_installed(package_name):
|
|
||||||
if '--index-url' in package_name:
|
|
||||||
s = package_name.split('--index-url')
|
|
||||||
install_cmd = [sys.executable, "-m", "pip", "install", s[0].strip(), '--index-url', s[1].strip()]
|
|
||||||
else:
|
|
||||||
install_cmd = [sys.executable, "-m", "pip", "install", package_name]
|
|
||||||
|
|
||||||
process_wrap(install_cmd, repo_path)
|
lines = manager_util.robust_readlines(requirements_path)
|
||||||
|
for line in lines:
|
||||||
|
package_name = remap_pip_package(line.strip())
|
||||||
|
package_name = package_name.split('#')[0].strip()
|
||||||
|
if package_name and not is_installed(package_name):
|
||||||
|
if '--index-url' in package_name:
|
||||||
|
s = package_name.split('--index-url')
|
||||||
|
install_cmd = manager_util.make_pip_cmd(["install", s[0].strip(), '--index-url', s[1].strip()])
|
||||||
|
else:
|
||||||
|
install_cmd = manager_util.make_pip_cmd(["install", package_name])
|
||||||
|
|
||||||
|
process_wrap(install_cmd, repo_path)
|
||||||
|
|
||||||
if os.path.exists(install_script_path) and f'{repo_path}/install.py' not in processed_install:
|
if os.path.exists(install_script_path) and f'{repo_path}/install.py' not in processed_install:
|
||||||
processed_install.add(f'{repo_path}/install.py')
|
processed_install.add(f'{repo_path}/install.py')
|
||||||
@@ -518,7 +620,8 @@ def execute_lazy_install_script(repo_path, executable):
|
|||||||
install_cmd = [executable, "install.py"]
|
install_cmd = [executable, "install.py"]
|
||||||
|
|
||||||
new_env = os.environ.copy()
|
new_env = os.environ.copy()
|
||||||
new_env["COMFYUI_PATH"] = comfy_path
|
if 'COMFYUI_FOLDERS_BASE_PATH' not in new_env:
|
||||||
|
new_env["COMFYUI_FOLDERS_BASE_PATH"] = comfy_path
|
||||||
process_wrap(install_cmd, repo_path, env=new_env)
|
process_wrap(install_cmd, repo_path, env=new_env)
|
||||||
|
|
||||||
|
|
||||||
@@ -573,19 +676,43 @@ def execute_lazy_cnr_switch(target, zip_url, from_path, to_path, no_deps, custom
|
|||||||
file.write('\n'.join(list(extracted)))
|
file.write('\n'.join(list(extracted)))
|
||||||
|
|
||||||
|
|
||||||
def execute_migration(moves):
|
script_executed = False
|
||||||
import shutil
|
|
||||||
for x in moves:
|
|
||||||
if os.path.exists(x[0]) and not os.path.exists(x[1]):
|
|
||||||
shutil.move(x[0], x[1])
|
|
||||||
print(f"[ComfyUI-Manager] MIGRATION: '{x[0]}' -> '{x[1]}'")
|
|
||||||
|
|
||||||
|
def execute_startup_script():
|
||||||
# Check if script_list_path exists
|
global script_executed
|
||||||
if os.path.exists(script_list_path):
|
|
||||||
print("\n#######################################################################")
|
print("\n#######################################################################")
|
||||||
print("[ComfyUI-Manager] Starting dependency installation/(de)activation for the extension\n")
|
print("[ComfyUI-Manager] Starting dependency installation/(de)activation for the extension\n")
|
||||||
|
|
||||||
|
custom_nodelist_cache = None
|
||||||
|
|
||||||
|
def get_custom_node_paths():
|
||||||
|
nonlocal custom_nodelist_cache
|
||||||
|
if custom_nodelist_cache is None:
|
||||||
|
custom_nodelist_cache = set()
|
||||||
|
for base in folder_paths.get_folder_paths('custom_nodes'):
|
||||||
|
for x in os.listdir(base):
|
||||||
|
fullpath = os.path.join(base, x)
|
||||||
|
if os.path.isdir(fullpath):
|
||||||
|
custom_nodelist_cache.add(fullpath)
|
||||||
|
|
||||||
|
return custom_nodelist_cache
|
||||||
|
|
||||||
|
def execute_lazy_delete(path):
|
||||||
|
# Validate to prevent arbitrary paths from being deleted
|
||||||
|
if path not in get_custom_node_paths():
|
||||||
|
logging.error(f"## ComfyUI-Manager: The scheduled '{path}' is not a custom node path, so the deletion has been canceled.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not os.path.exists(path):
|
||||||
|
logging.info(f"## ComfyUI-Manager: SKIP-DELETE => '{path}' (already deleted)")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
shutil.rmtree(path)
|
||||||
|
logging.info(f"## ComfyUI-Manager: DELETE => '{path}'")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"## ComfyUI-Manager: Failed to delete '{path}' ({e})")
|
||||||
|
|
||||||
executed = set()
|
executed = set()
|
||||||
# Read each line from the file and convert it to a list using eval
|
# Read each line from the file and convert it to a list using eval
|
||||||
with open(script_list_path, 'r', encoding="UTF-8", errors="ignore") as file:
|
with open(script_list_path, 'r', encoding="UTF-8", errors="ignore") as file:
|
||||||
@@ -606,8 +733,8 @@ if os.path.exists(script_list_path):
|
|||||||
execute_lazy_cnr_switch(script[0], script[2], script[3], script[4], script[5], script[6])
|
execute_lazy_cnr_switch(script[0], script[2], script[3], script[4], script[5], script[6])
|
||||||
execute_lazy_install_script(script[3], script[7])
|
execute_lazy_install_script(script[3], script[7])
|
||||||
|
|
||||||
elif script[1] == "#LAZY-MIGRATION":
|
elif script[1] == "#LAZY-DELETE-NODEPACK":
|
||||||
execute_migration(script[2])
|
execute_lazy_delete(script[2])
|
||||||
|
|
||||||
elif os.path.exists(script[0]):
|
elif os.path.exists(script[0]):
|
||||||
if script[1] == "#FORCE":
|
if script[1] == "#FORCE":
|
||||||
@@ -617,40 +744,73 @@ if os.path.exists(script_list_path):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
print(f"\n## ComfyUI-Manager: EXECUTE => {script[1:]}")
|
print(f"\n## ComfyUI-Manager: EXECUTE => {script[1:]}")
|
||||||
print(f"\n## Execute install/(de)activation script for '{script[0]}'")
|
print(f"\n## Execute management script for '{script[0]}'")
|
||||||
|
|
||||||
new_env = os.environ.copy()
|
new_env = os.environ.copy()
|
||||||
new_env["COMFYUI_PATH"] = comfy_path
|
if 'COMFYUI_FOLDERS_BASE_PATH' not in new_env:
|
||||||
|
new_env["COMFYUI_FOLDERS_BASE_PATH"] = comfy_path
|
||||||
exit_code = process_wrap(script[1:], script[0], env=new_env)
|
exit_code = process_wrap(script[1:], script[0], env=new_env)
|
||||||
|
|
||||||
if exit_code != 0:
|
if exit_code != 0:
|
||||||
print(f"install/(de)activation script failed: {script[0]}")
|
print(f"management script failed: {script[0]}")
|
||||||
else:
|
else:
|
||||||
print(f"\n## ComfyUI-Manager: CANCELED => {script[1:]}")
|
print(f"\n## ComfyUI-Manager: CANCELED => {script[1:]}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[ERROR] Failed to execute install/(de)activation script: {line} / {e}")
|
print(f"[ERROR] Failed to execute management script: {line} / {e}")
|
||||||
|
|
||||||
# Remove the script_list_path file
|
# Remove the script_list_path file
|
||||||
if os.path.exists(script_list_path):
|
if os.path.exists(script_list_path):
|
||||||
|
script_executed = True
|
||||||
os.remove(script_list_path)
|
os.remove(script_list_path)
|
||||||
|
|
||||||
print("\n[ComfyUI-Manager] Startup script completed.")
|
print("\n[ComfyUI-Manager] Startup script completed.")
|
||||||
print("#######################################################################\n")
|
print("#######################################################################\n")
|
||||||
|
|
||||||
|
|
||||||
|
# Check if script_list_path exists
|
||||||
|
if os.path.exists(script_list_path):
|
||||||
|
execute_startup_script()
|
||||||
|
|
||||||
|
|
||||||
pip_fixer.fix_broken()
|
pip_fixer.fix_broken()
|
||||||
|
|
||||||
del processed_install
|
del processed_install
|
||||||
del pip_fixer
|
del pip_fixer
|
||||||
manager_util.clear_pip_cache()
|
manager_util.clear_pip_cache()
|
||||||
|
|
||||||
|
if script_executed:
|
||||||
|
# Restart
|
||||||
|
print("[ComfyUI-Manager] Restarting to reapply dependency installation.")
|
||||||
|
|
||||||
|
if '__COMFY_CLI_SESSION__' in os.environ:
|
||||||
|
with open(os.path.join(os.environ['__COMFY_CLI_SESSION__'] + '.reboot'), 'w'):
|
||||||
|
pass
|
||||||
|
|
||||||
|
print("--------------------------------------------------------------------------\n")
|
||||||
|
exit(0)
|
||||||
|
else:
|
||||||
|
sys_argv = sys.argv.copy()
|
||||||
|
|
||||||
|
if sys_argv[0].endswith("__main__.py"): # this is a python module
|
||||||
|
module_name = os.path.basename(os.path.dirname(sys_argv[0]))
|
||||||
|
cmds = [sys.executable, '-m', module_name] + sys_argv[1:]
|
||||||
|
elif sys.platform.startswith('win32'):
|
||||||
|
cmds = ['"' + sys.executable + '"', '"' + sys_argv[0] + '"'] + sys_argv[1:]
|
||||||
|
else:
|
||||||
|
cmds = [sys.executable] + sys_argv
|
||||||
|
|
||||||
|
print(f"Command: {cmds}", flush=True)
|
||||||
|
print("--------------------------------------------------------------------------\n")
|
||||||
|
|
||||||
|
os.execv(sys.executable, cmds)
|
||||||
|
|
||||||
|
|
||||||
def check_windows_event_loop_policy():
|
def check_windows_event_loop_policy():
|
||||||
try:
|
try:
|
||||||
import configparser
|
import configparser
|
||||||
config_path = os.path.join(os.path.dirname(__file__), "config.ini")
|
config = configparser.ConfigParser(strict=False)
|
||||||
config = configparser.ConfigParser()
|
config.read(manager_config_path)
|
||||||
config.read(config_path)
|
|
||||||
default_conf = config['default']
|
default_conf = config['default']
|
||||||
|
|
||||||
if 'windows_selector_event_loop_policy' in default_conf and default_conf['windows_selector_event_loop_policy'].lower() == 'true':
|
if 'windows_selector_event_loop_policy' in default_conf and default_conf['windows_selector_event_loop_policy'].lower() == 'true':
|
||||||
9811
custom-node-list.json
Normal file → Executable file
9811
custom-node-list.json
Normal file → Executable file
File diff suppressed because it is too large
Load Diff
41
docs/README.md
Normal file
41
docs/README.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# ComfyUI-Manager: Documentation
|
||||||
|
|
||||||
|
This directory contains documentation for the ComfyUI-Manager, providing guides and tutorials for users in multiple languages.
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
The documentation is organized into language-specific directories:
|
||||||
|
|
||||||
|
- **en/**: English documentation
|
||||||
|
- **ko/**: Korean documentation
|
||||||
|
|
||||||
|
## Core Documentation Files
|
||||||
|
|
||||||
|
### Command-Line Interface
|
||||||
|
|
||||||
|
- **cm-cli.md**: Documentation for the ComfyUI-Manager Command Line Interface (CLI), which allows using manager functionality without the UI.
|
||||||
|
|
||||||
|
### Advanced Features
|
||||||
|
|
||||||
|
- **use_aria2.md**: Guide for using the aria2 download accelerator with ComfyUI-Manager for faster model downloads.
|
||||||
|
|
||||||
|
## Documentation Standards
|
||||||
|
|
||||||
|
The documentation follows these standards:
|
||||||
|
|
||||||
|
1. **Markdown Format**: All documentation is written in Markdown for easy rendering on GitHub and other platforms
|
||||||
|
2. **Language-specific Directories**: Content is separated by language to facilitate localization
|
||||||
|
3. **Feature-focused Documentation**: Each major feature has its own documentation file
|
||||||
|
4. **Updated with Releases**: Documentation is kept in sync with software releases
|
||||||
|
|
||||||
|
## Contributing to Documentation
|
||||||
|
|
||||||
|
When contributing new documentation:
|
||||||
|
|
||||||
|
1. Place files in the appropriate language directory
|
||||||
|
2. Use clear, concise language appropriate for the target audience
|
||||||
|
3. Include examples where helpful
|
||||||
|
4. Consider adding screenshots or diagrams for complex features
|
||||||
|
5. Maintain consistent formatting with existing documentation
|
||||||
|
|
||||||
|
This documentation directory will continue to grow to support the expanding feature set of ComfyUI-Manager.
|
||||||
@@ -122,7 +122,8 @@ ComfyUI-Loopchain
|
|||||||
* `--pip-non-url`: Restore for pip packages registered on PyPI.
|
* `--pip-non-url`: Restore for pip packages registered on PyPI.
|
||||||
* `--pip-non-local-url`: Restore for pip packages registered at web URLs.
|
* `--pip-non-local-url`: Restore for pip packages registered at web URLs.
|
||||||
* `--pip-local-url`: Restore for pip packages specified by local paths.
|
* `--pip-local-url`: Restore for pip packages specified by local paths.
|
||||||
|
* `--user-directory`: Set the user directory.
|
||||||
|
* `--restore-to`: The path where the restored custom nodes will be installed. (When this option is applied, only the custom nodes installed in the target path are recognized as installed.)
|
||||||
|
|
||||||
### 5. CLI Only Mode
|
### 5. CLI Only Mode
|
||||||
|
|
||||||
|
|||||||
@@ -123,7 +123,8 @@ ComfyUI-Loopchain
|
|||||||
* `--pip-non-url`: PyPI 에 등록된 pip 패키지들에 대해서 복구를 수행
|
* `--pip-non-url`: PyPI 에 등록된 pip 패키지들에 대해서 복구를 수행
|
||||||
* `--pip-non-local-url`: web URL에 등록된 pip 패키지들에 대해서 복구를 수행
|
* `--pip-non-local-url`: web URL에 등록된 pip 패키지들에 대해서 복구를 수행
|
||||||
* `--pip-local-url`: local 경로를 지정하고 있는 pip 패키지들에 대해서 복구를 수행
|
* `--pip-local-url`: local 경로를 지정하고 있는 pip 패키지들에 대해서 복구를 수행
|
||||||
|
* `--user-directory`: 사용자 디렉토리 설정
|
||||||
|
* `--restore-to`: 복구될 커스텀 노드가 설치될 경로. (이 옵션을 적용할 경우 오직 대상 경로에 설치된 custom nodes 만 설치된 것으로 인식함.)
|
||||||
|
|
||||||
### 5. CLI only mode
|
### 5. CLI only mode
|
||||||
|
|
||||||
|
|||||||
13276
extension-node-map.json
13276
extension-node-map.json
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@
|
|||||||
"comfyui-layerdiffuse",
|
"comfyui-layerdiffuse",
|
||||||
"comfyui-liveportraitkj",
|
"comfyui-liveportraitkj",
|
||||||
"aigodlike-comfyui-translation",
|
"aigodlike-comfyui-translation",
|
||||||
"comfyui-reactor-node",
|
"comfyui-reactor",
|
||||||
"comfyui_instantid",
|
"comfyui_instantid",
|
||||||
"sd-dynamic-thresholding",
|
"sd-dynamic-thresholding",
|
||||||
"pr-was-node-suite-comfyui-47064894",
|
"pr-was-node-suite-comfyui-47064894",
|
||||||
|
|||||||
12089
github-stats.json
12089
github-stats.json
File diff suppressed because it is too large
Load Diff
@@ -1,131 +0,0 @@
|
|||||||
import requests
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import List
|
|
||||||
import manager_util
|
|
||||||
import toml
|
|
||||||
import os
|
|
||||||
|
|
||||||
base_url = "https://api.comfy.org"
|
|
||||||
|
|
||||||
|
|
||||||
async def get_cnr_data(page=1, limit=1000, cache_mode=True):
|
|
||||||
try:
|
|
||||||
uri = f'{base_url}/nodes?page={page}&limit={limit}'
|
|
||||||
json_obj = await manager_util.get_data_with_cache(uri, cache_mode=cache_mode)
|
|
||||||
|
|
||||||
for v in json_obj['nodes']:
|
|
||||||
if 'latest_version' not in v:
|
|
||||||
v['latest_version'] = dict(version='nightly')
|
|
||||||
|
|
||||||
return json_obj['nodes']
|
|
||||||
except:
|
|
||||||
res = {}
|
|
||||||
print("Cannot connect to comfyregistry.")
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class NodeVersion:
|
|
||||||
changelog: str
|
|
||||||
dependencies: List[str]
|
|
||||||
deprecated: bool
|
|
||||||
id: str
|
|
||||||
version: str
|
|
||||||
download_url: str
|
|
||||||
|
|
||||||
|
|
||||||
def map_node_version(api_node_version):
|
|
||||||
"""
|
|
||||||
Maps node version data from API response to NodeVersion dataclass.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
api_data (dict): The 'node_version' part of the API response.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
NodeVersion: An instance of NodeVersion dataclass populated with data from the API.
|
|
||||||
"""
|
|
||||||
return NodeVersion(
|
|
||||||
changelog=api_node_version.get(
|
|
||||||
"changelog", ""
|
|
||||||
), # Provide a default value if 'changelog' is missing
|
|
||||||
dependencies=api_node_version.get(
|
|
||||||
"dependencies", []
|
|
||||||
), # Provide a default empty list if 'dependencies' is missing
|
|
||||||
deprecated=api_node_version.get(
|
|
||||||
"deprecated", False
|
|
||||||
), # Assume False if 'deprecated' is not specified
|
|
||||||
id=api_node_version[
|
|
||||||
"id"
|
|
||||||
], # 'id' should be mandatory; raise KeyError if missing
|
|
||||||
version=api_node_version[
|
|
||||||
"version"
|
|
||||||
], # 'version' should be mandatory; raise KeyError if missing
|
|
||||||
download_url=api_node_version.get(
|
|
||||||
"downloadUrl", ""
|
|
||||||
), # Provide a default value if 'downloadUrl' is missing
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def install_node(node_id, version=None):
|
|
||||||
"""
|
|
||||||
Retrieves the node version for installation.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
node_id (str): The unique identifier of the node.
|
|
||||||
version (str, optional): Specific version of the node to retrieve. If omitted, the latest version is returned.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
NodeVersion: Node version data or error message.
|
|
||||||
"""
|
|
||||||
if version is None:
|
|
||||||
url = f"{base_url}/nodes/{node_id}/install"
|
|
||||||
else:
|
|
||||||
url = f"{base_url}/nodes/{node_id}/install?version={version}"
|
|
||||||
|
|
||||||
response = requests.get(url)
|
|
||||||
if response.status_code == 200:
|
|
||||||
# Convert the API response to a NodeVersion object
|
|
||||||
return map_node_version(response.json())
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def all_versions_of_node(node_id):
|
|
||||||
url = f"https://api.comfy.org/nodes/{node_id}/versions?statuses=NodeVersionStatusActive&statuses=NodeVersionStatusPending"
|
|
||||||
|
|
||||||
response = requests.get(url)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return response.json()
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def read_cnr_info(fullpath):
|
|
||||||
try:
|
|
||||||
toml_path = os.path.join(fullpath, 'pyproject.toml')
|
|
||||||
tracking_path = os.path.join(fullpath, '.tracking')
|
|
||||||
|
|
||||||
if not os.path.exists(toml_path) or not os.path.exists(tracking_path):
|
|
||||||
return None # not valid CNR node pack
|
|
||||||
|
|
||||||
with open(toml_path, "r", encoding="utf-8") as f:
|
|
||||||
data = toml.load(f)
|
|
||||||
|
|
||||||
project = data.get('project', {})
|
|
||||||
name = project.get('name')
|
|
||||||
version = project.get('version')
|
|
||||||
|
|
||||||
urls = project.get('urls', {})
|
|
||||||
repository = urls.get('Repository')
|
|
||||||
|
|
||||||
if name and version: # repository is optional
|
|
||||||
return {
|
|
||||||
"id": name,
|
|
||||||
"version": version,
|
|
||||||
"url": repository
|
|
||||||
}
|
|
||||||
|
|
||||||
return None
|
|
||||||
except Exception:
|
|
||||||
return None # not valid CNR node pack
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,316 +0,0 @@
|
|||||||
import aiohttp
|
|
||||||
import json
|
|
||||||
import threading
|
|
||||||
import os
|
|
||||||
from datetime import datetime
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
|
|
||||||
cache_lock = threading.Lock()
|
|
||||||
|
|
||||||
comfyui_manager_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
|
|
||||||
cache_dir = os.path.join(comfyui_manager_path, '.cache')
|
|
||||||
|
|
||||||
|
|
||||||
# DON'T USE StrictVersion - cannot handle pre_release version
|
|
||||||
# try:
|
|
||||||
# from distutils.version import StrictVersion
|
|
||||||
# except:
|
|
||||||
# print(f"[ComfyUI-Manager] 'distutils' package not found. Activating fallback mode for compatibility.")
|
|
||||||
class StrictVersion:
|
|
||||||
def __init__(self, version_string):
|
|
||||||
self.version_string = version_string
|
|
||||||
self.major = 0
|
|
||||||
self.minor = 0
|
|
||||||
self.patch = 0
|
|
||||||
self.pre_release = None
|
|
||||||
self.parse_version_string()
|
|
||||||
|
|
||||||
def parse_version_string(self):
|
|
||||||
parts = self.version_string.split('.')
|
|
||||||
if not parts:
|
|
||||||
raise ValueError("Version string must not be empty")
|
|
||||||
|
|
||||||
self.major = int(parts[0])
|
|
||||||
self.minor = int(parts[1]) if len(parts) > 1 else 0
|
|
||||||
self.patch = int(parts[2]) if len(parts) > 2 else 0
|
|
||||||
|
|
||||||
# Handling pre-release versions if present
|
|
||||||
if len(parts) > 3:
|
|
||||||
self.pre_release = parts[3]
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
version = f"{self.major}.{self.minor}.{self.patch}"
|
|
||||||
if self.pre_release:
|
|
||||||
version += f"-{self.pre_release}"
|
|
||||||
return version
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return (self.major, self.minor, self.patch, self.pre_release) == \
|
|
||||||
(other.major, other.minor, other.patch, other.pre_release)
|
|
||||||
|
|
||||||
def __lt__(self, other):
|
|
||||||
if (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch):
|
|
||||||
return self.pre_release_compare(self.pre_release, other.pre_release) < 0
|
|
||||||
return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def pre_release_compare(pre1, pre2):
|
|
||||||
if pre1 == pre2:
|
|
||||||
return 0
|
|
||||||
if pre1 is None:
|
|
||||||
return 1
|
|
||||||
if pre2 is None:
|
|
||||||
return -1
|
|
||||||
return -1 if pre1 < pre2 else 1
|
|
||||||
|
|
||||||
def __le__(self, other):
|
|
||||||
return self == other or self < other
|
|
||||||
|
|
||||||
def __gt__(self, other):
|
|
||||||
return not self <= other
|
|
||||||
|
|
||||||
def __ge__(self, other):
|
|
||||||
return not self < other
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self == other
|
|
||||||
|
|
||||||
|
|
||||||
def simple_hash(input_string):
|
|
||||||
hash_value = 0
|
|
||||||
for char in input_string:
|
|
||||||
hash_value = (hash_value * 31 + ord(char)) % (2**32)
|
|
||||||
|
|
||||||
return hash_value
|
|
||||||
|
|
||||||
|
|
||||||
def is_file_created_within_one_day(file_path):
|
|
||||||
if not os.path.exists(file_path):
|
|
||||||
return False
|
|
||||||
|
|
||||||
file_creation_time = os.path.getctime(file_path)
|
|
||||||
current_time = datetime.now().timestamp()
|
|
||||||
time_difference = current_time - file_creation_time
|
|
||||||
|
|
||||||
return time_difference <= 86400
|
|
||||||
|
|
||||||
|
|
||||||
async def get_data(uri, silent=False):
|
|
||||||
if not silent:
|
|
||||||
print(f"FETCH DATA from: {uri}", end="")
|
|
||||||
|
|
||||||
if uri.startswith("http"):
|
|
||||||
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
|
|
||||||
headers = {
|
|
||||||
'Cache-Control': 'no-cache',
|
|
||||||
'Pragma': 'no-cache',
|
|
||||||
'Expires': '0'
|
|
||||||
}
|
|
||||||
async with session.get(uri, headers=headers) as resp:
|
|
||||||
json_text = await resp.text()
|
|
||||||
else:
|
|
||||||
with cache_lock:
|
|
||||||
with open(uri, "r", encoding="utf-8") as f:
|
|
||||||
json_text = f.read()
|
|
||||||
|
|
||||||
json_obj = json.loads(json_text)
|
|
||||||
|
|
||||||
if not silent:
|
|
||||||
print(" [DONE]")
|
|
||||||
|
|
||||||
return json_obj
|
|
||||||
|
|
||||||
|
|
||||||
async def get_data_with_cache(uri, silent=False, cache_mode=True):
|
|
||||||
cache_uri = str(simple_hash(uri)) + '_' + os.path.basename(uri).replace('&', "_").replace('?', "_").replace('=', "_")
|
|
||||||
cache_uri = os.path.join(cache_dir, cache_uri+'.json')
|
|
||||||
|
|
||||||
if cache_mode and is_file_created_within_one_day(cache_uri):
|
|
||||||
json_obj = await get_data(cache_uri, silent=silent)
|
|
||||||
else:
|
|
||||||
json_obj = await get_data(uri, silent=silent)
|
|
||||||
|
|
||||||
with cache_lock:
|
|
||||||
with open(cache_uri, "w", encoding='utf-8') as file:
|
|
||||||
json.dump(json_obj, file, indent=4, sort_keys=True)
|
|
||||||
if not silent:
|
|
||||||
print(f"[ComfyUI-Manager] default cache updated: {uri}")
|
|
||||||
|
|
||||||
return json_obj
|
|
||||||
|
|
||||||
|
|
||||||
def sanitize_tag(x):
|
|
||||||
return x.replace('<', '<').replace('>', '>')
|
|
||||||
|
|
||||||
|
|
||||||
def extract_package_as_zip(file_path, extract_path):
|
|
||||||
import zipfile
|
|
||||||
try:
|
|
||||||
with zipfile.ZipFile(file_path, "r") as zip_ref:
|
|
||||||
zip_ref.extractall(extract_path)
|
|
||||||
extracted_files = zip_ref.namelist()
|
|
||||||
print(f"Extracted zip file to {extract_path}")
|
|
||||||
return extracted_files
|
|
||||||
except zipfile.BadZipFile:
|
|
||||||
print(f"File '{file_path}' is not a zip or is corrupted.")
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
pip_map = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_installed_packages(renew=False):
|
|
||||||
global pip_map
|
|
||||||
|
|
||||||
if renew or pip_map is None:
|
|
||||||
try:
|
|
||||||
result = subprocess.check_output([sys.executable, '-m', 'pip', 'list'], universal_newlines=True)
|
|
||||||
|
|
||||||
pip_map = {}
|
|
||||||
for line in result.split('\n'):
|
|
||||||
x = line.strip()
|
|
||||||
if x:
|
|
||||||
y = line.split()
|
|
||||||
if y[0] == 'Package' or y[0].startswith('-'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
pip_map[y[0]] = y[1]
|
|
||||||
except subprocess.CalledProcessError:
|
|
||||||
print("[ComfyUI-Manager] Failed to retrieve the information of installed pip packages.")
|
|
||||||
return set()
|
|
||||||
|
|
||||||
return pip_map
|
|
||||||
|
|
||||||
|
|
||||||
def clear_pip_cache():
|
|
||||||
global pip_map
|
|
||||||
pip_map = None
|
|
||||||
|
|
||||||
|
|
||||||
torch_torchvision_version_map = {
|
|
||||||
'2.5.1': '0.20.1',
|
|
||||||
'2.5.0': '0.20.0',
|
|
||||||
'2.4.1': '0.19.1',
|
|
||||||
'2.4.0': '0.19.0',
|
|
||||||
'2.3.1': '0.18.1',
|
|
||||||
'2.3.0': '0.18.0',
|
|
||||||
'2.2.2': '0.17.2',
|
|
||||||
'2.2.1': '0.17.1',
|
|
||||||
'2.2.0': '0.17.0',
|
|
||||||
'2.1.2': '0.16.2',
|
|
||||||
'2.1.1': '0.16.1',
|
|
||||||
'2.1.0': '0.16.0',
|
|
||||||
'2.0.1': '0.15.2',
|
|
||||||
'2.0.0': '0.15.1',
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class PIPFixer:
|
|
||||||
def __init__(self, prev_pip_versions):
|
|
||||||
self.prev_pip_versions = { **prev_pip_versions }
|
|
||||||
|
|
||||||
def torch_rollback(self):
|
|
||||||
spec = self.prev_pip_versions['torch'].split('+')
|
|
||||||
if len(spec) > 0:
|
|
||||||
platform = spec[1]
|
|
||||||
else:
|
|
||||||
cmd = [sys.executable, '-m', 'pip', 'install', '--force', 'torch', 'torchvision', 'torchaudio']
|
|
||||||
subprocess.check_output(cmd, universal_newlines=True)
|
|
||||||
print(cmd)
|
|
||||||
return
|
|
||||||
|
|
||||||
torch_ver = StrictVersion(spec[0])
|
|
||||||
torch_ver = f"{torch_ver.major}.{torch_ver.minor}.{torch_ver.patch}"
|
|
||||||
torchvision_ver = torch_torchvision_version_map.get(torch_ver)
|
|
||||||
|
|
||||||
if torchvision_ver is None:
|
|
||||||
cmd = [sys.executable, '-m', 'pip', 'install', '--pre',
|
|
||||||
'torch', 'torchvision', 'torchaudio',
|
|
||||||
'--index-url', f"https://download.pytorch.org/whl/nightly/{platform}"]
|
|
||||||
print("[manager-core] restore PyTorch to nightly version")
|
|
||||||
else:
|
|
||||||
cmd = [sys.executable, '-m', 'pip', 'install',
|
|
||||||
f'torch=={torch_ver}', f'torchvision=={torchvision_ver}', f"torchaudio=={torch_ver}",
|
|
||||||
'--index-url', f"https://download.pytorch.org/whl/{platform}"]
|
|
||||||
print(f"[manager-core] restore PyTorch to {torch_ver}+{platform}")
|
|
||||||
|
|
||||||
subprocess.check_output(cmd, universal_newlines=True)
|
|
||||||
|
|
||||||
def fix_broken(self):
|
|
||||||
new_pip_versions = get_installed_packages(True)
|
|
||||||
|
|
||||||
# remove `comfy` python package
|
|
||||||
try:
|
|
||||||
if 'comfy' in new_pip_versions:
|
|
||||||
cmd = [sys.executable, '-m', 'pip', 'uninstall', 'comfy']
|
|
||||||
subprocess.check_output(cmd, universal_newlines=True)
|
|
||||||
|
|
||||||
print("[manager-core] 'comfy' python package is uninstalled.\nWARN: The 'comfy' package is completely unrelated to ComfyUI and should never be installed as it causes conflicts with ComfyUI.")
|
|
||||||
except Exception as e:
|
|
||||||
print("[manager-core] Failed to uninstall `comfy` python package")
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
# fix torch - reinstall torch packages if version is changed
|
|
||||||
try:
|
|
||||||
if self.prev_pip_versions['torch'] != new_pip_versions['torch'] \
|
|
||||||
or self.prev_pip_versions['torchvision'] != new_pip_versions['torchvision'] \
|
|
||||||
or self.prev_pip_versions['torchaudio'] != new_pip_versions['torchaudio']:
|
|
||||||
self.torch_rollback()
|
|
||||||
except Exception as e:
|
|
||||||
print("[manager-core] Failed to restore PyTorch")
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
# fix opencv
|
|
||||||
try:
|
|
||||||
ocp = new_pip_versions.get('opencv-contrib-python')
|
|
||||||
ocph = new_pip_versions.get('opencv-contrib-python-headless')
|
|
||||||
op = new_pip_versions.get('opencv-python')
|
|
||||||
oph = new_pip_versions.get('opencv-python-headless')
|
|
||||||
|
|
||||||
versions = [ocp, ocph, op, oph]
|
|
||||||
versions = [StrictVersion(x) for x in versions if x is not None]
|
|
||||||
versions.sort(reverse=True)
|
|
||||||
|
|
||||||
if len(versions) > 0:
|
|
||||||
# upgrade to maximum version
|
|
||||||
targets = []
|
|
||||||
cur = versions[0]
|
|
||||||
if ocp is not None and StrictVersion(ocp) != cur:
|
|
||||||
targets.append('opencv-contrib-python')
|
|
||||||
if ocph is not None and StrictVersion(ocph) != cur:
|
|
||||||
targets.append('opencv-contrib-python-headless')
|
|
||||||
if op is not None and StrictVersion(op) != cur:
|
|
||||||
targets.append('opencv-python')
|
|
||||||
if oph is not None and StrictVersion(oph) != cur:
|
|
||||||
targets.append('opencv-python-headless')
|
|
||||||
|
|
||||||
if len(targets) > 0:
|
|
||||||
for x in targets:
|
|
||||||
cmd = [sys.executable, '-m', 'pip', 'install', f"{x}=={versions[0].version_string}"]
|
|
||||||
subprocess.check_output(cmd, universal_newlines=True)
|
|
||||||
|
|
||||||
print(f"[manager-core] 'opencv' dependencies were fixed: {targets}")
|
|
||||||
except Exception as e:
|
|
||||||
print("[manager-core] Failed to restore opencv")
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
# fix numpy
|
|
||||||
try:
|
|
||||||
np = new_pip_versions.get('numpy')
|
|
||||||
if np is not None:
|
|
||||||
if StrictVersion(np) >= StrictVersion('2'):
|
|
||||||
subprocess.check_output([sys.executable, '-m', 'pip', 'install', "numpy<2"], universal_newlines=True)
|
|
||||||
except Exception as e:
|
|
||||||
print("[manager-core] Failed to restore numpy")
|
|
||||||
print(e)
|
|
||||||
|
|
||||||
|
|
||||||
def sanitize(data):
|
|
||||||
return data.replace("<", "<").replace(">", ">")
|
|
||||||
|
|
||||||
|
|
||||||
def sanitize_filename(input_string):
|
|
||||||
result_string = re.sub(r'[^a-zA-Z0-9_]', '_', input_string)
|
|
||||||
return result_string
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
/**
|
|
||||||
* Attaches metadata to the workflow on save
|
|
||||||
* - custom node pack version to all custom nodes used in the workflow
|
|
||||||
*
|
|
||||||
* Example metadata:
|
|
||||||
"extra": {
|
|
||||||
"node_versions": {
|
|
||||||
"comfy-core": "v0.3.8-4-g0b2eb7f",
|
|
||||||
"comfyui-easy-use": "1.2.5"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { app } from "../../scripts/app.js";
|
|
||||||
import { api } from "../../scripts/api.js";
|
|
||||||
|
|
||||||
class WorkflowMetadataExtension {
|
|
||||||
constructor() {
|
|
||||||
this.name = "Comfy.CustomNodesManager.WorkflowMetadata";
|
|
||||||
this.installedNodeVersions = {};
|
|
||||||
this.comfyCoreVersion = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the installed node versions
|
|
||||||
* @returns {Promise<Record<string, string>>} The mapping from node name to version
|
|
||||||
* version can either be a git commit hash or a semantic version such as "1.0.0"
|
|
||||||
*/
|
|
||||||
async getInstalledNodeVersions() {
|
|
||||||
const res = await api.fetchApi("/customnode/installed");
|
|
||||||
return await res.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the node versions for the given graph
|
|
||||||
* @param {LGraph} graph The graph to get the node versions for
|
|
||||||
* @returns {Promise<Record<string, string>>} The mapping from node name to version
|
|
||||||
*/
|
|
||||||
getGraphNodeVersions(graph) {
|
|
||||||
const nodeVersions = {};
|
|
||||||
for (const node of graph.nodes) {
|
|
||||||
const nodeData = node.constructor.nodeData;
|
|
||||||
// Frontend only nodes don't have nodeData
|
|
||||||
if (!nodeData) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const modules = nodeData.python_module.split(".");
|
|
||||||
|
|
||||||
if (modules[0] === "custom_nodes") {
|
|
||||||
const nodePackageName = modules[1];
|
|
||||||
const nodeVersion = this.installedNodeVersions[nodePackageName];
|
|
||||||
nodeVersions[nodePackageName] = nodeVersion;
|
|
||||||
} else if (["nodes", "comfy_extras"].includes(modules[0])) {
|
|
||||||
nodeVersions["comfy-core"] = this.comfyCoreVersion;
|
|
||||||
} else {
|
|
||||||
console.warn(`Unknown node source: ${nodeData.python_module}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nodeVersions;
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
const extension = this;
|
|
||||||
this.installedNodeVersions = await this.getInstalledNodeVersions();
|
|
||||||
this.comfyCoreVersion = (await api.getSystemStats()).system.comfyui_version;
|
|
||||||
|
|
||||||
// Attach metadata when app.graphToPrompt is called.
|
|
||||||
const originalSerialize = LGraph.prototype.serialize;
|
|
||||||
LGraph.prototype.serialize = function () {
|
|
||||||
const workflow = originalSerialize.apply(this, arguments);
|
|
||||||
|
|
||||||
// Add metadata to the workflow
|
|
||||||
if (!workflow.extra) {
|
|
||||||
workflow.extra = {};
|
|
||||||
}
|
|
||||||
const graph = this;
|
|
||||||
workflow.extra["node_versions"] = extension.getGraphNodeVersions(graph);
|
|
||||||
|
|
||||||
return workflow;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.registerExtension(new WorkflowMetadataExtension());
|
|
||||||
741
model-list.json
741
model-list.json
@@ -209,28 +209,6 @@
|
|||||||
"url": "https://huggingface.co/stabilityai/stable-diffusion-x4-upscaler/resolve/main/x4-upscaler-ema.safetensors",
|
"url": "https://huggingface.co/stabilityai/stable-diffusion-x4-upscaler/resolve/main/x4-upscaler-ema.safetensors",
|
||||||
"size": "3.53GB"
|
"size": "3.53GB"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Inswapper-fp16 (face swap)",
|
|
||||||
"type": "insightface",
|
|
||||||
"base": "inswapper",
|
|
||||||
"save_path": "insightface",
|
|
||||||
"description": "Checkpoint of the insightface swapper model\n(used by ComfyUI-FaceSwap, comfyui-reactor-node, CharacterFaceSwap,\nComfyUI roop and comfy_mtb)",
|
|
||||||
"reference": "https://github.com/facefusion/facefusion-assets",
|
|
||||||
"filename": "inswapper_128_fp16.onnx",
|
|
||||||
"url": "https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128_fp16.onnx",
|
|
||||||
"size": "277.7MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Inswapper (face swap)",
|
|
||||||
"type": "insightface",
|
|
||||||
"base": "inswapper",
|
|
||||||
"save_path": "insightface",
|
|
||||||
"description": "Checkpoint of the insightface swapper model\n(used by ComfyUI-FaceSwap, comfyui-reactor-node, CharacterFaceSwap,\nComfyUI roop and comfy_mtb)",
|
|
||||||
"reference": "https://github.com/facefusion/facefusion-assets",
|
|
||||||
"filename": "inswapper_128.onnx",
|
|
||||||
"url": "https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128.onnx",
|
|
||||||
"size": "555.3MB"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Deepbump",
|
"name": "Deepbump",
|
||||||
"type": "deepbump",
|
"type": "deepbump",
|
||||||
@@ -310,7 +288,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "negative_hand Negative Embedding",
|
"name": "negative_hand Negative Embedding",
|
||||||
"type": "embeddings",
|
"type": "embedding",
|
||||||
"base": "SD1.5",
|
"base": "SD1.5",
|
||||||
"save_path": "embeddings/SD1.5",
|
"save_path": "embeddings/SD1.5",
|
||||||
"description": "If you use this embedding with negatives, you can solve the issue of damaging your hands.",
|
"description": "If you use this embedding with negatives, you can solve the issue of damaging your hands.",
|
||||||
@@ -321,7 +299,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bad_prompt Negative Embedding",
|
"name": "bad_prompt Negative Embedding",
|
||||||
"type": "embeddings",
|
"type": "embedding",
|
||||||
"base": "SD1.5",
|
"base": "SD1.5",
|
||||||
"save_path": "embeddings/SD1.5",
|
"save_path": "embeddings/SD1.5",
|
||||||
"description": "The idea behind this embedding was to somehow train the negative prompt as an embedding, thus unifying the basis of the negative prompt into one word or embedding.",
|
"description": "The idea behind this embedding was to somehow train the negative prompt as an embedding, thus unifying the basis of the negative prompt into one word or embedding.",
|
||||||
@@ -332,7 +310,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Deep Negative V1.75",
|
"name": "Deep Negative V1.75",
|
||||||
"type": "embeddings",
|
"type": "embedding",
|
||||||
"base": "SD1.5",
|
"base": "SD1.5",
|
||||||
"save_path": "embeddings/SD1.5",
|
"save_path": "embeddings/SD1.5",
|
||||||
"description": "These embedding learn what disgusting compositions and color patterns are, including faulty human anatomy, offensive color schemes, upside-down spatial structures, and more. Placing it in the negative can go a long way to avoiding these things.",
|
"description": "These embedding learn what disgusting compositions and color patterns are, including faulty human anatomy, offensive color schemes, upside-down spatial structures, and more. Placing it in the negative can go a long way to avoiding these things.",
|
||||||
@@ -343,7 +321,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "EasyNegative",
|
"name": "EasyNegative",
|
||||||
"type": "embeddings",
|
"type": "embedding",
|
||||||
"base": "SD1.5",
|
"base": "SD1.5",
|
||||||
"save_path": "embeddings/SD1.5",
|
"save_path": "embeddings/SD1.5",
|
||||||
"description": "This embedding should be used in your NEGATIVE prompt. Adjust the strength as desired (seems to scale well without any distortions), the strength required may vary based on positive and negative prompts.",
|
"description": "This embedding should be used in your NEGATIVE prompt. Adjust the strength as desired (seems to scale well without any distortions), the strength required may vary based on positive and negative prompts.",
|
||||||
@@ -488,7 +466,7 @@
|
|||||||
"name": "stabilityai/Stable Cascade: text_encoder (CLIP)",
|
"name": "stabilityai/Stable Cascade: text_encoder (CLIP)",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "Stable Cascade",
|
"base": "Stable Cascade",
|
||||||
"save_path": "clip/Stable-Cascade",
|
"save_path": "text_encoders/Stable-Cascade",
|
||||||
"description": "Stable Cascade: text_encoder",
|
"description": "Stable Cascade: text_encoder",
|
||||||
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
"reference": "https://huggingface.co/stabilityai/stable-cascade",
|
||||||
"filename": "model.safetensors",
|
"filename": "model.safetensors",
|
||||||
@@ -771,8 +749,8 @@
|
|||||||
"save_path": "loras/HyperSD/SDXL",
|
"save_path": "loras/HyperSD/SDXL",
|
||||||
"description": "Hyper-SD LoRA (4steps) - SDXL",
|
"description": "Hyper-SD LoRA (4steps) - SDXL",
|
||||||
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
||||||
"filename": "Hyper-SD15-4steps-lora.safetensors",
|
"filename": "Hyper-SDXL-4steps-lora.safetensors",
|
||||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-4steps-lora.safetensors",
|
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-4steps-lora.safetensors",
|
||||||
"size": "787MB"
|
"size": "787MB"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -877,11 +855,67 @@
|
|||||||
"size": "5.16GB"
|
"size": "5.16GB"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "comfyanonymous/cosmos_cv8x8x8_1.0.safetensors",
|
||||||
|
"type": "VAE",
|
||||||
|
"base": "Cosmos-1.0",
|
||||||
|
"save_path": "default",
|
||||||
|
"description": "VAE model for Cosmos 1.0",
|
||||||
|
"reference": "https://huggingface.co/comfyanonymous/cosmos_1.0_text_encoder_and_VAE_ComfyUI/tree/main",
|
||||||
|
"filename": "cosmos_cv8x8x8_1.0.safetensors",
|
||||||
|
"url": "https://huggingface.co/comfyanonymous/cosmos_1.0_text_encoder_and_VAE_ComfyUI/resolve/main/vae/cosmos_cv8x8x8_1.0.safetensors",
|
||||||
|
"size": "211MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mcmonkey/Cosmos-1_0-Diffusion-7B-Text2World.safetensors",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Cosmos-1.0",
|
||||||
|
"save_path": "diffusion_models/cosmos-1.0",
|
||||||
|
"description": "Cosmos 1.0 Text2World Diffusion Model (7B)",
|
||||||
|
"reference": "https://huggingface.co/mcmonkey/cosmos-1.0",
|
||||||
|
"filename": "Cosmos-1_0-Diffusion-7B-Text2World.safetensors",
|
||||||
|
"url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-7B-Text2World.safetensors",
|
||||||
|
"size": "14.5GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mcmonkey/Cosmos-1_0-Diffusion-7B-Video2World.safetensors",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Cosmos-1.0",
|
||||||
|
"save_path": "diffusion_models/cosmos-1.0",
|
||||||
|
"description": "Cosmos 1.0 Video2World Diffusion Model (7B)",
|
||||||
|
"reference": "https://huggingface.co/mcmonkey/cosmos-1.0",
|
||||||
|
"filename": "Cosmos-1_0-Diffusion-7B-Video2World.safetensors",
|
||||||
|
"url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-7B-Video2World.safetensors",
|
||||||
|
"size": "14.5GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mcmonkey/Cosmos-1_0-Diffusion-14B-Text2World.safetensors",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Cosmos-1.0",
|
||||||
|
"save_path": "diffusion_models/cosmos-1.0",
|
||||||
|
"description": "Cosmos 1.0 Text2World Diffusion Model (14B)",
|
||||||
|
"reference": "https://huggingface.co/mcmonkey/cosmos-1.0",
|
||||||
|
"filename": "Cosmos-1_0-Diffusion-14B-Text2World.safetensors",
|
||||||
|
"url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-14B-Text2World.safetensors",
|
||||||
|
"size": "28.5GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mcmonkey/Cosmos-1_0-Diffusion-14B-Video2World.safetensors",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Cosmos-1.0",
|
||||||
|
"save_path": "diffusion_models/cosmos-1.0",
|
||||||
|
"description": "Cosmos 1.0 Video2World Diffusion Model (14B)",
|
||||||
|
"reference": "https://huggingface.co/mcmonkey/cosmos-1.0",
|
||||||
|
"filename": "Cosmos-1_0-Diffusion-14B-Video2World.safetensors",
|
||||||
|
"url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-14B-Video2World.safetensors",
|
||||||
|
"size": "28.5GB"
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "google-t5/t5-base",
|
"name": "google-t5/t5-base",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "t5-base",
|
"base": "t5-base",
|
||||||
"save_path": "clip/t5-base",
|
"save_path": "text_encoders/t5-base",
|
||||||
"description": "T5 Base: Text-To-Text Transfer Transformer. This model can be loaded via CLIPLoader for Stable Audio workflow.",
|
"description": "T5 Base: Text-To-Text Transfer Transformer. This model can be loaded via CLIPLoader for Stable Audio workflow.",
|
||||||
"reference": "https://huggingface.co/google-t5/t5-base",
|
"reference": "https://huggingface.co/google-t5/t5-base",
|
||||||
"filename": "model.safetensors",
|
"filename": "model.safetensors",
|
||||||
@@ -892,7 +926,7 @@
|
|||||||
"name": "google-t5/t5-v1_1-xxl_encoderonly-fp16",
|
"name": "google-t5/t5-v1_1-xxl_encoderonly-fp16",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "t5",
|
"base": "t5",
|
||||||
"save_path": "clip/t5",
|
"save_path": "text_encoders/t5",
|
||||||
"description": "The encoder part of https://huggingface.co/google/t5-v1_1-xxl, used with SD3 and Flux1",
|
"description": "The encoder part of https://huggingface.co/google/t5-v1_1-xxl, used with SD3 and Flux1",
|
||||||
"reference": "https://huggingface.co/mcmonkey/google_t5-v1_1-xxl_encoderonly",
|
"reference": "https://huggingface.co/mcmonkey/google_t5-v1_1-xxl_encoderonly",
|
||||||
"filename": "google_t5-v1_1-xxl_encoderonly-fp16.safetensors",
|
"filename": "google_t5-v1_1-xxl_encoderonly-fp16.safetensors",
|
||||||
@@ -903,7 +937,7 @@
|
|||||||
"name": "google-t5/t5-v1_1-xxl_encoderonly-fp8_e4m3fn",
|
"name": "google-t5/t5-v1_1-xxl_encoderonly-fp8_e4m3fn",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "t5",
|
"base": "t5",
|
||||||
"save_path": "clip/t5",
|
"save_path": "text_encoders/t5",
|
||||||
"description": "The encoder part of https://huggingface.co/google/t5-v1_1-xxl, used with SD3 and Flux1",
|
"description": "The encoder part of https://huggingface.co/google/t5-v1_1-xxl, used with SD3 and Flux1",
|
||||||
"reference": "https://huggingface.co/mcmonkey/google_t5-v1_1-xxl_encoderonly",
|
"reference": "https://huggingface.co/mcmonkey/google_t5-v1_1-xxl_encoderonly",
|
||||||
"filename": "google_t5-v1_1-xxl_encoderonly-fp8_e4m3fn.safetensors",
|
"filename": "google_t5-v1_1-xxl_encoderonly-fp8_e4m3fn.safetensors",
|
||||||
@@ -916,7 +950,7 @@
|
|||||||
"name": "city96/t5-v1_1-xxl-encoder-Q3_K_L.gguf",
|
"name": "city96/t5-v1_1-xxl-encoder-Q3_K_L.gguf",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "t5",
|
"base": "t5",
|
||||||
"save_path": "clip/t5",
|
"save_path": "text_encoders/t5",
|
||||||
"description": "t5xxl Text Encoder GGUF model. (Q3_K_L quantized)",
|
"description": "t5xxl Text Encoder GGUF model. (Q3_K_L quantized)",
|
||||||
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
||||||
"filename": "t5-v1_1-xxl-encoder-Q3_K_L.gguf",
|
"filename": "t5-v1_1-xxl-encoder-Q3_K_L.gguf",
|
||||||
@@ -927,7 +961,7 @@
|
|||||||
"name": "city96/t5-v1_1-xxl-encoder-Q3_K_M.gguf",
|
"name": "city96/t5-v1_1-xxl-encoder-Q3_K_M.gguf",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "t5",
|
"base": "t5",
|
||||||
"save_path": "clip/t5",
|
"save_path": "text_encoders/t5",
|
||||||
"description": "t5xxl Text Encoder GGUF model. (Q3_K_M quantized)",
|
"description": "t5xxl Text Encoder GGUF model. (Q3_K_M quantized)",
|
||||||
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
||||||
"filename": "t5-v1_1-xxl-encoder-Q3_K_M.gguf",
|
"filename": "t5-v1_1-xxl-encoder-Q3_K_M.gguf",
|
||||||
@@ -938,7 +972,7 @@
|
|||||||
"name": "city96/t5-v1_1-xxl-encoder-Q3_K_S.gguf",
|
"name": "city96/t5-v1_1-xxl-encoder-Q3_K_S.gguf",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "t5",
|
"base": "t5",
|
||||||
"save_path": "clip/t5",
|
"save_path": "text_encoders/t5",
|
||||||
"description": "t5xxl Text Encoder GGUF model. (Q3_K_S quantized)",
|
"description": "t5xxl Text Encoder GGUF model. (Q3_K_S quantized)",
|
||||||
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
||||||
"filename": "t5-v1_1-xxl-encoder-Q3_K_S.gguf",
|
"filename": "t5-v1_1-xxl-encoder-Q3_K_S.gguf",
|
||||||
@@ -949,7 +983,7 @@
|
|||||||
"name": "city96/t5-v1_1-xxl-encoder-Q4_K_M.gguf",
|
"name": "city96/t5-v1_1-xxl-encoder-Q4_K_M.gguf",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "t5",
|
"base": "t5",
|
||||||
"save_path": "clip/t5",
|
"save_path": "text_encoders/t5",
|
||||||
"description": "t5xxl Text Encoder GGUF model. (Q4_K_M quantized)",
|
"description": "t5xxl Text Encoder GGUF model. (Q4_K_M quantized)",
|
||||||
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
||||||
"filename": "t5-v1_1-xxl-encoder-Q4_K_M.gguf",
|
"filename": "t5-v1_1-xxl-encoder-Q4_K_M.gguf",
|
||||||
@@ -960,7 +994,7 @@
|
|||||||
"name": "city96/t5-v1_1-xxl-encoder-Q4_K_S.gguf",
|
"name": "city96/t5-v1_1-xxl-encoder-Q4_K_S.gguf",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "t5",
|
"base": "t5",
|
||||||
"save_path": "clip/t5",
|
"save_path": "text_encoders/t5",
|
||||||
"description": "t5xxl Text Encoder GGUF model. (Q4_K_S quantized)",
|
"description": "t5xxl Text Encoder GGUF model. (Q4_K_S quantized)",
|
||||||
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
||||||
"filename": "t5-v1_1-xxl-encoder-Q4_K_S.gguf",
|
"filename": "t5-v1_1-xxl-encoder-Q4_K_S.gguf",
|
||||||
@@ -971,7 +1005,7 @@
|
|||||||
"name": "city96/t5-v1_1-xxl-encoder-Q5_K_M.gguf",
|
"name": "city96/t5-v1_1-xxl-encoder-Q5_K_M.gguf",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "t5",
|
"base": "t5",
|
||||||
"save_path": "clip/t5",
|
"save_path": "text_encoders/t5",
|
||||||
"description": "t5xxl Text Encoder GGUF model. (Q5_K_M quantized)",
|
"description": "t5xxl Text Encoder GGUF model. (Q5_K_M quantized)",
|
||||||
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
||||||
"filename": "t5-v1_1-xxl-encoder-Q5_K_M.gguf",
|
"filename": "t5-v1_1-xxl-encoder-Q5_K_M.gguf",
|
||||||
@@ -982,7 +1016,7 @@
|
|||||||
"name": "city96/t5-v1_1-xxl-encoder-Q5_K_S.gguf",
|
"name": "city96/t5-v1_1-xxl-encoder-Q5_K_S.gguf",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "t5",
|
"base": "t5",
|
||||||
"save_path": "clip/t5",
|
"save_path": "text_encoders/t5",
|
||||||
"description": "t5xxl Text Encoder GGUF model. (Q5_K_S quantized)",
|
"description": "t5xxl Text Encoder GGUF model. (Q5_K_S quantized)",
|
||||||
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
||||||
"filename": "t5-v1_1-xxl-encoder-Q5_K_S.gguf",
|
"filename": "t5-v1_1-xxl-encoder-Q5_K_S.gguf",
|
||||||
@@ -993,7 +1027,7 @@
|
|||||||
"name": "city96/t5-v1_1-xxl-encoder-Q6_K.gguf",
|
"name": "city96/t5-v1_1-xxl-encoder-Q6_K.gguf",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "t5",
|
"base": "t5",
|
||||||
"save_path": "clip/t5",
|
"save_path": "text_encoders/t5",
|
||||||
"description": "t5xxl Text Encoder GGUF model. (Q6_K quantized)",
|
"description": "t5xxl Text Encoder GGUF model. (Q6_K quantized)",
|
||||||
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
||||||
"filename": "t5-v1_1-xxl-encoder-Q6_K.gguf",
|
"filename": "t5-v1_1-xxl-encoder-Q6_K.gguf",
|
||||||
@@ -1004,7 +1038,7 @@
|
|||||||
"name": "city96/t5-v1_1-xxl-encoder-Q8_0.gguf",
|
"name": "city96/t5-v1_1-xxl-encoder-Q8_0.gguf",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "t5",
|
"base": "t5",
|
||||||
"save_path": "clip/t5",
|
"save_path": "text_encoders/t5",
|
||||||
"description": "t5xxl Text Encoder GGUF model. (Q8_0 quantized)",
|
"description": "t5xxl Text Encoder GGUF model. (Q8_0 quantized)",
|
||||||
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
||||||
"filename": "t5-v1_1-xxl-encoder-Q8_0.gguf",
|
"filename": "t5-v1_1-xxl-encoder-Q8_0.gguf",
|
||||||
@@ -1015,7 +1049,7 @@
|
|||||||
"name": "city96/t5-v1_1-xxl-encoder-f16.gguf",
|
"name": "city96/t5-v1_1-xxl-encoder-f16.gguf",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "t5",
|
"base": "t5",
|
||||||
"save_path": "clip/t5",
|
"save_path": "text_encoders/t5",
|
||||||
"description": "t5xxl Text Encoder GGUF model. (float 16)",
|
"description": "t5xxl Text Encoder GGUF model. (float 16)",
|
||||||
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
||||||
"filename": "t5-v1_1-xxl-encoder-f16.gguf",
|
"filename": "t5-v1_1-xxl-encoder-f16.gguf",
|
||||||
@@ -1026,7 +1060,7 @@
|
|||||||
"name": "city96/t5-v1_1-xxl-encoder-f32.gguf",
|
"name": "city96/t5-v1_1-xxl-encoder-f32.gguf",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "t5",
|
"base": "t5",
|
||||||
"save_path": "clip/t5",
|
"save_path": "text_encoders/t5",
|
||||||
"description": "t5xxl Text Encoder GGUF model. (float 32)",
|
"description": "t5xxl Text Encoder GGUF model. (float 32)",
|
||||||
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
"reference": "https://huggingface.co/city96/t5-v1_1-xxl-encoder-gguf",
|
||||||
"filename": "t5-v1_1-xxl-encoder-f32.gguf",
|
"filename": "t5-v1_1-xxl-encoder-f32.gguf",
|
||||||
@@ -1034,18 +1068,28 @@
|
|||||||
"size": "19.1GB"
|
"size": "19.1GB"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "comfyanonymous/clip_l",
|
"name": "Comfy-Org/clip_l",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "clip",
|
"base": "clip",
|
||||||
"save_path": "default",
|
"save_path": "default",
|
||||||
"description": "clip_l model",
|
"description": "clip_l model (for SD1.x, SD2.x, SDXL, SD3.5, FLUX.1, HunyuanVideo, ...) ",
|
||||||
"reference": "https://huggingface.co/comfyanonymous/flux_text_encoders/tree/main",
|
"reference": "https://huggingface.co/Comfy-Org/stable-diffusion-3.5-fp8",
|
||||||
"filename": "clip_l.safetensors",
|
"filename": "clip_l.safetensors",
|
||||||
"url": "https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/clip_l.safetensors",
|
"url": "https://huggingface.co/Comfy-Org/stable-diffusion-3.5-fp8/resolve/main/text_encoders/clip_l.safetensors",
|
||||||
"size": "246MB"
|
"size": "246MB"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/clip_g",
|
||||||
|
"type": "clip",
|
||||||
|
"base": "clip",
|
||||||
|
"save_path": "default",
|
||||||
|
"description": "clip_g model (for SDXL, SD3.5)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/stable-diffusion-3.5-fp8",
|
||||||
|
"filename": "clip_g.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/stable-diffusion-3.5-fp8/resolve/main/text_encoders/clip_g.safetensors",
|
||||||
|
"size": "1.39GB"
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "v1-5-pruned-emaonly.ckpt",
|
"name": "v1-5-pruned-emaonly.ckpt",
|
||||||
@@ -1665,17 +1709,6 @@
|
|||||||
"url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-sketch-rank256.safetensors",
|
"url": "https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-sketch-rank256.safetensors",
|
||||||
"size": "774.5MB"
|
"size": "774.5MB"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "kohya-ss/ControlNet-LLLite: SDXL Canny Anime",
|
|
||||||
"type": "controlnet",
|
|
||||||
"base": "SDXL",
|
|
||||||
"save_path": "custom_nodes/ControlNet-LLLite-ComfyUI/models",
|
|
||||||
"description": "An extremely compactly designed controlnet model (a.k.a. ControlNet-LLLite). Note: The model structure is highly experimental and may be subject to change in the future.",
|
|
||||||
"reference": "https://huggingface.co/kohya-ss/controlnet-lllite",
|
|
||||||
"filename": "controllllite_v01032064e_sdxl_canny_anime.safetensors",
|
|
||||||
"url": "https://huggingface.co/kohya-ss/controlnet-lllite/resolve/main/controllllite_v01032064e_sdxl_canny_anime.safetensors",
|
|
||||||
"size": "46.2MB"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "SDXL-controlnet: OpenPose (v2)",
|
"name": "SDXL-controlnet: OpenPose (v2)",
|
||||||
"type": "controlnet",
|
"type": "controlnet",
|
||||||
@@ -2259,6 +2292,18 @@
|
|||||||
"url": "https://huggingface.co/CiaraRowles/TemporalDiff/resolve/main/temporaldiff-v1-animatediff.ckpt",
|
"url": "https://huggingface.co/CiaraRowles/TemporalDiff/resolve/main/temporaldiff-v1-animatediff.ckpt",
|
||||||
"size": "1.67GB"
|
"size": "1.67GB"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Leoxing/pia.ckpt",
|
||||||
|
"type": "animatediff-pia",
|
||||||
|
"base": "SD1.x",
|
||||||
|
"save_path": "animatediff_models",
|
||||||
|
"description": "AnimateDiff-PIA Model",
|
||||||
|
"reference": "https://huggingface.co/Leoxing/PIA/tree/main",
|
||||||
|
"filename": "pia.ckpt",
|
||||||
|
"url": "https://huggingface.co/Leoxing/PIA/resolve/main/pia.ckpt",
|
||||||
|
"size": "1.67GB"
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "animatediff/v2_lora_PanLeft.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
"name": "animatediff/v2_lora_PanLeft.ckpt (ComfyUI-AnimateDiff-Evolved) (Updated path)",
|
||||||
"type": "motion lora",
|
"type": "motion lora",
|
||||||
@@ -2746,39 +2791,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "pfg-novel-n10.pt",
|
|
||||||
"type": "PFG",
|
|
||||||
"base": "SD1.5",
|
|
||||||
"save_path": "custom_nodes/pfg-ComfyUI/models",
|
|
||||||
"description": "Pressing 'install' directly downloads the model from the pfg-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
|
||||||
"reference": "https://huggingface.co/furusu/PFG",
|
|
||||||
"filename": "pfg-novel-n10.pt",
|
|
||||||
"url": "https://huggingface.co/furusu/PFG/resolve/main/pfg-novel-n10.pt",
|
|
||||||
"size": "23.6MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pfg-wd14-n10.pt",
|
|
||||||
"type": "PFG",
|
|
||||||
"base": "SD1.5",
|
|
||||||
"save_path": "custom_nodes/pfg-ComfyUI/models",
|
|
||||||
"description": "Pressing 'install' directly downloads the model from the pfg-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
|
||||||
"reference": "https://huggingface.co/furusu/PFG",
|
|
||||||
"filename": "pfg-wd14-n10.pt",
|
|
||||||
"url": "https://huggingface.co/furusu/PFG/resolve/main/pfg-wd14-n10.pt",
|
|
||||||
"size": "31.5MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pfg-wd15beta2-n10.pt",
|
|
||||||
"type": "PFG",
|
|
||||||
"base": "SD1.5",
|
|
||||||
"save_path": "custom_nodes/pfg-ComfyUI/models",
|
|
||||||
"description": "Pressing 'install' directly downloads the model from the pfg-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
|
||||||
"reference": "https://huggingface.co/furusu/PFG",
|
|
||||||
"filename": "pfg-wd15beta2-n10.pt",
|
|
||||||
"url": "https://huggingface.co/furusu/PFG/resolve/main/pfg-wd15beta2-n10.pt",
|
|
||||||
"size": "31.5MB"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "GFPGANv1.4.pth",
|
"name": "GFPGANv1.4.pth",
|
||||||
"type": "GFPGAN",
|
"type": "GFPGAN",
|
||||||
@@ -2944,50 +2956,6 @@
|
|||||||
"url": "https://huggingface.co/InstantX/InstantID/resolve/main/ControlNetModel/diffusion_pytorch_model.safetensors",
|
"url": "https://huggingface.co/InstantX/InstantID/resolve/main/ControlNetModel/diffusion_pytorch_model.safetensors",
|
||||||
"size": "2.50GB"
|
"size": "2.50GB"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "efficient_sam_s_cpu.jit [ComfyUI-YoloWorld-EfficientSAM]",
|
|
||||||
"type": "efficient_sam",
|
|
||||||
"base": "efficient_sam",
|
|
||||||
"save_path": "custom_nodes/ComfyUI-YoloWorld-EfficientSAM",
|
|
||||||
"description": "Install efficient_sam_s_cpu.jit into ComfyUI-YoloWorld-EfficientSAM",
|
|
||||||
"reference": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/tree/main",
|
|
||||||
"filename": "efficient_sam_s_cpu.jit",
|
|
||||||
"url": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/resolve/main/efficient_sam_s_cpu.jit",
|
|
||||||
"size": "106.0MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "efficient_sam_s_gpu.jit [ComfyUI-YoloWorld-EfficientSAM]",
|
|
||||||
"type": "efficient_sam",
|
|
||||||
"base": "efficient_sam",
|
|
||||||
"save_path": "custom_nodes/ComfyUI-YoloWorld-EfficientSAM",
|
|
||||||
"description": "Install efficient_sam_s_gpu.jit into ComfyUI-YoloWorld-EfficientSAM",
|
|
||||||
"reference": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/tree/main",
|
|
||||||
"filename": "efficient_sam_s_gpu.jit",
|
|
||||||
"url": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/resolve/main/efficient_sam_s_gpu.jit",
|
|
||||||
"size": "106.0MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "shape_predictor_68_face_landmarks.dat [Face Analysis]",
|
|
||||||
"type": "Shape Predictor",
|
|
||||||
"base": "DLIB",
|
|
||||||
"save_path": "custom_nodes/ComfyUI_FaceAnalysis/dlib",
|
|
||||||
"description": "To use the Face Analysis for ComfyUI custom node, installation of this model is needed.",
|
|
||||||
"reference": "https://huggingface.co/matt3ounstable/dlib_predictor_recognition/tree/main",
|
|
||||||
"filename": "shape_predictor_68_face_landmarks.dat",
|
|
||||||
"url": "https://huggingface.co/matt3ounstable/dlib_predictor_recognition/resolve/main/shape_predictor_68_face_landmarks.dat",
|
|
||||||
"size": "99.7MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "dlib_face_recognition_resnet_model_v1.dat [Face Analysis]",
|
|
||||||
"type": "Face Recognition",
|
|
||||||
"base": "DLIB",
|
|
||||||
"save_path": "custom_nodes/ComfyUI_FaceAnalysis/dlib",
|
|
||||||
"description": "To use the Face Analysis for ComfyUI custom node, installation of this model is needed.",
|
|
||||||
"reference": "https://huggingface.co/matt3ounstable/dlib_predictor_recognition/tree/main",
|
|
||||||
"filename": "dlib_face_recognition_resnet_model_v1.dat",
|
|
||||||
"url": "https://huggingface.co/matt3ounstable/dlib_predictor_recognition/resolve/main/dlib_face_recognition_resnet_model_v1.dat",
|
|
||||||
"size": "22.5MB"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "InstanceDiffusion/fusers",
|
"name": "InstanceDiffusion/fusers",
|
||||||
"type": "InstanceDiffusion",
|
"type": "InstanceDiffusion",
|
||||||
@@ -3362,50 +3330,6 @@
|
|||||||
"url": "https://huggingface.co/lllyasviel/ic-light/resolve/main/iclight_sd15_fcon.safetensors",
|
"url": "https://huggingface.co/lllyasviel/ic-light/resolve/main/iclight_sd15_fcon.safetensors",
|
||||||
"size": "1.72GB"
|
"size": "1.72GB"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "ID-Animator/animator.ckpt",
|
|
||||||
"type": "ID-Animator",
|
|
||||||
"base": "SD1.5",
|
|
||||||
"save_path": "custom_nodes/ComfyUI_ID_Animator/models",
|
|
||||||
"description": "ID-Animator checkpoint",
|
|
||||||
"reference": "https://huggingface.co/spaces/ID-Animator/ID-Animator",
|
|
||||||
"filename": "animator.ckpt",
|
|
||||||
"url": "https://huggingface.co/spaces/ID-Animator/ID-Animator/resolve/main/animator.ckpt",
|
|
||||||
"size": "247.3MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ID-Animator/mm_sd_v15_v2.ckpt",
|
|
||||||
"type": "ID-Animator",
|
|
||||||
"base": "SD1.5",
|
|
||||||
"save_path": "custom_nodes/ComfyUI_ID_Animator/models/animatediff_models",
|
|
||||||
"description": "AnimateDiff checkpoint for ID-Animator",
|
|
||||||
"reference": "https://huggingface.co/spaces/ID-Animator/ID-Animator",
|
|
||||||
"filename": "mm_sd_v15_v2.ckpt",
|
|
||||||
"url": "https://huggingface.co/spaces/ID-Animator/ID-Animator/resolve/main/mm_sd_v15_v2.ckpt",
|
|
||||||
"size": "1.82GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ID-Animator/image_encoder",
|
|
||||||
"type": "ID-Animator",
|
|
||||||
"base": "SD1.5",
|
|
||||||
"save_path": "custom_nodes/ComfyUI_ID_Animator/models/image_encoder",
|
|
||||||
"description": "CLIP Image encoder for ID-Animator",
|
|
||||||
"reference": "https://huggingface.co/spaces/ID-Animator/ID-Animator",
|
|
||||||
"filename": "model.safetensors",
|
|
||||||
"url": "https://huggingface.co/spaces/ID-Animator/ID-Animator/resolve/main/image_encoder/model.safetensors",
|
|
||||||
"size": "2.53GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "TencentARC/CustomNet",
|
|
||||||
"type": "CustomNet",
|
|
||||||
"base": "CustomNet",
|
|
||||||
"save_path": "custom_nodes/ComfyUI_CustomNet/pretrain",
|
|
||||||
"description": "CustomNet pretrained model for ComfyUI_CustomNet",
|
|
||||||
"reference": "https://huggingface.co/TencentARC/CustomNet/tree/main",
|
|
||||||
"filename": "customnet_v1.pt",
|
|
||||||
"url": "https://huggingface.co/TencentARC/CustomNet/resolve/main/customnet_v1.pt",
|
|
||||||
"size": "5.71GB"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "TTPlanet/TTPLanet_SDXL_Controlnet_Tile_Realistic v2 (fp16)",
|
"name": "TTPlanet/TTPLanet_SDXL_Controlnet_Tile_Realistic v2 (fp16)",
|
||||||
"type": "controlnet",
|
"type": "controlnet",
|
||||||
@@ -3494,17 +3418,6 @@
|
|||||||
"url": "https://huggingface.co/ViperYX/RGT/resolve/main/RGT_S/RGT_S_x4.pth",
|
"url": "https://huggingface.co/ViperYX/RGT/resolve/main/RGT_S/RGT_S_x4.pth",
|
||||||
"size": "136.0MB"
|
"size": "136.0MB"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Doubiiu/ToonCrafter model checkpoint",
|
|
||||||
"type": "checkpoint",
|
|
||||||
"base": "ToonCrafter",
|
|
||||||
"save_path": "custom_nodes/ComfyUI-ToonCrafter/ToonCrafter/checkpoints/tooncrafter_512_interp_v1",
|
|
||||||
"description": "ToonCrafter checkpoint model for ComfyUI-ToonCrafter",
|
|
||||||
"reference": "https://huggingface.co/Doubiiu/ToonCrafter/tree/main",
|
|
||||||
"filename": "model.ckpt",
|
|
||||||
"url": "https://huggingface.co/Doubiiu/ToonCrafter/resolve/main/model.ckpt",
|
|
||||||
"size": "10.5GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "InstantX/FLUX.1-dev Controlnet (Union)",
|
"name": "InstantX/FLUX.1-dev Controlnet (Union)",
|
||||||
@@ -4040,13 +3953,24 @@
|
|||||||
"name": "Comfy-Org/hunyuan_video_vae_bf16.safetensors",
|
"name": "Comfy-Org/hunyuan_video_vae_bf16.safetensors",
|
||||||
"type": "VAE",
|
"type": "VAE",
|
||||||
"base": "Hunyuan Video",
|
"base": "Hunyuan Video",
|
||||||
"save_path": "VAE",
|
"save_path": "default",
|
||||||
"description": "Huyuan Video VAE model. repackaged version.",
|
"description": "Huyuan Video VAE model. repackaged version.",
|
||||||
"reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged",
|
"reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged",
|
||||||
"filename": "hunyuan_video_vae_bf16.safetensors",
|
"filename": "hunyuan_video_vae_bf16.safetensors",
|
||||||
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/vae/hunyuan_video_vae_bf16.safetensors",
|
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/vae/hunyuan_video_vae_bf16.safetensors",
|
||||||
"size": "493MB"
|
"size": "493MB"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/hunyuan_video_image_to_video_720p_bf16.safetensors",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Hunyuan Video",
|
||||||
|
"save_path": "diffusion_models/hunyuan_video",
|
||||||
|
"description": "Huyuan Video Image2Video diffusion model. repackaged version.",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged",
|
||||||
|
"filename": "hunyuan_video_image_to_video_720p_bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/diffusion_models/hunyuan_video_image_to_video_720p_bf16.safetensors",
|
||||||
|
"size": "25.6GB"
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Comfy-Org/llava_llama3_fp8_scaled.safetensors",
|
"name": "Comfy-Org/llava_llama3_fp8_scaled.safetensors",
|
||||||
@@ -4070,6 +3994,17 @@
|
|||||||
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/text_encoders/llava_llama3_fp16.safetensors",
|
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/text_encoders/llava_llama3_fp16.safetensors",
|
||||||
"size": "16.1GB"
|
"size": "16.1GB"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/llava_llama3_vision.safetensors",
|
||||||
|
"type": "clip_vision",
|
||||||
|
"base": "LLaVA-Llama-3",
|
||||||
|
"save_path": "text_encoders",
|
||||||
|
"description": "llava_llama3_vision clip vison model. This is required for using Hunyuan Video Image2Video.",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged",
|
||||||
|
"filename": "llava_llama3_vision.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/clip_vision/llava_llama3_vision.safetensors",
|
||||||
|
"size": "649MB"
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "FLUX.1 [Schnell] Diffusion model",
|
"name": "FLUX.1 [Schnell] Diffusion model",
|
||||||
@@ -4397,23 +4332,11 @@
|
|||||||
"size": "12.7GB"
|
"size": "12.7GB"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
|
||||||
"name": "BAAI/SegGPT",
|
|
||||||
"type": "SegGPT",
|
|
||||||
"base": "SegGPT",
|
|
||||||
"save_path": "custom_nodes/comfyui-SegGPT",
|
|
||||||
"description": "SegGPT",
|
|
||||||
"reference": "https://huggingface.co/BAAI/SegGPT",
|
|
||||||
"filename": "seggpt_vit_large.pth",
|
|
||||||
"url": "https://huggingface.co/BAAI/SegGPT/resolve/main/seggpt_vit_large.pth",
|
|
||||||
"size": "1.48GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors [Long CLIP L]",
|
"name": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors [Long CLIP L]",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "clip",
|
"base": "clip",
|
||||||
"save_path": "clip/long_clip",
|
"save_path": "text_encoders/long_clip",
|
||||||
"description": "Greatly improved TEXT + Detail (as CLIP-L for Flux.1)",
|
"description": "Greatly improved TEXT + Detail (as CLIP-L for Flux.1)",
|
||||||
"reference": "https://huggingface.co/zer0int",
|
"reference": "https://huggingface.co/zer0int",
|
||||||
"filename": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors",
|
"filename": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors",
|
||||||
@@ -4424,7 +4347,7 @@
|
|||||||
"name": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors [Long CLIP L]",
|
"name": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors [Long CLIP L]",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
"base": "clip",
|
"base": "clip",
|
||||||
"save_path": "clip/long_clip",
|
"save_path": "text_encoders/long_clip",
|
||||||
"description": "Greatly improved TEXT + Detail (as CLIP-L for Flux.1)",
|
"description": "Greatly improved TEXT + Detail (as CLIP-L for Flux.1)",
|
||||||
"reference": "https://huggingface.co/zer0int",
|
"reference": "https://huggingface.co/zer0int",
|
||||||
"filename": "ViT-L-14-TEXT-detail-improved-hiT-GmP-TE-only-HF.safetensors",
|
"filename": "ViT-L-14-TEXT-detail-improved-hiT-GmP-TE-only-HF.safetensors",
|
||||||
@@ -4646,6 +4569,17 @@
|
|||||||
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltx-video-2b-v0.9.1.safetensors",
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltx-video-2b-v0.9.1.safetensors",
|
||||||
"size": "5.72GB"
|
"size": "5.72GB"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 2B v0.9.5 Checkpoint",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "LTX-Video is the first DiT-based video generation model capable of generating high-quality videos in real-time. It produces 24 FPS videos at a 768x512 resolution faster than they can be watched. Trained on a large-scale dataset of diverse videos, the model generates high-resolution videos with realistic and varied content.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltx-video-2b-v0.9.5.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltx-video-2b-v0.9.5.safetensors",
|
||||||
|
"size": "6.34GB"
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "XLabs-AI/flux-canny-controlnet-v3.safetensors",
|
"name": "XLabs-AI/flux-canny-controlnet-v3.safetensors",
|
||||||
@@ -4725,6 +4659,401 @@
|
|||||||
"filename": "ip_adapter.safetensors",
|
"filename": "ip_adapter.safetensors",
|
||||||
"url": "https://huggingface.co/XLabs-AI/flux-ip-adapter/resolve/main/ip_adapter.safetensors",
|
"url": "https://huggingface.co/XLabs-AI/flux-ip-adapter/resolve/main/ip_adapter.safetensors",
|
||||||
"size": "982MB"
|
"size": "982MB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "efficient_sam_s_cpu.jit [ComfyUI-YoloWorld-EfficientSAM]",
|
||||||
|
"type": "efficient_sam",
|
||||||
|
"base": "efficient_sam",
|
||||||
|
"save_path": "yolo_world",
|
||||||
|
"description": "Install efficient_sam_s_cpu.jit into ComfyUI-YoloWorld-EfficientSAM",
|
||||||
|
"reference": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/tree/main",
|
||||||
|
"filename": "efficient_sam_s_cpu.jit",
|
||||||
|
"url": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/resolve/main/efficient_sam_s_cpu.jit",
|
||||||
|
"size": "106.0MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "efficient_sam_s_gpu.jit [ComfyUI-YoloWorld-EfficientSAM]",
|
||||||
|
"type": "efficient_sam",
|
||||||
|
"base": "efficient_sam",
|
||||||
|
"save_path": "yolo_world",
|
||||||
|
"description": "Install efficient_sam_s_gpu.jit into ComfyUI-YoloWorld-EfficientSAM",
|
||||||
|
"reference": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/tree/main",
|
||||||
|
"filename": "efficient_sam_s_gpu.jit",
|
||||||
|
"url": "https://huggingface.co/camenduru/YoloWorld-EfficientSAM/resolve/main/efficient_sam_s_gpu.jit",
|
||||||
|
"size": "106.0MB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "TencentARC/CustomNet V1",
|
||||||
|
"type": "CustomNet",
|
||||||
|
"base": "CustomNet",
|
||||||
|
"save_path": "checkpoints/customnet",
|
||||||
|
"description": "CustomNet pretrained model for ComfyUI_CustomNet",
|
||||||
|
"reference": "https://huggingface.co/TencentARC/CustomNet/tree/main",
|
||||||
|
"filename": "customnet_v1.pt",
|
||||||
|
"url": "https://huggingface.co/TencentARC/CustomNet/resolve/main/customnet_v1.pt",
|
||||||
|
"size": "5.71GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TencentARC/CustomNet Inpaint V1",
|
||||||
|
"type": "CustomNet",
|
||||||
|
"base": "CustomNet",
|
||||||
|
"save_path": "checkpoints/customnet",
|
||||||
|
"description": "CustomNet Inpaint pretrained model for ComfyUI_CustomNet",
|
||||||
|
"reference": "https://huggingface.co/TencentARC/CustomNet/tree/main",
|
||||||
|
"filename": "customnet_inpaint_v1.pt",
|
||||||
|
"url": "https://huggingface.co/TencentARC/CustomNet/resolve/main/customnet_inpaint_v1.pt",
|
||||||
|
"size": "5.71GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "deepseek-ai/Janus-Pro-1B",
|
||||||
|
"type": "Janus-Pro",
|
||||||
|
"base": "Janus-Pro",
|
||||||
|
"save_path": "Janus-Pro",
|
||||||
|
"description": "[SNAPSHOT] Janus-Pro-1B model.[w/You cannot download this item on ComfyUI-Manager versions below V3.18]",
|
||||||
|
"reference": "https://huggingface.co/deepseek-ai/Janus-Pro-1B",
|
||||||
|
"filename": "<huggingface>",
|
||||||
|
"url": "deepseek-ai/Janus-Pro-1B",
|
||||||
|
"size": "7.8GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "deepseek-ai/Janus-Pro-7B",
|
||||||
|
"type": "Janus-Pro",
|
||||||
|
"base": "Janus-Pro",
|
||||||
|
"save_path": "Janus-Pro",
|
||||||
|
"description": "[SNAPSHOT] Janus-Pro-7B model.[w/You cannot download this item on ComfyUI-Manager versions below V3.18]",
|
||||||
|
"reference": "https://huggingface.co/deepseek-ai/Janus-Pro-7B",
|
||||||
|
"filename": "<huggingface>",
|
||||||
|
"url": "deepseek-ai/Janus-Pro-7B",
|
||||||
|
"size": "14.85GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "kolors/vae/diffusion_pytorch_model.fp16.safetensors",
|
||||||
|
"type": "VAE",
|
||||||
|
"base": "Kolors",
|
||||||
|
"save_path": "vae/kolors",
|
||||||
|
"description": "Kolors VAE",
|
||||||
|
"reference": "https://huggingface.co/Kwai-Kolors/Kolors",
|
||||||
|
"filename": "diffusion_pytorch_model.fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Kwai-Kolors/Kolors/resolve/main/vae/diffusion_pytorch_model.fp16.safetensors",
|
||||||
|
"size": "167MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "kolors/vae/diffusion_pytorch_model.safetensors",
|
||||||
|
"type": "VAE",
|
||||||
|
"base": "Kolors",
|
||||||
|
"save_path": "vae/kolors",
|
||||||
|
"description": "Kolors VAE",
|
||||||
|
"reference": "https://huggingface.co/Kwai-Kolors/Kolors",
|
||||||
|
"filename": "diffusion_pytorch_model.safetensors",
|
||||||
|
"url": "https://huggingface.co/Kwai-Kolors/Kolors/resolve/main/vae/diffusion_pytorch_model.safetensors",
|
||||||
|
"size": "335MB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (bf16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 480p 14B (bf16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_480p_14B_bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_bf16.safetensors",
|
||||||
|
"size": "32.8GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 480p 14B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_480p_14B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp16.safetensors",
|
||||||
|
"size": "32.8GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp8_e4m3fn)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 480p 14B (fp8_e4m3fn)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_480p_14B_fp8_e4m3fn.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp8_e4m3fn.safetensors",
|
||||||
|
"size": "16.4GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp8_scaled)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 480p 14B (fp8_scaled)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_480p_14B_fp8_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp8_scaled.safetensors",
|
||||||
|
"size": "16.4GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (bf16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 720p 14B (bf16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_720p_14B_bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_bf16.safetensors",
|
||||||
|
"size": "32.8GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 720p 14B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_720p_14B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp16.safetensors",
|
||||||
|
"size": "32.8GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp8_e4m3fn)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 720p 14B (fp8_e4m3fn)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_720p_14B_fp8_e4m3fn.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp8_e4m3fn.safetensors",
|
||||||
|
"size": "16.4GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp8_scaled)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 720p 14B (fp8_scaled)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_720p_14B_fp8_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp8_scaled.safetensors",
|
||||||
|
"size": "16.4GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 t2v 1.3B (bf16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for t2v 1.3B (bf16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_t2v_1.3B_bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_1.3B_bf16.safetensors",
|
||||||
|
"size": "2.84GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 t2v 1.3B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for t2v 1.3B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_t2v_1.3B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_1.3B_fp16.safetensors",
|
||||||
|
"size": "2.84GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 t2v 14B (bf16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for t2v 14B (bf16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_t2v_14B_bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_bf16.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 t2v 14B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for t2v 14B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_t2v_14B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp16.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 t2v 14B (fp8_e4m3fn)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for t2v 14B (fp8_e4m3fn)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_t2v_14B_fp8_e4m3fn.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp8_e4m3fn.safetensors",
|
||||||
|
"size": "14.3GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 t2v 14B (fp8_scaled)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for t2v 14B (fp8_scaled)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_t2v_14B_fp8_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp8_scaled.safetensors",
|
||||||
|
"size": "14.3GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 VAE",
|
||||||
|
"type": "vae",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "vae",
|
||||||
|
"description": "Wan2.1 VAE model",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan_2.1_vae.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/vae/wan_2.1_vae.safetensors",
|
||||||
|
"size": "254MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/clip_vision_h.safetensors",
|
||||||
|
"type": "clip_vision",
|
||||||
|
"base": "clip_vision_h",
|
||||||
|
"save_path": "clip_vision",
|
||||||
|
"description": "clip_vision_h model for Wan2.1",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "clip_vision_h.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/clip_vision/clip_vision_h.safetensors",
|
||||||
|
"size": "1.26GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/umt5_xxl_fp16.safetensors",
|
||||||
|
"type": "clip",
|
||||||
|
"base": "umt5_xxl",
|
||||||
|
"save_path": "text_encoders",
|
||||||
|
"description": "umt5_xxl_fp16 text encoder for Wan2.1",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "umt5_xxl_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp16.safetensors",
|
||||||
|
"size": "11.4GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/umt5_xxl_fp8_e4m3fn_scaled.safetensors",
|
||||||
|
"type": "clip",
|
||||||
|
"base": "umt5_xxl",
|
||||||
|
"save_path": "text_encoders",
|
||||||
|
"description": "umt5_xxl_fp8_e4m3fn_scaled text encoder for Wan2.1",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors",
|
||||||
|
"size": "6.74GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "lllyasviel/FramePackI2V_HY",
|
||||||
|
"type": "FramePackI2V",
|
||||||
|
"base": "FramePackI2V",
|
||||||
|
"save_path": "diffusers/lllyasviel",
|
||||||
|
"description": "[SNAPSHOT] This is the f1k1_x_g9_f1k1f2k2f16k4_td FramePack for HY. [w/You cannot download this item on ComfyUI-Manager versions below V3.18]",
|
||||||
|
"reference": "https://huggingface.co/lllyasviel/FramePackI2V_HY",
|
||||||
|
"filename": "<huggingface>",
|
||||||
|
"url": "lllyasviel/FramePackI2V_HY",
|
||||||
|
"size": "25.75GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "LTX-Video Spatial Upscaler v0.9.7",
|
||||||
|
"type": "upscale",
|
||||||
|
"base": "upscale",
|
||||||
|
"save_path": "default",
|
||||||
|
"description": "Spatial upscaler model for LTX-Video. This model enhances the spatial resolution of generated videos.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-spatial-upscaler-0.9.7.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-spatial-upscaler-0.9.7.safetensors",
|
||||||
|
"size": "505MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video Temporal Upscaler v0.9.7",
|
||||||
|
"type": "upscale",
|
||||||
|
"base": "upscale",
|
||||||
|
"save_path": "default",
|
||||||
|
"description": "Temporal upscaler model for LTX-Video. This model enhances the temporal resolution and smoothness of generated videos.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-temporal-upscaler-0.9.7.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-temporal-upscaler-0.9.7.safetensors",
|
||||||
|
"size": "524MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 13B v0.9.7",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "High-resolution quality LTX-Video 13B model.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-13b-0.9.7-dev.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-dev.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 13B FP8 v0.9.7",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "Quantized version of the LTX-Video 13B model, optimized for lower VRAM usage while maintaining high quality.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-13b-0.9.7-dev-fp8.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-dev-fp8.safetensors",
|
||||||
|
"size": "15.7GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 13B Distilled v0.9.7",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "Distilled version of the LTX-Video 13B model, providing improved efficiency while maintaining high-resolution quality.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-13b-0.9.7-distilled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 13B Distilled FP8 v0.9.7",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "Quantized distilled version of the LTX-Video 13B model, optimized for even lower VRAM usage while maintaining quality.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-13b-0.9.7-distilled-fp8.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled-fp8.safetensors",
|
||||||
|
"size": "15.7GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 13B Distilled LoRA v0.9.7",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "loras",
|
||||||
|
"description": "A LoRA adapter that transforms the standard LTX-Video 13B model into a distilled version when loaded.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-13b-0.9.7-distilled-lora128.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled-lora128.safetensors",
|
||||||
|
"size": "1.33GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Latent Bridge Matching for Image Relighting",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "LBM",
|
||||||
|
"save_path": "diffusion_models/LBM",
|
||||||
|
"description": "Latent Bridge Matching (LBM) Relighting model",
|
||||||
|
"reference": "https://huggingface.co/jasperai/LBM_relighting",
|
||||||
|
"filename": "LBM_relighting.safetensors",
|
||||||
|
"url": "https://huggingface.co/jasperai/LBM_relighting/resolve/main/model.safetensors",
|
||||||
|
"size": "5.02GB"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
95
node_db/README.md
Normal file
95
node_db/README.md
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
# ComfyUI-Manager: Node Database (node_db)
|
||||||
|
|
||||||
|
This directory contains the JSON database files that power ComfyUI-Manager's legacy node registry system. While the manager is gradually transitioning to the online Custom Node Registry (CNR), these local JSON files continue to provide important metadata about custom nodes, models, and their integrations.
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
The node_db directory is organized into several subdirectories, each serving a specific purpose:
|
||||||
|
|
||||||
|
- **dev/**: Development channel files with latest additions and experimental nodes
|
||||||
|
- **legacy/**: Historical/legacy nodes that may require special handling
|
||||||
|
- **new/**: New nodes that have passed initial verification but are still being evaluated
|
||||||
|
- **forked/**: Forks of existing nodes with modifications
|
||||||
|
- **tutorial/**: Example and tutorial nodes designed for learning purposes
|
||||||
|
|
||||||
|
## Core Database Files
|
||||||
|
|
||||||
|
Each subdirectory contains a standard set of JSON files:
|
||||||
|
|
||||||
|
- **custom-node-list.json**: Primary database of custom nodes with metadata
|
||||||
|
- **extension-node-map.json**: Maps between extensions and individual nodes they provide
|
||||||
|
- **model-list.json**: Catalog of models that can be downloaded through the manager
|
||||||
|
- **alter-list.json**: Alternative implementations of nodes for compatibility or functionality
|
||||||
|
- **github-stats.json**: GitHub repository statistics for node popularity metrics
|
||||||
|
|
||||||
|
## Database Schema
|
||||||
|
|
||||||
|
### custom-node-list.json
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"custom_nodes": [
|
||||||
|
{
|
||||||
|
"title": "Node display name",
|
||||||
|
"name": "Repository name",
|
||||||
|
"reference": "Original repository if forked",
|
||||||
|
"files": ["GitHub URL or other source location"],
|
||||||
|
"install_type": "git",
|
||||||
|
"description": "Description of the node's functionality",
|
||||||
|
"pip": ["optional pip dependencies"],
|
||||||
|
"js": ["optional JavaScript files"],
|
||||||
|
"tags": ["categorization tags"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### extension-node-map.json
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"extension-id": [
|
||||||
|
["list", "of", "node", "classes"],
|
||||||
|
{
|
||||||
|
"author": "Author name",
|
||||||
|
"description": "Extension description",
|
||||||
|
"nodename_pattern": "Optional regex pattern for node name matching"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transition to Custom Node Registry (CNR)
|
||||||
|
|
||||||
|
This local database system is being progressively replaced by the online Custom Node Registry (CNR), which provides:
|
||||||
|
- Real-time updates without manual JSON maintenance
|
||||||
|
- Improved versioning support
|
||||||
|
- Better security validation
|
||||||
|
- Enhanced metadata
|
||||||
|
|
||||||
|
The Manager supports both systems simultaneously during the transition period.
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
- The database follows a channel-based architecture for different sources
|
||||||
|
- Multiple database modes are supported: Channel, Local, and Remote
|
||||||
|
- The system supports differential updates to minimize bandwidth usage
|
||||||
|
- Security levels are enforced for different node installations based on source
|
||||||
|
|
||||||
|
## Usage in the Application
|
||||||
|
|
||||||
|
The Manager's backend uses these database files to:
|
||||||
|
|
||||||
|
1. Provide browsable lists of available nodes and models
|
||||||
|
2. Resolve dependencies for installation
|
||||||
|
3. Track updates and new versions
|
||||||
|
4. Map node classes to their source repositories
|
||||||
|
5. Assess risk levels for installation security
|
||||||
|
|
||||||
|
## Maintenance Scripts
|
||||||
|
|
||||||
|
Each subdirectory contains a `scan.sh` script that assists with:
|
||||||
|
- Scanning repositories for new nodes
|
||||||
|
- Updating metadata
|
||||||
|
- Validating database integrity
|
||||||
|
- Generating proper JSON structures
|
||||||
|
|
||||||
|
This database system enables a flexible, secure, and comprehensive management system for the ComfyUI ecosystem while the transition to CNR continues.
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,25 @@
|
|||||||
{
|
{
|
||||||
"custom_nodes": [
|
"custom_nodes": [
|
||||||
|
{
|
||||||
|
"author": "SanDiegoDude",
|
||||||
|
"title": "ComfyUI-HiDream-Sampler [WIP]",
|
||||||
|
"reference": "https://github.com/SanDiegoDude/ComfyUI-HiDream-Sampler",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/SanDiegoDude/ComfyUI-HiDream-Sampler"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "A collection of enhanced nodes for ComfyUI that provide powerful additional functionality to your workflows.\nNOTE: The files in the repo are not organized."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "PramaLLC",
|
||||||
|
"title": "ComfyUI BEN - Background Erase Network",
|
||||||
|
"reference": "https://github.com/PramaLLC/BEN2_ComfyUI",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/PramaLLC/BEN2_ComfyUI"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Remove backgrounds from images with [a/BEN2](https://huggingface.co/PramaLLC/BEN2) in ComfyUI\nOriginal repo: [a/https://github.com/DoctorDiffusion/ComfyUI-BEN](https://github.com/DoctorDiffusion/ComfyUI-BEN)"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"author": "BlenderNeko",
|
"author": "BlenderNeko",
|
||||||
"title": "ltdrdata/ComfyUI_TiledKSampler",
|
"title": "ltdrdata/ComfyUI_TiledKSampler",
|
||||||
@@ -119,6 +139,16 @@
|
|||||||
],
|
],
|
||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "A forked version of ComfyUI_ExtraModels. (modified by Efficient-Large-Model)"
|
"description": "A forked version of ComfyUI_ExtraModels. (modified by Efficient-Large-Model)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Pablerdo",
|
||||||
|
"title": "ComfyUI-PSNodes",
|
||||||
|
"reference": "https://github.com/Pablerdo/ComfyUI-PSNodes",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/Pablerdo/ComfyUI-PSNodes"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "A fork of KJNodes for ComfyUI.\nVarious quality of life -nodes for ComfyUI, mostly just visual stuff to improve usability"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +1,148 @@
|
|||||||
{
|
{
|
||||||
"models": []
|
"models": [
|
||||||
|
{
|
||||||
|
"name": "Inswapper-fp16 (face swap) [REMOVED]",
|
||||||
|
"type": "insightface",
|
||||||
|
"base": "inswapper",
|
||||||
|
"save_path": "insightface",
|
||||||
|
"description": "Checkpoint of the insightface swapper model\n(used by ComfyUI-FaceSwap, comfyui-reactor-node, CharacterFaceSwap,\nComfyUI roop and comfy_mtb)",
|
||||||
|
"reference": "https://github.com/facefusion/facefusion-assets",
|
||||||
|
"filename": "inswapper_128_fp16.onnx",
|
||||||
|
"url": "https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128_fp16.onnx",
|
||||||
|
"size": "277.7MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Inswapper (face swap) [REMOVED]",
|
||||||
|
"type": "insightface",
|
||||||
|
"base": "inswapper",
|
||||||
|
"save_path": "insightface",
|
||||||
|
"description": "Checkpoint of the insightface swapper model\n(used by ComfyUI-FaceSwap, comfyui-reactor-node, CharacterFaceSwap,\nComfyUI roop and comfy_mtb)",
|
||||||
|
"reference": "https://github.com/facefusion/facefusion-assets",
|
||||||
|
"filename": "inswapper_128.onnx",
|
||||||
|
"url": "https://github.com/facefusion/facefusion-assets/releases/download/models/inswapper_128.onnx",
|
||||||
|
"size": "555.3MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pfg-novel-n10.pt",
|
||||||
|
"type": "PFG",
|
||||||
|
"base": "SD1.5",
|
||||||
|
"save_path": "custom_nodes/pfg-ComfyUI/models",
|
||||||
|
"description": "Pressing 'install' directly downloads the model from the pfg-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||||
|
"reference": "https://huggingface.co/furusu/PFG",
|
||||||
|
"filename": "pfg-novel-n10.pt",
|
||||||
|
"url": "https://huggingface.co/furusu/PFG/resolve/main/pfg-novel-n10.pt",
|
||||||
|
"size": "23.6MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pfg-wd14-n10.pt",
|
||||||
|
"type": "PFG",
|
||||||
|
"base": "SD1.5",
|
||||||
|
"save_path": "custom_nodes/pfg-ComfyUI/models",
|
||||||
|
"description": "Pressing 'install' directly downloads the model from the pfg-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||||
|
"reference": "https://huggingface.co/furusu/PFG",
|
||||||
|
"filename": "pfg-wd14-n10.pt",
|
||||||
|
"url": "https://huggingface.co/furusu/PFG/resolve/main/pfg-wd14-n10.pt",
|
||||||
|
"size": "31.5MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pfg-wd15beta2-n10.pt",
|
||||||
|
"type": "PFG",
|
||||||
|
"base": "SD1.5",
|
||||||
|
"save_path": "custom_nodes/pfg-ComfyUI/models",
|
||||||
|
"description": "Pressing 'install' directly downloads the model from the pfg-ComfyUI/models extension node. (Note: Requires ComfyUI-Manager V0.24 or above)",
|
||||||
|
"reference": "https://huggingface.co/furusu/PFG",
|
||||||
|
"filename": "pfg-wd15beta2-n10.pt",
|
||||||
|
"url": "https://huggingface.co/furusu/PFG/resolve/main/pfg-wd15beta2-n10.pt",
|
||||||
|
"size": "31.5MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "shape_predictor_68_face_landmarks.dat [Face Analysis]",
|
||||||
|
"type": "Shape Predictor",
|
||||||
|
"base": "DLIB",
|
||||||
|
"save_path": "custom_nodes/comfyui_faceanalysis/dlib",
|
||||||
|
"description": "To use the Face Analysis for ComfyUI custom node, installation of this model is needed.",
|
||||||
|
"reference": "https://huggingface.co/matt3ounstable/dlib_predictor_recognition/tree/main",
|
||||||
|
"filename": "shape_predictor_68_face_landmarks.dat",
|
||||||
|
"url": "https://huggingface.co/matt3ounstable/dlib_predictor_recognition/resolve/main/shape_predictor_68_face_landmarks.dat",
|
||||||
|
"size": "99.7MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dlib_face_recognition_resnet_model_v1.dat [Face Analysis]",
|
||||||
|
"type": "Face Recognition",
|
||||||
|
"base": "DLIB",
|
||||||
|
"save_path": "custom_nodes/comfyui_faceanalysis/dlib",
|
||||||
|
"description": "To use the Face Analysis for ComfyUI custom node, installation of this model is needed.",
|
||||||
|
"reference": "https://huggingface.co/matt3ounstable/dlib_predictor_recognition/tree/main",
|
||||||
|
"filename": "dlib_face_recognition_resnet_model_v1.dat",
|
||||||
|
"url": "https://huggingface.co/matt3ounstable/dlib_predictor_recognition/resolve/main/dlib_face_recognition_resnet_model_v1.dat",
|
||||||
|
"size": "22.5MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ID-Animator/animator.ckpt",
|
||||||
|
"type": "ID-Animator",
|
||||||
|
"base": "SD1.5",
|
||||||
|
"save_path": "custom_nodes/comfyui_id_animator/models",
|
||||||
|
"description": "ID-Animator checkpoint",
|
||||||
|
"reference": "https://huggingface.co/spaces/ID-Animator/ID-Animator",
|
||||||
|
"filename": "animator.ckpt",
|
||||||
|
"url": "https://huggingface.co/spaces/ID-Animator/ID-Animator/resolve/main/animator.ckpt",
|
||||||
|
"size": "247.3MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ID-Animator/mm_sd_v15_v2.ckpt",
|
||||||
|
"type": "ID-Animator",
|
||||||
|
"base": "SD1.5",
|
||||||
|
"save_path": "custom_nodes/comfyui_id_animator/models/animatediff_models",
|
||||||
|
"description": "AnimateDiff checkpoint for ID-Animator",
|
||||||
|
"reference": "https://huggingface.co/spaces/ID-Animator/ID-Animator",
|
||||||
|
"filename": "mm_sd_v15_v2.ckpt",
|
||||||
|
"url": "https://huggingface.co/spaces/ID-Animator/ID-Animator/resolve/main/mm_sd_v15_v2.ckpt",
|
||||||
|
"size": "1.82GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ID-Animator/image_encoder",
|
||||||
|
"type": "ID-Animator",
|
||||||
|
"base": "SD1.5",
|
||||||
|
"save_path": "custom_nodes/comfyui_id_animator/models/image_encoder",
|
||||||
|
"description": "CLIP Image encoder for ID-Animator",
|
||||||
|
"reference": "https://huggingface.co/spaces/ID-Animator/ID-Animator",
|
||||||
|
"filename": "model.safetensors",
|
||||||
|
"url": "https://huggingface.co/spaces/ID-Animator/ID-Animator/resolve/main/image_encoder/model.safetensors",
|
||||||
|
"size": "2.53GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Doubiiu/ToonCrafter model checkpoint",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "ToonCrafter",
|
||||||
|
"save_path": "custom_nodes/comfyui-tooncrafter/ToonCrafter/checkpoints/tooncrafter_512_interp_v1",
|
||||||
|
"description": "ToonCrafter checkpoint model for ComfyUI-ToonCrafter",
|
||||||
|
"reference": "https://huggingface.co/Doubiiu/ToonCrafter/tree/main",
|
||||||
|
"filename": "model.ckpt",
|
||||||
|
"url": "https://huggingface.co/Doubiiu/ToonCrafter/resolve/main/model.ckpt",
|
||||||
|
"size": "10.5GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "BAAI/SegGPT",
|
||||||
|
"type": "SegGPT",
|
||||||
|
"base": "SegGPT",
|
||||||
|
"save_path": "custom_nodes/comfyui-seggpt",
|
||||||
|
"description": "SegGPT",
|
||||||
|
"reference": "https://huggingface.co/BAAI/SegGPT",
|
||||||
|
"filename": "seggpt_vit_large.pth",
|
||||||
|
"url": "https://huggingface.co/BAAI/SegGPT/resolve/main/seggpt_vit_large.pth",
|
||||||
|
"size": "1.48GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "kohya-ss/ControlNet-LLLite: SDXL Canny Anime",
|
||||||
|
"type": "controlnet",
|
||||||
|
"base": "SDXL",
|
||||||
|
"save_path": "custom_nodes/ControlNet-LLLite-ComfyUI/models",
|
||||||
|
"description": "An extremely compactly designed controlnet model (a.k.a. ControlNet-LLLite). Note: The model structure is highly experimental and may be subject to change in the future.",
|
||||||
|
"reference": "https://huggingface.co/kohya-ss/controlnet-lllite",
|
||||||
|
"filename": "controllllite_v01032064e_sdxl_canny_anime.safetensors",
|
||||||
|
"url": "https://huggingface.co/kohya-ss/controlnet-lllite/resolve/main/controllllite_v01032064e_sdxl_canny_anime.safetensors",
|
||||||
|
"size": "46.2MB"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,457 @@
|
|||||||
{
|
{
|
||||||
"models": [
|
"models": [
|
||||||
|
{
|
||||||
|
"name": "Latent Bridge Matching for Image Relighting",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "LBM",
|
||||||
|
"save_path": "diffusion_models/LBM",
|
||||||
|
"description": "Latent Bridge Matching (LBM) Relighting model",
|
||||||
|
"reference": "https://huggingface.co/jasperai/LBM_relighting",
|
||||||
|
"filename": "LBM_relighting.safetensors",
|
||||||
|
"url": "https://huggingface.co/jasperai/LBM_relighting/resolve/main/model.safetensors",
|
||||||
|
"size": "5.02GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 13B Distilled v0.9.7",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "Distilled version of the LTX-Video 13B model, providing improved efficiency while maintaining high-resolution quality.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-13b-0.9.7-distilled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 13B Distilled FP8 v0.9.7",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "Quantized distilled version of the LTX-Video 13B model, optimized for even lower VRAM usage while maintaining quality.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-13b-0.9.7-distilled-fp8.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled-fp8.safetensors",
|
||||||
|
"size": "15.7GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 13B Distilled LoRA v0.9.7",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "loras",
|
||||||
|
"description": "A LoRA adapter that transforms the standard LTX-Video 13B model into a distilled version when loaded.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-13b-0.9.7-distilled-lora128.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-distilled-lora128.safetensors",
|
||||||
|
"size": "1.33GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lllyasviel/FramePackI2V_HY",
|
||||||
|
"type": "FramePackI2V",
|
||||||
|
"base": "FramePackI2V",
|
||||||
|
"save_path": "diffusers/lllyasviel",
|
||||||
|
"description": "[SNAPSHOT] This is the f1k1_x_g9_f1k1f2k2f16k4_td FramePack for HY. [w/You cannot download this item on ComfyUI-Manager versions below V3.18]",
|
||||||
|
"reference": "https://huggingface.co/lllyasviel/FramePackI2V_HY",
|
||||||
|
"filename": "<huggingface>",
|
||||||
|
"url": "lllyasviel/FramePackI2V_HY",
|
||||||
|
"size": "25.75GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "LTX-Video Spatial Upscaler v0.9.7",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "Spatial upscaler model for LTX-Video. This model enhances the spatial resolution of generated videos.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-spatial-upscaler-0.9.7.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-spatial-upscaler-0.9.7.safetensors",
|
||||||
|
"size": "505MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video Temporal Upscaler v0.9.7",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "Temporal upscaler model for LTX-Video. This model enhances the temporal resolution and smoothness of generated videos.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-temporal-upscaler-0.9.7.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-temporal-upscaler-0.9.7.safetensors",
|
||||||
|
"size": "524MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 13B v0.9.7",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "High-resolution quality LTX-Video 13B model.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-13b-0.9.7-dev.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-dev.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 13B FP8 v0.9.7",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "Quantized version of the LTX-Video 13B model, optimized for lower VRAM usage while maintaining high quality.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-13b-0.9.7-dev-fp8.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.7-dev-fp8.safetensors",
|
||||||
|
"size": "15.7GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (bf16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 480p 14B (bf16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_480p_14B_bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_bf16.safetensors",
|
||||||
|
"size": "32.8GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 480p 14B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_480p_14B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp16.safetensors",
|
||||||
|
"size": "32.8GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp8_e4m3fn)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 480p 14B (fp8_e4m3fn)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_480p_14B_fp8_e4m3fn.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp8_e4m3fn.safetensors",
|
||||||
|
"size": "16.4GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 480p 14B (fp8_scaled)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 480p 14B (fp8_scaled)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_480p_14B_fp8_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_480p_14B_fp8_scaled.safetensors",
|
||||||
|
"size": "16.4GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (bf16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 720p 14B (bf16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_720p_14B_bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_bf16.safetensors",
|
||||||
|
"size": "32.8GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 720p 14B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_720p_14B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp16.safetensors",
|
||||||
|
"size": "32.8GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp8_e4m3fn)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 720p 14B (fp8_e4m3fn)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_720p_14B_fp8_e4m3fn.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp8_e4m3fn.safetensors",
|
||||||
|
"size": "16.4GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 i2v 720p 14B (fp8_scaled)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for i2v 720p 14B (fp8_scaled)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_i2v_720p_14B_fp8_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_i2v_720p_14B_fp8_scaled.safetensors",
|
||||||
|
"size": "16.4GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/clip_vision_h.safetensors",
|
||||||
|
"type": "clip_vision",
|
||||||
|
"base": "clip_vision_h",
|
||||||
|
"save_path": "clip_vision",
|
||||||
|
"description": "clip_vision_h model for Wan2.1",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "clip_vision_h.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/clip_vision/clip_vision_h.safetensors",
|
||||||
|
"size": "1.26GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 t2v 1.3B (bf16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for t2v 1.3B (bf16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_t2v_1.3B_bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_1.3B_bf16.safetensors",
|
||||||
|
"size": "2.84GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 t2v 1.3B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for t2v 1.3B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_t2v_1.3B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_1.3B_fp16.safetensors",
|
||||||
|
"size": "2.84GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 t2v 14B (bf16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for t2v 14B (bf16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_t2v_14B_bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_bf16.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 t2v 14B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for t2v 14B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_t2v_14B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp16.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 t2v 14B (fp8_e4m3fn)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for t2v 14B (fp8_e4m3fn)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_t2v_14B_fp8_e4m3fn.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp8_e4m3fn.safetensors",
|
||||||
|
"size": "14.3GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 t2v 14B (fp8_scaled)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "diffusion_models/Wan2.1",
|
||||||
|
"description": "Wan2.1 difussion model for t2v 14B (fp8_scaled)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan2.1_t2v_14B_fp8_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/wan2.1_t2v_14B_fp8_scaled.safetensors",
|
||||||
|
"size": "14.3GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.1 VAE",
|
||||||
|
"type": "vae",
|
||||||
|
"base": "Wan2.1",
|
||||||
|
"save_path": "vae",
|
||||||
|
"description": "Wan2.1 VAE model",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "wan_2.1_vae.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/vae/wan_2.1_vae.safetensors",
|
||||||
|
"size": "254MB"
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/umt5_xxl_fp16.safetensors",
|
||||||
|
"type": "clip",
|
||||||
|
"base": "umt5_xxl",
|
||||||
|
"save_path": "text_encoders",
|
||||||
|
"description": "umt5_xxl_fp16 text encoder for Wan2.1",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "umt5_xxl_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp16.safetensors",
|
||||||
|
"size": "11.4GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/umt5_xxl_fp8_e4m3fn_scaled.safetensors",
|
||||||
|
"type": "clip",
|
||||||
|
"base": "umt5_xxl",
|
||||||
|
"save_path": "text_encoders",
|
||||||
|
"description": "umt5_xxl_fp8_e4m3fn_scaled text encoder for Wan2.1",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged",
|
||||||
|
"filename": "umt5_xxl_fp8_e4m3fn_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/text_encoders/umt5_xxl_fp8_e4m3fn_scaled.safetensors",
|
||||||
|
"size": "6.74GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/hunyuan_video_image_to_video_720p_bf16.safetensors",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Hunyuan Video",
|
||||||
|
"save_path": "diffusion_models/hunyuan_video",
|
||||||
|
"description": "Huyuan Video Image2Video diffusion model. repackaged version.",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged",
|
||||||
|
"filename": "hunyuan_video_image_to_video_720p_bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/diffusion_models/hunyuan_video_image_to_video_720p_bf16.safetensors",
|
||||||
|
"size": "25.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/llava_llama3_vision.safetensors",
|
||||||
|
"type": "clip_vision",
|
||||||
|
"base": "LLaVA-Llama-3",
|
||||||
|
"save_path": "text_encoders",
|
||||||
|
"description": "llava_llama3_vision clip vison model. This is required for using Hunyuan Video Image2Video.",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged",
|
||||||
|
"filename": "llava_llama3_vision.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/clip_vision/llava_llama3_vision.safetensors",
|
||||||
|
"size": "649MB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 2B v0.9.5 Checkpoint",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "LTX-Video is the first DiT-based video generation model capable of generating high-quality videos in real-time. It produces 24 FPS videos at a 768x512 resolution faster than they can be watched. Trained on a large-scale dataset of diverse videos, the model generates high-resolution videos with realistic and varied content.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltx-video-2b-v0.9.5.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltx-video-2b-v0.9.5.safetensors",
|
||||||
|
"size": "6.34GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "kolors/vae/diffusion_pytorch_model.fp16.safetensors",
|
||||||
|
"type": "VAE",
|
||||||
|
"base": "Kolors",
|
||||||
|
"save_path": "vae/kolors",
|
||||||
|
"description": "Kolors VAE",
|
||||||
|
"reference": "https://huggingface.co/Kwai-Kolors/Kolors",
|
||||||
|
"filename": "diffusion_pytorch_model.fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Kwai-Kolors/Kolors/resolve/main/vae/diffusion_pytorch_model.fp16.safetensors",
|
||||||
|
"size": "167MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "kolors/vae/diffusion_pytorch_model.safetensors",
|
||||||
|
"type": "VAE",
|
||||||
|
"base": "Kolors",
|
||||||
|
"save_path": "vae/kolors",
|
||||||
|
"description": "Kolors VAE",
|
||||||
|
"reference": "https://huggingface.co/Kwai-Kolors/Kolors",
|
||||||
|
"filename": "diffusion_pytorch_model.safetensors",
|
||||||
|
"url": "https://huggingface.co/Kwai-Kolors/Kolors/resolve/main/vae/diffusion_pytorch_model.safetensors",
|
||||||
|
"size": "335MB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "deepseek-ai/Janus-Pro-1B",
|
||||||
|
"type": "Janus-Pro",
|
||||||
|
"base": "Janus-Pro",
|
||||||
|
"save_path": "Janus-Pro",
|
||||||
|
"description": "[SNAPSHOT] Janus-Pro-1B model.[w/You cannot download this item on ComfyUI-Manager versions below V3.18]",
|
||||||
|
"reference": "https://huggingface.co/deepseek-ai/Janus-Pro-1B",
|
||||||
|
"filename": "<huggingface>",
|
||||||
|
"url": "deepseek-ai/Janus-Pro-1B",
|
||||||
|
"size": "7.8GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "deepseek-ai/Janus-Pro-7B",
|
||||||
|
"type": "Janus-Pro",
|
||||||
|
"base": "Janus-Pro",
|
||||||
|
"save_path": "Janus-Pro",
|
||||||
|
"description": "[SNAPSHOT] Janus-Pro-7B model.[w/You cannot download this item on ComfyUI-Manager versions below V3.18]",
|
||||||
|
"reference": "https://huggingface.co/deepseek-ai/Janus-Pro-7B",
|
||||||
|
"filename": "<huggingface>",
|
||||||
|
"url": "deepseek-ai/Janus-Pro-7B",
|
||||||
|
"size": "14.85GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Leoxing/pia.ckpt",
|
||||||
|
"type": "animatediff-pia",
|
||||||
|
"base": "SD1.x",
|
||||||
|
"save_path": "animatediff_models",
|
||||||
|
"description": "AnimateDiff-PIA Model",
|
||||||
|
"reference": "https://huggingface.co/Leoxing/PIA/tree/main",
|
||||||
|
"filename": "pia.ckpt",
|
||||||
|
"url": "https://huggingface.co/Leoxing/PIA/resolve/main/pia.ckpt",
|
||||||
|
"size": "1.67GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "comfyanonymous/cosmos_cv8x8x8_1.0.safetensors",
|
||||||
|
"type": "VAE",
|
||||||
|
"base": "Cosmos-1.0",
|
||||||
|
"save_path": "default",
|
||||||
|
"description": "VAE model for Cosmos 1.0",
|
||||||
|
"reference": "https://huggingface.co/comfyanonymous/cosmos_1.0_text_encoder_and_VAE_ComfyUI/tree/main",
|
||||||
|
"filename": "cosmos_cv8x8x8_1.0.safetensors",
|
||||||
|
"url": "https://huggingface.co/comfyanonymous/cosmos_1.0_text_encoder_and_VAE_ComfyUI/resolve/main/vae/cosmos_cv8x8x8_1.0.safetensors",
|
||||||
|
"size": "211MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mcmonkey/Cosmos-1_0-Diffusion-7B-Text2World.safetensors",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Cosmos-1.0",
|
||||||
|
"save_path": "diffusion_models/cosmos-1.0",
|
||||||
|
"description": "Cosmos 1.0 Text2World Diffusion Model (7B)",
|
||||||
|
"reference": "https://huggingface.co/mcmonkey/cosmos-1.0",
|
||||||
|
"filename": "Cosmos-1_0-Diffusion-7B-Text2World.safetensors",
|
||||||
|
"url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-7B-Text2World.safetensors",
|
||||||
|
"size": "14.5GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mcmonkey/Cosmos-1_0-Diffusion-7B-Video2World.safetensors",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Cosmos-1.0",
|
||||||
|
"save_path": "diffusion_models/cosmos-1.0",
|
||||||
|
"description": "Cosmos 1.0 Video2World Diffusion Model (7B)",
|
||||||
|
"reference": "https://huggingface.co/mcmonkey/cosmos-1.0",
|
||||||
|
"filename": "Cosmos-1_0-Diffusion-7B-Video2World.safetensors",
|
||||||
|
"url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-7B-Video2World.safetensors",
|
||||||
|
"size": "14.5GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mcmonkey/Cosmos-1_0-Diffusion-14B-Text2World.safetensors",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Cosmos-1.0",
|
||||||
|
"save_path": "diffusion_models/cosmos-1.0",
|
||||||
|
"description": "Cosmos 1.0 Text2World Diffusion Model (14B)",
|
||||||
|
"reference": "https://huggingface.co/mcmonkey/cosmos-1.0",
|
||||||
|
"filename": "Cosmos-1_0-Diffusion-14B-Text2World.safetensors",
|
||||||
|
"url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-14B-Text2World.safetensors",
|
||||||
|
"size": "28.5GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "mcmonkey/Cosmos-1_0-Diffusion-14B-Video2World.safetensors",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Cosmos-1.0",
|
||||||
|
"save_path": "diffusion_models/cosmos-1.0",
|
||||||
|
"description": "Cosmos 1.0 Video2World Diffusion Model (14B)",
|
||||||
|
"reference": "https://huggingface.co/mcmonkey/cosmos-1.0",
|
||||||
|
"filename": "Cosmos-1_0-Diffusion-14B-Video2World.safetensors",
|
||||||
|
"url": "https://huggingface.co/mcmonkey/cosmos-1.0/resolve/main/Cosmos-1_0-Diffusion-14B-Video2World.safetensors",
|
||||||
|
"size": "28.5GB"
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Comfy-Org/llava_llama3_fp8_scaled.safetensors",
|
"name": "Comfy-Org/llava_llama3_fp8_scaled.safetensors",
|
||||||
"type": "clip",
|
"type": "clip",
|
||||||
@@ -239,495 +691,6 @@
|
|||||||
"filename": "sigclip_vision_patch14_384.safetensors",
|
"filename": "sigclip_vision_patch14_384.safetensors",
|
||||||
"url": "https://huggingface.co/Comfy-Org/sigclip_vision_384/resolve/main/sigclip_vision_patch14_384.safetensors",
|
"url": "https://huggingface.co/Comfy-Org/sigclip_vision_384/resolve/main/sigclip_vision_patch14_384.safetensors",
|
||||||
"size": "857MB"
|
"size": "857MB"
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "comfyanonymous/flux_text_encoders - t5xxl (fp16)",
|
|
||||||
"type": "clip",
|
|
||||||
"base": "t5",
|
|
||||||
"save_path": "text_encoders/t5",
|
|
||||||
"description": "Text Encoders for FLUX (fp16)",
|
|
||||||
"reference": "https://huggingface.co/comfyanonymous/flux_text_encoders",
|
|
||||||
"filename": "t5xxl_fp16.safetensors",
|
|
||||||
"url": "https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/t5xxl_fp16.safetensors",
|
|
||||||
"size": "9.79GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "comfyanonymous/flux_text_encoders - t5xxl (fp8_e4m3fn)",
|
|
||||||
"type": "clip",
|
|
||||||
"base": "t5",
|
|
||||||
"save_path": "text_encoders/t5",
|
|
||||||
"description": "Text Encoders for FLUX (fp8_e4m3fn)",
|
|
||||||
"reference": "https://huggingface.co/comfyanonymous/flux_text_encoders",
|
|
||||||
"filename": "t5xxl_fp8_e4m3fn.safetensors",
|
|
||||||
"url": "https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/t5xxl_fp8_e4m3fn.safetensors",
|
|
||||||
"size": "4.89GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "comfyanonymous/flux_text_encoders - t5xxl (fp8_e4m3fn_scaled)",
|
|
||||||
"type": "clip",
|
|
||||||
"base": "t5",
|
|
||||||
"save_path": "text_encoders/t5",
|
|
||||||
"description": "Text Encoders for FLUX (fp16)",
|
|
||||||
"reference": "https://huggingface.co/comfyanonymous/flux_text_encoders",
|
|
||||||
"filename": "t5xxl_fp8_e4m3fn_scaled.safetensors",
|
|
||||||
"url": "https://huggingface.co/comfyanonymous/flux_text_encoders/resolve/main/t5xxl_fp8_e4m3fn_scaled.safetensors",
|
|
||||||
"size": "5.16GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "FLUX.1 [Dev] Diffusion model (scaled fp8)",
|
|
||||||
"type": "diffusion_model",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "diffusion_models/FLUX1",
|
|
||||||
"description": "FLUX.1 [Dev] Diffusion model (scaled fp8)[w/Due to the large size of the model, it is recommended to download it through a browser if possible.]",
|
|
||||||
"reference": "https://huggingface.co/comfyanonymous/flux_dev_scaled_fp8_test",
|
|
||||||
"filename": "flux_dev_fp8_scaled_diffusion_model.safetensors",
|
|
||||||
"url": "https://huggingface.co/comfyanonymous/flux_dev_scaled_fp8_test/resolve/main/flux_dev_fp8_scaled_diffusion_model.safetensors",
|
|
||||||
"size": "11.9GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "kijai/MoGe_ViT_L_fp16.safetensors",
|
|
||||||
"type": "MoGe",
|
|
||||||
"base": "MoGe",
|
|
||||||
"save_path": "MoGe",
|
|
||||||
"description": "Safetensors versions of [a/https://github.com/microsoft/MoGe](https://github.com/microsoft/MoGe)",
|
|
||||||
"reference": "https://huggingface.co/Kijai/MoGe_safetensors",
|
|
||||||
"filename": "MoGe_ViT_L_fp16.safetensors",
|
|
||||||
"url": "https://huggingface.co/Kijai/MoGe_safetensors/resolve/main/MoGe_ViT_L_fp16.safetensors",
|
|
||||||
"size": "628MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "kijai/MoGe_ViT_L_fp16.safetensors",
|
|
||||||
"type": "MoGe",
|
|
||||||
"base": "MoGe",
|
|
||||||
"save_path": "MoGe",
|
|
||||||
"description": "Safetensors versions of [a/https://github.com/microsoft/MoGe](https://github.com/microsoft/MoGe)",
|
|
||||||
"reference": "https://huggingface.co/Kijai/MoGe_safetensors",
|
|
||||||
"filename": "MoGe_ViT_L_fp16.safetensors",
|
|
||||||
"url": "https://huggingface.co/Kijai/MoGe_safetensors/resolve/main/MoGe_ViT_L_fp16.safetensors",
|
|
||||||
"size": "1.26GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "pulid_flux_v0.9.1.safetensors",
|
|
||||||
"type": "PuLID",
|
|
||||||
"base": "FLUX",
|
|
||||||
"save_path": "pulid",
|
|
||||||
"description": "This is required for PuLID (FLUX)",
|
|
||||||
"reference": "https://huggingface.co/guozinan/PuLID",
|
|
||||||
"filename": "pulid_flux_v0.9.1.safetensors",
|
|
||||||
"url": "https://huggingface.co/guozinan/PuLID/resolve/main/pulid_flux_v0.9.1.safetensors",
|
|
||||||
"size": "1.14GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "pulid_v1.1.safetensors",
|
|
||||||
"type": "PuLID",
|
|
||||||
"base": "SDXL",
|
|
||||||
"save_path": "pulid",
|
|
||||||
"description": "This is required for PuLID (SDXL)",
|
|
||||||
"reference": "https://huggingface.co/guozinan/PuLID",
|
|
||||||
"filename": "pulid_v1.1.safetensors",
|
|
||||||
"url": "https://huggingface.co/guozinan/PuLID/resolve/main/pulid_v1.1.safetensors",
|
|
||||||
"size": "984MB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "Kolors-IP-Adapter-Plus.bin (Kwai-Kolors/Kolors-IP-Adapter-Plus)",
|
|
||||||
"type": "IP-Adapter",
|
|
||||||
"base": "Kolors",
|
|
||||||
"save_path": "ipadapter",
|
|
||||||
"description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.",
|
|
||||||
"reference": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-Plus",
|
|
||||||
"filename": "Kolors-IP-Adapter-Plus.bin",
|
|
||||||
"url": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-Plus/resolve/main/ip_adapter_plus_general.bin",
|
|
||||||
"size": "1.01GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Kolors-IP-Adapter-FaceID-Plus.bin (Kwai-Kolors/Kolors-IP-Adapter-Plus)",
|
|
||||||
"type": "IP-Adapter",
|
|
||||||
"base": "Kolors",
|
|
||||||
"save_path": "ipadapter",
|
|
||||||
"description": "You can use this model in the [a/ComfyUI IPAdapter plus](https://github.com/cubiq/ComfyUI_IPAdapter_plus) extension.",
|
|
||||||
"reference": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-FaceID-Plus",
|
|
||||||
"filename": "Kolors-IP-Adapter-FaceID-Plus.bin",
|
|
||||||
"url": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-FaceID-Plus/resolve/main/ipa-faceid-plus.bin",
|
|
||||||
"size": "2.39GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "CLIPVision model (Kwai-Kolors/Kolors-IP-Adapter-Plus/clip-vit-large)",
|
|
||||||
"type": "clip_vision",
|
|
||||||
"base": "ViT-L",
|
|
||||||
"save_path": "clip_vision",
|
|
||||||
"description": "CLIPVision model (This is required in cubiq/ComfyUI_IPAdapter_plus)",
|
|
||||||
"reference": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-Plus",
|
|
||||||
"filename": "clip-vit-large-patch14-336.bin",
|
|
||||||
"url": "https://huggingface.co/Kwai-Kolors/Kolors-IP-Adapter-Plus/resolve/main/image_encoder/pytorch_model.bin",
|
|
||||||
"size": "1.71GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "kijai/lotus depth d model v1.1 (fp16)",
|
|
||||||
"type": "diffusion_model",
|
|
||||||
"base": "lotus",
|
|
||||||
"save_path": "diffusion_models",
|
|
||||||
"description": "lotus depth d model v1.1 (fp16). This model can be used in ComfyUI-Lotus custom nodes.",
|
|
||||||
"reference": "https://huggingface.co/Kijai/lotus-comfyui",
|
|
||||||
"filename": "lotus-depth-d-v-1-1-fp16.safetensors",
|
|
||||||
"url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-depth-d-v-1-1-fp16.safetensors",
|
|
||||||
"size": "1.74GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "kijai/lotus depth g model v1.0 (fp16)",
|
|
||||||
"type": "diffusion_model",
|
|
||||||
"base": "lotus",
|
|
||||||
"save_path": "diffusion_models",
|
|
||||||
"description": "lotus depth g model v1.0 (fp16). This model can be used in ComfyUI-Lotus custom nodes.",
|
|
||||||
"reference": "https://huggingface.co/Kijai/lotus-comfyui",
|
|
||||||
"filename": "lotus-depth-g-v1-0-fp16.safetensors",
|
|
||||||
"url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-depth-g-v1-0-fp16.safetensors",
|
|
||||||
"size": "1.74GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "kijai/lotus depth g model v1.0",
|
|
||||||
"type": "diffusion_model",
|
|
||||||
"base": "lotus",
|
|
||||||
"save_path": "diffusion_models",
|
|
||||||
"description": "lotus depth g model v1.0. This model can be used in ComfyUI-Lotus custom nodes.",
|
|
||||||
"reference": "https://huggingface.co/Kijai/lotus-comfyui",
|
|
||||||
"filename": "lotus-depth-g-v1-0.safetensors",
|
|
||||||
"url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-depth-g-v1-0.safetensors",
|
|
||||||
"size": "3.47GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "kijai/lotus normal d model v1.0 (fp16)",
|
|
||||||
"type": "diffusion_model",
|
|
||||||
"base": "lotus",
|
|
||||||
"save_path": "diffusion_models",
|
|
||||||
"description": "lotus normal d model v1.0 (fp16). This model can be used in ComfyUI-Lotus custom nodes.",
|
|
||||||
"reference": "https://huggingface.co/Kijai/lotus-comfyui",
|
|
||||||
"filename": "lotus-normal-d-v1-0-fp16.safetensors",
|
|
||||||
"url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-normal-d-v1-0-fp16.safetensors",
|
|
||||||
"size": "1.74GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "kijai/lotus normal d model v1.0",
|
|
||||||
"type": "diffusion_model",
|
|
||||||
"base": "lotus",
|
|
||||||
"save_path": "diffusion_models",
|
|
||||||
"description": "lotus normal d model v1.0. This model can be used in ComfyUI-Lotus custom nodes.",
|
|
||||||
"reference": "https://huggingface.co/Kijai/lotus-comfyui",
|
|
||||||
"filename": "lotus-normal-d-v1-0.safetensors",
|
|
||||||
"url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-normal-d-v1-0.safetensors",
|
|
||||||
"size": "3.47GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "kijai/lotus normal g model v1.0 (fp16)",
|
|
||||||
"type": "diffusion_model",
|
|
||||||
"base": "lotus",
|
|
||||||
"save_path": "diffusion_models",
|
|
||||||
"description": "lotus normal g model v1.0 (fp16). This model can be used in ComfyUI-Lotus custom nodes.",
|
|
||||||
"reference": "https://huggingface.co/Kijai/lotus-comfyui",
|
|
||||||
"filename": "lotus-normal-g-v1-0-fp16.safetensors",
|
|
||||||
"url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-normal-g-v1-0-fp16.safetensors",
|
|
||||||
"size": "1.74GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "kijai/lotus normal g model v1.0",
|
|
||||||
"type": "diffusion_model",
|
|
||||||
"base": "lotus",
|
|
||||||
"save_path": "diffusion_models",
|
|
||||||
"description": "lotus normal g model v1.0. This model can be used in ComfyUI-Lotus custom nodes.",
|
|
||||||
"reference": "https://huggingface.co/Kijai/lotus-comfyui",
|
|
||||||
"filename": "lotus-normal-g-v1-0.safetensors",
|
|
||||||
"url": "https://huggingface.co/Kijai/lotus-comfyui/resolve/main/lotus-normal-g-v1-0.safetensors",
|
|
||||||
"size": "3.47GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "Depth Pro model",
|
|
||||||
"type": "depth-pro",
|
|
||||||
"base": "depth-pro",
|
|
||||||
"save_path": "depth/ml-depth-pro",
|
|
||||||
"description": "Depth pro model for [a/ComfyUI-Depth-Pro](https://github.com/spacepxl/ComfyUI-Depth-Pro)",
|
|
||||||
"reference": "https://huggingface.co/spacepxl/ml-depth-pro",
|
|
||||||
"filename": "depth_pro.fp16.safetensors",
|
|
||||||
"url": "https://huggingface.co/spacepxl/ml-depth-pro/resolve/main/depth_pro.fp16.safetensors",
|
|
||||||
"size": "1.9GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "jasperai/FLUX.1-dev-Controlnet-Upscaler",
|
|
||||||
"type": "controlnet",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "controlnet/FLUX.1/jasperai-dev-Upscaler",
|
|
||||||
"description": "This is Flux.1-dev ControlNet for low resolution images developed by Jasper research team.",
|
|
||||||
"reference": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Upscaler",
|
|
||||||
"filename": "diffusion_pytorch_model.safetensors",
|
|
||||||
"url": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Upscaler/resolve/main/diffusion_pytorch_model.safetensors",
|
|
||||||
"size": "3.58GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "jasperai/FLUX.1-dev-Controlnet-Depth",
|
|
||||||
"type": "controlnet",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "controlnet/FLUX.1/jasperai-dev-Depth",
|
|
||||||
"description": "This is Flux.1-dev ControlNet for Depth map developed by Jasper research team.",
|
|
||||||
"reference": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Depth",
|
|
||||||
"filename": "diffusion_pytorch_model.safetensors",
|
|
||||||
"url": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Depth/resolve/main/diffusion_pytorch_model.safetensors",
|
|
||||||
"size": "3.58GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "jasperai/Flux.1-dev-Controlnet-Surface-Normals",
|
|
||||||
"type": "controlnet",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "controlnet/FLUX.1/jasperai-dev-Surface-Normals",
|
|
||||||
"description": "This is Flux.1-dev ControlNet for Surface Normals map developed by Jasper research team.",
|
|
||||||
"reference": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Surface-Normals",
|
|
||||||
"filename": "diffusion_pytorch_model.safetensors",
|
|
||||||
"url": "https://huggingface.co/jasperai/Flux.1-dev-Controlnet-Surface-Normals/resolve/main/diffusion_pytorch_model.safetensors",
|
|
||||||
"size": "3.58GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro (fp8_e4m3fn) by Kijai",
|
|
||||||
"type": "controlnet",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "controlnet/FLUX.1",
|
|
||||||
"description": "FLUX.1 [Dev] Union Controlnet. Supports Canny, Tile, Depth, Blur, Pose, Gray, Low Quality\nVersion quantized to fp8_e4m3fn by Kijai",
|
|
||||||
"reference": "https://huggingface.co/Kijai/flux-fp8",
|
|
||||||
"filename": "flux_shakker_labs_union_pro-fp8_e4m3fn.safetensors",
|
|
||||||
"url": "https://huggingface.co/Kijai/flux-fp8/resolve/main/flux_shakker_labs_union_pro-fp8_e4m3fn.safetensors",
|
|
||||||
"size": "3.3GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors [Long CLIP L]",
|
|
||||||
"type": "clip",
|
|
||||||
"base": "clip",
|
|
||||||
"save_path": "clip/long_clip",
|
|
||||||
"description": "Greatly improved TEXT + Detail (as CLIP-L for Flux.1)",
|
|
||||||
"reference": "https://huggingface.co/zer0int",
|
|
||||||
"filename": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors",
|
|
||||||
"url": "https://huggingface.co/zer0int/CLIP-GmP-ViT-L-14/resolve/main/ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors",
|
|
||||||
"size": "931MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "ViT-L-14-TEXT-detail-improved-hiT-GmP-HF.safetensors [Long CLIP L]",
|
|
||||||
"type": "clip",
|
|
||||||
"base": "clip",
|
|
||||||
"save_path": "clip/long_clip",
|
|
||||||
"description": "Greatly improved TEXT + Detail (as CLIP-L for Flux.1)",
|
|
||||||
"reference": "https://huggingface.co/zer0int",
|
|
||||||
"filename": "ViT-L-14-TEXT-detail-improved-hiT-GmP-TE-only-HF.safetensors",
|
|
||||||
"url": "https://huggingface.co/zer0int/CLIP-GmP-ViT-L-14/resolve/main/ViT-L-14-TEXT-detail-improved-hiT-GmP-TE-only-HF.safetensors",
|
|
||||||
"size": "323MB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro",
|
|
||||||
"type": "controlnet",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "controlnet/FLUX.1/Shakker-Labs-ControlNet-Union-Pro",
|
|
||||||
"description": "FLUX.1 [Dev] Union Controlnet. Supports Canny, Tile, Depth, Blur, Pose, Gray, Low Quality",
|
|
||||||
"reference": "https://huggingface.co/Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro",
|
|
||||||
"filename": "diffusion_pytorch_model.safetensors",
|
|
||||||
"url": "https://huggingface.co/Shakker-Labs/FLUX.1-dev-ControlNet-Union-Pro/resolve/main/diffusion_pytorch_model.safetensors",
|
|
||||||
"size": "6.6GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "Hyper-SD LoRA (8steps) - FLUX.1 [Dev]",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "loras/HyperSD/FLUX.1",
|
|
||||||
"description": "Hyper-SD LoRA (8steps) - FLUX.1 [Dev]",
|
|
||||||
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
|
||||||
"filename": "Hyper-FLUX.1-dev-8steps-lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-FLUX.1-dev-8steps-lora.safetensors",
|
|
||||||
"size": "1.39GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Hyper-SD LoRA (16steps) - FLUX.1 [Dev]",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "loras/HyperSD/FLUX.1",
|
|
||||||
"description": "Hyper-SD LoRA (16steps) - FLUX.1 [Dev]",
|
|
||||||
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
|
||||||
"filename": "Hyper-FLUX.1-dev-16steps-lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-FLUX.1-dev-16steps-lora.safetensors",
|
|
||||||
"size": "1.39GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "BAAI/SegGPT",
|
|
||||||
"type": "SegGPT",
|
|
||||||
"base": "SegGPT",
|
|
||||||
"save_path": "custom_nodes/comfyui-SegGPT",
|
|
||||||
"description": "SegGPT",
|
|
||||||
"reference": "https://huggingface.co/BAAI/SegGPT",
|
|
||||||
"filename": "seggpt_vit_large.pth",
|
|
||||||
"url": "https://huggingface.co/BAAI/SegGPT/resolve/main/seggpt_vit_large.pth",
|
|
||||||
"size": "1.48GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "DMD2 LoRA (4steps)",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "SDXL",
|
|
||||||
"save_path": "loras/DMD2",
|
|
||||||
"description": "DMD2 LoRA (4steps)",
|
|
||||||
"reference": "https://huggingface.co/tianweiy/DMD2",
|
|
||||||
"filename": "dmd2_sdxl_4step_lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/tianweiy/DMD2/resolve/main/dmd2_sdxl_4step_lora.safetensors",
|
|
||||||
"size": "787MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "DMD2 LoRA (4steps/fp16)",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "SDXL",
|
|
||||||
"save_path": "loras/DMD2",
|
|
||||||
"description": "DMD2 LoRA (4steps/fp16)",
|
|
||||||
"reference": "https://huggingface.co/tianweiy/DMD2",
|
|
||||||
"filename": "dmd2_sdxl_4step_lora_fp16.safetensors",
|
|
||||||
"url": "https://huggingface.co/tianweiy/DMD2/resolve/main/dmd2_sdxl_4step_lora_fp16.safetensors",
|
|
||||||
"size": "394MB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "Hyper-SD LoRA (1step) - SD1.5",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "SD1.5",
|
|
||||||
"save_path": "loras/HyperSD/SD15",
|
|
||||||
"description": "Hyper-SD LoRA (1step) - SD1.5",
|
|
||||||
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
|
||||||
"filename": "Hyper-SD15-1step-lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-1step-lora.safetensors",
|
|
||||||
"size": "269MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Hyper-SD LoRA (2steps) - SD1.5",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "SD1.5",
|
|
||||||
"save_path": "loras/HyperSD/SD15",
|
|
||||||
"description": "Hyper-SD LoRA (2steps) - SD1.5",
|
|
||||||
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
|
||||||
"filename": "Hyper-SD15-2steps-lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-2steps-lora.safetensors",
|
|
||||||
"size": "269MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Hyper-SD LoRA (4steps) - SD1.5",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "SD1.5",
|
|
||||||
"save_path": "loras/HyperSD/SD15",
|
|
||||||
"description": "Hyper-SD LoRA (4steps)",
|
|
||||||
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
|
||||||
"filename": "Hyper-SD15-4steps-lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-4steps-lora.safetensors",
|
|
||||||
"size": "269MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Hyper-SD LoRA (8steps) - SD1.5",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "SD1.5",
|
|
||||||
"save_path": "loras/HyperSD/SD15",
|
|
||||||
"description": "Hyper-SD LoRA (8steps)",
|
|
||||||
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
|
||||||
"filename": "Hyper-SD15-8steps-lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-8steps-lora.safetensors",
|
|
||||||
"size": "269MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Hyper-SD CFG LoRA (8steps) - SD1.5",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "SD1.5",
|
|
||||||
"save_path": "loras/HyperSD/SD15",
|
|
||||||
"description": "Hyper-SD CFG LoRA (8steps)",
|
|
||||||
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
|
||||||
"filename": "Hyper-SD15-8steps-CFG-lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-8steps-CFG-lora.safetensors",
|
|
||||||
"size": "269MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Hyper-SD CFG LoRA (12steps) - SD1.5",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "SD1.5",
|
|
||||||
"save_path": "loras/HyperSD/SD15",
|
|
||||||
"description": "Hyper-SD CFG LoRA (12steps)",
|
|
||||||
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
|
||||||
"filename": "Hyper-SD15-12steps-CFG-lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-12steps-CFG-lora.safetensors",
|
|
||||||
"size": "269MB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "Hyper-SD LoRA (1step) - SDXL",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "SDXL",
|
|
||||||
"save_path": "loras/HyperSD/SDXL",
|
|
||||||
"description": "Hyper-SD LoRA (1step) - SDXL",
|
|
||||||
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
|
||||||
"filename": "Hyper-SDXL-1step-lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-1step-lora.safetensors",
|
|
||||||
"size": "787MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Hyper-SD LoRA (2steps) - SDXL",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "SDXL",
|
|
||||||
"save_path": "loras/HyperSD/SDXL",
|
|
||||||
"description": "Hyper-SD LoRA (2steps) - SDXL",
|
|
||||||
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
|
||||||
"filename": "Hyper-SDXL-2steps-lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-2steps-lora.safetensors",
|
|
||||||
"size": "787MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Hyper-SD LoRA (4steps) - SDXL",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "SDXL",
|
|
||||||
"save_path": "loras/HyperSD/SDXL",
|
|
||||||
"description": "Hyper-SD LoRA (4steps) - SDXL",
|
|
||||||
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
|
||||||
"filename": "Hyper-SD15-4steps-lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SD15-4steps-lora.safetensors",
|
|
||||||
"size": "787MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Hyper-SD LoRA (8steps) - SDXL",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "SDXL",
|
|
||||||
"save_path": "loras/HyperSD/SDXL",
|
|
||||||
"description": "Hyper-SD LoRA (8steps) - SDXL",
|
|
||||||
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
|
||||||
"filename": "Hyper-SDXL-8steps-lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-8steps-lora.safetensors",
|
|
||||||
"size": "787MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Hyper-SD CFG LoRA (8steps) - SDXL",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "SDXL",
|
|
||||||
"save_path": "loras/HyperSD/SDXL",
|
|
||||||
"description": "Hyper-SD CFG LoRA (8steps) - SDXL",
|
|
||||||
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
|
||||||
"filename": "Hyper-SDXL-8steps-CFG-lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-8steps-CFG-lora.safetensors",
|
|
||||||
"size": "787MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Hyper-SD CFG LoRA (12steps) - SDXL",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "SDXL",
|
|
||||||
"save_path": "loras/HyperSD/SDXL",
|
|
||||||
"description": "Hyper-SD CFG LoRA (12steps) - SDXL",
|
|
||||||
"reference": "https://huggingface.co/ByteDance/Hyper-SD",
|
|
||||||
"filename": "Hyper-SDXL-12steps-CFG-lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/ByteDance/Hyper-SD/resolve/main/Hyper-SDXL-12steps-CFG-lora.safetensors",
|
|
||||||
"size": "787MB"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
{
|
{
|
||||||
"custom_nodes": [
|
"custom_nodes": [
|
||||||
|
{
|
||||||
|
"author": "Comfy-Org",
|
||||||
|
"title": "ComfyUI React Extension Template",
|
||||||
|
"reference": "https://github.com/Comfy-Org/ComfyUI-React-Extension-Template",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/Comfy-Org/ComfyUI-React-Extension-Template"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "A minimal template for creating React/TypeScript frontend extensions for ComfyUI, with complete boilerplate setup including internationalization and unit testing."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"author": "Suzie1",
|
"author": "Suzie1",
|
||||||
"title": "Guide To Making Custom Nodes in ComfyUI",
|
"title": "Guide To Making Custom Nodes in ComfyUI",
|
||||||
@@ -260,6 +270,67 @@
|
|||||||
],
|
],
|
||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "This is the ComfyUI custom node template repository that anyone can use to create their own custom nodes."
|
"description": "This is the ComfyUI custom node template repository that anyone can use to create their own custom nodes."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "laogou666",
|
||||||
|
"title": "Comfyui_LG_Advertisement",
|
||||||
|
"reference": "https://github.com/LAOGOU-666/Comfyui_LG_Advertisement",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/LAOGOU-666/Comfyui_LG_Advertisement"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "A node for demonstration."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "amorano",
|
||||||
|
"title": "cozy_spoke",
|
||||||
|
"reference": "https://github.com/cozy-comfyui/cozy_spoke",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/cozy-comfyui/cozy_spoke"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Example node communicating between ComfyUI Javascript and Python."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "amorano",
|
||||||
|
"title": "Cozy Link Toggle",
|
||||||
|
"id": "cozyLinkToggle",
|
||||||
|
"reference": "https://github.com/cozy-comfyui/cozy_link_toggle",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/cozy-comfyui/cozy_link_toggle"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Example of using ComfyUI Toolbar to Toggle ComfyUI links on/off"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "xhiroga",
|
||||||
|
"title": "ComfyUI-TypeScript-CustomNode",
|
||||||
|
"reference": "https://github.com/xhiroga/ComfyUI-TypeScript-CustomNode",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/xhiroga/ComfyUI-TypeScript-CustomNode"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "This project is generated from xhiroga/ComfyUI-TypeScript-CustomNode"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "zentrocdot",
|
||||||
|
"title": "ComfyUI-Turtle_Graphics_Demos",
|
||||||
|
"reference": "https://github.com/zentrocdot/ComfyUI-Turtle_Graphics_Demo",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/zentrocdot/ComfyUI-Turtle_Graphics_Demo"
|
||||||
|
],
|
||||||
|
"description": "ComfyUI node for creating some Turtle Graphic demos.",
|
||||||
|
"install_type": "git-clone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "cozy-comfyui",
|
||||||
|
"title": "cozy_ex_dynamic",
|
||||||
|
"reference": "https://github.com/cozy-comfyui/cozy_ex_dynamic",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/cozy-comfyui/cozy_ex_dynamic"
|
||||||
|
],
|
||||||
|
"description": "Dynamic Node examples for ComfyUI",
|
||||||
|
"install_type": "git-clone"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -172,6 +172,7 @@
|
|||||||
"CLIPTextEncodeControlnet",
|
"CLIPTextEncodeControlnet",
|
||||||
"CLIPTextEncodeFlux",
|
"CLIPTextEncodeFlux",
|
||||||
"CLIPTextEncodeHunyuanDiT",
|
"CLIPTextEncodeHunyuanDiT",
|
||||||
|
"CLIPTextEncodePixArtAlpha",
|
||||||
"CLIPTextEncodeSD3",
|
"CLIPTextEncodeSD3",
|
||||||
"CLIPTextEncodeSDXL",
|
"CLIPTextEncodeSDXL",
|
||||||
"CLIPTextEncodeSDXLRefiner",
|
"CLIPTextEncodeSDXLRefiner",
|
||||||
@@ -189,6 +190,7 @@
|
|||||||
"ConditioningSetAreaStrength",
|
"ConditioningSetAreaStrength",
|
||||||
"ConditioningSetMask",
|
"ConditioningSetMask",
|
||||||
"ConditioningSetTimestepRange",
|
"ConditioningSetTimestepRange",
|
||||||
|
"ConditioningStableAudio",
|
||||||
"ConditioningZeroOut",
|
"ConditioningZeroOut",
|
||||||
"ControlNetApply",
|
"ControlNetApply",
|
||||||
"ControlNetApplyAdvanced",
|
"ControlNetApplyAdvanced",
|
||||||
@@ -202,7 +204,9 @@
|
|||||||
"DisableNoise",
|
"DisableNoise",
|
||||||
"DualCFGGuider",
|
"DualCFGGuider",
|
||||||
"DualCLIPLoader",
|
"DualCLIPLoader",
|
||||||
|
"EmptyHunyuanLatentVideo",
|
||||||
"EmptyImage",
|
"EmptyImage",
|
||||||
|
"EmptyLTXVLatentVideo",
|
||||||
"EmptyLatentAudio",
|
"EmptyLatentAudio",
|
||||||
"EmptyLatentImage",
|
"EmptyLatentImage",
|
||||||
"EmptyMochiLatentVideo",
|
"EmptyMochiLatentVideo",
|
||||||
@@ -245,6 +249,9 @@
|
|||||||
"KSamplerAdvanced",
|
"KSamplerAdvanced",
|
||||||
"KSamplerSelect",
|
"KSamplerSelect",
|
||||||
"KarrasScheduler",
|
"KarrasScheduler",
|
||||||
|
"LTXVConditioning",
|
||||||
|
"LTXVImgToVideo",
|
||||||
|
"LTXVScheduler",
|
||||||
"LaplaceScheduler",
|
"LaplaceScheduler",
|
||||||
"LatentAdd",
|
"LatentAdd",
|
||||||
"LatentApplyOperation",
|
"LatentApplyOperation",
|
||||||
@@ -265,6 +272,8 @@
|
|||||||
"LatentSubtract",
|
"LatentSubtract",
|
||||||
"LatentUpscale",
|
"LatentUpscale",
|
||||||
"LatentUpscaleBy",
|
"LatentUpscaleBy",
|
||||||
|
"Load3D",
|
||||||
|
"Load3DAnimation",
|
||||||
"LoadAudio",
|
"LoadAudio",
|
||||||
"LoadImage",
|
"LoadImage",
|
||||||
"LoadImageMask",
|
"LoadImageMask",
|
||||||
@@ -272,11 +281,15 @@
|
|||||||
"LoraLoader",
|
"LoraLoader",
|
||||||
"LoraLoaderModelOnly",
|
"LoraLoaderModelOnly",
|
||||||
"LoraSave",
|
"LoraSave",
|
||||||
|
"Mahiro",
|
||||||
"MaskComposite",
|
"MaskComposite",
|
||||||
"MaskToImage",
|
"MaskToImage",
|
||||||
"ModelMergeAdd",
|
"ModelMergeAdd",
|
||||||
|
"ModelMergeAuraflow",
|
||||||
"ModelMergeBlocks",
|
"ModelMergeBlocks",
|
||||||
"ModelMergeFlux1",
|
"ModelMergeFlux1",
|
||||||
|
"ModelMergeLTXV",
|
||||||
|
"ModelMergeMochiPreview",
|
||||||
"ModelMergeSD1",
|
"ModelMergeSD1",
|
||||||
"ModelMergeSD2",
|
"ModelMergeSD2",
|
||||||
"ModelMergeSD35_Large",
|
"ModelMergeSD35_Large",
|
||||||
@@ -289,6 +302,7 @@
|
|||||||
"ModelSamplingContinuousV",
|
"ModelSamplingContinuousV",
|
||||||
"ModelSamplingDiscrete",
|
"ModelSamplingDiscrete",
|
||||||
"ModelSamplingFlux",
|
"ModelSamplingFlux",
|
||||||
|
"ModelSamplingLTXV",
|
||||||
"ModelSamplingSD3",
|
"ModelSamplingSD3",
|
||||||
"ModelSamplingStableCascade",
|
"ModelSamplingStableCascade",
|
||||||
"ModelSave",
|
"ModelSave",
|
||||||
@@ -301,6 +315,7 @@
|
|||||||
"PhotoMakerLoader",
|
"PhotoMakerLoader",
|
||||||
"PolyexponentialScheduler",
|
"PolyexponentialScheduler",
|
||||||
"PorterDuffImageComposite",
|
"PorterDuffImageComposite",
|
||||||
|
"Preview3D",
|
||||||
"PreviewAudio",
|
"PreviewAudio",
|
||||||
"PreviewImage",
|
"PreviewImage",
|
||||||
"RandomNoise",
|
"RandomNoise",
|
||||||
@@ -334,6 +349,7 @@
|
|||||||
"SelfAttentionGuidance",
|
"SelfAttentionGuidance",
|
||||||
"SetLatentNoiseMask",
|
"SetLatentNoiseMask",
|
||||||
"SetUnionControlNetType",
|
"SetUnionControlNetType",
|
||||||
|
"SkipLayerGuidanceDiT",
|
||||||
"SkipLayerGuidanceSD3",
|
"SkipLayerGuidanceSD3",
|
||||||
"SolidMask",
|
"SolidMask",
|
||||||
"SplitImageWithAlpha",
|
"SplitImageWithAlpha",
|
||||||
@@ -462,6 +478,17 @@
|
|||||||
"title_aux": "comfyui-custom-nodes"
|
"title_aux": "comfyui-custom-nodes"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"https://github.com/jhj0517/ComfyUI-CustomNodes-Template": [
|
||||||
|
[
|
||||||
|
"(Down)Load My Model",
|
||||||
|
"Calculate Minus",
|
||||||
|
"Calculate Plus",
|
||||||
|
"Example Output Node"
|
||||||
|
],
|
||||||
|
{
|
||||||
|
"title_aux": "ComfyUI-CustomNodes-Template"
|
||||||
|
}
|
||||||
|
],
|
||||||
"https://github.com/jtong/comfyui-jtong-workflow": [
|
"https://github.com/jtong/comfyui-jtong-workflow": [
|
||||||
[
|
[
|
||||||
"Example",
|
"Example",
|
||||||
|
|||||||
@@ -1,373 +0,0 @@
|
|||||||
{
|
|
||||||
"cells": [
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"id": "aaaaaaaaaa"
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"Git clone the repo and install the requirements. (ignore the pip errors about protobuf)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {
|
|
||||||
"id": "bbbbbbbbbb"
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"# #@title Environment Setup\n",
|
|
||||||
"\n",
|
|
||||||
"from pathlib import Path\n",
|
|
||||||
"\n",
|
|
||||||
"OPTIONS = {}\n",
|
|
||||||
"\n",
|
|
||||||
"USE_GOOGLE_DRIVE = True #@param {type:\"boolean\"}\n",
|
|
||||||
"UPDATE_COMFY_UI = True #@param {type:\"boolean\"}\n",
|
|
||||||
"USE_COMFYUI_MANAGER = True #@param {type:\"boolean\"}\n",
|
|
||||||
"INSTALL_CUSTOM_NODES_DEPENDENCIES = True #@param {type:\"boolean\"}\n",
|
|
||||||
"OPTIONS['USE_GOOGLE_DRIVE'] = USE_GOOGLE_DRIVE\n",
|
|
||||||
"OPTIONS['UPDATE_COMFY_UI'] = UPDATE_COMFY_UI\n",
|
|
||||||
"OPTIONS['USE_COMFYUI_MANAGER'] = USE_COMFYUI_MANAGER\n",
|
|
||||||
"OPTIONS['INSTALL_CUSTOM_NODES_DEPENDENCIES'] = INSTALL_CUSTOM_NODES_DEPENDENCIES\n",
|
|
||||||
"\n",
|
|
||||||
"current_dir = !pwd\n",
|
|
||||||
"WORKSPACE = f\"{current_dir[0]}/ComfyUI\"\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",
|
|
||||||
"\n",
|
|
||||||
" # Correction of the issue of permissions being deleted on Google Drive.\n",
|
|
||||||
" ![ -f \".ci/nightly/update_windows/update_comfyui_and_python_dependencies.bat\" ] && chmod 755 .ci/nightly/update_windows/update_comfyui_and_python_dependencies.bat\n",
|
|
||||||
" ![ -f \".ci/nightly/windows_base_files/run_nvidia_gpu.bat\" ] && chmod 755 .ci/nightly/windows_base_files/run_nvidia_gpu.bat\n",
|
|
||||||
" ![ -f \".ci/update_windows/update_comfyui_and_python_dependencies.bat\" ] && chmod 755 .ci/update_windows/update_comfyui_and_python_dependencies.bat\n",
|
|
||||||
" ![ -f \".ci/update_windows_cu118/update_comfyui_and_python_dependencies.bat\" ] && chmod 755 .ci/update_windows_cu118/update_comfyui_and_python_dependencies.bat\n",
|
|
||||||
" ![ -f \".ci/update_windows/update.py\" ] && chmod 755 .ci/update_windows/update.py\n",
|
|
||||||
" ![ -f \".ci/update_windows/update_comfyui.bat\" ] && chmod 755 .ci/update_windows/update_comfyui.bat\n",
|
|
||||||
" ![ -f \".ci/update_windows/README_VERY_IMPORTANT.txt\" ] && chmod 755 .ci/update_windows/README_VERY_IMPORTANT.txt\n",
|
|
||||||
" ![ -f \".ci/update_windows/run_cpu.bat\" ] && chmod 755 .ci/update_windows/run_cpu.bat\n",
|
|
||||||
" ![ -f \".ci/update_windows/run_nvidia_gpu.bat\" ] && chmod 755 .ci/update_windows/run_nvidia_gpu.bat\n",
|
|
||||||
"\n",
|
|
||||||
" !git pull\n",
|
|
||||||
"\n",
|
|
||||||
"!echo -= Install dependencies =-\n",
|
|
||||||
"!pip3 install accelerate\n",
|
|
||||||
"!pip3 install einops transformers>=4.28.1 safetensors>=0.4.2 aiohttp pyyaml Pillow scipy tqdm psutil tokenizers>=0.13.3\n",
|
|
||||||
"!pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121\n",
|
|
||||||
"!pip3 install torchsde\n",
|
|
||||||
"!pip3 install kornia>=0.7.1 spandrel soundfile sentencepiece\n",
|
|
||||||
"\n",
|
|
||||||
"if OPTIONS['USE_COMFYUI_MANAGER']:\n",
|
|
||||||
" %cd custom_nodes\n",
|
|
||||||
"\n",
|
|
||||||
" # Correction of the issue of permissions being deleted on Google Drive.\n",
|
|
||||||
" ![ -f \"ComfyUI-Manager/check.sh\" ] && chmod 755 ComfyUI-Manager/check.sh\n",
|
|
||||||
" ![ -f \"ComfyUI-Manager/scan.sh\" ] && chmod 755 ComfyUI-Manager/scan.sh\n",
|
|
||||||
" ![ -f \"ComfyUI-Manager/node_db/dev/scan.sh\" ] && chmod 755 ComfyUI-Manager/node_db/dev/scan.sh\n",
|
|
||||||
" ![ -f \"ComfyUI-Manager/node_db/tutorial/scan.sh\" ] && chmod 755 ComfyUI-Manager/node_db/tutorial/scan.sh\n",
|
|
||||||
" ![ -f \"ComfyUI-Manager/scripts/install-comfyui-venv-linux.sh\" ] && chmod 755 ComfyUI-Manager/scripts/install-comfyui-venv-linux.sh\n",
|
|
||||||
" ![ -f \"ComfyUI-Manager/scripts/install-comfyui-venv-win.bat\" ] && chmod 755 ComfyUI-Manager/scripts/install-comfyui-venv-win.bat\n",
|
|
||||||
"\n",
|
|
||||||
" ![ ! -d ComfyUI-Manager ] && echo -= Initial setup ComfyUI-Manager =- && git clone https://github.com/ltdrdata/ComfyUI-Manager\n",
|
|
||||||
" %cd ComfyUI-Manager\n",
|
|
||||||
" !git pull\n",
|
|
||||||
"\n",
|
|
||||||
"%cd $WORKSPACE\n",
|
|
||||||
"\n",
|
|
||||||
"if OPTIONS['INSTALL_CUSTOM_NODES_DEPENDENCIES']:\n",
|
|
||||||
" !echo -= Install custom nodes dependencies =-\n",
|
|
||||||
" !pip install GitPython\n",
|
|
||||||
" !python custom_nodes/ComfyUI-Manager/cm-cli.py restore-dependencies\n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"id": "cccccccccc"
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"Download some models/checkpoints/vae or custom comfyui nodes (uncomment the commands for the ones you want)"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {
|
|
||||||
"id": "dddddddddd"
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"# Checkpoints\n",
|
|
||||||
"\n",
|
|
||||||
"### SDXL\n",
|
|
||||||
"### I recommend these workflow examples: https://comfyanonymous.github.io/ComfyUI_examples/sdxl/\n",
|
|
||||||
"\n",
|
|
||||||
"#!wget -c https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0.safetensors -P ./models/checkpoints/\n",
|
|
||||||
"#!wget -c https://huggingface.co/stabilityai/stable-diffusion-xl-refiner-1.0/resolve/main/sd_xl_refiner_1.0.safetensors -P ./models/checkpoints/\n",
|
|
||||||
"\n",
|
|
||||||
"# SDXL ReVision\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/clip_vision_g/resolve/main/clip_vision_g.safetensors -P ./models/clip_vision/\n",
|
|
||||||
"\n",
|
|
||||||
"# SD1.5\n",
|
|
||||||
"!wget -c https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.ckpt -P ./models/checkpoints/\n",
|
|
||||||
"\n",
|
|
||||||
"# SD2\n",
|
|
||||||
"#!wget -c https://huggingface.co/stabilityai/stable-diffusion-2-1-base/resolve/main/v2-1_512-ema-pruned.safetensors -P ./models/checkpoints/\n",
|
|
||||||
"#!wget -c https://huggingface.co/stabilityai/stable-diffusion-2-1/resolve/main/v2-1_768-ema-pruned.safetensors -P ./models/checkpoints/\n",
|
|
||||||
"\n",
|
|
||||||
"# Some SD1.5 anime style\n",
|
|
||||||
"#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix2/AbyssOrangeMix2_hard.safetensors -P ./models/checkpoints/\n",
|
|
||||||
"#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix3/AOM3A1_orangemixs.safetensors -P ./models/checkpoints/\n",
|
|
||||||
"#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/Models/AbyssOrangeMix3/AOM3A3_orangemixs.safetensors -P ./models/checkpoints/\n",
|
|
||||||
"#!wget -c https://huggingface.co/Linaqruf/anything-v3.0/resolve/main/anything-v3-fp16-pruned.safetensors -P ./models/checkpoints/\n",
|
|
||||||
"\n",
|
|
||||||
"# Waifu Diffusion 1.5 (anime style SD2.x 768-v)\n",
|
|
||||||
"#!wget -c https://huggingface.co/waifu-diffusion/wd-1-5-beta3/resolve/main/wd-illusion-fp16.safetensors -P ./models/checkpoints/\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# unCLIP models\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/illuminatiDiffusionV1_v11_unCLIP/resolve/main/illuminatiDiffusionV1_v11-unclip-h-fp16.safetensors -P ./models/checkpoints/\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/wd-1.5-beta2_unCLIP/resolve/main/wd-1-5-beta2-aesthetic-unclip-h-fp16.safetensors -P ./models/checkpoints/\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# VAE\n",
|
|
||||||
"!wget -c https://huggingface.co/stabilityai/sd-vae-ft-mse-original/resolve/main/vae-ft-mse-840000-ema-pruned.safetensors -P ./models/vae/\n",
|
|
||||||
"#!wget -c https://huggingface.co/WarriorMama777/OrangeMixs/resolve/main/VAEs/orangemix.vae.pt -P ./models/vae/\n",
|
|
||||||
"#!wget -c https://huggingface.co/hakurei/waifu-diffusion-v1-4/resolve/main/vae/kl-f8-anime2.ckpt -P ./models/vae/\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# Loras\n",
|
|
||||||
"#!wget -c https://civitai.com/api/download/models/10350 -O ./models/loras/theovercomer8sContrastFix_sd21768.safetensors #theovercomer8sContrastFix SD2.x 768-v\n",
|
|
||||||
"#!wget -c https://civitai.com/api/download/models/10638 -O ./models/loras/theovercomer8sContrastFix_sd15.safetensors #theovercomer8sContrastFix SD1.x\n",
|
|
||||||
"#!wget -c https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors -P ./models/loras/ #SDXL offset noise lora\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# T2I-Adapter\n",
|
|
||||||
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_depth_sd14v1.pth -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_seg_sd14v1.pth -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_sketch_sd14v1.pth -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_keypose_sd14v1.pth -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_openpose_sd14v1.pth -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_color_sd14v1.pth -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_canny_sd14v1.pth -P ./models/controlnet/\n",
|
|
||||||
"\n",
|
|
||||||
"# T2I Styles Model\n",
|
|
||||||
"#!wget -c https://huggingface.co/TencentARC/T2I-Adapter/resolve/main/models/t2iadapter_style_sd14v1.pth -P ./models/style_models/\n",
|
|
||||||
"\n",
|
|
||||||
"# CLIPVision model (needed for styles model)\n",
|
|
||||||
"#!wget -c https://huggingface.co/openai/clip-vit-large-patch14/resolve/main/pytorch_model.bin -O ./models/clip_vision/clip_vit14.bin\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# ControlNet\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11e_sd15_ip2p_fp16.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11e_sd15_shuffle_fp16.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_canny_fp16.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11f1p_sd15_depth_fp16.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_inpaint_fp16.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_lineart_fp16.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_mlsd_fp16.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_normalbae_fp16.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_openpose_fp16.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_scribble_fp16.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_seg_fp16.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15_softedge_fp16.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11p_sd15s2_lineart_anime_fp16.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/ControlNet-v1-1_fp16_safetensors/resolve/main/control_v11u_sd15_tile_fp16.safetensors -P ./models/controlnet/\n",
|
|
||||||
"\n",
|
|
||||||
"# ControlNet SDXL\n",
|
|
||||||
"#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-canny-rank256.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-depth-rank256.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-recolor-rank256.safetensors -P ./models/controlnet/\n",
|
|
||||||
"#!wget -c https://huggingface.co/stabilityai/control-lora/resolve/main/control-LoRAs-rank256/control-lora-sketch-rank256.safetensors -P ./models/controlnet/\n",
|
|
||||||
"\n",
|
|
||||||
"# Controlnet Preprocessor nodes by Fannovel16\n",
|
|
||||||
"#!cd custom_nodes && git clone https://github.com/Fannovel16/comfy_controlnet_preprocessors; cd comfy_controlnet_preprocessors && python install.py\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# GLIGEN\n",
|
|
||||||
"#!wget -c https://huggingface.co/comfyanonymous/GLIGEN_pruned_safetensors/resolve/main/gligen_sd14_textbox_pruned_fp16.safetensors -P ./models/gligen/\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"# ESRGAN upscale model\n",
|
|
||||||
"#!wget -c https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth -P ./models/upscale_models/\n",
|
|
||||||
"#!wget -c https://huggingface.co/sberbank-ai/Real-ESRGAN/resolve/main/RealESRGAN_x2.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"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"id": "kkkkkkkkkkkkkkk"
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"### Run ComfyUI with cloudflared (Recommended Way)\n",
|
|
||||||
"\n",
|
|
||||||
"\n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {
|
|
||||||
"id": "jjjjjjjjjjjjjj"
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"!wget -P ~ https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb\n",
|
|
||||||
"!dpkg -i ~/cloudflared-linux-amd64.deb\n",
|
|
||||||
"\n",
|
|
||||||
"import subprocess\n",
|
|
||||||
"import threading\n",
|
|
||||||
"import time\n",
|
|
||||||
"import socket\n",
|
|
||||||
"import urllib.request\n",
|
|
||||||
"\n",
|
|
||||||
"def iframe_thread(port):\n",
|
|
||||||
" while True:\n",
|
|
||||||
" time.sleep(0.5)\n",
|
|
||||||
" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n",
|
|
||||||
" result = sock.connect_ex(('127.0.0.1', port))\n",
|
|
||||||
" if result == 0:\n",
|
|
||||||
" break\n",
|
|
||||||
" sock.close()\n",
|
|
||||||
" print(\"\\nComfyUI finished loading, trying to launch cloudflared (if it gets stuck here cloudflared is having issues)\\n\")\n",
|
|
||||||
"\n",
|
|
||||||
" p = subprocess.Popen([\"cloudflared\", \"tunnel\", \"--url\", \"http://127.0.0.1:{}\".format(port)], stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n",
|
|
||||||
" for line in p.stderr:\n",
|
|
||||||
" l = line.decode()\n",
|
|
||||||
" if \"trycloudflare.com \" in l:\n",
|
|
||||||
" print(\"This is the URL to access ComfyUI:\", l[l.find(\"http\"):], end='')\n",
|
|
||||||
" #print(l, end='')\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n",
|
|
||||||
"\n",
|
|
||||||
"!python main.py --dont-print-server"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"id": "kkkkkkkkkkkkkk"
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"### Run ComfyUI with localtunnel\n",
|
|
||||||
"\n",
|
|
||||||
"\n"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {
|
|
||||||
"id": "jjjjjjjjjjjjj"
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"!npm install -g localtunnel\n",
|
|
||||||
"\n",
|
|
||||||
"import subprocess\n",
|
|
||||||
"import threading\n",
|
|
||||||
"import time\n",
|
|
||||||
"import socket\n",
|
|
||||||
"import urllib.request\n",
|
|
||||||
"\n",
|
|
||||||
"def iframe_thread(port):\n",
|
|
||||||
" while True:\n",
|
|
||||||
" time.sleep(0.5)\n",
|
|
||||||
" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n",
|
|
||||||
" result = sock.connect_ex(('127.0.0.1', port))\n",
|
|
||||||
" if result == 0:\n",
|
|
||||||
" break\n",
|
|
||||||
" sock.close()\n",
|
|
||||||
" print(\"\\nComfyUI finished loading, trying to launch localtunnel (if it gets stuck here localtunnel is having issues)\\n\")\n",
|
|
||||||
"\n",
|
|
||||||
" print(\"The password/enpoint ip for localtunnel is:\", urllib.request.urlopen('https://ipv4.icanhazip.com').read().decode('utf8').strip(\"\\n\"))\n",
|
|
||||||
" p = subprocess.Popen([\"lt\", \"--port\", \"{}\".format(port)], stdout=subprocess.PIPE)\n",
|
|
||||||
" for line in p.stdout:\n",
|
|
||||||
" print(line.decode(), end='')\n",
|
|
||||||
"\n",
|
|
||||||
"\n",
|
|
||||||
"threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n",
|
|
||||||
"\n",
|
|
||||||
"!python main.py --dont-print-server"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "markdown",
|
|
||||||
"metadata": {
|
|
||||||
"id": "gggggggggg"
|
|
||||||
},
|
|
||||||
"source": [
|
|
||||||
"### Run ComfyUI with colab iframe (use only in case the previous way with localtunnel doesn't work)\n",
|
|
||||||
"\n",
|
|
||||||
"You should see the ui appear in an iframe. If you get a 403 error, it's your firefox settings or an extension that's messing things up.\n",
|
|
||||||
"\n",
|
|
||||||
"If you want to open it in another window use the link.\n",
|
|
||||||
"\n",
|
|
||||||
"Note that some UI features like live image previews won't work because the colab iframe blocks websockets."
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"cell_type": "code",
|
|
||||||
"execution_count": null,
|
|
||||||
"metadata": {
|
|
||||||
"id": "hhhhhhhhhh"
|
|
||||||
},
|
|
||||||
"outputs": [],
|
|
||||||
"source": [
|
|
||||||
"import threading\n",
|
|
||||||
"import time\n",
|
|
||||||
"import socket\n",
|
|
||||||
"def iframe_thread(port):\n",
|
|
||||||
" while True:\n",
|
|
||||||
" time.sleep(0.5)\n",
|
|
||||||
" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n",
|
|
||||||
" result = sock.connect_ex(('127.0.0.1', port))\n",
|
|
||||||
" if result == 0:\n",
|
|
||||||
" break\n",
|
|
||||||
" sock.close()\n",
|
|
||||||
" from google.colab import output\n",
|
|
||||||
" output.serve_kernel_port_as_iframe(port, height=1024)\n",
|
|
||||||
" print(\"to open it in a window you can open this link here:\")\n",
|
|
||||||
" output.serve_kernel_port_as_window(port)\n",
|
|
||||||
"\n",
|
|
||||||
"threading.Thread(target=iframe_thread, daemon=True, args=(8188,)).start()\n",
|
|
||||||
"\n",
|
|
||||||
"!python main.py --dont-print-server"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"metadata": {
|
|
||||||
"accelerator": "GPU",
|
|
||||||
"colab": {
|
|
||||||
"provenance": []
|
|
||||||
},
|
|
||||||
"gpuClass": "standard",
|
|
||||||
"kernelspec": {
|
|
||||||
"display_name": "Python 3",
|
|
||||||
"name": "python3"
|
|
||||||
},
|
|
||||||
"language_info": {
|
|
||||||
"name": "python"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nbformat": 4,
|
|
||||||
"nbformat_minor": 0
|
|
||||||
}
|
|
||||||
1261
openapi.yaml
Normal file
1261
openapi.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,65 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools >= 61.0"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "comfyui-manager"
|
name = "comfyui-manager"
|
||||||
|
license = { text = "GPL-3.0-only" }
|
||||||
|
version = "4.0.0-beta.4"
|
||||||
|
requires-python = ">= 3.9"
|
||||||
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
|
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
|
||||||
version = "3.3.10"
|
readme = "README.md"
|
||||||
license = { file = "LICENSE.txt" }
|
keywords = ["comfyui", "comfyui-manager"]
|
||||||
dependencies = ["GitPython", "PyGithub", "matrix-client==0.4.0", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions"]
|
|
||||||
|
maintainers = [
|
||||||
|
{ name = "Dr.Lt.Data", email = "dr.lt.data@gmail.com" },
|
||||||
|
{ name = "Yoland Yan", email = "yoland@comfy.org" },
|
||||||
|
{ name = "James Kwon", email = "hongilkwon316@gmail.com" },
|
||||||
|
{ name = "Robin Huang", email = "robin@comfy.org" },
|
||||||
|
]
|
||||||
|
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Intended Audience :: Developers",
|
||||||
|
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||||
|
]
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
"GitPython",
|
||||||
|
"PyGithub",
|
||||||
|
"matrix-client==0.4.0",
|
||||||
|
"transformers",
|
||||||
|
"huggingface-hub>0.20",
|
||||||
|
"typer",
|
||||||
|
"rich",
|
||||||
|
"typing-extensions",
|
||||||
|
"toml",
|
||||||
|
"uv",
|
||||||
|
"chardet"
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
dev = ["pre-commit", "pytest", "ruff", "pytest-cov"]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Repository = "https://github.com/ltdrdata/ComfyUI-Manager"
|
Repository = "https://github.com/ltdrdata/ComfyUI-Manager"
|
||||||
# Used by Comfy Registry https://comfyregistry.org
|
|
||||||
|
|
||||||
[tool.comfy]
|
[tool.setuptools.packages.find]
|
||||||
PublisherId = "drltdata"
|
where = ["."]
|
||||||
DisplayName = "ComfyUI-Manager"
|
include = ["comfyui_manager*"]
|
||||||
Icon = ""
|
|
||||||
|
[project.scripts]
|
||||||
|
cm-cli = "comfyui_manager.cm_cli.__main__:main"
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 120
|
||||||
|
target-version = "py39"
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
select = [
|
||||||
|
"E4", # default
|
||||||
|
"E7", # default
|
||||||
|
"E9", # default
|
||||||
|
"F", # default
|
||||||
|
"I", # isort-like behavior (import statement sorting)
|
||||||
|
]
|
||||||
|
|||||||
13
pytest.ini
Normal file
13
pytest.ini
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[tool:pytest]
|
||||||
|
testpaths = tests
|
||||||
|
python_files = test_*.py
|
||||||
|
python_classes = Test*
|
||||||
|
python_functions = test_*
|
||||||
|
addopts =
|
||||||
|
-v
|
||||||
|
--tb=short
|
||||||
|
--strict-markers
|
||||||
|
--disable-warnings
|
||||||
|
markers =
|
||||||
|
slow: marks tests as slow (deselect with '-m "not slow"')
|
||||||
|
integration: marks tests as integration tests
|
||||||
@@ -7,3 +7,5 @@ typer
|
|||||||
rich
|
rich
|
||||||
typing-extensions
|
typing-extensions
|
||||||
toml
|
toml
|
||||||
|
uv
|
||||||
|
chardet
|
||||||
|
|||||||
42
run_tests.py
Normal file
42
run_tests.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Simple test runner for ComfyUI-Manager tests.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python run_tests.py # Run all tests
|
||||||
|
python run_tests.py -k test_task_queue # Run specific tests
|
||||||
|
python run_tests.py --cov # Run with coverage
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run pytest with appropriate arguments"""
|
||||||
|
# Ensure we're in the project directory
|
||||||
|
project_root = Path(__file__).parent
|
||||||
|
|
||||||
|
# Base pytest command
|
||||||
|
cmd = [sys.executable, "-m", "pytest"]
|
||||||
|
|
||||||
|
# Add any command line arguments passed to this script
|
||||||
|
cmd.extend(sys.argv[1:])
|
||||||
|
|
||||||
|
# Add default arguments if none provided
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
cmd.extend([
|
||||||
|
"tests/",
|
||||||
|
"-v",
|
||||||
|
"--tb=short"
|
||||||
|
])
|
||||||
|
|
||||||
|
print(f"Running: {' '.join(cmd)}")
|
||||||
|
print(f"Working directory: {project_root}")
|
||||||
|
|
||||||
|
# Run pytest
|
||||||
|
result = subprocess.run(cmd, cwd=project_root)
|
||||||
|
sys.exit(result.returncode)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
14
scanner.py
14
scanner.py
@@ -94,7 +94,7 @@ def extract_nodes(code_text):
|
|||||||
return s
|
return s
|
||||||
else:
|
else:
|
||||||
return set()
|
return set()
|
||||||
except:
|
except Exception:
|
||||||
return set()
|
return set()
|
||||||
|
|
||||||
|
|
||||||
@@ -102,12 +102,8 @@ def extract_nodes(code_text):
|
|||||||
def scan_in_file(filename, is_builtin=False):
|
def scan_in_file(filename, is_builtin=False):
|
||||||
global builtin_nodes
|
global builtin_nodes
|
||||||
|
|
||||||
try:
|
with open(filename, encoding='utf-8', errors='ignore') as file:
|
||||||
with open(filename, encoding='utf-8') as file:
|
code = file.read()
|
||||||
code = file.read()
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
with open(filename, encoding='cp949') as file:
|
|
||||||
code = file.read()
|
|
||||||
|
|
||||||
pattern = r"_CLASS_MAPPINGS\s*=\s*{([^}]*)}"
|
pattern = r"_CLASS_MAPPINGS\s*=\s*{([^}]*)}"
|
||||||
regex = re.compile(pattern, re.MULTILINE | re.DOTALL)
|
regex = re.compile(pattern, re.MULTILINE | re.DOTALL)
|
||||||
@@ -297,7 +293,7 @@ def update_custom_nodes():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def is_rate_limit_exceeded():
|
def is_rate_limit_exceeded():
|
||||||
return g.rate_limiting[0] == 0
|
return g.rate_limiting[0] <= 20
|
||||||
|
|
||||||
if is_rate_limit_exceeded():
|
if is_rate_limit_exceeded():
|
||||||
print(f"GitHub API Rate Limit Exceeded: remained - {(g.rate_limiting_resettime - datetime.datetime.now().timestamp())/60:.2f} min")
|
print(f"GitHub API Rate Limit Exceeded: remained - {(g.rate_limiting_resettime - datetime.datetime.now().timestamp())/60:.2f} min")
|
||||||
@@ -400,7 +396,7 @@ def update_custom_nodes():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
download_url(url, temp_dir)
|
download_url(url, temp_dir)
|
||||||
except:
|
except Exception:
|
||||||
print(f"[ERROR] Cannot download '{url}'")
|
print(f"[ERROR] Cannot download '{url}'")
|
||||||
|
|
||||||
with concurrent.futures.ThreadPoolExecutor(10) as executor:
|
with concurrent.futures.ThreadPoolExecutor(10) as executor:
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
import os
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
def get_enabled_subdirectories_with_files(base_directory):
|
|
||||||
subdirs_with_files = []
|
|
||||||
for subdir in os.listdir(base_directory):
|
|
||||||
try:
|
|
||||||
full_path = os.path.join(base_directory, subdir)
|
|
||||||
if os.path.isdir(full_path) and not subdir.endswith(".disabled") and not subdir.startswith('.') and subdir != '__pycache__':
|
|
||||||
print(f"## Install dependencies for '{subdir}'")
|
|
||||||
requirements_file = os.path.join(full_path, "requirements.txt")
|
|
||||||
install_script = os.path.join(full_path, "install.py")
|
|
||||||
|
|
||||||
if os.path.exists(requirements_file) or os.path.exists(install_script):
|
|
||||||
subdirs_with_files.append((full_path, requirements_file, install_script))
|
|
||||||
except Exception as e:
|
|
||||||
print(f"EXCEPTION During Dependencies INSTALL on '{subdir}':\n{e}")
|
|
||||||
|
|
||||||
return subdirs_with_files
|
|
||||||
|
|
||||||
|
|
||||||
def install_requirements(requirements_file_path):
|
|
||||||
if os.path.exists(requirements_file_path):
|
|
||||||
subprocess.run(["pip", "install", "-r", requirements_file_path])
|
|
||||||
|
|
||||||
|
|
||||||
def run_install_script(install_script_path):
|
|
||||||
if os.path.exists(install_script_path):
|
|
||||||
subprocess.run(["python", install_script_path])
|
|
||||||
|
|
||||||
|
|
||||||
custom_nodes_directory = "custom_nodes"
|
|
||||||
subdirs_with_files = get_enabled_subdirectories_with_files(custom_nodes_directory)
|
|
||||||
|
|
||||||
|
|
||||||
for subdir, requirements_file, install_script in subdirs_with_files:
|
|
||||||
install_requirements(requirements_file)
|
|
||||||
run_install_script(install_script)
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
git clone https://github.com/comfyanonymous/ComfyUI
|
|
||||||
cd ComfyUI/custom_nodes
|
|
||||||
git clone https://github.com/ltdrdata/ComfyUI-Manager comfyui-manager
|
|
||||||
cd ..
|
|
||||||
python -m venv venv
|
|
||||||
source venv/bin/activate
|
|
||||||
python -m pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu121
|
|
||||||
python -m pip install -r requirements.txt
|
|
||||||
python -m pip install -r custom_nodes/ComfyUI-Manager/requirements.txt
|
|
||||||
cd ..
|
|
||||||
echo "#!/bin/bash" > run_gpu.sh
|
|
||||||
echo "cd ComfyUI" >> run_gpu.sh
|
|
||||||
echo "source venv/bin/activate" >> run_gpu.sh
|
|
||||||
echo "python main.py --preview-method auto" >> run_gpu.sh
|
|
||||||
chmod +x run_gpu.sh
|
|
||||||
|
|
||||||
echo "#!/bin/bash" > run_cpu.sh
|
|
||||||
echo "cd ComfyUI" >> run_cpu.sh
|
|
||||||
echo "source venv/bin/activate" >> run_cpu.sh
|
|
||||||
echo "python main.py --preview-method auto --cpu" >> run_cpu.sh
|
|
||||||
chmod +x run_cpu.sh
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
git clone https://github.com/comfyanonymous/ComfyUI
|
|
||||||
cd ComfyUI/custom_nodes
|
|
||||||
git clone https://github.com/ltdrdata/ComfyUI-Manager comfyui-manager
|
|
||||||
cd ..
|
|
||||||
python -m venv venv
|
|
||||||
call venv/Scripts/activate
|
|
||||||
python -m pip install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu121
|
|
||||||
python -m pip install -r requirements.txt
|
|
||||||
python -m pip install -r custom_nodes/ComfyUI-Manager/requirements.txt
|
|
||||||
cd ..
|
|
||||||
echo "cd ComfyUI" >> run_gpu.bat
|
|
||||||
echo "call venv/Scripts/activate" >> run_gpu.bat
|
|
||||||
echo "python main.py" >> run_gpu.bat
|
|
||||||
|
|
||||||
echo "cd ComfyUI" >> run_cpu.bat
|
|
||||||
echo "call venv/Scripts/activate" >> run_cpu.bat
|
|
||||||
echo "python main.py --cpu" >> run_cpu.bat
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user