Compare commits
747 Commits
feat/add-t
...
manager-v4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3425fb7a14 | ||
|
|
c69e7bcf03 | ||
|
|
85ebcd9897 | ||
|
|
69b6f1a66b | ||
|
|
e4a90089ab | ||
|
|
674b9f3705 | ||
|
|
4941fb8aa0 | ||
|
|
183af0dfa5 | ||
|
|
45ac5429f8 | ||
|
|
c771977a95 | ||
|
|
668d7bbb2c | ||
|
|
926cfabb58 | ||
|
|
a9a8d05115 | ||
|
|
e368f4366a | ||
|
|
dc5bddbc17 | ||
|
|
358a480408 | ||
|
|
c96fdb3c7a | ||
|
|
c090abcc02 | ||
|
|
1ff02be35f | ||
|
|
10fbfb88f7 | ||
|
|
9753df72ed | ||
|
|
095cc3f792 | ||
|
|
656171037b | ||
|
|
7ac10f9442 | ||
|
|
3925ba27b4 | ||
|
|
44ba79aa31 | ||
|
|
14d0e31268 | ||
|
|
033acffad1 | ||
|
|
d29ff808a5 | ||
|
|
dc9b6d655b | ||
|
|
d340c85013 | ||
|
|
e328353664 | ||
|
|
02785af8fd | ||
|
|
736ae5d63e | ||
|
|
e1eeb617d2 | ||
|
|
23b6c7f0de | ||
|
|
997f97e1fc | ||
|
|
ff335ff1a0 | ||
|
|
cb3036ef81 | ||
|
|
f762906188 | ||
|
|
dde7920f8c | ||
|
|
1a0d24110a | ||
|
|
e79f6c4471 | ||
|
|
a8a7024a84 | ||
|
|
d93d002da0 | ||
|
|
baaa0479e8 | ||
|
|
cc3bd7a056 | ||
|
|
4ecefb3b71 | ||
|
|
f24b5aa251 | ||
|
|
de547da4cd | ||
|
|
0f884166a6 | ||
|
|
63379f759d | ||
|
|
8fdff20243 | ||
|
|
5dfa07ca03 | ||
|
|
343645be6a | ||
|
|
91bf21d7a8 | ||
|
|
be6516cfd3 | ||
|
|
61f1e516a3 | ||
|
|
73b2278b45 | ||
|
|
aa625e30b6 | ||
|
|
29a46fe4ce | ||
|
|
5b3ee49530 | ||
|
|
a9158a101f | ||
|
|
feed8abb34 | ||
|
|
70decc740f | ||
|
|
5b5c83f8c5 | ||
|
|
773c06f40d | ||
|
|
737e6ad5ed | ||
|
|
81bca9c94e | ||
|
|
eef0654de2 | ||
|
|
997a00b8a2 | ||
|
|
4d25232c5f | ||
|
|
135befa101 | ||
|
|
44cac3fc43 | ||
|
|
449fa3510e | ||
|
|
d958af8aad | ||
|
|
09f8d5cb2d | ||
|
|
aedc99cefd | ||
|
|
b32cab6e9a | ||
|
|
a95186965e | ||
|
|
7067de1bb2 | ||
|
|
f45d878d21 | ||
|
|
a0532b938d | ||
|
|
6ad12b7652 | ||
|
|
02887c6c9b | ||
|
|
1b645e1cc3 | ||
|
|
0a4b2a0488 | ||
|
|
d4ce6ddc52 | ||
|
|
5a5b989533 | ||
|
|
b57cffb0fa | ||
|
|
72aa95cacf | ||
|
|
14ef448937 | ||
|
|
1c10607c06 | ||
|
|
41e53a1f2a | ||
|
|
cde83e9a38 | ||
|
|
6c206b1c72 | ||
|
|
a474219e7b | ||
|
|
3b8d25eb31 | ||
|
|
0faa0aa668 | ||
|
|
0fcdcc93a2 | ||
|
|
461d5e72fe | ||
|
|
3f030a2121 | ||
|
|
7fb8e8662f | ||
|
|
dd3ab9cff2 | ||
|
|
97b518de12 | ||
|
|
d2a795c866 | ||
|
|
8a3d65be20 | ||
|
|
b2126d8ba5 | ||
|
|
6386411d21 | ||
|
|
4250244136 | ||
|
|
77c4f9993d | ||
|
|
c7c8417577 | ||
|
|
9d0985ded8 | ||
|
|
3663e10e33 | ||
|
|
5f37a82c3c | ||
|
|
026bf1dfd7 | ||
|
|
643a6e5080 | ||
|
|
5267502896 | ||
|
|
c3c152122d | ||
|
|
afeac097e5 | ||
|
|
e5cea64132 | ||
|
|
26da78cf15 | ||
|
|
179a1e1ca0 | ||
|
|
b379d275d1 | ||
|
|
133cdfb203 | ||
|
|
2b79edd9be | ||
|
|
3862a92e04 | ||
|
|
f4e3817fcc | ||
|
|
61f0f5d67c | ||
|
|
87f57551ea | ||
|
|
ee51efed69 | ||
|
|
5dab865681 | ||
|
|
8c0581eebc | ||
|
|
a72f9f422c | ||
|
|
1354a8c970 | ||
|
|
00a5115267 | ||
|
|
00282eab7b | ||
|
|
bec128de58 | ||
|
|
9edfa7b4fa | ||
|
|
a9af70e5f0 | ||
|
|
910caf593f | ||
|
|
02dc072dc7 | ||
|
|
78fb354452 | ||
|
|
66f5eca7fa | ||
|
|
d3906e3cbc | ||
|
|
079ac254ce | ||
|
|
be95396a57 | ||
|
|
59cbed429f | ||
|
|
d49df7aebb | ||
|
|
0aa9faad2e | ||
|
|
1337def888 | ||
|
|
4b100c558b | ||
|
|
1425a71ece | ||
|
|
e0640e7014 | ||
|
|
a8524508fe | ||
|
|
a5ff973d53 | ||
|
|
337c9aa2c7 | ||
|
|
f1448403ac | ||
|
|
d0b5f77ec6 | ||
|
|
9cb22ffb60 | ||
|
|
f556962d82 | ||
|
|
d28448d519 | ||
|
|
c590a88ffd | ||
|
|
a1fc6c817b | ||
|
|
5554e52799 | ||
|
|
ca749eb4d2 | ||
|
|
41ceee3d24 | ||
|
|
5acfd52986 | ||
|
|
ec4c7b2f6a | ||
|
|
22a3d8f95f | ||
|
|
06b89ca277 | ||
|
|
9e5ffbd00a | ||
|
|
39e92ed778 | ||
|
|
68a3ec567a | ||
|
|
28231e81b3 | ||
|
|
b2ee0feeaa | ||
|
|
5541b6b366 | ||
|
|
408a5fe27e | ||
|
|
bffc73f976 | ||
|
|
bd6edfc9dd | ||
|
|
2cb24e8a94 | ||
|
|
a49779c4d2 | ||
|
|
15a5a5f5df | ||
|
|
b5e0558d6e | ||
|
|
4d683b23fc | ||
|
|
c13da606b2 | ||
|
|
c792f9277c | ||
|
|
b430f42622 | ||
|
|
fee822f5ae | ||
|
|
192659ecbd | ||
|
|
810431b9e2 | ||
|
|
02d845adf3 | ||
|
|
89c7b960fb | ||
|
|
ed1e399a56 | ||
|
|
8a3ce1ae57 | ||
|
|
d89ff649f8 | ||
|
|
24a73b5d1c | ||
|
|
4d0c40ff8a | ||
|
|
b5a2bed539 | ||
|
|
0efb79f571 | ||
|
|
df944b9a0f | ||
|
|
2c11846430 | ||
|
|
0035c01186 | ||
|
|
34be3384fe | ||
|
|
ebbc7b3335 | ||
|
|
4ccc8c3086 | ||
|
|
af9ebc9568 | ||
|
|
ca4b61c5f0 | ||
|
|
393839b3ab | ||
|
|
dadfc96e00 | ||
|
|
a0a33aef03 | ||
|
|
99ed81e0f5 | ||
|
|
5b697db219 | ||
|
|
8e5bf46e14 | ||
|
|
9f649b0900 | ||
|
|
abb15e06d3 | ||
|
|
11a317493e | ||
|
|
e8cece0c1b | ||
|
|
1ab882f81d | ||
|
|
b9338186e3 | ||
|
|
7c3cbff425 | ||
|
|
1ff0afc633 | ||
|
|
bfe7ee8fba | ||
|
|
49c73ed10e | ||
|
|
f571baacf9 | ||
|
|
6f02e1114c | ||
|
|
e230f43565 | ||
|
|
0d9593e71b | ||
|
|
20778ecfb0 | ||
|
|
2ea991d960 | ||
|
|
119c107834 | ||
|
|
800a0d0449 | ||
|
|
95c43f0189 | ||
|
|
9c77176c7f | ||
|
|
ddb6a55cd6 | ||
|
|
56a4f6fdd7 | ||
|
|
8a30f788b5 | ||
|
|
380a1c2c8c | ||
|
|
cd8e8335cf | ||
|
|
6e1beb54a4 | ||
|
|
9217c965dd | ||
|
|
a4d71ef487 | ||
|
|
518f332047 | ||
|
|
9257d497b8 | ||
|
|
07cf5de4f7 | ||
|
|
43ad69e48d | ||
|
|
c62e236cc6 | ||
|
|
15a2fbb293 | ||
|
|
16800c3fa0 | ||
|
|
ce09f41aa3 | ||
|
|
47dc2f036a | ||
|
|
f27a154bfd | ||
|
|
79757366e8 | ||
|
|
2cd9a417d6 | ||
|
|
deb05c6cc3 | ||
|
|
b6f171de51 | ||
|
|
a58d5f6999 | ||
|
|
e0b3f3eb45 | ||
|
|
4bbc8594a7 | ||
|
|
1ab2b1aeb3 | ||
|
|
3a377300e1 | ||
|
|
33a07e3a86 | ||
|
|
212cafc1d7 | ||
|
|
2643b3cbcc | ||
|
|
d445229b6d | ||
|
|
dab5c451b0 | ||
|
|
7bdf06131a | ||
|
|
854648d5af | ||
|
|
c5f7b97359 | ||
|
|
dd8a727ad6 | ||
|
|
6c627fe422 | ||
|
|
ee980e1caf | ||
|
|
22bfaf6527 | ||
|
|
48ab48cc30 | ||
|
|
a0b14d4127 | ||
|
|
03f9fe1a70 | ||
|
|
8915b8d796 | ||
|
|
c77ffeeec0 | ||
|
|
4acf5660b2 | ||
|
|
2d9f0a668c | ||
|
|
9e6cb246cc | ||
|
|
14544ca63d | ||
|
|
26b347c04c | ||
|
|
36f75d1811 | ||
|
|
ffaeb6d3ff | ||
|
|
6cc1ad4cc0 | ||
|
|
27fc787294 | ||
|
|
d23286d390 | ||
|
|
7c3ccc76c3 | ||
|
|
892dc5d4f3 | ||
|
|
e278692749 | ||
|
|
8d77dd2246 | ||
|
|
14ede2a585 | ||
|
|
5b525622f1 | ||
|
|
a24b11905c | ||
|
|
5d70858341 | ||
|
|
3daa006741 | ||
|
|
0bcc0c2101 | ||
|
|
b8850c808c | ||
|
|
f4f2c01ac1 | ||
|
|
7072e82dff | ||
|
|
53dc36c4cf | ||
|
|
5aadc3af00 | ||
|
|
8c28a698ed | ||
|
|
5ed6d8b202 | ||
|
|
b73dc7bf5e | ||
|
|
d7799964de | ||
|
|
71d0f4ab63 | ||
|
|
d479dcde81 | ||
|
|
ae536017d5 | ||
|
|
67ddfce279 | ||
|
|
b1f39b34d7 | ||
|
|
6cf958ccce | ||
|
|
5378f0a8e9 | ||
|
|
e13bf68775 | ||
|
|
eaed3677d3 | ||
|
|
b9c88da54d | ||
|
|
104ae77f7a | ||
|
|
bfcb2ce61b | ||
|
|
d970fe68ea | ||
|
|
63ba5fed09 | ||
|
|
98a8464933 | ||
|
|
7e3e6726e0 | ||
|
|
09567b2bb2 | ||
|
|
f3bd116184 | ||
|
|
7509737563 | ||
|
|
cfb815d879 | ||
|
|
44241fb967 | ||
|
|
c4b45129bd | ||
|
|
70741008ca | ||
|
|
6c2d2cae2a | ||
|
|
28f13d3311 | ||
|
|
4e31aaa8fb | ||
|
|
ba99f0c2cc | ||
|
|
e0a96b4937 | ||
|
|
82c055f527 | ||
|
|
f94008192c | ||
|
|
3895d5279e | ||
|
|
41be94690f | ||
|
|
3d85ecc525 | ||
|
|
7da00796e5 | ||
|
|
6086419cb6 | ||
|
|
5bc1f2f2c0 | ||
|
|
32a83b211e | ||
|
|
bead7b3a7f | ||
|
|
815d6d6572 | ||
|
|
fbecbee4c3 | ||
|
|
b9a7d2a78c | ||
|
|
95ce812992 | ||
|
|
9a36f4748c | ||
|
|
50b7849a35 | ||
|
|
6f1245b27c | ||
|
|
cc87ed3899 | ||
|
|
1d9037fefe | ||
|
|
03016e2d16 | ||
|
|
bdfb70a58a | ||
|
|
3d41617f4e | ||
|
|
35151ffdd1 | ||
|
|
4527d41a7a | ||
|
|
553cba12f3 | ||
|
|
00fb9c88e1 | ||
|
|
116e068ac3 | ||
|
|
1010dd2d28 | ||
|
|
68bc8302fd | ||
|
|
596dad5cda | ||
|
|
a924c280fb | ||
|
|
7354242906 | ||
|
|
3d0bcf5979 | ||
|
|
e7d0b158e9 | ||
|
|
10ff90787c | ||
|
|
330c4657b1 | ||
|
|
72a109f109 | ||
|
|
cf45c51dfb | ||
|
|
0b013adb34 | ||
|
|
7457d91f64 | ||
|
|
7fe1159426 | ||
|
|
c2665e3677 | ||
|
|
d63de803a4 | ||
|
|
11aca3513c | ||
|
|
561c9f40e5 | ||
|
|
54ed13aadf | ||
|
|
109cc21337 | ||
|
|
7e46b30fa5 | ||
|
|
0ba112c2c7 | ||
|
|
fc15d94170 | ||
|
|
dcb37d9c55 | ||
|
|
755b9d6342 | ||
|
|
3d6151c94f | ||
|
|
590bd8c4b9 | ||
|
|
e99aafd876 | ||
|
|
1f0adf8bcf | ||
|
|
dbd5d5fb43 | ||
|
|
a8b0e3641b | ||
|
|
9efb350be9 | ||
|
|
8d9820b3fb | ||
|
|
103f89551a | ||
|
|
6030d961ad | ||
|
|
ee08c9e17f | ||
|
|
48dd9a3240 | ||
|
|
e122e206a6 | ||
|
|
398b905758 | ||
|
|
dc2ec08fe3 | ||
|
|
3bf5edf5c9 | ||
|
|
134bca526c | ||
|
|
3393e58b06 | ||
|
|
648d7e73c6 | ||
|
|
eab6cdeee4 | ||
|
|
e8ec1ce8e3 | ||
|
|
b3581564ed | ||
|
|
29e1bd95fd | ||
|
|
8bff401c14 | ||
|
|
41798e9255 | ||
|
|
9e4f0228d1 | ||
|
|
76ee93c98c | ||
|
|
fb1a89efb7 | ||
|
|
aface43554 | ||
|
|
a35f0157b2 | ||
|
|
9b32162906 | ||
|
|
21bba62572 | ||
|
|
302327d6b3 | ||
|
|
5667e8bcbb | ||
|
|
ae66bd0e31 | ||
|
|
48dfadc02d | ||
|
|
3df6272bb6 | ||
|
|
e7f9bcda01 | ||
|
|
205044ca66 | ||
|
|
d497eb1f00 | ||
|
|
4e6f970ee9 | ||
|
|
0b6cdda6f5 | ||
|
|
a896ded763 | ||
|
|
fb5dd9ebc2 | ||
|
|
c8b7db6c38 | ||
|
|
44a3191be3 | ||
|
|
b4f7cdc9e7 | ||
|
|
8da07018d5 | ||
|
|
0c19a27065 | ||
|
|
3296b0ecdf | ||
|
|
0a07261124 | ||
|
|
33106d0ecf | ||
|
|
5bb887206a | ||
|
|
b30b0e27cb | ||
|
|
363736489c | ||
|
|
8dbf5e87a0 | ||
|
|
0b30f2cb50 | ||
|
|
ba5265dac4 | ||
|
|
ecb9c65917 | ||
|
|
8a98474600 | ||
|
|
b072216e67 | ||
|
|
cfb3181716 | ||
|
|
ab684cdc99 | ||
|
|
facadc3a44 | ||
|
|
f599bc22d7 | ||
|
|
281319d2da | ||
|
|
5cb203685c | ||
|
|
300c6e7406 | ||
|
|
9c4d6a0773 | ||
|
|
01fa37900b | ||
|
|
edbe744e17 | ||
|
|
2a32a1a4a8 | ||
|
|
404bdb21e6 | ||
|
|
b260c9a512 | ||
|
|
4b941adb6a | ||
|
|
bd752550a8 | ||
|
|
b8b71bb961 | ||
|
|
5aaf7a4092 | ||
|
|
030e02ffb8 | ||
|
|
60746c6253 | ||
|
|
d962aa03f4 | ||
|
|
121a5a1888 | ||
|
|
9e4a2aae43 | ||
|
|
ee6eb685e7 | ||
|
|
09a38a32ce | ||
|
|
d13b19d43d | ||
|
|
5316ec1b4d | ||
|
|
e730dca1ad | ||
|
|
8da30640bb | ||
|
|
6f4eb88e07 | ||
|
|
d9592b9dab | ||
|
|
b87ada72aa | ||
|
|
83363ba1f0 | ||
|
|
a2a7349ce4 | ||
|
|
23ebe7f718 | ||
|
|
e04264cfa3 | ||
|
|
8d29e5037f | ||
|
|
6926ed45b0 | ||
|
|
736b85b8bb | ||
|
|
9e3361bc31 | ||
|
|
6e10381020 | ||
|
|
a1d37d379c | ||
|
|
07d87db7a2 | ||
|
|
4e556673d2 | ||
|
|
f421304fc1 | ||
|
|
6867616973 | ||
|
|
c9271b1686 | ||
|
|
12eb6863da | ||
|
|
4834874091 | ||
|
|
8759ebf200 | ||
|
|
d4715aebef | ||
|
|
0fe2ade7bb | ||
|
|
0c71565535 | ||
|
|
cf8029ecd4 | ||
|
|
6a637091a2 | ||
|
|
31eba60012 | ||
|
|
51e58e9078 | ||
|
|
4a1e76730a | ||
|
|
5599bb028b | ||
|
|
552c6da0cc | ||
|
|
cc6817a891 | ||
|
|
fb48d1b485 | ||
|
|
1c336dad6b | ||
|
|
a4940d46cd | ||
|
|
499b2f44c1 | ||
|
|
2b200c9281 | ||
|
|
36a900c98f | ||
|
|
5236b03f66 | ||
|
|
8be35e3621 | ||
|
|
509f00fe89 | ||
|
|
a98b87f148 | ||
|
|
ae9b2b3b72 | ||
|
|
02e1ec0ae3 | ||
|
|
daefb0f120 | ||
|
|
ff0604e3b6 | ||
|
|
20e41e22fa | ||
|
|
59264c1fd9 | ||
|
|
a0e3bdd594 | ||
|
|
6580aaf3ad | ||
|
|
0b46701b60 | ||
|
|
0bb4effede | ||
|
|
b07082a52d | ||
|
|
04f267f5a7 | ||
|
|
03ccce2804 | ||
|
|
e894bd9f24 | ||
|
|
10e6988273 | ||
|
|
905b61e5d8 | ||
|
|
ee69d393ae | ||
|
|
cab39973ae | ||
|
|
d93f5d07bb | ||
|
|
ba00ffe1ae | ||
|
|
6afaf5eaf5 | ||
|
|
d30459cc34 | ||
|
|
e92fbb7b1b | ||
|
|
42d464b532 | ||
|
|
c2e9e5c63a | ||
|
|
bc36726925 | ||
|
|
22725b0188 | ||
|
|
7abbff8c31 | ||
|
|
6236f4bcf4 | ||
|
|
3c3e80f77f | ||
|
|
4aae2fb289 | ||
|
|
66ff07752f | ||
|
|
5cf92f2742 | ||
|
|
6d3fddc474 | ||
|
|
66d4ad6174 | ||
|
|
2a366a1607 | ||
|
|
d87a0995b4 | ||
|
|
9a73a41e04 | ||
|
|
ba041b36bc | ||
|
|
f5f9de69b4 | ||
|
|
71e56c62e8 | ||
|
|
a0b0c2b963 | ||
|
|
0f496619fd | ||
|
|
5fdd6a441a | ||
|
|
00f287bb63 | ||
|
|
785268efa6 | ||
|
|
2c976d9394 | ||
|
|
1e32582642 | ||
|
|
6f8f6d07f5 | ||
|
|
3958111e76 | ||
|
|
86fcc4af74 | ||
|
|
2fd26756df | ||
|
|
478f4b74d8 | ||
|
|
73d0d2a1bb | ||
|
|
546db08ec4 | ||
|
|
0dd41a8670 | ||
|
|
82c0c89f46 | ||
|
|
f4ce0fd5f1 | ||
|
|
c3798bf4c2 | ||
|
|
ff80b6ccb0 | ||
|
|
e729217116 | ||
|
|
94c695daca | ||
|
|
9f189f0420 | ||
|
|
ad09e53f60 | ||
|
|
092a7a5f3f | ||
|
|
f45649bd25 | ||
|
|
2595cc5ed7 | ||
|
|
2f62190c6f | ||
|
|
577314984c | ||
|
|
f0346b955b | ||
|
|
70139ded4a | ||
|
|
bf379900e1 | ||
|
|
9bafc90f5e | ||
|
|
fce0d9e88e | ||
|
|
2b3b154989 | ||
|
|
948d2440a1 | ||
|
|
5adbe1ce7a | ||
|
|
8157d34ffa | ||
|
|
3ec8cb2204 | ||
|
|
0daa826543 | ||
|
|
a66028da58 | ||
|
|
807c9e6872 | ||
|
|
e71f3774ba | ||
|
|
dd7314bf10 | ||
|
|
f33bc127dc | ||
|
|
db92b87782 | ||
|
|
eba41c8693 | ||
|
|
c855308162 | ||
|
|
73d971bed8 | ||
|
|
bcfe0c2874 | ||
|
|
931ff666ae | ||
|
|
18b6d86cc4 | ||
|
|
086040f858 | ||
|
|
adbeb527d6 | ||
|
|
043176168d | ||
|
|
3c5efa0662 | ||
|
|
9b739bcbbf | ||
|
|
db89076e48 | ||
|
|
19b341ef18 | ||
|
|
be3713b1a3 | ||
|
|
99c4415cfb | ||
|
|
7b311f2ccf | ||
|
|
4aeabfe0a7 | ||
|
|
431ed02194 | ||
|
|
07f587ed83 | ||
|
|
0408341d82 | ||
|
|
5b3c9432f3 | ||
|
|
4a197e63f9 | ||
|
|
ad79a2ef45 | ||
|
|
0876a12fe9 | ||
|
|
c43c7ecc03 | ||
|
|
4a6dee3044 | ||
|
|
019acdd840 | ||
|
|
1c98512720 | ||
|
|
43041cebed | ||
|
|
23a09ad546 | ||
|
|
0836e8fe7c | ||
|
|
90196af8f8 | ||
|
|
002e549a86 | ||
|
|
1de6f859bf | ||
|
|
566fe05772 | ||
|
|
18772c6292 | ||
|
|
6278bddc9b | ||
|
|
f74bf71735 | ||
|
|
efe9ed68b2 | ||
|
|
7c1e75865d | ||
|
|
89530fc4e7 | ||
|
|
a0aee41f1a | ||
|
|
2049dd75f4 | ||
|
|
0864c35ba9 | ||
|
|
92c9f66671 | ||
|
|
223d6dad51 | ||
|
|
815784e809 | ||
|
|
2795d00d1e | ||
|
|
86dd0b4963 | ||
|
|
77a4f4819f | ||
|
|
b63d603482 | ||
|
|
e569b4e613 | ||
|
|
8a70997546 | ||
|
|
80d0a0f882 | ||
|
|
70b3997874 | ||
|
|
e8e4311068 | ||
|
|
cb0fa5829d | ||
|
|
a66f86d4af | ||
|
|
35d98dcea8 | ||
|
|
38fefde06d | ||
|
|
75ecb31f8c | ||
|
|
77133375ad | ||
|
|
c58b93ff51 | ||
|
|
7d8ebfe91b | ||
|
|
810381eab2 | ||
|
|
61dc6cf2de | ||
|
|
0205ebad2a | ||
|
|
09a94133ac | ||
|
|
1eb3c3b219 | ||
|
|
457845bb51 | ||
|
|
0c11b46585 | ||
|
|
c35100d9e9 | ||
|
|
847031cb04 | ||
|
|
d1ca6288a3 | ||
|
|
624ad4cfe6 | ||
|
|
f8d87bb452 | ||
|
|
f60b3505e0 | ||
|
|
addefbc511 | ||
|
|
c4314b25a3 | ||
|
|
921bb86127 | ||
|
|
d912fb0f8b | ||
|
|
e8fc053a32 | ||
|
|
ce3b2bab39 | ||
|
|
15e3699535 | ||
|
|
a4bf6bddbf | ||
|
|
f1b3c6b735 | ||
|
|
e923434d08 | ||
|
|
ddc9cd0fd5 | ||
|
|
d081db0c30 | ||
|
|
14298b0859 | ||
|
|
03ecda3cfe | ||
|
|
350cb767c3 | ||
|
|
f450dcbb57 | ||
|
|
32e003965a | ||
|
|
65f0764338 | ||
|
|
1bdb026079 | ||
|
|
b3a7fb9c3e | ||
|
|
c143c81a7e | ||
|
|
dd389ba0f8 | ||
|
|
46b1649ab8 | ||
|
|
89710412e4 | ||
|
|
931973b632 | ||
|
|
60aaa838e3 | ||
|
|
7e51286313 | ||
|
|
1246538bbb | ||
|
|
80518abf9d | ||
|
|
fc1ae2a18e | ||
|
|
3fd8d2049c | ||
|
|
35a6bcf20c | ||
|
|
0d75fc331e | ||
|
|
0a23e793e3 | ||
|
|
2c1c03e063 | ||
|
|
64059d2949 | ||
|
|
648aa7c4d3 | ||
|
|
274bb81a08 | ||
|
|
e2c90b4681 | ||
|
|
fa0a98ac6e | ||
|
|
e6e7b42415 | ||
|
|
0b7ef2e1d4 | ||
|
|
2fac67a9f9 | ||
|
|
8b9892de2e | ||
|
|
b3290dc909 | ||
|
|
3e3176eddb | ||
|
|
b1ef84894a | ||
|
|
c6cffc92c4 | ||
|
|
efb9fd2712 | ||
|
|
94b294ff93 | ||
|
|
99a9e33648 | ||
|
|
055d94a919 | ||
|
|
0978005240 | ||
|
|
1f796581ec | ||
|
|
f3a1716dad | ||
|
|
a1c3a0db1f | ||
|
|
9f80cc8a6b | ||
|
|
133786846e | ||
|
|
bdf297a5c6 | ||
|
|
6767254eb0 | ||
|
|
691cebd479 | ||
|
|
f3932cbf29 | ||
|
|
3f73a97037 | ||
|
|
226f1f5be4 | ||
|
|
7e45c07660 | ||
|
|
0c815036b9 |
30
.github/workflows/publish-to-pypi.yml
vendored
30
.github/workflows/publish-to-pypi.yml
vendored
@@ -4,7 +4,7 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- manager-v4
|
||||||
paths:
|
paths:
|
||||||
- "pyproject.toml"
|
- "pyproject.toml"
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: '3.9'
|
python-version: '3.x'
|
||||||
|
|
||||||
- name: Install build dependencies
|
- name: Install build dependencies
|
||||||
run: |
|
run: |
|
||||||
@@ -31,27 +31,27 @@ jobs:
|
|||||||
- name: Get current version
|
- name: Get current version
|
||||||
id: current_version
|
id: current_version
|
||||||
run: |
|
run: |
|
||||||
CURRENT_VERSION=$(grep -oP 'version = "\K[^"]+' pyproject.toml)
|
CURRENT_VERSION=$(grep -oP '^version = "\K[^"]+' pyproject.toml)
|
||||||
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||||
echo "Current version: $CURRENT_VERSION"
|
echo "Current version: $CURRENT_VERSION"
|
||||||
|
|
||||||
- name: Build package
|
- name: Build package
|
||||||
run: python -m build
|
run: python -m build
|
||||||
|
|
||||||
- name: Create GitHub Release
|
# - name: Create GitHub Release
|
||||||
id: create_release
|
# id: create_release
|
||||||
uses: softprops/action-gh-release@v2
|
# uses: softprops/action-gh-release@v2
|
||||||
env:
|
# env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
# GITHUB_TOKEN: ${{ github.token }}
|
||||||
with:
|
# with:
|
||||||
files: dist/*
|
# files: dist/*
|
||||||
tag_name: v${{ steps.current_version.outputs.version }}
|
# tag_name: v${{ steps.current_version.outputs.version }}
|
||||||
draft: false
|
# draft: false
|
||||||
prerelease: false
|
# prerelease: false
|
||||||
generate_release_notes: true
|
# generate_release_notes: true
|
||||||
|
|
||||||
- name: Publish to PyPI
|
- name: Publish to PyPI
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc
|
||||||
with:
|
with:
|
||||||
password: ${{ secrets.PYPI_TOKEN }}
|
password: ${{ secrets.PYPI_TOKEN }}
|
||||||
skip-existing: true
|
skip-existing: true
|
||||||
|
|||||||
25
.github/workflows/publish.yml
vendored
25
.github/workflows/publish.yml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Publish to Comfy registry
|
|
||||||
on:
|
|
||||||
workflow_dispatch:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main-blocked
|
|
||||||
paths:
|
|
||||||
- "pyproject.toml"
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
issues: write
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
publish-node:
|
|
||||||
name: Publish Custom Node to registry
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
if: ${{ github.repository_owner == 'ltdrdata' || github.repository_owner == 'Comfy-Org' }}
|
|
||||||
steps:
|
|
||||||
- name: Check out code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
- name: Publish Custom Node
|
|
||||||
uses: Comfy-Org/publish-node-action@v1
|
|
||||||
with:
|
|
||||||
## Add your own personal access token to your Github Repository secrets and reference it here.
|
|
||||||
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
|
|
||||||
93
README.md
93
README.md
@@ -89,20 +89,20 @@
|
|||||||
|
|
||||||
|
|
||||||
## Paths
|
## Paths
|
||||||
In `ComfyUI-Manager` V3.0 and later, configuration files and dynamically generated files are located under `<USER_DIRECTORY>/default/ComfyUI-Manager/`.
|
In `ComfyUI-Manager` V4.0.3b4 and later, configuration files and dynamically generated files are located under `<USER_DIRECTORY>/__manager/`.
|
||||||
|
|
||||||
* <USER_DIRECTORY>
|
* <USER_DIRECTORY>
|
||||||
* If executed without any options, the path defaults to ComfyUI/user.
|
* If executed without any options, the path defaults to ComfyUI/user.
|
||||||
* It can be set using --user-directory <USER_DIRECTORY>.
|
* It can be set using --user-directory <USER_DIRECTORY>.
|
||||||
|
|
||||||
* Basic config files: `<USER_DIRECTORY>/default/ComfyUI-Manager/config.ini`
|
* Basic config files: `<USER_DIRECTORY>/__manager/config.ini`
|
||||||
* Configurable channel lists: `<USER_DIRECTORY>/default/ComfyUI-Manager/channels.ini`
|
* Configurable channel lists: `<USER_DIRECTORY>/__manager/channels.ini`
|
||||||
* Configurable pip overrides: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_overrides.json`
|
* Configurable pip overrides: `<USER_DIRECTORY>/__manager/pip_overrides.json`
|
||||||
* Configurable pip blacklist: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_blacklist.list`
|
* Configurable pip blacklist: `<USER_DIRECTORY>/__manager/pip_blacklist.list`
|
||||||
* Configurable pip auto fix: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_auto_fix.list`
|
* Configurable pip auto fix: `<USER_DIRECTORY>/__manager/pip_auto_fix.list`
|
||||||
* Saved snapshot files: `<USER_DIRECTORY>/default/ComfyUI-Manager/snapshots`
|
* Saved snapshot files: `<USER_DIRECTORY>/__manager/snapshots`
|
||||||
* Startup script files: `<USER_DIRECTORY>/default/ComfyUI-Manager/startup-scripts`
|
* Startup script files: `<USER_DIRECTORY>/__manager/startup-scripts`
|
||||||
* Component files: `<USER_DIRECTORY>/default/ComfyUI-Manager/components`
|
* Component files: `<USER_DIRECTORY>/__manager/components`
|
||||||
|
|
||||||
|
|
||||||
## `extra_model_paths.yaml` Configuration
|
## `extra_model_paths.yaml` Configuration
|
||||||
@@ -115,17 +115,17 @@ The following settings are applied based on the section marked as `is_default`.
|
|||||||
|
|
||||||
## 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>/__manager/snapshots`
|
||||||
* You can rename snapshot file.
|
* You can rename snapshot file.
|
||||||
* Press the "Restore" button to revert to the installation status of the respective snapshot.
|
* Press the "Restore" button to revert to the installation status of the respective snapshot.
|
||||||
* However, for custom nodes not managed by Git, snapshot support is incomplete.
|
* However, for custom nodes not managed by Git, snapshot support is incomplete.
|
||||||
* When you press `Restore`, it will take effect on the next ComfyUI startup.
|
* When you press `Restore`, it will take effect on the next ComfyUI startup.
|
||||||
* The selected snapshot file is saved in `<USER_DIRECTORY>/default/ComfyUI-Manager/startup-scripts/restore-snapshot.json`, and upon restarting ComfyUI, the snapshot is applied and then deleted.
|
* The selected snapshot file is saved in `<USER_DIRECTORY>/__manager/startup-scripts/restore-snapshot.json`, and upon restarting ComfyUI, the snapshot is applied and then deleted.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
## cm-cli: command line tools for power user
|
## cm-cli: command line tools for power users
|
||||||
* A tool is provided that allows you to use the features of ComfyUI-Manager without running ComfyUI.
|
* A tool is provided that allows you to use the features of ComfyUI-Manager without running ComfyUI.
|
||||||
* For more details, please refer to the [cm-cli documentation](docs/en/cm-cli.md).
|
* For more details, please refer to the [cm-cli documentation](docs/en/cm-cli.md).
|
||||||
|
|
||||||
@@ -169,12 +169,12 @@ The following settings are applied based on the section marked as `is_default`.
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
* `<current timestamp>` Ensure that the timestamp is always unique.
|
* `<current timestamp>` Ensure that the timestamp is always unique.
|
||||||
* "components" should have the same structure as the content of the file stored in `<USER_DIRECTORY>/default/ComfyUI-Manager/components`.
|
* "components" should have the same structure as the content of the file stored in `<USER_DIRECTORY>/__manager/components`.
|
||||||
* `<component name>`: The name should be in the format `<prefix>::<node name>`.
|
* `<component name>`: The name should be in the format `<prefix>::<node name>`.
|
||||||
* `<compnent nodeata>`: In the nodedata of the group node.
|
* `<component node data>`: In the node data of the group node.
|
||||||
* `<version>`: Only two formats are allowed: `major.minor.patch` or `major.minor`. (e.g. `1.0`, `2.2.1`)
|
* `<version>`: Only two formats are allowed: `major.minor.patch` or `major.minor`. (e.g. `1.0`, `2.2.1`)
|
||||||
* `<datetime>`: Saved time
|
* `<datetime>`: Saved time
|
||||||
* `<packname>`: If the packname is not empty, the category becomes packname/workflow, and it is saved in the <packname>.pack file in `<USER_DIRECTORY>/default/ComfyUI-Manager/components`.
|
* `<packname>`: If the packname is not empty, the category becomes packname/workflow, and it is saved in the <packname>.pack file in `<USER_DIRECTORY>/__manager/components`.
|
||||||
* `<category>`: If there is neither a category nor a packname, it is saved in the components category.
|
* `<category>`: If there is neither a category nor a packname, it is saved in the components category.
|
||||||
```
|
```
|
||||||
"version":"1.0",
|
"version":"1.0",
|
||||||
@@ -189,7 +189,7 @@ The following settings are applied based on the section marked as `is_default`.
|
|||||||
* Dragging and dropping or pasting a single component will add a node. However, when adding multiple components, nodes will not be added.
|
* Dragging and dropping or pasting a single component will add a node. However, when adding multiple components, nodes will not be added.
|
||||||
|
|
||||||
|
|
||||||
## Support of missing nodes installation
|
## Support for installing missing nodes
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -215,23 +215,24 @@ The following settings are applied based on the section marked as `is_default`.
|
|||||||
downgrade_blacklist = <Set a list of packages to prevent downgrades. List them separated by commas.>
|
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>
|
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.>
|
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 = <Set the network mode => public|private|offline|personal_cloud>
|
||||||
```
|
```
|
||||||
|
|
||||||
* network_mode:
|
* network_mode:
|
||||||
- public: An environment that uses a typical public network.
|
- 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)
|
- 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)
|
- offline: An environment that does not use any external connections when using an offline network. (Uses cache if available)
|
||||||
|
- personal_cloud: Applies relaxed security features in cloud environments such as Google Colab or Runpod, where strong security is not required.
|
||||||
|
|
||||||
|
|
||||||
## 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`.
|
||||||
|
|
||||||
* Fix node(recreate): When right-clicking on a node and selecting `Fix node (recreate)`, you can recreate the node. The widget's values are reset, while the connections maintain those with the same names.
|
* Fix node (recreate): When right-clicking on a node and selecting `Fix node (recreate)`, you can recreate the node. The widget's values are reset, while the connections maintain those with the same names.
|
||||||
* It is used to correct errors in nodes of old workflows created before, which are incompatible with the version changes of custom nodes.
|
* It is used to correct errors in nodes of old workflows created before, which are incompatible with the version changes of custom nodes.
|
||||||
|
|
||||||
* Double-Click Node Title: You can set the double click behavior of nodes in the ComfyUI-Manager menu.
|
* Double-Click Node Title: You can set the double-click behavior of nodes in the ComfyUI-Manager menu.
|
||||||
* `Copy All Connections`, `Copy Input Connections`: Double-clicking a node copies the connections of the nearest node.
|
* `Copy All Connections`, `Copy Input Connections`: Double-clicking a node copies the connections of the nearest node.
|
||||||
* This action targets the nearest node within a straight-line distance of 1000 pixels from the center of the node.
|
* This action targets the nearest node within a straight-line distance of 1000 pixels from the center of the node.
|
||||||
* In the case of `Copy All Connections`, it duplicates existing outputs, but since it does not allow duplicate connections, the existing output connections of the original node are disconnected.
|
* In the case of `Copy All Connections`, it duplicates existing outputs, but since it does not allow duplicate connections, the existing output connections of the original node are disconnected.
|
||||||
@@ -297,46 +298,48 @@ When you run the `scan.sh` script:
|
|||||||
|
|
||||||
* It updates the `github-stats.json`.
|
* It updates the `github-stats.json`.
|
||||||
* This uses the GitHub API, so set your token with `export GITHUB_TOKEN=your_token_here` to avoid quickly reaching the rate limit and malfunctioning.
|
* This uses the GitHub API, so set your token with `export GITHUB_TOKEN=your_token_here` to avoid quickly reaching the rate limit and malfunctioning.
|
||||||
* To skip this step, add the `--skip-update-stat` option.
|
* To skip this step, add the `--skip-stat-update` option.
|
||||||
|
|
||||||
* The `--skip-all` option applies both `--skip-update` and `--skip-stat-update`.
|
* The `--skip-all` option applies both `--skip-update` and `--skip-stat-update`.
|
||||||
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
* If your `git.exe` is installed in a specific location other than system git, please install ComfyUI-Manager and run ComfyUI. Then, specify the path including the file name in `git_exe = ` in the `<USER_DIRECTORY>/default/ComfyUI-Manager/config.ini` file that is generated.
|
* If your `git.exe` is installed in a specific location other than system git, please install ComfyUI-Manager and run ComfyUI. Then, specify the path including the file name in `git_exe = ` in the `<USER_DIRECTORY>/__manager/config.ini` file that is generated.
|
||||||
* If updating ComfyUI-Manager itself fails, please go to the **ComfyUI-Manager** directory and execute the command `git update-ref refs/remotes/origin/main a361cc1 && git fetch --all && git pull`.
|
* If updating ComfyUI-Manager itself fails, please go to the **ComfyUI-Manager** directory and execute the command `git update-ref refs/remotes/origin/main a361cc1 && git fetch --all && git pull`.
|
||||||
* If you encounter the error message `Overlapped Object has pending operation at deallocation on Comfyui Manager load` under Windows
|
* If you encounter the error message `Overlapped Object has pending operation at deallocation on ComfyUI Manager load` under Windows
|
||||||
* Edit `config.ini` file: add `windows_selector_event_loop_policy = True`
|
* Edit `config.ini` file: add `windows_selector_event_loop_policy = True`
|
||||||
* if `SSL: CERTIFICATE_VERIFY_FAILED` error is occured.
|
* If the `SSL: CERTIFICATE_VERIFY_FAILED` error occurs.
|
||||||
* 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>`
|
|
||||||
* `strong`
|
|
||||||
* doesn't allow `high` and `middle` level risky feature
|
|
||||||
* `normal`
|
|
||||||
* doesn't allow `high` level risky feature
|
|
||||||
* `middle` level risky feature is available
|
|
||||||
* `normal-`
|
|
||||||
* doesn't allow `high` level risky feature if `--listen` is specified and not starts with `127.`
|
|
||||||
* `middle` level risky feature is available
|
|
||||||
* `weak`
|
|
||||||
* all feature is available
|
|
||||||
|
|
||||||
* `high` level risky features
|
The security settings are applied based on whether the ComfyUI server's listener is non-local and whether the network mode is set to `personal_cloud`.
|
||||||
* `Install via git url`, `pip install`
|
|
||||||
* Installation of custom nodes registered not in the `default channel`.
|
|
||||||
* Fix custom nodes
|
|
||||||
|
|
||||||
* `middle` level risky features
|
* **non-local**: When the server is launched with `--listen` and is bound to a network range other than the local `127.` range, allowing remote IP access.
|
||||||
* Uninstall/Update
|
* **personal\_cloud**: When the `network_mode` is set to `personal_cloud`.
|
||||||
* Installation of custom nodes registered in the `default channel`.
|
|
||||||
* Restore/Remove Snapshot
|
|
||||||
* Restart
|
### Risky Level Table
|
||||||
|
|
||||||
|
| Risky Level | features |
|
||||||
|
|-------------|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| high+ | * `Install via git url`, `pip install`<BR>* Installation of nodepack registered not in the `default channel`. |
|
||||||
|
| high | * Fix nodepack |
|
||||||
|
| middle+ | * Uninstall/Update<BR>* Installation of nodepack registered in the `default channel`.<BR>* Restore/Remove Snapshot<BR>* Install model |
|
||||||
|
| middle | * Restart |
|
||||||
|
| low | * Update ComfyUI |
|
||||||
|
|
||||||
|
|
||||||
|
### Security Level Table
|
||||||
|
|
||||||
|
| Security Level | local | non-local (personal_cloud) | non-local (not personal_cloud) |
|
||||||
|
|----------------|--------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------|--------------------------------|
|
||||||
|
| strong | * Only `weak` level risky features are allowed | * Only `weak` level risky features are allowed | * Only `weak` level risky features are allowed |
|
||||||
|
| normal | * `high+` and `high` level risky features are not allowed<BR>* `middle+` and `middle` level risky features are available | * `high+` and `high` level risky features are not allowed<BR>* `middle+` and `middle` level risky features are available | * `high+`, `high` and `middle+` level risky features are not allowed<BR>* `middle` level risky features are available
|
||||||
|
| normal- | * All features are available | * `high+` and `high` level risky features are not allowed<BR>* `middle+` and `middle` level risky features are available | * `high+`, `high` and `middle+` level risky features are not allowed<BR>* `middle` level risky features are available
|
||||||
|
| weak | * All features are available | * All features are available | * `high+` and `middle+` level risky features are not allowed<BR>* `high`, `middle` and `low` level risky features are available
|
||||||
|
|
||||||
* `low` level risky features
|
|
||||||
* Update ComfyUI
|
|
||||||
|
|
||||||
|
|
||||||
# Disclaimer
|
# Disclaimer
|
||||||
|
|||||||
4
check.sh
4
check.sh
@@ -37,7 +37,7 @@ find ~/.tmp/default -name "*.py" -print0 | xargs -0 grep -E "crypto|^_A="
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
echo CHECK3
|
echo CHECK3
|
||||||
find ~/.tmp/default -name "requirements.txt" | xargs grep "^\s*https\\?:"
|
find ~/.tmp/default -name "requirements.txt" | xargs grep "^\s*[^#]*https\?:"
|
||||||
find ~/.tmp/default -name "requirements.txt" | xargs grep "\.whl"
|
find ~/.tmp/default -name "requirements.txt" | xargs grep "^\s*[^#].*\.whl"
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
from aiohttp import web
|
||||||
|
from .common.manager_security import HANDLER_POLICY
|
||||||
|
from .common import manager_security
|
||||||
|
from comfy.cli_args import args
|
||||||
|
|
||||||
|
|
||||||
def prestartup():
|
def prestartup():
|
||||||
from . import prestartup_script # noqa: F401
|
from . import prestartup_script # noqa: F401
|
||||||
@@ -7,25 +12,29 @@ def prestartup():
|
|||||||
|
|
||||||
|
|
||||||
def start():
|
def start():
|
||||||
from comfy.cli_args import args
|
|
||||||
|
|
||||||
logging.info('[START] ComfyUI-Manager')
|
logging.info('[START] ComfyUI-Manager')
|
||||||
from .common import cm_global # noqa: F401
|
from .common import cm_global # noqa: F401
|
||||||
|
|
||||||
if not args.disable_manager:
|
if args.enable_manager:
|
||||||
if args.enable_manager_legacy_ui:
|
if args.enable_manager_legacy_ui:
|
||||||
try:
|
try:
|
||||||
from .legacy import manager_server # noqa: F401
|
from .legacy import manager_server # noqa: F401
|
||||||
from .legacy import share_3rdparty # noqa: F401
|
from .legacy import share_3rdparty # noqa: F401
|
||||||
|
from .legacy import manager_core as core
|
||||||
import nodes
|
import nodes
|
||||||
|
|
||||||
logging.info("[ComfyUI-Manager] Legacy UI is enabled.")
|
logging.info("[ComfyUI-Manager] Legacy UI is enabled.")
|
||||||
nodes.EXTENSION_WEB_DIRS['comfyui-manager-legacy'] = os.path.join(os.path.dirname(__file__), 'js')
|
nodes.EXTENSION_WEB_DIRS['comfyui-manager-legacy'] = os.path.join(os.path.dirname(__file__), 'js')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Error enabling legacy ComfyUI Manager frontend:", e)
|
print("Error enabling legacy ComfyUI Manager frontend:", e)
|
||||||
|
core = None
|
||||||
else:
|
else:
|
||||||
from .glob import manager_server # noqa: F401
|
from .glob import manager_server # noqa: F401
|
||||||
from .glob import share_3rdparty # noqa: F401
|
from .glob import share_3rdparty # noqa: F401
|
||||||
|
from .glob import manager_core as core
|
||||||
|
|
||||||
|
if core is not None:
|
||||||
|
manager_security.is_personal_cloud_mode = core.get_config()['network_mode'].lower() == 'personal_cloud'
|
||||||
|
|
||||||
|
|
||||||
def should_be_disabled(fullpath:str) -> bool:
|
def should_be_disabled(fullpath:str) -> bool:
|
||||||
@@ -33,9 +42,7 @@ def should_be_disabled(fullpath:str) -> bool:
|
|||||||
1. Disables the legacy ComfyUI-Manager.
|
1. Disables the legacy ComfyUI-Manager.
|
||||||
2. The blocklist can be expanded later based on policies.
|
2. The blocklist can be expanded later based on policies.
|
||||||
"""
|
"""
|
||||||
from comfy.cli_args import args
|
if args.enable_manager:
|
||||||
|
|
||||||
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.
|
# 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.
|
# 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()
|
dir_name = os.path.basename(fullpath).lower()
|
||||||
@@ -43,3 +50,55 @@ def should_be_disabled(fullpath:str) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def get_client_ip(request):
|
||||||
|
peername = request.transport.get_extra_info("peername")
|
||||||
|
if peername is not None:
|
||||||
|
host, port = peername
|
||||||
|
return host
|
||||||
|
|
||||||
|
return "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
def create_middleware():
|
||||||
|
connected_clients = set()
|
||||||
|
is_local_mode = manager_security.is_loopback(args.listen)
|
||||||
|
|
||||||
|
@web.middleware
|
||||||
|
async def manager_middleware(request: web.Request, handler):
|
||||||
|
nonlocal connected_clients
|
||||||
|
|
||||||
|
# security policy for remote environments
|
||||||
|
prev_client_count = len(connected_clients)
|
||||||
|
client_ip = get_client_ip(request)
|
||||||
|
connected_clients.add(client_ip)
|
||||||
|
next_client_count = len(connected_clients)
|
||||||
|
|
||||||
|
if prev_client_count == 1 and next_client_count > 1:
|
||||||
|
manager_security.multiple_remote_alert()
|
||||||
|
|
||||||
|
policy = manager_security.get_handler_policy(handler)
|
||||||
|
is_banned = False
|
||||||
|
|
||||||
|
# policy check
|
||||||
|
if len(connected_clients) > 1:
|
||||||
|
if is_local_mode:
|
||||||
|
if HANDLER_POLICY.MULTIPLE_REMOTE_BAN_NON_LOCAL in policy:
|
||||||
|
is_banned = True
|
||||||
|
if HANDLER_POLICY.MULTIPLE_REMOTE_BAN_NOT_PERSONAL_CLOUD in policy:
|
||||||
|
is_banned = not manager_security.is_personal_cloud_mode
|
||||||
|
|
||||||
|
if HANDLER_POLICY.BANNED in policy:
|
||||||
|
is_banned = True
|
||||||
|
|
||||||
|
if is_banned:
|
||||||
|
logging.warning(f"[Manager] Banning request from {client_ip}: {request.path}")
|
||||||
|
response = web.Response(text="[Manager] This request is banned.", status=403)
|
||||||
|
else:
|
||||||
|
response: web.Response = await handler(request)
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
return manager_middleware
|
||||||
|
|
||||||
@@ -46,10 +46,7 @@ comfyui_manager_path = os.path.abspath(os.path.dirname(__file__))
|
|||||||
cm_global.pip_blacklist = {'torch', 'torchaudio', 'torchsde', 'torchvision'}
|
cm_global.pip_blacklist = {'torch', 'torchaudio', 'torchsde', 'torchvision'}
|
||||||
cm_global.pip_downgrade_blacklist = ['torch', 'torchaudio', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
|
cm_global.pip_downgrade_blacklist = ['torch', 'torchaudio', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
|
||||||
|
|
||||||
if sys.version_info < (3, 13):
|
cm_global.pip_overrides = {}
|
||||||
cm_global.pip_overrides = {'numpy': 'numpy<2'}
|
|
||||||
else:
|
|
||||||
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:
|
||||||
@@ -152,9 +149,6 @@ class Ctx:
|
|||||||
with open(context.manager_pip_overrides_path, 'r', encoding="UTF-8", errors="ignore") as json_file:
|
with open(context.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):
|
if os.path.exists(context.manager_pip_blacklist_path):
|
||||||
with open(context.manager_pip_blacklist_path, 'r', encoding="UTF-8", errors="ignore") as f:
|
with open(context.manager_pip_blacklist_path, 'r', encoding="UTF-8", errors="ignore") as f:
|
||||||
for x in f.readlines():
|
for x in f.readlines():
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from . import manager_util
|
|||||||
|
|
||||||
import requests
|
import requests
|
||||||
import toml
|
import toml
|
||||||
|
import logging
|
||||||
|
|
||||||
base_url = "https://api.comfy.org"
|
base_url = "https://api.comfy.org"
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ async def get_cnr_data(cache_mode=True, dont_wait=True):
|
|||||||
try:
|
try:
|
||||||
return await _get_cnr_data(cache_mode, dont_wait)
|
return await _get_cnr_data(cache_mode, dont_wait)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
print("A timeout occurred during the fetch process from ComfyRegistry.")
|
logging.info("A timeout occurred during the fetch process from ComfyRegistry.")
|
||||||
return await _get_cnr_data(cache_mode=True, dont_wait=True) # timeout fallback
|
return await _get_cnr_data(cache_mode=True, dont_wait=True) # timeout fallback
|
||||||
|
|
||||||
async def _get_cnr_data(cache_mode=True, dont_wait=True):
|
async def _get_cnr_data(cache_mode=True, dont_wait=True):
|
||||||
@@ -79,12 +80,12 @@ async def _get_cnr_data(cache_mode=True, dont_wait=True):
|
|||||||
full_nodes[x['id']] = x
|
full_nodes[x['id']] = x
|
||||||
|
|
||||||
if page % 5 == 0:
|
if page % 5 == 0:
|
||||||
print(f"FETCH ComfyRegistry Data: {page}/{sub_json_obj['totalPages']}")
|
logging.info(f"FETCH ComfyRegistry Data: {page}/{sub_json_obj['totalPages']}")
|
||||||
|
|
||||||
page += 1
|
page += 1
|
||||||
time.sleep(0.5)
|
time.sleep(0.5)
|
||||||
|
|
||||||
print("FETCH ComfyRegistry Data [DONE]")
|
logging.info("FETCH ComfyRegistry Data [DONE]")
|
||||||
|
|
||||||
for v in full_nodes.values():
|
for v in full_nodes.values():
|
||||||
if 'latest_version' not in v:
|
if 'latest_version' not in v:
|
||||||
@@ -100,7 +101,7 @@ async def _get_cnr_data(cache_mode=True, dont_wait=True):
|
|||||||
if cache_state == 'not-cached':
|
if cache_state == 'not-cached':
|
||||||
return {}
|
return {}
|
||||||
else:
|
else:
|
||||||
print("[ComfyUI-Manager] The ComfyRegistry cache update is still in progress, so an outdated cache is being used.")
|
logging.info("[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:
|
with open(manager_util.get_cache_path(uri), 'r', encoding="UTF-8", errors="ignore") as json_file:
|
||||||
return json.load(json_file)['nodes']
|
return json.load(json_file)['nodes']
|
||||||
|
|
||||||
@@ -114,7 +115,7 @@ async def _get_cnr_data(cache_mode=True, dont_wait=True):
|
|||||||
return json_obj['nodes']
|
return json_obj['nodes']
|
||||||
except Exception:
|
except Exception:
|
||||||
res = {}
|
res = {}
|
||||||
print("Cannot connect to comfyregistry.")
|
logging.warning("Cannot connect to comfyregistry.")
|
||||||
finally:
|
finally:
|
||||||
if cache_mode:
|
if cache_mode:
|
||||||
is_cache_loading = False
|
is_cache_loading = False
|
||||||
@@ -180,7 +181,7 @@ def install_node(node_id, version=None):
|
|||||||
else:
|
else:
|
||||||
url = f"{base_url}/nodes/{node_id}/install?version={version}"
|
url = f"{base_url}/nodes/{node_id}/install?version={version}"
|
||||||
|
|
||||||
response = requests.get(url)
|
response = requests.get(url, verify=not manager_util.bypass_ssl)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
# Convert the API response to a NodeVersion object
|
# Convert the API response to a NodeVersion object
|
||||||
return map_node_version(response.json())
|
return map_node_version(response.json())
|
||||||
@@ -191,7 +192,7 @@ def install_node(node_id, version=None):
|
|||||||
def all_versions_of_node(node_id):
|
def all_versions_of_node(node_id):
|
||||||
url = f"{base_url}/nodes/{node_id}/versions?statuses=NodeVersionStatusActive&statuses=NodeVersionStatusPending"
|
url = f"{base_url}/nodes/{node_id}/versions?statuses=NodeVersionStatusActive&statuses=NodeVersionStatusPending"
|
||||||
|
|
||||||
response = requests.get(url)
|
response = requests.get(url, verify=not manager_util.bypass_ssl)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
return response.json()
|
return response.json()
|
||||||
else:
|
else:
|
||||||
@@ -211,6 +212,7 @@ def read_cnr_info(fullpath):
|
|||||||
|
|
||||||
project = data.get('project', {})
|
project = data.get('project', {})
|
||||||
name = project.get('name').strip().lower()
|
name = project.get('name').strip().lower()
|
||||||
|
original_name = project.get('name')
|
||||||
|
|
||||||
# normalize version
|
# normalize version
|
||||||
# for example: 2.5 -> 2.5.0
|
# for example: 2.5 -> 2.5.0
|
||||||
@@ -222,6 +224,7 @@ def read_cnr_info(fullpath):
|
|||||||
if name and version: # repository is optional
|
if name and version: # repository is optional
|
||||||
return {
|
return {
|
||||||
"id": name,
|
"id": name,
|
||||||
|
"original_name": original_name,
|
||||||
"version": version,
|
"version": version,
|
||||||
"url": repository
|
"url": repository
|
||||||
}
|
}
|
||||||
@@ -238,7 +241,7 @@ def generate_cnr_id(fullpath, cnr_id):
|
|||||||
with open(cnr_id_path, "w") as f:
|
with open(cnr_id_path, "w") as f:
|
||||||
return f.write(cnr_id)
|
return f.write(cnr_id)
|
||||||
except Exception:
|
except Exception:
|
||||||
print(f"[ComfyUI Manager] unable to create file: {cnr_id_path}")
|
logging.error(f"[ComfyUI Manager] unable to create file: {cnr_id_path}")
|
||||||
|
|
||||||
|
|
||||||
def read_cnr_id(fullpath):
|
def read_cnr_id(fullpath):
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ manager_pip_blacklist_path = None
|
|||||||
manager_components_path = None
|
manager_components_path = None
|
||||||
manager_batch_history_path = None
|
manager_batch_history_path = None
|
||||||
|
|
||||||
def update_user_directory(user_dir):
|
def update_user_directory(manager_dir):
|
||||||
global manager_files_path
|
global manager_files_path
|
||||||
global manager_config_path
|
global manager_config_path
|
||||||
global manager_channel_list_path
|
global manager_channel_list_path
|
||||||
@@ -45,7 +45,7 @@ def update_user_directory(user_dir):
|
|||||||
global manager_components_path
|
global manager_components_path
|
||||||
global manager_batch_history_path
|
global manager_batch_history_path
|
||||||
|
|
||||||
manager_files_path = os.path.abspath(os.path.join(user_dir, 'default', 'ComfyUI-Manager'))
|
manager_files_path = manager_dir
|
||||||
if not os.path.exists(manager_files_path):
|
if not os.path.exists(manager_files_path):
|
||||||
os.makedirs(manager_files_path)
|
os.makedirs(manager_files_path)
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ def update_user_directory(user_dir):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import folder_paths
|
import folder_paths
|
||||||
update_user_directory(folder_paths.get_user_directory())
|
update_user_directory(folder_paths.get_system_user_directory("manager"))
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
# fallback:
|
# fallback:
|
||||||
@@ -106,4 +106,3 @@ def get_comfyui_tag():
|
|||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ class NetworkMode(enum.Enum):
|
|||||||
PUBLIC = "public"
|
PUBLIC = "public"
|
||||||
PRIVATE = "private"
|
PRIVATE = "private"
|
||||||
OFFLINE = "offline"
|
OFFLINE = "offline"
|
||||||
|
PERSONAL_CLOUD = "personal_cloud"
|
||||||
|
|
||||||
class SecurityLevel(enum.Enum):
|
class SecurityLevel(enum.Enum):
|
||||||
STRONG = "strong"
|
STRONG = "strong"
|
||||||
|
|||||||
@@ -55,7 +55,11 @@ def download_url(model_url: str, model_dir: str, filename: str):
|
|||||||
return aria2_download_url(model_url, model_dir, filename)
|
return aria2_download_url(model_url, model_dir, filename)
|
||||||
else:
|
else:
|
||||||
from torchvision.datasets.utils import download_url as torchvision_download_url
|
from torchvision.datasets.utils import download_url as torchvision_download_url
|
||||||
|
try:
|
||||||
return torchvision_download_url(model_url, model_dir, filename)
|
return torchvision_download_url(model_url, model_dir, filename)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"[ComfyUI-Manager] Failed to download: {model_url} / {repr(e)}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
def aria2_find_task(dir: str, filename: str):
|
def aria2_find_task(dir: str, filename: str):
|
||||||
|
|||||||
36
comfyui_manager/common/manager_security.py
Normal file
36
comfyui_manager/common/manager_security.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
is_personal_cloud_mode = False
|
||||||
|
handler_policy = {}
|
||||||
|
|
||||||
|
class HANDLER_POLICY(Enum):
|
||||||
|
MULTIPLE_REMOTE_BAN_NON_LOCAL = 1
|
||||||
|
MULTIPLE_REMOTE_BAN_NOT_PERSONAL_CLOUD = 2
|
||||||
|
BANNED = 3
|
||||||
|
|
||||||
|
|
||||||
|
def is_loopback(address):
|
||||||
|
import ipaddress
|
||||||
|
try:
|
||||||
|
return ipaddress.ip_address(address).is_loopback
|
||||||
|
except ValueError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def do_nothing():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def get_handler_policy(x):
|
||||||
|
return handler_policy.get(x) or set()
|
||||||
|
|
||||||
|
def add_handler_policy(x, policy):
|
||||||
|
s = handler_policy.get(x)
|
||||||
|
if s is None:
|
||||||
|
s = set()
|
||||||
|
handler_policy[x] = s
|
||||||
|
|
||||||
|
s.add(policy)
|
||||||
|
|
||||||
|
|
||||||
|
multiple_remote_alert = do_nothing
|
||||||
@@ -15,7 +15,7 @@ import re
|
|||||||
import logging
|
import logging
|
||||||
import platform
|
import platform
|
||||||
import shlex
|
import shlex
|
||||||
from . import cm_global
|
from functools import lru_cache
|
||||||
|
|
||||||
|
|
||||||
cache_lock = threading.Lock()
|
cache_lock = threading.Lock()
|
||||||
@@ -25,6 +25,7 @@ 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**.
|
cache_dir = os.path.join(comfyui_manager_path, '.cache') # This path is also updated together in **manager_core.update_user_directory**.
|
||||||
|
|
||||||
use_uv = False
|
use_uv = False
|
||||||
|
bypass_ssl = False
|
||||||
|
|
||||||
def is_manager_pip_package():
|
def is_manager_pip_package():
|
||||||
return not os.path.exists(os.path.join(comfyui_manager_path, '..', 'custom_nodes'))
|
return not os.path.exists(os.path.join(comfyui_manager_path, '..', 'custom_nodes'))
|
||||||
@@ -38,18 +39,64 @@ def add_python_path_to_env():
|
|||||||
os.environ['PATH'] = os.path.dirname(sys.executable)+sep+os.environ['PATH']
|
os.environ['PATH'] = os.path.dirname(sys.executable)+sep+os.environ['PATH']
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=2)
|
||||||
|
def get_pip_cmd(force_uv=False):
|
||||||
|
"""
|
||||||
|
Get the base pip command, with automatic fallback to uv if pip is unavailable.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
force_uv (bool): If True, use uv directly without trying pip
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: Base command for pip operations
|
||||||
|
"""
|
||||||
|
embedded = 'python_embeded' in sys.executable
|
||||||
|
|
||||||
|
# Try pip first (unless forcing uv)
|
||||||
|
if not force_uv:
|
||||||
|
try:
|
||||||
|
test_cmd = [sys.executable] + (['-s'] if embedded else []) + ['-m', 'pip', '--version']
|
||||||
|
subprocess.check_output(test_cmd, stderr=subprocess.DEVNULL, timeout=5)
|
||||||
|
return [sys.executable] + (['-s'] if embedded else []) + ['-m', 'pip']
|
||||||
|
except Exception:
|
||||||
|
logging.warning("[ComfyUI-Manager] python -m pip not available. Falling back to uv.")
|
||||||
|
|
||||||
|
# Try uv (either forced or pip failed)
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
# Try uv as Python module
|
||||||
|
try:
|
||||||
|
test_cmd = [sys.executable] + (['-s'] if embedded else []) + ['-m', 'uv', '--version']
|
||||||
|
subprocess.check_output(test_cmd, stderr=subprocess.DEVNULL, timeout=5)
|
||||||
|
logging.info("[ComfyUI-Manager] Using uv as Python module for pip operations.")
|
||||||
|
return [sys.executable] + (['-s'] if embedded else []) + ['-m', 'uv', 'pip']
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Try standalone uv
|
||||||
|
if shutil.which('uv'):
|
||||||
|
logging.info("[ComfyUI-Manager] Using standalone uv for pip operations.")
|
||||||
|
return ['uv', 'pip']
|
||||||
|
|
||||||
|
# Nothing worked
|
||||||
|
logging.error("[ComfyUI-Manager] Neither python -m pip nor uv are available. Cannot proceed with package operations.")
|
||||||
|
raise Exception("Neither pip nor uv are available for package management")
|
||||||
|
|
||||||
|
|
||||||
def make_pip_cmd(cmd):
|
def make_pip_cmd(cmd):
|
||||||
if 'python_embeded' in sys.executable:
|
"""
|
||||||
if use_uv:
|
Create a pip command by combining the cached base pip command with the given arguments.
|
||||||
return [sys.executable, '-s', '-m', 'uv', 'pip'] + cmd
|
|
||||||
else:
|
Args:
|
||||||
return [sys.executable, '-s', '-m', 'pip'] + cmd
|
cmd (list): List of pip command arguments (e.g., ['install', 'package'])
|
||||||
else:
|
|
||||||
# FIXED: https://github.com/ltdrdata/ComfyUI-Manager/issues/1667
|
Returns:
|
||||||
if use_uv:
|
list: Complete command list ready for subprocess execution
|
||||||
return [sys.executable, '-m', 'uv', 'pip'] + cmd
|
"""
|
||||||
else:
|
global use_uv
|
||||||
return [sys.executable, '-m', 'pip'] + cmd
|
base_cmd = get_pip_cmd(force_uv=use_uv)
|
||||||
|
return base_cmd + cmd
|
||||||
|
|
||||||
|
|
||||||
# DON'T USE StrictVersion - cannot handle pre_release version
|
# DON'T USE StrictVersion - cannot handle pre_release version
|
||||||
# try:
|
# try:
|
||||||
@@ -140,7 +187,7 @@ async def get_data(uri, silent=False):
|
|||||||
print(f"FETCH DATA from: {uri}", end="")
|
print(f"FETCH DATA from: {uri}", end="")
|
||||||
|
|
||||||
if uri.startswith("http"):
|
if uri.startswith("http"):
|
||||||
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=False)) as session:
|
async with aiohttp.ClientSession(trust_env=True, connector=aiohttp.TCPConnector(verify_ssl=not bypass_ssl)) as session:
|
||||||
headers = {
|
headers = {
|
||||||
'Cache-Control': 'no-cache',
|
'Cache-Control': 'no-cache',
|
||||||
'Pragma': 'no-cache',
|
'Pragma': 'no-cache',
|
||||||
@@ -330,16 +377,9 @@ torch_torchvision_torchaudio_version_map = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def torch_rollback(prev):
|
||||||
class PIPFixer:
|
spec = prev.split('+')
|
||||||
def __init__(self, prev_pip_versions, comfyui_path, manager_files_path):
|
if len(spec) > 1:
|
||||||
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]
|
platform = spec[1]
|
||||||
else:
|
else:
|
||||||
cmd = make_pip_cmd(['install', '--force', 'torch', 'torchvision', 'torchaudio'])
|
cmd = make_pip_cmd(['install', '--force', 'torch', 'torchvision', 'torchaudio'])
|
||||||
@@ -363,6 +403,13 @@ class PIPFixer:
|
|||||||
|
|
||||||
subprocess.check_output(cmd, universal_newlines=True)
|
subprocess.check_output(cmd, universal_newlines=True)
|
||||||
|
|
||||||
|
|
||||||
|
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 fix_broken(self):
|
def fix_broken(self):
|
||||||
new_pip_versions = get_installed_packages(True)
|
new_pip_versions = get_installed_packages(True)
|
||||||
|
|
||||||
@@ -384,7 +431,7 @@ class PIPFixer:
|
|||||||
elif self.prev_pip_versions['torch'] != new_pip_versions['torch'] \
|
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['torchvision'] != new_pip_versions['torchvision'] \
|
||||||
or self.prev_pip_versions['torchaudio'] != new_pip_versions['torchaudio']:
|
or self.prev_pip_versions['torchaudio'] != new_pip_versions['torchaudio']:
|
||||||
self.torch_rollback()
|
torch_rollback(self.prev_pip_versions['torch'])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error("[ComfyUI-Manager] Failed to restore PyTorch")
|
logging.error("[ComfyUI-Manager] Failed to restore PyTorch")
|
||||||
logging.error(e)
|
logging.error(e)
|
||||||
@@ -415,8 +462,7 @@ class PIPFixer:
|
|||||||
|
|
||||||
if len(targets) > 0:
|
if len(targets) > 0:
|
||||||
for x in targets:
|
for x in targets:
|
||||||
if sys.version_info < (3, 13):
|
cmd = make_pip_cmd(['install', f"{x}=={versions[0].version_string}"])
|
||||||
cmd = make_pip_cmd(['install', f"{x}=={versions[0].version_string}", "numpy<2"])
|
|
||||||
subprocess.check_output(cmd, universal_newlines=True)
|
subprocess.check_output(cmd, universal_newlines=True)
|
||||||
|
|
||||||
logging.info(f"[ComfyUI-Manager] 'opencv' dependencies were fixed: {targets}")
|
logging.info(f"[ComfyUI-Manager] 'opencv' dependencies were fixed: {targets}")
|
||||||
@@ -424,23 +470,6 @@ class PIPFixer:
|
|||||||
logging.error("[ComfyUI-Manager] Failed to restore opencv")
|
logging.error("[ComfyUI-Manager] Failed to restore opencv")
|
||||||
logging.error(e)
|
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
|
# fix missing frontend
|
||||||
try:
|
try:
|
||||||
# NOTE: package name in requirements is 'comfyui-frontend-package'
|
# NOTE: package name in requirements is 'comfyui-frontend-package'
|
||||||
@@ -540,3 +569,69 @@ def robust_readlines(fullpath):
|
|||||||
|
|
||||||
print(f"[ComfyUI-Manager] Failed to recognize encoding for: {fullpath}")
|
print(f"[ComfyUI-Manager] Failed to recognize encoding for: {fullpath}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def restore_pip_snapshot(pips, options):
|
||||||
|
non_url = []
|
||||||
|
local_url = []
|
||||||
|
non_local_url = []
|
||||||
|
|
||||||
|
for k, v in pips.items():
|
||||||
|
# NOTE: skip torch related packages
|
||||||
|
if k.startswith("torch==") or k.startswith("torchvision==") or k.startswith("torchaudio==") or k.startswith("nvidia-"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if v == "":
|
||||||
|
non_url.append(k)
|
||||||
|
else:
|
||||||
|
if v.startswith('file:'):
|
||||||
|
local_url.append(v)
|
||||||
|
else:
|
||||||
|
non_local_url.append(v)
|
||||||
|
|
||||||
|
|
||||||
|
# restore other pips
|
||||||
|
failed = []
|
||||||
|
if '--pip-non-url' in options:
|
||||||
|
# try all at once
|
||||||
|
res = 1
|
||||||
|
try:
|
||||||
|
res = subprocess.check_output(make_pip_cmd(['install'] + non_url))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# fallback
|
||||||
|
if res != 0:
|
||||||
|
for x in non_url:
|
||||||
|
res = 1
|
||||||
|
try:
|
||||||
|
res = subprocess.check_output(make_pip_cmd(['install', '--no-deps', x]))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if res != 0:
|
||||||
|
failed.append(x)
|
||||||
|
|
||||||
|
if '--pip-non-local-url' in options:
|
||||||
|
for x in non_local_url:
|
||||||
|
res = 1
|
||||||
|
try:
|
||||||
|
res = subprocess.check_output(make_pip_cmd(['install', '--no-deps', x]))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if res != 0:
|
||||||
|
failed.append(x)
|
||||||
|
|
||||||
|
if '--pip-local-url' in options:
|
||||||
|
for x in local_url:
|
||||||
|
res = 1
|
||||||
|
try:
|
||||||
|
res = subprocess.check_output(make_pip_cmd(['install', '--no-deps', x]))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if res != 0:
|
||||||
|
failed.append(x)
|
||||||
|
|
||||||
|
print(f"Installation failed for pip packages: {failed}")
|
||||||
@@ -2,6 +2,8 @@ import sys
|
|||||||
import subprocess
|
import subprocess
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from . import manager_util
|
||||||
|
|
||||||
|
|
||||||
def security_check():
|
def security_check():
|
||||||
print("[START] Security scan")
|
print("[START] Security scan")
|
||||||
@@ -66,18 +68,23 @@ https://blog.comfy.org/comfyui-statement-on-the-ultralytics-crypto-miner-situati
|
|||||||
"lolMiner": [os.path.join(comfyui_path, 'lolMiner')]
|
"lolMiner": [os.path.join(comfyui_path, 'lolMiner')]
|
||||||
}
|
}
|
||||||
|
|
||||||
installed_pips = subprocess.check_output([sys.executable, '-m', "pip", "freeze"], text=True)
|
installed_pips = subprocess.check_output(manager_util.make_pip_cmd(["freeze"]), text=True)
|
||||||
|
|
||||||
detected = set()
|
detected = set()
|
||||||
try:
|
try:
|
||||||
anthropic_info = subprocess.check_output([sys.executable, '-m', "pip", "show", "anthropic"], text=True, stderr=subprocess.DEVNULL)
|
anthropic_info = subprocess.check_output(manager_util.make_pip_cmd(["show", "anthropic"]), text=True, stderr=subprocess.DEVNULL)
|
||||||
anthropic_reqs = [x for x in anthropic_info.split('\n') if x.startswith("Requires")][0].split(': ')[1]
|
requires_lines = [x for x in anthropic_info.split('\n') if x.startswith("Requires")]
|
||||||
|
if requires_lines:
|
||||||
|
anthropic_reqs = requires_lines[0].split(": ", 1)[1]
|
||||||
if "pycrypto" in anthropic_reqs:
|
if "pycrypto" in anthropic_reqs:
|
||||||
location = [x for x in anthropic_info.split('\n') if x.startswith("Location")][0].split(': ')[1]
|
location_lines = [x for x in anthropic_info.split('\n') if x.startswith("Location")]
|
||||||
|
if location_lines:
|
||||||
|
location = location_lines[0].split(": ", 1)[1]
|
||||||
for fi in os.listdir(location):
|
for fi in os.listdir(location):
|
||||||
if fi.startswith("anthropic"):
|
if fi.startswith("anthropic"):
|
||||||
guide["ComfyUI_LLMVISION"] = f"\n0.Remove {os.path.join(location, fi)}" + guide["ComfyUI_LLMVISION"]
|
guide["ComfyUI_LLMVISION"] = (f"\n0.Remove {os.path.join(location, fi)}" + guide["ComfyUI_LLMVISION"])
|
||||||
detected.add("ComfyUI_LLMVISION")
|
detected.add("ComfyUI_LLMVISION")
|
||||||
|
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
11287
custom-node-list.json → comfyui_manager/custom-node-list.json
Executable file → Normal file
11287
custom-node-list.json → comfyui_manager/custom-node-list.json
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
@@ -29,6 +29,7 @@ datamodel-codegen \
|
|||||||
--use-subclass-enum \
|
--use-subclass-enum \
|
||||||
--field-constraints \
|
--field-constraints \
|
||||||
--strict-types bytes \
|
--strict-types bytes \
|
||||||
|
--use-double-quotes \
|
||||||
--input openapi.yaml \
|
--input openapi.yaml \
|
||||||
--output comfyui_manager/data_models/generated_models.py \
|
--output comfyui_manager/data_models/generated_models.py \
|
||||||
--output-model-type pydantic_v2.BaseModel
|
--output-model-type pydantic_v2.BaseModel
|
||||||
|
|||||||
@@ -30,9 +30,15 @@ from .generated_models import (
|
|||||||
InstalledModelInfo,
|
InstalledModelInfo,
|
||||||
ComfyUIVersionInfo,
|
ComfyUIVersionInfo,
|
||||||
|
|
||||||
|
# Import Fail Info Models
|
||||||
|
ImportFailInfoBulkRequest,
|
||||||
|
ImportFailInfoBulkResponse,
|
||||||
|
ImportFailInfoItem,
|
||||||
|
ImportFailInfoItem1,
|
||||||
|
|
||||||
# Other models
|
# Other models
|
||||||
Kind,
|
OperationType,
|
||||||
StatusStr,
|
OperationResult,
|
||||||
ManagerPackInfo,
|
ManagerPackInfo,
|
||||||
ManagerPackInstalled,
|
ManagerPackInstalled,
|
||||||
SelectedVersion,
|
SelectedVersion,
|
||||||
@@ -49,6 +55,9 @@ from .generated_models import (
|
|||||||
UninstallPackParams,
|
UninstallPackParams,
|
||||||
DisablePackParams,
|
DisablePackParams,
|
||||||
EnablePackParams,
|
EnablePackParams,
|
||||||
|
UpdateAllQueryParams,
|
||||||
|
UpdateComfyUIQueryParams,
|
||||||
|
ComfyUISwitchVersionQueryParams,
|
||||||
QueueStatus,
|
QueueStatus,
|
||||||
ManagerMappings,
|
ManagerMappings,
|
||||||
ModelMetadata,
|
ModelMetadata,
|
||||||
@@ -59,8 +68,8 @@ from .generated_models import (
|
|||||||
HistoryResponse,
|
HistoryResponse,
|
||||||
HistoryListResponse,
|
HistoryListResponse,
|
||||||
InstallType,
|
InstallType,
|
||||||
OperationType,
|
SecurityLevel,
|
||||||
Result,
|
RiskLevel,
|
||||||
)
|
)
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@@ -85,9 +94,15 @@ __all__ = [
|
|||||||
"InstalledModelInfo",
|
"InstalledModelInfo",
|
||||||
"ComfyUIVersionInfo",
|
"ComfyUIVersionInfo",
|
||||||
|
|
||||||
|
# Import Fail Info Models
|
||||||
|
"ImportFailInfoBulkRequest",
|
||||||
|
"ImportFailInfoBulkResponse",
|
||||||
|
"ImportFailInfoItem",
|
||||||
|
"ImportFailInfoItem1",
|
||||||
|
|
||||||
# Other models
|
# Other models
|
||||||
"Kind",
|
"OperationType",
|
||||||
"StatusStr",
|
"OperationResult",
|
||||||
"ManagerPackInfo",
|
"ManagerPackInfo",
|
||||||
"ManagerPackInstalled",
|
"ManagerPackInstalled",
|
||||||
"SelectedVersion",
|
"SelectedVersion",
|
||||||
@@ -104,6 +119,9 @@ __all__ = [
|
|||||||
"UninstallPackParams",
|
"UninstallPackParams",
|
||||||
"DisablePackParams",
|
"DisablePackParams",
|
||||||
"EnablePackParams",
|
"EnablePackParams",
|
||||||
|
"UpdateAllQueryParams",
|
||||||
|
"UpdateComfyUIQueryParams",
|
||||||
|
"ComfyUISwitchVersionQueryParams",
|
||||||
"QueueStatus",
|
"QueueStatus",
|
||||||
"ManagerMappings",
|
"ManagerMappings",
|
||||||
"ModelMetadata",
|
"ModelMetadata",
|
||||||
@@ -114,6 +132,6 @@ __all__ = [
|
|||||||
"HistoryResponse",
|
"HistoryResponse",
|
||||||
"HistoryListResponse",
|
"HistoryListResponse",
|
||||||
"InstallType",
|
"InstallType",
|
||||||
"OperationType",
|
"SecurityLevel",
|
||||||
"Result",
|
"RiskLevel",
|
||||||
]
|
]
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# generated by datamodel-codegen:
|
# generated by datamodel-codegen:
|
||||||
# filename: openapi.yaml
|
# filename: openapi.yaml
|
||||||
# timestamp: 2025-06-14T01:44:21+00:00
|
# timestamp: 2025-07-31T04:52:26+00:00
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -11,252 +11,298 @@ from typing import Any, Dict, List, Optional, Union
|
|||||||
from pydantic import BaseModel, Field, RootModel
|
from pydantic import BaseModel, Field, RootModel
|
||||||
|
|
||||||
|
|
||||||
class Kind(str, Enum):
|
class OperationType(str, Enum):
|
||||||
install = 'install'
|
install = "install"
|
||||||
uninstall = 'uninstall'
|
uninstall = "uninstall"
|
||||||
update = 'update'
|
update = "update"
|
||||||
update_all = 'update-all'
|
update_comfyui = "update-comfyui"
|
||||||
update_comfyui = 'update-comfyui'
|
fix = "fix"
|
||||||
fix = 'fix'
|
disable = "disable"
|
||||||
disable = 'disable'
|
enable = "enable"
|
||||||
enable = 'enable'
|
install_model = "install-model"
|
||||||
install_model = 'install-model'
|
|
||||||
|
|
||||||
|
|
||||||
class StatusStr(str, Enum):
|
class OperationResult(str, Enum):
|
||||||
success = 'success'
|
success = "success"
|
||||||
error = 'error'
|
failed = "failed"
|
||||||
skip = 'skip'
|
skipped = "skipped"
|
||||||
|
error = "error"
|
||||||
|
skip = "skip"
|
||||||
|
|
||||||
|
|
||||||
class TaskExecutionStatus(BaseModel):
|
class TaskExecutionStatus(BaseModel):
|
||||||
status_str: StatusStr = Field(..., description='Overall task execution status')
|
status_str: OperationResult
|
||||||
completed: bool = Field(..., description='Whether the task completed')
|
completed: bool = Field(..., description="Whether the task completed")
|
||||||
messages: List[str] = Field(..., description='Additional status messages')
|
messages: List[str] = Field(..., description="Additional status messages")
|
||||||
|
|
||||||
|
|
||||||
class ManagerMessageName(str, Enum):
|
class ManagerMessageName(str, Enum):
|
||||||
cm_task_completed = 'cm-task-completed'
|
cm_task_completed = "cm-task-completed"
|
||||||
cm_task_started = 'cm-task-started'
|
cm_task_started = "cm-task-started"
|
||||||
cm_queue_status = 'cm-queue-status'
|
cm_queue_status = "cm-queue-status"
|
||||||
|
|
||||||
|
|
||||||
class ManagerPackInfo(BaseModel):
|
class ManagerPackInfo(BaseModel):
|
||||||
id: str = Field(
|
id: str = Field(
|
||||||
...,
|
...,
|
||||||
description='Either github-author/github-repo or name of pack from the registry',
|
description="Either github-author/github-repo or name of pack from the registry",
|
||||||
)
|
)
|
||||||
version: str = Field(..., description='Semantic version or Git commit hash')
|
version: str = Field(..., description="Semantic version or Git commit hash")
|
||||||
ui_id: Optional[str] = Field(None, description='Task ID - generated internally')
|
ui_id: Optional[str] = Field(None, description="Task ID - generated internally")
|
||||||
|
|
||||||
|
|
||||||
class ManagerPackInstalled(BaseModel):
|
class ManagerPackInstalled(BaseModel):
|
||||||
ver: str = Field(
|
ver: str = Field(
|
||||||
...,
|
...,
|
||||||
description='The version of the pack that is installed (Git commit hash or semantic version)',
|
description="The version of the pack that is installed (Git commit hash or semantic version)",
|
||||||
)
|
)
|
||||||
cnr_id: Optional[str] = Field(
|
cnr_id: Optional[str] = Field(
|
||||||
None, description='The name of the pack if installed from the registry'
|
None, description="The name of the pack if installed from the registry"
|
||||||
)
|
)
|
||||||
aux_id: Optional[str] = Field(
|
aux_id: Optional[str] = Field(
|
||||||
None,
|
None,
|
||||||
description='The name of the pack if installed from github (author/repo-name format)',
|
description="The name of the pack if installed from github (author/repo-name format)",
|
||||||
)
|
)
|
||||||
enabled: bool = Field(..., description='Whether the pack is enabled')
|
enabled: bool = Field(..., description="Whether the pack is enabled")
|
||||||
|
|
||||||
|
|
||||||
class SelectedVersion(str, Enum):
|
class SelectedVersion(str, Enum):
|
||||||
latest = 'latest'
|
latest = "latest"
|
||||||
nightly = 'nightly'
|
nightly = "nightly"
|
||||||
|
|
||||||
|
|
||||||
class ManagerChannel(str, Enum):
|
class ManagerChannel(str, Enum):
|
||||||
default = 'default'
|
default = "default"
|
||||||
recent = 'recent'
|
recent = "recent"
|
||||||
legacy = 'legacy'
|
legacy = "legacy"
|
||||||
forked = 'forked'
|
forked = "forked"
|
||||||
dev = 'dev'
|
dev = "dev"
|
||||||
tutorial = 'tutorial'
|
tutorial = "tutorial"
|
||||||
|
|
||||||
|
|
||||||
class ManagerDatabaseSource(str, Enum):
|
class ManagerDatabaseSource(str, Enum):
|
||||||
remote = 'remote'
|
remote = "remote"
|
||||||
local = 'local'
|
local = "local"
|
||||||
cache = 'cache'
|
cache = "cache"
|
||||||
|
|
||||||
|
|
||||||
class ManagerPackState(str, Enum):
|
class ManagerPackState(str, Enum):
|
||||||
installed = 'installed'
|
installed = "installed"
|
||||||
disabled = 'disabled'
|
disabled = "disabled"
|
||||||
not_installed = 'not_installed'
|
not_installed = "not_installed"
|
||||||
import_failed = 'import_failed'
|
import_failed = "import_failed"
|
||||||
needs_update = 'needs_update'
|
needs_update = "needs_update"
|
||||||
|
|
||||||
|
|
||||||
class ManagerPackInstallType(str, Enum):
|
class ManagerPackInstallType(str, Enum):
|
||||||
git_clone = 'git-clone'
|
git_clone = "git-clone"
|
||||||
copy = 'copy'
|
copy = "copy"
|
||||||
cnr = 'cnr'
|
cnr = "cnr"
|
||||||
|
|
||||||
|
|
||||||
|
class SecurityLevel(str, Enum):
|
||||||
|
strong = "strong"
|
||||||
|
normal = "normal"
|
||||||
|
normal_ = "normal-"
|
||||||
|
weak = "weak"
|
||||||
|
|
||||||
|
|
||||||
|
class RiskLevel(str, Enum):
|
||||||
|
block = "block"
|
||||||
|
high_ = "high+"
|
||||||
|
high = "high"
|
||||||
|
middle_ = "middle+"
|
||||||
|
middle = "middle"
|
||||||
|
|
||||||
|
|
||||||
class UpdateState(Enum):
|
class UpdateState(Enum):
|
||||||
false = 'false'
|
false = "false"
|
||||||
true = 'true'
|
true = "true"
|
||||||
|
|
||||||
|
|
||||||
class ManagerPack(ManagerPackInfo):
|
class ManagerPack(ManagerPackInfo):
|
||||||
author: Optional[str] = Field(
|
author: Optional[str] = Field(
|
||||||
None, description="Pack author name or 'Unclaimed' if added via GitHub crawl"
|
None, description="Pack author name or 'Unclaimed' if added via GitHub crawl"
|
||||||
)
|
)
|
||||||
files: Optional[List[str]] = Field(None, description='Files included in the pack')
|
files: Optional[List[str]] = Field(
|
||||||
reference: Optional[str] = Field(
|
None,
|
||||||
None, description='The type of installation reference'
|
description="Repository URLs for installation (typically contains one GitHub URL)",
|
||||||
)
|
)
|
||||||
title: Optional[str] = Field(None, description='The display name of 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
|
cnr_latest: Optional[SelectedVersion] = None
|
||||||
repository: Optional[str] = Field(None, description='GitHub repository URL')
|
repository: Optional[str] = Field(None, description="GitHub repository URL")
|
||||||
state: Optional[ManagerPackState] = None
|
state: Optional[ManagerPackState] = None
|
||||||
update_state: Optional[UpdateState] = Field(
|
update_state: Optional[UpdateState] = Field(
|
||||||
None, alias='update-state', description='Update availability status'
|
None, alias="update-state", description="Update availability status"
|
||||||
)
|
)
|
||||||
stars: Optional[int] = Field(None, description='GitHub stars count')
|
stars: Optional[int] = Field(None, description="GitHub stars count")
|
||||||
last_update: Optional[datetime] = Field(None, description='Last update timestamp')
|
last_update: Optional[datetime] = Field(None, description="Last update timestamp")
|
||||||
health: Optional[str] = Field(None, description='Health status of the pack')
|
health: Optional[str] = Field(None, description="Health status of the pack")
|
||||||
description: Optional[str] = Field(None, description='Pack description')
|
description: Optional[str] = Field(None, description="Pack description")
|
||||||
trust: Optional[bool] = Field(None, description='Whether the pack is trusted')
|
trust: Optional[bool] = Field(None, description="Whether the pack is trusted")
|
||||||
install_type: Optional[ManagerPackInstallType] = None
|
install_type: Optional[ManagerPackInstallType] = None
|
||||||
|
|
||||||
|
|
||||||
class InstallPackParams(ManagerPackInfo):
|
class InstallPackParams(ManagerPackInfo):
|
||||||
selected_version: Union[str, SelectedVersion] = Field(
|
selected_version: Union[str, SelectedVersion] = Field(
|
||||||
..., description='Semantic version, Git commit hash, latest, or nightly'
|
..., description="Semantic version, Git commit hash, latest, or nightly"
|
||||||
)
|
)
|
||||||
repository: Optional[str] = Field(
|
repository: Optional[str] = Field(
|
||||||
None,
|
None,
|
||||||
description='GitHub repository URL (required if selected_version is nightly)',
|
description="GitHub repository URL (required if selected_version is nightly)",
|
||||||
)
|
)
|
||||||
pip: Optional[List[str]] = Field(None, description='PyPi dependency names')
|
pip: Optional[List[str]] = Field(None, description="PyPi dependency names")
|
||||||
mode: ManagerDatabaseSource
|
mode: ManagerDatabaseSource
|
||||||
channel: ManagerChannel
|
channel: ManagerChannel
|
||||||
skip_post_install: Optional[bool] = Field(
|
skip_post_install: Optional[bool] = Field(
|
||||||
None, description='Whether to skip post-installation steps'
|
None, description="Whether to skip post-installation steps"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class UpdateAllPacksParams(BaseModel):
|
class UpdateAllPacksParams(BaseModel):
|
||||||
mode: Optional[ManagerDatabaseSource] = None
|
mode: Optional[ManagerDatabaseSource] = None
|
||||||
ui_id: Optional[str] = Field(None, description='Task ID - generated internally')
|
ui_id: Optional[str] = Field(None, description="Task ID - generated internally")
|
||||||
|
|
||||||
|
|
||||||
class UpdatePackParams(BaseModel):
|
class UpdatePackParams(BaseModel):
|
||||||
node_name: str = Field(..., description='Name of the node package to update')
|
node_name: str = Field(..., description="Name of the node package to update")
|
||||||
node_ver: Optional[str] = Field(
|
node_ver: Optional[str] = Field(
|
||||||
None, description='Current version of the node package'
|
None, description="Current version of the node package"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class UpdateComfyUIParams(BaseModel):
|
class UpdateComfyUIParams(BaseModel):
|
||||||
is_stable: Optional[bool] = Field(
|
is_stable: Optional[bool] = Field(
|
||||||
True,
|
True,
|
||||||
description='Whether to update to stable version (true) or nightly (false)',
|
description="Whether to update to stable version (true) or nightly (false)",
|
||||||
)
|
)
|
||||||
target_version: Optional[str] = Field(
|
target_version: Optional[str] = Field(
|
||||||
None,
|
None,
|
||||||
description='Specific version to switch to (for version switching operations)',
|
description="Specific version to switch to (for version switching operations)",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FixPackParams(BaseModel):
|
class FixPackParams(BaseModel):
|
||||||
node_name: str = Field(..., description='Name of the node package to fix')
|
node_name: str = Field(..., description="Name of the node package to fix")
|
||||||
node_ver: str = Field(..., description='Version of the node package')
|
node_ver: str = Field(..., description="Version of the node package")
|
||||||
|
|
||||||
|
|
||||||
class UninstallPackParams(BaseModel):
|
class UninstallPackParams(BaseModel):
|
||||||
node_name: str = Field(..., description='Name of the node package to uninstall')
|
node_name: str = Field(..., description="Name of the node package to uninstall")
|
||||||
is_unknown: Optional[bool] = Field(
|
is_unknown: Optional[bool] = Field(
|
||||||
False, description='Whether this is an unknown/unregistered package'
|
False, description="Whether this is an unknown/unregistered package"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class DisablePackParams(BaseModel):
|
class DisablePackParams(BaseModel):
|
||||||
node_name: str = Field(..., description='Name of the node package to disable')
|
node_name: str = Field(..., description="Name of the node package to disable")
|
||||||
is_unknown: Optional[bool] = Field(
|
is_unknown: Optional[bool] = Field(
|
||||||
False, description='Whether this is an unknown/unregistered package'
|
False, description="Whether this is an unknown/unregistered package"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class EnablePackParams(BaseModel):
|
class EnablePackParams(BaseModel):
|
||||||
cnr_id: str = Field(
|
cnr_id: str = Field(
|
||||||
..., description='ComfyUI Node Registry ID of the package to enable'
|
..., description="ComfyUI Node Registry ID of the package to enable"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateAllQueryParams(BaseModel):
|
||||||
|
client_id: str = Field(
|
||||||
|
..., description="Client identifier that initiated the request"
|
||||||
|
)
|
||||||
|
ui_id: str = Field(..., description="Base UI identifier for task tracking")
|
||||||
|
mode: Optional[ManagerDatabaseSource] = None
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateComfyUIQueryParams(BaseModel):
|
||||||
|
client_id: str = Field(
|
||||||
|
..., description="Client identifier that initiated the request"
|
||||||
|
)
|
||||||
|
ui_id: str = Field(..., description="UI identifier for task tracking")
|
||||||
|
stable: Optional[bool] = Field(
|
||||||
|
True,
|
||||||
|
description="Whether to update to stable version (true) or nightly (false)",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ComfyUISwitchVersionQueryParams(BaseModel):
|
||||||
|
ver: str = Field(..., description="Version to switch to")
|
||||||
|
client_id: str = Field(
|
||||||
|
..., description="Client identifier that initiated the request"
|
||||||
|
)
|
||||||
|
ui_id: str = Field(..., description="UI identifier for task tracking")
|
||||||
|
|
||||||
|
|
||||||
class QueueStatus(BaseModel):
|
class QueueStatus(BaseModel):
|
||||||
total_count: int = Field(
|
total_count: int = Field(
|
||||||
..., description='Total number of tasks (pending + running)'
|
..., description="Total number of tasks (pending + running)"
|
||||||
)
|
)
|
||||||
done_count: int = Field(..., description='Number of completed tasks')
|
done_count: int = Field(..., description="Number of completed tasks")
|
||||||
in_progress_count: int = Field(..., description='Number of tasks currently running')
|
in_progress_count: int = Field(..., description="Number of tasks currently running")
|
||||||
pending_count: Optional[int] = Field(
|
pending_count: Optional[int] = Field(
|
||||||
None, description='Number of tasks waiting to be executed'
|
None, description="Number of tasks waiting to be executed"
|
||||||
)
|
)
|
||||||
is_processing: bool = Field(..., description='Whether the task worker is active')
|
is_processing: bool = Field(..., description="Whether the task worker is active")
|
||||||
client_id: Optional[str] = Field(
|
client_id: Optional[str] = Field(
|
||||||
None, description='Client ID (when filtered by client)'
|
None, description="Client ID (when filtered by client)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ManagerMappings1(BaseModel):
|
class ManagerMappings1(BaseModel):
|
||||||
title_aux: Optional[str] = Field(None, description='The display name of the pack')
|
title_aux: Optional[str] = Field(None, description="The display name of the pack")
|
||||||
|
|
||||||
|
|
||||||
class ManagerMappings(
|
class ManagerMappings(
|
||||||
RootModel[Optional[Dict[str, List[Union[List[str], ManagerMappings1]]]]]
|
RootModel[Optional[Dict[str, List[Union[List[str], ManagerMappings1]]]]]
|
||||||
):
|
):
|
||||||
root: Optional[Dict[str, List[Union[List[str], ManagerMappings1]]]] = Field(
|
root: Optional[Dict[str, List[Union[List[str], ManagerMappings1]]]] = Field(
|
||||||
None, description='Tuple of [node_names, metadata]'
|
None, description="Tuple of [node_names, metadata]"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ModelMetadata(BaseModel):
|
class ModelMetadata(BaseModel):
|
||||||
name: str = Field(..., description='Name of the model')
|
name: str = Field(..., description="Name of the model")
|
||||||
type: str = Field(..., description='Type of model')
|
type: str = Field(..., description="Type of model")
|
||||||
base: Optional[str] = Field(None, description='Base model type')
|
base: Optional[str] = Field(None, description="Base model type")
|
||||||
save_path: Optional[str] = Field(None, description='Path for saving the model')
|
save_path: Optional[str] = Field(None, description="Path for saving the model")
|
||||||
url: str = Field(..., description='Download URL')
|
url: str = Field(..., description="Download URL")
|
||||||
filename: str = Field(..., description='Target filename')
|
filename: str = Field(..., description="Target filename")
|
||||||
ui_id: Optional[str] = Field(None, description='ID for UI reference')
|
ui_id: Optional[str] = Field(None, description="ID for UI reference")
|
||||||
|
|
||||||
|
|
||||||
class InstallType(str, Enum):
|
class InstallType(str, Enum):
|
||||||
git = 'git'
|
git = "git"
|
||||||
copy = 'copy'
|
copy = "copy"
|
||||||
pip = 'pip'
|
pip = "pip"
|
||||||
|
|
||||||
|
|
||||||
class NodePackageMetadata(BaseModel):
|
class NodePackageMetadata(BaseModel):
|
||||||
title: Optional[str] = Field(None, description='Display name of the node package')
|
title: Optional[str] = Field(None, description="Display name of the node package")
|
||||||
name: Optional[str] = Field(None, description='Repository/package name')
|
name: Optional[str] = Field(None, description="Repository/package name")
|
||||||
files: Optional[List[str]] = Field(None, description='Source URLs for the package')
|
files: Optional[List[str]] = Field(None, description="Source URLs for the package")
|
||||||
description: Optional[str] = Field(
|
description: Optional[str] = Field(
|
||||||
None, description='Description of the node package functionality'
|
None, description="Description of the node package functionality"
|
||||||
)
|
)
|
||||||
install_type: Optional[InstallType] = Field(None, description='Installation method')
|
install_type: Optional[InstallType] = Field(None, description="Installation method")
|
||||||
version: Optional[str] = Field(None, description='Version identifier')
|
version: Optional[str] = Field(None, description="Version identifier")
|
||||||
id: Optional[str] = Field(
|
id: Optional[str] = Field(
|
||||||
None, description='Unique identifier for the node package'
|
None, description="Unique identifier for the node package"
|
||||||
)
|
)
|
||||||
ui_id: Optional[str] = Field(None, description='ID for UI reference')
|
ui_id: Optional[str] = Field(None, description="ID for UI reference")
|
||||||
channel: Optional[str] = Field(None, description='Source channel')
|
channel: Optional[str] = Field(None, description="Source channel")
|
||||||
mode: Optional[str] = Field(None, description='Source mode')
|
mode: Optional[str] = Field(None, description="Source mode")
|
||||||
|
|
||||||
|
|
||||||
class SnapshotItem(RootModel[str]):
|
class SnapshotItem(RootModel[str]):
|
||||||
root: str = Field(..., description='Name of the snapshot')
|
root: str = Field(..., description="Name of the snapshot")
|
||||||
|
|
||||||
|
|
||||||
class Error(BaseModel):
|
class Error(BaseModel):
|
||||||
error: str = Field(..., description='Error message')
|
error: str = Field(..., description="Error message")
|
||||||
|
|
||||||
|
|
||||||
class InstalledPacksResponse(RootModel[Optional[Dict[str, ManagerPackInstalled]]]):
|
class InstalledPacksResponse(RootModel[Optional[Dict[str, ManagerPackInstalled]]]):
|
||||||
@@ -265,142 +311,171 @@ class InstalledPacksResponse(RootModel[Optional[Dict[str, ManagerPackInstalled]]
|
|||||||
|
|
||||||
class HistoryListResponse(BaseModel):
|
class HistoryListResponse(BaseModel):
|
||||||
ids: Optional[List[str]] = Field(
|
ids: Optional[List[str]] = Field(
|
||||||
None, description='List of available batch history IDs'
|
None, description="List of available batch history IDs"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class InstalledNodeInfo(BaseModel):
|
class InstalledNodeInfo(BaseModel):
|
||||||
name: str = Field(..., description='Node package name')
|
name: str = Field(..., description="Node package name")
|
||||||
version: str = Field(..., description='Installed version')
|
version: str = Field(..., description="Installed version")
|
||||||
repository_url: Optional[str] = Field(None, description='Git repository URL')
|
repository_url: Optional[str] = Field(None, description="Git repository URL")
|
||||||
install_method: str = Field(
|
install_method: str = Field(
|
||||||
..., description='Installation method (cnr, git, pip, etc.)'
|
..., description="Installation method (cnr, git, pip, etc.)"
|
||||||
)
|
)
|
||||||
enabled: Optional[bool] = Field(
|
enabled: Optional[bool] = Field(
|
||||||
True, description='Whether the node is currently enabled'
|
True, description="Whether the node is currently enabled"
|
||||||
)
|
)
|
||||||
install_date: Optional[datetime] = Field(
|
install_date: Optional[datetime] = Field(
|
||||||
None, description='ISO timestamp of installation'
|
None, description="ISO timestamp of installation"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class InstalledModelInfo(BaseModel):
|
class InstalledModelInfo(BaseModel):
|
||||||
name: str = Field(..., description='Model filename')
|
name: str = Field(..., description="Model filename")
|
||||||
path: str = Field(..., description='Full path to model file')
|
path: str = Field(..., description="Full path to model file")
|
||||||
type: str = Field(..., description='Model type (checkpoint, lora, vae, etc.)')
|
type: str = Field(..., description="Model type (checkpoint, lora, vae, etc.)")
|
||||||
size_bytes: Optional[int] = Field(None, description='File size in bytes', ge=0)
|
size_bytes: Optional[int] = Field(None, description="File size in bytes", ge=0)
|
||||||
hash: Optional[str] = Field(None, description='Model file hash for verification')
|
hash: Optional[str] = Field(None, description="Model file hash for verification")
|
||||||
install_date: Optional[datetime] = Field(
|
install_date: Optional[datetime] = Field(
|
||||||
None, description='ISO timestamp when added'
|
None, description="ISO timestamp when added"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ComfyUIVersionInfo(BaseModel):
|
class ComfyUIVersionInfo(BaseModel):
|
||||||
version: str = Field(..., description='ComfyUI version string')
|
version: str = Field(..., description="ComfyUI version string")
|
||||||
commit_hash: Optional[str] = Field(None, description='Git commit hash')
|
commit_hash: Optional[str] = Field(None, description="Git commit hash")
|
||||||
branch: Optional[str] = Field(None, description='Git branch name')
|
branch: Optional[str] = Field(None, description="Git branch name")
|
||||||
is_stable: Optional[bool] = Field(
|
is_stable: Optional[bool] = Field(
|
||||||
False, description='Whether this is a stable release'
|
False, description="Whether this is a stable release"
|
||||||
)
|
)
|
||||||
last_updated: Optional[datetime] = Field(
|
last_updated: Optional[datetime] = Field(
|
||||||
None, description='ISO timestamp of last update'
|
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):
|
class BatchOperation(BaseModel):
|
||||||
operation_id: str = Field(..., description='Unique operation identifier')
|
operation_id: str = Field(..., description="Unique operation identifier")
|
||||||
operation_type: OperationType = Field(..., description='Type of operation')
|
operation_type: OperationType
|
||||||
target: str = Field(
|
target: str = Field(
|
||||||
..., description='Target of the operation (node name, model name, etc.)'
|
..., description="Target of the operation (node name, model name, etc.)"
|
||||||
)
|
)
|
||||||
target_version: Optional[str] = Field(
|
target_version: Optional[str] = Field(
|
||||||
None, description='Target version for the operation'
|
None, description="Target version for the operation"
|
||||||
)
|
)
|
||||||
result: Result = Field(..., description='Operation result')
|
result: OperationResult
|
||||||
error_message: Optional[str] = Field(
|
error_message: Optional[str] = Field(
|
||||||
None, description='Error message if operation failed'
|
None, description="Error message if operation failed"
|
||||||
)
|
)
|
||||||
start_time: datetime = Field(
|
start_time: datetime = Field(
|
||||||
..., description='ISO timestamp when operation started'
|
..., description="ISO timestamp when operation started"
|
||||||
)
|
)
|
||||||
end_time: Optional[datetime] = Field(
|
end_time: Optional[datetime] = Field(
|
||||||
None, description='ISO timestamp when operation completed'
|
None, description="ISO timestamp when operation completed"
|
||||||
)
|
)
|
||||||
client_id: Optional[str] = Field(
|
client_id: Optional[str] = Field(
|
||||||
None, description='Client that initiated the operation'
|
None, description="Client that initiated the operation"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ComfyUISystemState(BaseModel):
|
class ComfyUISystemState(BaseModel):
|
||||||
snapshot_time: datetime = Field(
|
snapshot_time: datetime = Field(
|
||||||
..., description='ISO timestamp when snapshot was taken'
|
..., description="ISO timestamp when snapshot was taken"
|
||||||
)
|
)
|
||||||
comfyui_version: ComfyUIVersionInfo
|
comfyui_version: ComfyUIVersionInfo
|
||||||
frontend_version: Optional[str] = Field(
|
frontend_version: Optional[str] = Field(
|
||||||
None, description='ComfyUI frontend version if available'
|
None, description="ComfyUI frontend version if available"
|
||||||
)
|
)
|
||||||
python_version: str = Field(..., description='Python interpreter version')
|
python_version: str = Field(..., description="Python interpreter version")
|
||||||
platform_info: str = Field(
|
platform_info: str = Field(
|
||||||
..., description='Operating system and platform information'
|
..., description="Operating system and platform information"
|
||||||
)
|
)
|
||||||
installed_nodes: Optional[Dict[str, InstalledNodeInfo]] = Field(
|
installed_nodes: Optional[Dict[str, InstalledNodeInfo]] = Field(
|
||||||
None, description='Map of installed node packages by name'
|
None, description="Map of installed node packages by name"
|
||||||
)
|
)
|
||||||
installed_models: Optional[Dict[str, InstalledModelInfo]] = Field(
|
installed_models: Optional[Dict[str, InstalledModelInfo]] = Field(
|
||||||
None, description='Map of installed models by name'
|
None, description="Map of installed models by name"
|
||||||
)
|
)
|
||||||
manager_config: Optional[Dict[str, Any]] = Field(
|
manager_config: Optional[Dict[str, Any]] = Field(
|
||||||
None, description='ComfyUI Manager configuration settings'
|
None, description="ComfyUI Manager configuration settings"
|
||||||
|
)
|
||||||
|
comfyui_root_path: Optional[str] = Field(
|
||||||
|
None, description="ComfyUI root installation directory"
|
||||||
|
)
|
||||||
|
model_paths: Optional[Dict[str, List[str]]] = Field(
|
||||||
|
None, description="Map of model types to their configured paths"
|
||||||
|
)
|
||||||
|
manager_version: Optional[str] = Field(None, description="ComfyUI Manager version")
|
||||||
|
security_level: Optional[SecurityLevel] = None
|
||||||
|
network_mode: Optional[str] = Field(
|
||||||
|
None, description="Network mode (online, offline, private)"
|
||||||
|
)
|
||||||
|
cli_args: Optional[Dict[str, Any]] = Field(
|
||||||
|
None, description="Selected ComfyUI CLI arguments"
|
||||||
|
)
|
||||||
|
custom_nodes_count: Optional[int] = Field(
|
||||||
|
None, description="Total number of custom node packages", ge=0
|
||||||
|
)
|
||||||
|
failed_imports: Optional[List[str]] = Field(
|
||||||
|
None, description="List of custom nodes that failed to import"
|
||||||
|
)
|
||||||
|
pip_packages: Optional[Dict[str, str]] = Field(
|
||||||
|
None, description="Map of installed pip packages to their versions"
|
||||||
|
)
|
||||||
|
embedded_python: Optional[bool] = Field(
|
||||||
|
None,
|
||||||
|
description="Whether ComfyUI is running from an embedded Python distribution",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BatchExecutionRecord(BaseModel):
|
class BatchExecutionRecord(BaseModel):
|
||||||
batch_id: str = Field(..., description='Unique batch identifier')
|
batch_id: str = Field(..., description="Unique batch identifier")
|
||||||
start_time: datetime = Field(..., description='ISO timestamp when batch started')
|
start_time: datetime = Field(..., description="ISO timestamp when batch started")
|
||||||
end_time: Optional[datetime] = Field(
|
end_time: Optional[datetime] = Field(
|
||||||
None, description='ISO timestamp when batch completed'
|
None, description="ISO timestamp when batch completed"
|
||||||
)
|
)
|
||||||
state_before: ComfyUISystemState
|
state_before: ComfyUISystemState
|
||||||
state_after: Optional[ComfyUISystemState] = Field(
|
state_after: Optional[ComfyUISystemState] = Field(
|
||||||
None, description='System state after batch execution'
|
None, description="System state after batch execution"
|
||||||
)
|
)
|
||||||
operations: Optional[List[BatchOperation]] = Field(
|
operations: Optional[List[BatchOperation]] = Field(
|
||||||
None, description='List of operations performed in this batch'
|
None, description="List of operations performed in this batch"
|
||||||
)
|
)
|
||||||
total_operations: Optional[int] = Field(
|
total_operations: Optional[int] = Field(
|
||||||
0, description='Total number of operations in batch', ge=0
|
0, description="Total number of operations in batch", ge=0
|
||||||
)
|
)
|
||||||
successful_operations: Optional[int] = Field(
|
successful_operations: Optional[int] = Field(
|
||||||
0, description='Number of successful operations', ge=0
|
0, description="Number of successful operations", ge=0
|
||||||
)
|
)
|
||||||
failed_operations: Optional[int] = Field(
|
failed_operations: Optional[int] = Field(
|
||||||
0, description='Number of failed operations', ge=0
|
0, description="Number of failed operations", ge=0
|
||||||
)
|
)
|
||||||
skipped_operations: Optional[int] = Field(
|
skipped_operations: Optional[int] = Field(
|
||||||
0, description='Number of skipped operations', ge=0
|
0, description="Number of skipped operations", ge=0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ImportFailInfoBulkRequest(BaseModel):
|
||||||
|
cnr_ids: Optional[List[str]] = Field(
|
||||||
|
None, description="A list of CNR IDs to check."
|
||||||
|
)
|
||||||
|
urls: Optional[List[str]] = Field(
|
||||||
|
None, description="A list of repository URLs to check."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ImportFailInfoItem1(BaseModel):
|
||||||
|
error: Optional[str] = None
|
||||||
|
traceback: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class ImportFailInfoItem(RootModel[Optional[ImportFailInfoItem1]]):
|
||||||
|
root: Optional[ImportFailInfoItem1]
|
||||||
|
|
||||||
|
|
||||||
class QueueTaskItem(BaseModel):
|
class QueueTaskItem(BaseModel):
|
||||||
ui_id: str = Field(..., description='Unique identifier for the task')
|
ui_id: str = Field(..., description="Unique identifier for the task")
|
||||||
client_id: str = Field(..., description='Client identifier that initiated the task')
|
client_id: str = Field(..., description="Client identifier that initiated the task")
|
||||||
kind: Kind = Field(..., description='Type of task being performed')
|
kind: OperationType
|
||||||
params: Union[
|
params: Union[
|
||||||
InstallPackParams,
|
InstallPackParams,
|
||||||
UpdatePackParams,
|
UpdatePackParams,
|
||||||
@@ -415,50 +490,56 @@ class QueueTaskItem(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class TaskHistoryItem(BaseModel):
|
class TaskHistoryItem(BaseModel):
|
||||||
ui_id: str = Field(..., description='Unique identifier for the task')
|
ui_id: str = Field(..., description="Unique identifier for the task")
|
||||||
client_id: str = Field(..., description='Client identifier that initiated the task')
|
client_id: str = Field(..., description="Client identifier that initiated the task")
|
||||||
kind: str = Field(..., description='Type of task that was performed')
|
kind: str = Field(..., description="Type of task that was performed")
|
||||||
timestamp: datetime = Field(..., description='ISO timestamp when task completed')
|
timestamp: datetime = Field(..., description="ISO timestamp when task completed")
|
||||||
result: str = Field(..., description='Task result message or details')
|
result: str = Field(..., description="Task result message or details")
|
||||||
status: Optional[TaskExecutionStatus] = None
|
status: Optional[TaskExecutionStatus] = None
|
||||||
|
batch_id: Optional[str] = Field(
|
||||||
|
None, description="ID of the batch this task belongs to"
|
||||||
|
)
|
||||||
|
end_time: Optional[datetime] = Field(
|
||||||
|
None, description="ISO timestamp when task execution ended"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TaskStateMessage(BaseModel):
|
class TaskStateMessage(BaseModel):
|
||||||
history: Dict[str, TaskHistoryItem] = Field(
|
history: Dict[str, TaskHistoryItem] = Field(
|
||||||
..., description='Map of task IDs to their history items'
|
..., description="Map of task IDs to their history items"
|
||||||
)
|
)
|
||||||
running_queue: List[QueueTaskItem] = Field(
|
running_queue: List[QueueTaskItem] = Field(
|
||||||
..., description='Currently executing tasks'
|
..., description="Currently executing tasks"
|
||||||
)
|
)
|
||||||
pending_queue: List[QueueTaskItem] = Field(
|
pending_queue: List[QueueTaskItem] = Field(
|
||||||
..., description='Tasks waiting to be executed'
|
..., description="Tasks waiting to be executed"
|
||||||
)
|
)
|
||||||
installed_packs: Dict[str, ManagerPackInstalled] = Field(
|
installed_packs: Dict[str, ManagerPackInstalled] = Field(
|
||||||
..., description='Map of currently installed node packages by name'
|
..., description="Map of currently installed node packages by name"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class MessageTaskDone(BaseModel):
|
class MessageTaskDone(BaseModel):
|
||||||
ui_id: str = Field(..., description='Task identifier')
|
ui_id: str = Field(..., description="Task identifier")
|
||||||
result: str = Field(..., description='Task result message')
|
result: str = Field(..., description="Task result message")
|
||||||
kind: str = Field(..., description='Type of task')
|
kind: str = Field(..., description="Type of task")
|
||||||
status: Optional[TaskExecutionStatus] = None
|
status: Optional[TaskExecutionStatus] = None
|
||||||
timestamp: datetime = Field(..., description='ISO timestamp when task completed')
|
timestamp: datetime = Field(..., description="ISO timestamp when task completed")
|
||||||
state: TaskStateMessage
|
state: TaskStateMessage
|
||||||
|
|
||||||
|
|
||||||
class MessageTaskStarted(BaseModel):
|
class MessageTaskStarted(BaseModel):
|
||||||
ui_id: str = Field(..., description='Task identifier')
|
ui_id: str = Field(..., description="Task identifier")
|
||||||
kind: str = Field(..., description='Type of task')
|
kind: str = Field(..., description="Type of task")
|
||||||
timestamp: datetime = Field(..., description='ISO timestamp when task started')
|
timestamp: datetime = Field(..., description="ISO timestamp when task started")
|
||||||
state: TaskStateMessage
|
state: TaskStateMessage
|
||||||
|
|
||||||
|
|
||||||
class MessageTaskFailed(BaseModel):
|
class MessageTaskFailed(BaseModel):
|
||||||
ui_id: str = Field(..., description='Task identifier')
|
ui_id: str = Field(..., description="Task identifier")
|
||||||
error: str = Field(..., description='Error message')
|
error: str = Field(..., description="Error message")
|
||||||
kind: str = Field(..., description='Type of task')
|
kind: str = Field(..., description="Type of task")
|
||||||
timestamp: datetime = Field(..., description='ISO timestamp when task failed')
|
timestamp: datetime = Field(..., description="ISO timestamp when task failed")
|
||||||
state: TaskStateMessage
|
state: TaskStateMessage
|
||||||
|
|
||||||
|
|
||||||
@@ -466,11 +547,15 @@ class MessageUpdate(
|
|||||||
RootModel[Union[MessageTaskDone, MessageTaskStarted, MessageTaskFailed]]
|
RootModel[Union[MessageTaskDone, MessageTaskStarted, MessageTaskFailed]]
|
||||||
):
|
):
|
||||||
root: Union[MessageTaskDone, MessageTaskStarted, MessageTaskFailed] = Field(
|
root: Union[MessageTaskDone, MessageTaskStarted, MessageTaskFailed] = Field(
|
||||||
..., description='Union type for all possible WebSocket message updates'
|
..., description="Union type for all possible WebSocket message updates"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class HistoryResponse(BaseModel):
|
class HistoryResponse(BaseModel):
|
||||||
history: Optional[Dict[str, TaskHistoryItem]] = Field(
|
history: Optional[Dict[str, TaskHistoryItem]] = Field(
|
||||||
None, description='Map of task IDs to their history items'
|
None, description="Map of task IDs to their history items"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ImportFailInfoBulkResponse(RootModel[Optional[Dict[str, ImportFailInfoItem]]]):
|
||||||
|
root: Optional[Dict[str, ImportFailInfoItem]] = None
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -8,3 +8,4 @@
|
|||||||
7. Adjust the `__init__.py` files in the `data_models` directory to match/export the new data model
|
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
|
8. Only then, make the changes to the rest of the codebase
|
||||||
9. Run the CI tests to verify that the changes are working
|
9. Run the CI tests to verify that the changes are working
|
||||||
|
- The comfyui_manager is a python package that is used to manage the comfyui server. There are two sub-packages `glob` and `legacy`. These represent the current version (`glob`) and the previous version (`legacy`), not including common utilities and data models. When developing, we work in the `glob` package. You can ignore the `legacy` package entirely, unless you have a very good reason to research how things were done in the legacy or prior major versions of the package. But in those cases, you should just look for the sake of knowledge or reflection, not for changing code (unless explicitly asked to do so).
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
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_MIDDLE = "ERROR: To use this action, a security_level of `normal or below` is required. Please contact the administrator.\nReference: https://github.com/ltdrdata/ComfyUI-Manager#security-policy"
|
||||||
|
SECURITY_MESSAGE_MIDDLE_P = "ERROR: To use this action, security_level must be `normal or below`, and network_mode must be set to `personal_cloud`. 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_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_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."
|
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."
|
||||||
@@ -15,9 +15,6 @@ def is_loopback(address):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
is_local_mode = is_loopback(args.listen)
|
|
||||||
|
|
||||||
|
|
||||||
model_dir_name_map = {
|
model_dir_name_map = {
|
||||||
"checkpoints": "checkpoints",
|
"checkpoints": "checkpoints",
|
||||||
"checkpoint": "checkpoints",
|
"checkpoint": "checkpoints",
|
||||||
@@ -37,3 +34,22 @@ model_dir_name_map = {
|
|||||||
"unet": "diffusion_models",
|
"unet": "diffusion_models",
|
||||||
"diffusion_model": "diffusion_models",
|
"diffusion_model": "diffusion_models",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# List of all model directory names used for checking installed models
|
||||||
|
MODEL_DIR_NAMES = [
|
||||||
|
"checkpoints",
|
||||||
|
"loras",
|
||||||
|
"vae",
|
||||||
|
"text_encoders",
|
||||||
|
"diffusion_models",
|
||||||
|
"clip_vision",
|
||||||
|
"embeddings",
|
||||||
|
"diffusers",
|
||||||
|
"vae_approx",
|
||||||
|
"controlnet",
|
||||||
|
"gligen",
|
||||||
|
"upscale_models",
|
||||||
|
"hypernetworks",
|
||||||
|
"photomaker",
|
||||||
|
"classifiers",
|
||||||
|
]
|
||||||
|
|||||||
@@ -41,11 +41,12 @@ from ..common.enums import NetworkMode, SecurityLevel, DBMode
|
|||||||
from ..common import context
|
from ..common import context
|
||||||
|
|
||||||
|
|
||||||
version_code = [4, 0]
|
version_code = [4, 0, 3]
|
||||||
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_CHANNEL = "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main"
|
DEFAULT_CHANNEL = "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main"
|
||||||
|
DEFAULT_CHANNEL_LEGACY = "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main"
|
||||||
|
|
||||||
|
|
||||||
default_custom_nodes_path = None
|
default_custom_nodes_path = None
|
||||||
@@ -153,14 +154,8 @@ def check_invalid_nodes():
|
|||||||
cached_config = None
|
cached_config = None
|
||||||
js_path = None
|
js_path = None
|
||||||
|
|
||||||
comfy_ui_required_revision = 1930
|
|
||||||
comfy_ui_required_commit_datetime = datetime(2024, 1, 24, 0, 0, 0)
|
|
||||||
|
|
||||||
comfy_ui_revision = "Unknown"
|
|
||||||
comfy_ui_commit_datetime = datetime(1900, 1, 1, 0, 0, 0)
|
|
||||||
|
|
||||||
channel_dict = None
|
channel_dict = None
|
||||||
valid_channels = {'default', 'local'}
|
valid_channels = {'default', 'local', DEFAULT_CHANNEL, DEFAULT_CHANNEL_LEGACY}
|
||||||
channel_list = None
|
channel_list = None
|
||||||
|
|
||||||
|
|
||||||
@@ -304,16 +299,84 @@ class ManagedResult:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class NormalizedKeyDict:
|
||||||
|
def __init__(self):
|
||||||
|
self._store = {}
|
||||||
|
self._key_map = {}
|
||||||
|
|
||||||
|
def _normalize_key(self, key):
|
||||||
|
if isinstance(key, str):
|
||||||
|
return key.strip().lower()
|
||||||
|
return key
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
norm_key = self._normalize_key(key)
|
||||||
|
self._key_map[norm_key] = key
|
||||||
|
self._store[key] = value
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
norm_key = self._normalize_key(key)
|
||||||
|
original_key = self._key_map[norm_key]
|
||||||
|
return self._store[original_key]
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
norm_key = self._normalize_key(key)
|
||||||
|
original_key = self._key_map.pop(norm_key)
|
||||||
|
del self._store[original_key]
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return self._normalize_key(key) in self._key_map
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return self[key] if key in self else default
|
||||||
|
|
||||||
|
def setdefault(self, key, default=None):
|
||||||
|
if key in self:
|
||||||
|
return self[key]
|
||||||
|
self[key] = default
|
||||||
|
return default
|
||||||
|
|
||||||
|
def pop(self, key, default=None):
|
||||||
|
if key in self:
|
||||||
|
val = self[key]
|
||||||
|
del self[key]
|
||||||
|
return val
|
||||||
|
if default is not None:
|
||||||
|
return default
|
||||||
|
raise KeyError(key)
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self._store.keys()
|
||||||
|
|
||||||
|
def values(self):
|
||||||
|
return self._store.values()
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return self._store.items()
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._store)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._store)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return repr(self._store)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return dict(self._store)
|
||||||
|
|
||||||
|
|
||||||
class UnifiedManager:
|
class UnifiedManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.installed_node_packages: dict[str, InstalledNodePackage] = {}
|
self.installed_node_packages: dict[str, InstalledNodePackage] = {}
|
||||||
|
|
||||||
self.cnr_inactive_nodes = {} # node_id -> node_version -> fullpath
|
self.cnr_inactive_nodes = NormalizedKeyDict() # node_id -> node_version -> fullpath
|
||||||
self.nightly_inactive_nodes = {} # node_id -> fullpath
|
self.nightly_inactive_nodes = NormalizedKeyDict() # node_id -> fullpath
|
||||||
self.unknown_inactive_nodes = {} # node_id -> repo url * fullpath
|
self.unknown_inactive_nodes = {} # node_id -> repo url * fullpath
|
||||||
self.active_nodes = {} # node_id -> node_version * fullpath
|
self.active_nodes = NormalizedKeyDict() # node_id -> node_version * fullpath
|
||||||
self.unknown_active_nodes = {} # node_id -> repo url * fullpath
|
self.unknown_active_nodes = {} # node_id -> repo url * fullpath
|
||||||
self.cnr_map = {} # node_id -> cnr info
|
self.cnr_map = NormalizedKeyDict() # node_id -> cnr info
|
||||||
self.repo_cnr_map = {} # repo_url -> cnr info
|
self.repo_cnr_map = {} # repo_url -> cnr info
|
||||||
self.custom_node_map_cache = {} # (channel, mode) -> augmented custom node list json
|
self.custom_node_map_cache = {} # (channel, mode) -> augmented custom node list json
|
||||||
self.processed_install = set()
|
self.processed_install = set()
|
||||||
@@ -721,7 +784,7 @@ class UnifiedManager:
|
|||||||
channel = normalize_channel(channel)
|
channel = normalize_channel(channel)
|
||||||
nodes = await self.load_nightly(channel, mode)
|
nodes = await self.load_nightly(channel, mode)
|
||||||
|
|
||||||
res = {}
|
res = NormalizedKeyDict()
|
||||||
added_cnr = set()
|
added_cnr = set()
|
||||||
for v in nodes.values():
|
for v in nodes.values():
|
||||||
v = v[0]
|
v = v[0]
|
||||||
@@ -940,7 +1003,6 @@ class UnifiedManager:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
result = ManagedResult('enable')
|
result = ManagedResult('enable')
|
||||||
|
|
||||||
if 'comfyui-manager' in node_id.lower():
|
if 'comfyui-manager' in node_id.lower():
|
||||||
return result.fail(f"ignored: enabling '{node_id}'")
|
return result.fail(f"ignored: enabling '{node_id}'")
|
||||||
|
|
||||||
@@ -1411,7 +1473,7 @@ def identify_node_pack_from_path(fullpath):
|
|||||||
# cnr
|
# cnr
|
||||||
cnr = cnr_utils.read_cnr_info(fullpath)
|
cnr = cnr_utils.read_cnr_info(fullpath)
|
||||||
if cnr is not None:
|
if cnr is not None:
|
||||||
return module_name, cnr['version'], cnr['id'], None
|
return module_name, cnr['version'], cnr['original_name'], None
|
||||||
|
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
@@ -1461,6 +1523,9 @@ def get_installed_node_packs():
|
|||||||
if info is None:
|
if info is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# NOTE: don't add disabled nodepack if there is enabled nodepack
|
||||||
|
original_name = info[0].split('@')[0]
|
||||||
|
if original_name not in res:
|
||||||
res[info[0]] = { 'ver': info[1], 'cnr_id': info[2], 'aux_id': info[3], 'enabled': False }
|
res[info[0]] = { 'ver': info[1], 'cnr_id': info[2], 'aux_id': info[3], 'enabled': False }
|
||||||
|
|
||||||
return res
|
return res
|
||||||
@@ -1558,16 +1623,18 @@ def read_config():
|
|||||||
config = configparser.ConfigParser(strict=False)
|
config = configparser.ConfigParser(strict=False)
|
||||||
config.read(context.manager_config_path)
|
config.read(context.manager_config_path)
|
||||||
default_conf = config['default']
|
default_conf = config['default']
|
||||||
manager_util.use_uv = default_conf['use_uv'].lower() == 'true' if 'use_uv' in default_conf else False
|
|
||||||
|
|
||||||
def get_bool(key, default_value):
|
def get_bool(key, default_value):
|
||||||
return default_conf[key].lower() == 'true' if key in default_conf else False
|
return default_conf[key].lower() == 'true' if key in default_conf else False
|
||||||
|
|
||||||
|
manager_util.use_uv = default_conf['use_uv'].lower() == 'true' if 'use_uv' in default_conf else False
|
||||||
|
manager_util.bypass_ssl = get_bool('bypass_ssl', False)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'http_channel_enabled': get_bool('http_channel_enabled', False),
|
'http_channel_enabled': get_bool('http_channel_enabled', False),
|
||||||
'preview_method': default_conf.get('preview_method', manager_funcs.get_current_preview_method()).lower(),
|
'preview_method': default_conf.get('preview_method', manager_funcs.get_current_preview_method()).lower(),
|
||||||
'git_exe': default_conf.get('git_exe', ''),
|
'git_exe': default_conf.get('git_exe', ''),
|
||||||
'use_uv': get_bool('use_uv', False),
|
'use_uv': get_bool('use_uv', True),
|
||||||
'channel_url': default_conf.get('channel_url', DEFAULT_CHANNEL),
|
'channel_url': default_conf.get('channel_url', DEFAULT_CHANNEL),
|
||||||
'default_cache_as_channel_url': get_bool('default_cache_as_channel_url', False),
|
'default_cache_as_channel_url': get_bool('default_cache_as_channel_url', False),
|
||||||
'share_option': default_conf.get('share_option', 'all').lower(),
|
'share_option': default_conf.get('share_option', 'all').lower(),
|
||||||
@@ -1585,16 +1652,20 @@ def read_config():
|
|||||||
}
|
}
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
manager_util.use_uv = False
|
import importlib.util
|
||||||
|
# temporary disable `uv` on Windows by default (https://github.com/Comfy-Org/ComfyUI-Manager/issues/1969)
|
||||||
|
manager_util.use_uv = importlib.util.find_spec("uv") is not None and platform.system() != "Windows"
|
||||||
|
manager_util.bypass_ssl = False
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'http_channel_enabled': False,
|
'http_channel_enabled': False,
|
||||||
'preview_method': manager_funcs.get_current_preview_method(),
|
'preview_method': manager_funcs.get_current_preview_method(),
|
||||||
'git_exe': '',
|
'git_exe': '',
|
||||||
'use_uv': False,
|
'use_uv': manager_util.use_uv,
|
||||||
'channel_url': DEFAULT_CHANNEL,
|
'channel_url': DEFAULT_CHANNEL,
|
||||||
'default_cache_as_channel_url': False,
|
'default_cache_as_channel_url': False,
|
||||||
'share_option': 'all',
|
'share_option': 'all',
|
||||||
'bypass_ssl': False,
|
'bypass_ssl': manager_util.bypass_ssl,
|
||||||
'file_logging': True,
|
'file_logging': True,
|
||||||
'component_policy': 'workflow',
|
'component_policy': 'workflow',
|
||||||
'update_policy': 'stable-comfyui',
|
'update_policy': 'stable-comfyui',
|
||||||
@@ -1712,16 +1783,6 @@ def try_install_script(url, repo_path, install_cmd, instant_execution=False):
|
|||||||
print(f"\n## ComfyUI-Manager: EXECUTE => {install_cmd}")
|
print(f"\n## ComfyUI-Manager: EXECUTE => {install_cmd}")
|
||||||
code = manager_funcs.run_script(install_cmd, cwd=repo_path)
|
code = manager_funcs.run_script(install_cmd, cwd=repo_path)
|
||||||
|
|
||||||
if platform.system() != "Windows":
|
|
||||||
try:
|
|
||||||
if not os.environ.get('__COMFYUI_DESKTOP_VERSION__') and comfy_ui_commit_datetime.date() < comfy_ui_required_commit_datetime.date():
|
|
||||||
print("\n\n###################################################################")
|
|
||||||
print(f"[WARN] ComfyUI-Manager: Your ComfyUI version ({comfy_ui_revision})[{comfy_ui_commit_datetime.date()}] is too old. Please update to the latest version.")
|
|
||||||
print("[WARN] The extension installation feature may not work properly in the current installed ComfyUI version on Windows environment.")
|
|
||||||
print("###################################################################\n\n")
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if code != 0:
|
if code != 0:
|
||||||
if url is None:
|
if url is None:
|
||||||
url = os.path.dirname(repo_path)
|
url = os.path.dirname(repo_path)
|
||||||
@@ -1840,6 +1901,27 @@ def execute_install_script(url, repo_path, lazy_mode=False, instant_execution=Fa
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def install_manager_requirements(repo_path):
|
||||||
|
"""
|
||||||
|
Install packages from manager_requirements.txt if it exists.
|
||||||
|
This is specifically for ComfyUI's manager_requirements.txt.
|
||||||
|
"""
|
||||||
|
manager_requirements_path = os.path.join(repo_path, "manager_requirements.txt")
|
||||||
|
if not os.path.exists(manager_requirements_path):
|
||||||
|
return
|
||||||
|
|
||||||
|
logging.info("[ComfyUI-Manager] Installing manager_requirements.txt")
|
||||||
|
with open(manager_requirements_path, "r") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith('#'):
|
||||||
|
if '#' in line:
|
||||||
|
line = line.split('#')[0].strip()
|
||||||
|
if line:
|
||||||
|
install_cmd = manager_util.make_pip_cmd(["install", line])
|
||||||
|
subprocess.run(install_cmd)
|
||||||
|
|
||||||
|
|
||||||
def git_repo_update_check_with(path, do_fetch=False, do_update=False, no_deps=False):
|
def git_repo_update_check_with(path, do_fetch=False, do_update=False, no_deps=False):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -2373,6 +2455,7 @@ def update_to_stable_comfyui(repo_path):
|
|||||||
else:
|
else:
|
||||||
logging.info(f"[ComfyUI-Manager] Updating ComfyUI: {current_tag} -> {latest_tag}")
|
logging.info(f"[ComfyUI-Manager] Updating ComfyUI: {current_tag} -> {latest_tag}")
|
||||||
repo.git.checkout(latest_tag)
|
repo.git.checkout(latest_tag)
|
||||||
|
execute_install_script("ComfyUI", repo_path, instant_execution=False, no_deps=False)
|
||||||
return 'updated', latest_tag
|
return 'updated', latest_tag
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@@ -2791,7 +2874,7 @@ async def get_unified_total_nodes(channel, mode, regsitry_cache_mode='cache'):
|
|||||||
|
|
||||||
if cnr_id is not None:
|
if cnr_id is not None:
|
||||||
# cnr or nightly version
|
# cnr or nightly version
|
||||||
cnr_ids.remove(cnr_id)
|
cnr_ids.discard(cnr_id)
|
||||||
updatable = False
|
updatable = False
|
||||||
cnr = unified_manager.cnr_map[cnr_id]
|
cnr = unified_manager.cnr_map[cnr_id]
|
||||||
|
|
||||||
@@ -2955,6 +3038,11 @@ async def restore_snapshot(snapshot_path, git_helper_extras=None):
|
|||||||
info = yaml.load(snapshot_file, Loader=yaml.SafeLoader)
|
info = yaml.load(snapshot_file, Loader=yaml.SafeLoader)
|
||||||
info = info['custom_nodes']
|
info = info['custom_nodes']
|
||||||
|
|
||||||
|
if 'pips' in info and info['pips']:
|
||||||
|
pips = info['pips']
|
||||||
|
else:
|
||||||
|
pips = {}
|
||||||
|
|
||||||
# for cnr restore
|
# for cnr restore
|
||||||
cnr_info = info.get('cnr_custom_nodes')
|
cnr_info = info.get('cnr_custom_nodes')
|
||||||
if cnr_info is not None:
|
if cnr_info is not None:
|
||||||
@@ -3161,6 +3249,8 @@ async def restore_snapshot(snapshot_path, git_helper_extras=None):
|
|||||||
unified_manager.repo_install(repo_url, to_path, instant_execution=True, no_deps=False, return_postinstall=False)
|
unified_manager.repo_install(repo_url, to_path, instant_execution=True, no_deps=False, return_postinstall=False)
|
||||||
cloned_repos.append(repo_name)
|
cloned_repos.append(repo_name)
|
||||||
|
|
||||||
|
manager_util.restore_pip_snapshot(pips, git_helper_extras)
|
||||||
|
|
||||||
# print summary
|
# print summary
|
||||||
for x in cloned_repos:
|
for x in cloned_repos:
|
||||||
print(f"[ INSTALLED ] {x}")
|
print(f"[ INSTALLED ] {x}")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,15 @@ import hashlib
|
|||||||
import folder_paths
|
import folder_paths
|
||||||
from server import PromptServer
|
from server import PromptServer
|
||||||
import logging
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from nio import AsyncClient, LoginResponse, UploadResponse
|
||||||
|
matrix_nio_is_available = True
|
||||||
|
except Exception:
|
||||||
|
logging.warning(f"[ComfyUI-Manager] The matrix sharing feature has been disabled because the `matrix-nio` dependency is not installed.\n\tTo use this feature, please run the following command:\n\t{sys.executable} -m pip install matrix-nio\n")
|
||||||
|
matrix_nio_is_available = False
|
||||||
|
|
||||||
|
|
||||||
def extract_model_file_names(json_data):
|
def extract_model_file_names(json_data):
|
||||||
@@ -193,6 +202,14 @@ async def get_esheep_workflow_and_images(request):
|
|||||||
return web.Response(status=200, text=json.dumps(data))
|
return web.Response(status=200, text=json.dumps(data))
|
||||||
|
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/v2/manager/get_matrix_dep_status")
|
||||||
|
async def get_matrix_dep_status(request):
|
||||||
|
if matrix_nio_is_available:
|
||||||
|
return web.Response(status=200, text='available')
|
||||||
|
else:
|
||||||
|
return web.Response(status=200, text='unavailable')
|
||||||
|
|
||||||
|
|
||||||
def set_matrix_auth(json_data):
|
def set_matrix_auth(json_data):
|
||||||
homeserver = json_data['homeserver']
|
homeserver = json_data['homeserver']
|
||||||
username = json_data['username']
|
username = json_data['username']
|
||||||
@@ -332,15 +349,12 @@ async def share_art(request):
|
|||||||
workflowId = upload_workflow_json["workflowId"]
|
workflowId = upload_workflow_json["workflowId"]
|
||||||
|
|
||||||
# check if the user has provided Matrix credentials
|
# check if the user has provided Matrix credentials
|
||||||
if "matrix" in share_destinations:
|
if matrix_nio_is_available and "matrix" in share_destinations:
|
||||||
comfyui_share_room_id = '!LGYSoacpJPhIfBqVfb:matrix.org'
|
comfyui_share_room_id = '!LGYSoacpJPhIfBqVfb:matrix.org'
|
||||||
filename = os.path.basename(asset_filepath)
|
filename = os.path.basename(asset_filepath)
|
||||||
content_type = assetFileType
|
content_type = assetFileType
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from matrix_client.api import MatrixHttpApi
|
|
||||||
from matrix_client.client import MatrixClient
|
|
||||||
|
|
||||||
homeserver = 'matrix.org'
|
homeserver = 'matrix.org'
|
||||||
if matrix_auth:
|
if matrix_auth:
|
||||||
homeserver = matrix_auth.get('homeserver', 'matrix.org')
|
homeserver = matrix_auth.get('homeserver', 'matrix.org')
|
||||||
@@ -348,20 +362,35 @@ async def share_art(request):
|
|||||||
if not homeserver.startswith("https://"):
|
if not homeserver.startswith("https://"):
|
||||||
homeserver = "https://" + homeserver
|
homeserver = "https://" + homeserver
|
||||||
|
|
||||||
client = MatrixClient(homeserver)
|
client = AsyncClient(homeserver, matrix_auth['username'])
|
||||||
try:
|
|
||||||
token = client.login(username=matrix_auth['username'], password=matrix_auth['password'])
|
# Login
|
||||||
if not token:
|
login_resp = await client.login(matrix_auth['password'])
|
||||||
return web.json_response({"error": "Invalid Matrix credentials."}, content_type='application/json', status=400)
|
if not isinstance(login_resp, LoginResponse) or not login_resp.access_token:
|
||||||
except Exception:
|
await client.close()
|
||||||
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)
|
# Upload asset
|
||||||
with open(asset_filepath, 'rb') as f:
|
with open(asset_filepath, 'rb') as f:
|
||||||
mxc_url = matrix.media_upload(f.read(), content_type, filename=filename)['content_uri']
|
upload_resp, _maybe_keys = await client.upload(f, content_type=content_type, filename=filename)
|
||||||
|
asset_data = f.seek(0) or f.read() # get size for info below
|
||||||
|
if not isinstance(upload_resp, UploadResponse) or not upload_resp.content_uri:
|
||||||
|
await client.close()
|
||||||
|
return web.json_response({"error": "Failed to upload asset to Matrix."}, content_type='application/json', status=500)
|
||||||
|
mxc_url = upload_resp.content_uri
|
||||||
|
|
||||||
workflow_json_mxc_url = matrix.media_upload(prompt['workflow'], 'application/json', filename='workflow.json')['content_uri']
|
# Upload workflow JSON
|
||||||
|
import io
|
||||||
|
workflow_json_bytes = json.dumps(prompt['workflow']).encode('utf-8')
|
||||||
|
workflow_io = io.BytesIO(workflow_json_bytes)
|
||||||
|
upload_workflow_resp, _maybe_keys = await client.upload(workflow_io, content_type='application/json', filename='workflow.json')
|
||||||
|
workflow_io.seek(0)
|
||||||
|
if not isinstance(upload_workflow_resp, UploadResponse) or not upload_workflow_resp.content_uri:
|
||||||
|
await client.close()
|
||||||
|
return web.json_response({"error": "Failed to upload workflow to Matrix."}, content_type='application/json', status=500)
|
||||||
|
workflow_json_mxc_url = upload_workflow_resp.content_uri
|
||||||
|
|
||||||
|
# Send text message
|
||||||
text_content = ""
|
text_content = ""
|
||||||
if title:
|
if title:
|
||||||
text_content += f"{title}\n"
|
text_content += f"{title}\n"
|
||||||
@@ -369,11 +398,47 @@ async def share_art(request):
|
|||||||
text_content += f"{description}\n"
|
text_content += f"{description}\n"
|
||||||
if credits:
|
if credits:
|
||||||
text_content += f"\ncredits: {credits}\n"
|
text_content += f"\ncredits: {credits}\n"
|
||||||
matrix.send_message(comfyui_share_room_id, text_content)
|
await client.room_send(
|
||||||
matrix.send_content(comfyui_share_room_id, mxc_url, filename, 'm.image')
|
room_id=comfyui_share_room_id,
|
||||||
matrix.send_content(comfyui_share_room_id, workflow_json_mxc_url, 'workflow.json', 'm.file')
|
message_type="m.room.message",
|
||||||
except Exception:
|
content={"msgtype": "m.text", "body": text_content}
|
||||||
logging.exception("An error occurred")
|
)
|
||||||
|
|
||||||
|
# Send image
|
||||||
|
await client.room_send(
|
||||||
|
room_id=comfyui_share_room_id,
|
||||||
|
message_type="m.room.message",
|
||||||
|
content={
|
||||||
|
"msgtype": "m.image",
|
||||||
|
"body": filename,
|
||||||
|
"url": mxc_url,
|
||||||
|
"info": {
|
||||||
|
"mimetype": content_type,
|
||||||
|
"size": len(asset_data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Send workflow JSON file
|
||||||
|
await client.room_send(
|
||||||
|
room_id=comfyui_share_room_id,
|
||||||
|
message_type="m.room.message",
|
||||||
|
content={
|
||||||
|
"msgtype": "m.file",
|
||||||
|
"body": "workflow.json",
|
||||||
|
"url": workflow_json_mxc_url,
|
||||||
|
"info": {
|
||||||
|
"mimetype": "application/json",
|
||||||
|
"size": len(workflow_json_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await client.close()
|
||||||
|
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
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)
|
||||||
|
|
||||||
return web.json_response({
|
return web.json_response({
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
|
import concurrent.futures
|
||||||
import folder_paths
|
import folder_paths
|
||||||
|
|
||||||
from comfyui_manager.glob import manager_core as core
|
from comfyui_manager.glob import manager_core as core
|
||||||
from comfyui_manager.glob.constants import model_dir_name_map
|
from comfyui_manager.glob.constants import model_dir_name_map, MODEL_DIR_NAMES
|
||||||
|
|
||||||
|
|
||||||
def get_model_dir(data, show_log=False):
|
def get_model_dir(data, show_log=False):
|
||||||
@@ -72,3 +73,89 @@ def get_model_path(data, show_log=False):
|
|||||||
return os.path.join(base_model, os.path.basename(data["url"]))
|
return os.path.join(base_model, os.path.basename(data["url"]))
|
||||||
else:
|
else:
|
||||||
return os.path.join(base_model, data["filename"])
|
return os.path.join(base_model, data["filename"])
|
||||||
|
|
||||||
|
|
||||||
|
def check_model_installed(json_obj):
|
||||||
|
def is_exists(model_dir_name, filename, url):
|
||||||
|
if filename == "<huggingface>":
|
||||||
|
filename = os.path.basename(url)
|
||||||
|
|
||||||
|
dirs = folder_paths.get_folder_paths(model_dir_name)
|
||||||
|
|
||||||
|
for x in dirs:
|
||||||
|
if os.path.exists(os.path.join(x, filename)):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
total_models_files = set()
|
||||||
|
for x in MODEL_DIR_NAMES:
|
||||||
|
for y in folder_paths.get_filename_list(x):
|
||||||
|
total_models_files.add(y)
|
||||||
|
|
||||||
|
def process_model_phase(item):
|
||||||
|
if (
|
||||||
|
"diffusion" not in item["filename"]
|
||||||
|
and "pytorch" not in item["filename"]
|
||||||
|
and "model" not in item["filename"]
|
||||||
|
):
|
||||||
|
# non-general name case
|
||||||
|
if item["filename"] in total_models_files:
|
||||||
|
item["installed"] = "True"
|
||||||
|
return
|
||||||
|
|
||||||
|
if item["save_path"] == "default":
|
||||||
|
model_dir_name = model_dir_name_map.get(item["type"].lower())
|
||||||
|
if model_dir_name is not None:
|
||||||
|
item["installed"] = str(
|
||||||
|
is_exists(model_dir_name, item["filename"], item["url"])
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
item["installed"] = "False"
|
||||||
|
else:
|
||||||
|
model_dir_name = item["save_path"].split("/")[0]
|
||||||
|
if model_dir_name in folder_paths.folder_names_and_paths:
|
||||||
|
if is_exists(model_dir_name, item["filename"], item["url"]):
|
||||||
|
item["installed"] = "True"
|
||||||
|
|
||||||
|
if "installed" not in item:
|
||||||
|
if item["filename"] == "<huggingface>":
|
||||||
|
filename = os.path.basename(item["url"])
|
||||||
|
else:
|
||||||
|
filename = item["filename"]
|
||||||
|
|
||||||
|
fullpath = os.path.join(
|
||||||
|
folder_paths.models_dir, item["save_path"], filename
|
||||||
|
)
|
||||||
|
|
||||||
|
item["installed"] = "True" if os.path.exists(fullpath) else "False"
|
||||||
|
|
||||||
|
with concurrent.futures.ThreadPoolExecutor(8) as executor:
|
||||||
|
for item in json_obj["models"]:
|
||||||
|
executor.submit(process_model_phase, item)
|
||||||
|
|
||||||
|
|
||||||
|
async def check_whitelist_for_model(item):
|
||||||
|
from comfyui_manager.data_models import ManagerDatabaseSource
|
||||||
|
|
||||||
|
json_obj = await core.get_data_by_mode(ManagerDatabaseSource.cache.value, "model-list.json")
|
||||||
|
|
||||||
|
for x in json_obj.get("models", []):
|
||||||
|
if (
|
||||||
|
x["save_path"] == item["save_path"]
|
||||||
|
and x["base"] == item["base"]
|
||||||
|
and x["filename"] == item["filename"]
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
json_obj = await core.get_data_by_mode(ManagerDatabaseSource.local.value, "model-list.json")
|
||||||
|
|
||||||
|
for x in json_obj.get("models", []):
|
||||||
|
if (
|
||||||
|
x["save_path"] == item["save_path"]
|
||||||
|
and x["base"] == item["base"]
|
||||||
|
and x["filename"] == item["filename"]
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from comfyui_manager.glob import manager_core as core
|
from comfyui_manager.glob import manager_core as core
|
||||||
from comfy.cli_args import args
|
from comfy.cli_args import args
|
||||||
|
from comfyui_manager.data_models import SecurityLevel, RiskLevel, ManagerDatabaseSource
|
||||||
|
|
||||||
|
|
||||||
def is_loopback(address):
|
def is_loopback(address):
|
||||||
@@ -12,24 +13,37 @@ def is_loopback(address):
|
|||||||
|
|
||||||
def is_allowed_security_level(level):
|
def is_allowed_security_level(level):
|
||||||
is_local_mode = is_loopback(args.listen)
|
is_local_mode = is_loopback(args.listen)
|
||||||
|
is_personal_cloud = core.get_config()['network_mode'].lower() == 'personal_cloud'
|
||||||
|
|
||||||
if level == "block":
|
if level == RiskLevel.block.value:
|
||||||
return False
|
return False
|
||||||
elif level == "high":
|
elif level == RiskLevel.high_.value:
|
||||||
if is_local_mode:
|
if is_local_mode:
|
||||||
return core.get_config()["security_level"] in ["weak", "normal-"]
|
return core.get_config()['security_level'] in [SecurityLevel.weak.value, SecurityLevel.normal_.value]
|
||||||
|
elif is_personal_cloud:
|
||||||
|
return core.get_config()['security_level'] == SecurityLevel.weak.value
|
||||||
else:
|
else:
|
||||||
return core.get_config()["security_level"] == "weak"
|
return False
|
||||||
elif level == "middle":
|
elif level == RiskLevel.high.value:
|
||||||
return core.get_config()["security_level"] in ["weak", "normal", "normal-"]
|
if is_local_mode:
|
||||||
|
return core.get_config()['security_level'] in [SecurityLevel.weak.value, SecurityLevel.normal_.value]
|
||||||
|
else:
|
||||||
|
return core.get_config()['security_level'] == SecurityLevel.weak.value
|
||||||
|
elif level == RiskLevel.middle_.value:
|
||||||
|
if is_local_mode or is_personal_cloud:
|
||||||
|
return core.get_config()['security_level'] in [SecurityLevel.weak.value, SecurityLevel.normal.value, SecurityLevel.normal_.value]
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
elif level == RiskLevel.middle.value:
|
||||||
|
return core.get_config()['security_level'] in [SecurityLevel.weak.value, SecurityLevel.normal.value, SecurityLevel.normal_.value]
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def get_risky_level(files, pip_packages):
|
async def get_risky_level(files, pip_packages):
|
||||||
json_data1 = await core.get_data_by_mode("local", "custom-node-list.json")
|
json_data1 = await core.get_data_by_mode(ManagerDatabaseSource.local.value, "custom-node-list.json")
|
||||||
json_data2 = await core.get_data_by_mode(
|
json_data2 = await core.get_data_by_mode(
|
||||||
"cache",
|
ManagerDatabaseSource.cache.value,
|
||||||
"custom-node-list.json",
|
"custom-node-list.json",
|
||||||
channel_url="https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main",
|
channel_url="https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main",
|
||||||
)
|
)
|
||||||
@@ -40,7 +54,7 @@ async def get_risky_level(files, pip_packages):
|
|||||||
|
|
||||||
for x in files:
|
for x in files:
|
||||||
if x not in all_urls:
|
if x not in all_urls:
|
||||||
return "high"
|
return RiskLevel.high_.value
|
||||||
|
|
||||||
all_pip_packages = set()
|
all_pip_packages = set()
|
||||||
for x in json_data1["custom_nodes"] + json_data2["custom_nodes"]:
|
for x in json_data1["custom_nodes"] + json_data2["custom_nodes"]:
|
||||||
@@ -48,6 +62,6 @@ async def get_risky_level(files, pip_packages):
|
|||||||
|
|
||||||
for p in pip_packages:
|
for p in pip_packages:
|
||||||
if p not in all_pip_packages:
|
if p not in all_pip_packages:
|
||||||
return "block"
|
return RiskLevel.block.value
|
||||||
|
|
||||||
return "middle"
|
return RiskLevel.middle_.value
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ This directory contains the JavaScript frontend implementation for ComfyUI-Manag
|
|||||||
## Sharing Components
|
## Sharing Components
|
||||||
|
|
||||||
- **comfyui-share-common.js**: Base functionality for workflow sharing features.
|
- **comfyui-share-common.js**: Base functionality for workflow sharing features.
|
||||||
- **comfyui-share-copus.js**: Integration with the ComfyUI Opus sharing platform.
|
- **comfyui-share-copus.js**: Integration with the ComfyUI Copus sharing platform.
|
||||||
- **comfyui-share-openart.js**: Integration with the OpenArt sharing platform.
|
- **comfyui-share-openart.js**: Integration with the OpenArt sharing platform.
|
||||||
- **comfyui-share-youml.js**: Integration with the YouML sharing platform.
|
- **comfyui-share-youml.js**: Integration with the YouML sharing platform.
|
||||||
|
|
||||||
|
|||||||
@@ -222,9 +222,6 @@ function isBeforeFrontendVersion(compareVersion) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const is_legacy_front = () => isBeforeFrontendVersion('1.2.49');
|
|
||||||
const isNotNewManagerUI = () => isBeforeFrontendVersion('1.16.4');
|
|
||||||
|
|
||||||
document.head.appendChild(docStyle);
|
document.head.appendChild(docStyle);
|
||||||
|
|
||||||
var update_comfyui_button = null;
|
var update_comfyui_button = null;
|
||||||
@@ -1517,11 +1514,6 @@ app.registerExtension({
|
|||||||
tooltip: "Share"
|
tooltip: "Share"
|
||||||
}).element
|
}).element
|
||||||
);
|
);
|
||||||
|
|
||||||
const shouldShowLegacyMenuItems = isNotNewManagerUI();
|
|
||||||
if (shouldShowLegacyMenuItems) {
|
|
||||||
app.menu?.settingsGroup.element.before(cmGroup.element);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch(exception) {
|
catch(exception) {
|
||||||
console.log('ComfyUI is outdated. New style menu based features are disabled.');
|
console.log('ComfyUI is outdated. New style menu based features are disabled.');
|
||||||
|
|||||||
@@ -552,6 +552,20 @@ export class ShareDialog extends ComfyDialog {
|
|||||||
this.matrix_destination_checkbox.style.color = "var(--fg-color)";
|
this.matrix_destination_checkbox.style.color = "var(--fg-color)";
|
||||||
this.matrix_destination_checkbox.checked = this.share_option === 'matrix'; //true;
|
this.matrix_destination_checkbox.checked = this.share_option === 'matrix'; //true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
api.fetchApi(`/v2/manager/get_matrix_dep_status`)
|
||||||
|
.then(response => response.text())
|
||||||
|
.then(data => {
|
||||||
|
if(data == 'unavailable') {
|
||||||
|
matrix_destination_checkbox_text.style.textDecoration = "line-through";
|
||||||
|
this.matrix_destination_checkbox.disabled = true;
|
||||||
|
this.matrix_destination_checkbox.title = "It has been disabled because the 'matrix-nio' dependency is not installed. Please install this dependency to use the matrix sharing feature.";
|
||||||
|
matrix_destination_checkbox_text.title = "It has been disabled because the 'matrix-nio' dependency is not installed. Please install this dependency to use the matrix sharing feature.";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {});
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
this.comfyworkflows_destination_checkbox = $el("input", { type: 'checkbox', id: "comfyworkflows_destination" }, [])
|
this.comfyworkflows_destination_checkbox = $el("input", { type: 'checkbox', id: "comfyworkflows_destination" }, [])
|
||||||
const comfyworkflows_destination_checkbox_text = $el("label", {}, [" ComfyWorkflows.com"])
|
const comfyworkflows_destination_checkbox_text = $el("label", {}, [" ComfyWorkflows.com"])
|
||||||
this.comfyworkflows_destination_checkbox.style.color = "var(--fg-color)";
|
this.comfyworkflows_destination_checkbox.style.color = "var(--fg-color)";
|
||||||
|
|||||||
@@ -201,13 +201,15 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
});
|
});
|
||||||
this.LockInput = $el("input", {
|
this.LockInput = $el("input", {
|
||||||
type: "text",
|
type: "text",
|
||||||
placeholder: "",
|
placeholder: "0",
|
||||||
style: {
|
style: {
|
||||||
width: "100px",
|
width: "100px",
|
||||||
padding: "7px",
|
padding: "7px",
|
||||||
|
paddingLeft: "30px",
|
||||||
borderRadius: "4px",
|
borderRadius: "4px",
|
||||||
border: "1px solid #ddd",
|
border: "1px solid #ddd",
|
||||||
boxSizing: "border-box",
|
boxSizing: "border-box",
|
||||||
|
position: "relative",
|
||||||
},
|
},
|
||||||
oninput: (event) => {
|
oninput: (event) => {
|
||||||
let input = event.target.value;
|
let input = event.target.value;
|
||||||
@@ -342,15 +344,11 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
["0/70"]
|
["0/70"]
|
||||||
);
|
);
|
||||||
// Additional Inputs Section
|
// Additional Inputs Section
|
||||||
const additionalInputsSection = $el(
|
const additionalInputsSection = $el("div", { style: { ...sectionStyle } }, [
|
||||||
"div",
|
|
||||||
{ style: { ...sectionStyle, } },
|
|
||||||
[
|
|
||||||
$el("label", { style: labelStyle }, ["3️⃣ Title "]),
|
$el("label", { style: labelStyle }, ["3️⃣ Title "]),
|
||||||
this.TitleInput,
|
this.TitleInput,
|
||||||
titleNumDom,
|
titleNumDom,
|
||||||
]
|
]);
|
||||||
);
|
|
||||||
const SubtitleSection = $el("div", { style: sectionStyle }, [
|
const SubtitleSection = $el("div", { style: sectionStyle }, [
|
||||||
$el("label", { style: labelStyle }, ["4️⃣ Subtitle "]),
|
$el("label", { style: labelStyle }, ["4️⃣ Subtitle "]),
|
||||||
this.SubTitleInput,
|
this.SubTitleInput,
|
||||||
@@ -379,7 +377,7 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const blockChainSection_lock = $el("div", { style: sectionStyle }, [
|
const blockChainSection_lock = $el("div", { style: sectionStyle }, [
|
||||||
$el("label", { style: labelStyle }, ["6️⃣ Pay to download"]),
|
$el("label", { style: labelStyle }, ["6️⃣ Download threshold"]),
|
||||||
$el(
|
$el(
|
||||||
"label",
|
"label",
|
||||||
{
|
{
|
||||||
@@ -392,11 +390,42 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
this.radioButtonsCheck_lock,
|
this.radioButtonsCheck_lock,
|
||||||
$el("div", { style: { marginLeft: "5px" ,display:'flex',alignItems:'center'} }, [
|
$el(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
marginLeft: "5px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
position: "relative",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[
|
||||||
$el("span", { style: { marginLeft: "5px" } }, ["ON"]),
|
$el("span", { style: { marginLeft: "5px" } }, ["ON"]),
|
||||||
$el("span", { style: { marginLeft: "20px",marginRight:'10px' ,color:'#fff'} }, ["Price US$"]),
|
$el(
|
||||||
this.LockInput
|
"span",
|
||||||
]),
|
{
|
||||||
|
style: {
|
||||||
|
marginLeft: "20px",
|
||||||
|
marginRight: "10px",
|
||||||
|
color: "#fff",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
["Unlock with"]
|
||||||
|
),
|
||||||
|
$el("img", {
|
||||||
|
style: {
|
||||||
|
width: "16px",
|
||||||
|
height: "16px",
|
||||||
|
position: "absolute",
|
||||||
|
right: "75px",
|
||||||
|
zIndex: "100",
|
||||||
|
},
|
||||||
|
src: "https://static.copus.io/images/admin/202507/prod/e2919a1d8f3c2d99d3b8fe27ff94b841.png",
|
||||||
|
}),
|
||||||
|
this.LockInput,
|
||||||
|
]
|
||||||
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
$el(
|
$el(
|
||||||
@@ -404,14 +433,25 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
{ style: { display: "flex", alignItems: "center", cursor: "pointer" } },
|
{ style: { display: "flex", alignItems: "center", cursor: "pointer" } },
|
||||||
[
|
[
|
||||||
this.radioButtonsCheckOff_lock,
|
this.radioButtonsCheckOff_lock,
|
||||||
$el("span", { style: { marginLeft: "5px" } }, ["OFF"]),
|
$el(
|
||||||
|
"div",
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
marginLeft: "5px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[$el("span", { style: { marginLeft: "5px" } }, ["OFF"])]
|
||||||
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
|
|
||||||
$el(
|
$el(
|
||||||
"p",
|
"p",
|
||||||
{ style: { fontSize: "16px", color: "#fff", margin: "10px 0 0 0" } },
|
{ style: { fontSize: "16px", color: "#fff", margin: "10px 0 0 0" } },
|
||||||
["Get paid from your workflow. You can change the price and withdraw your earnings on Copus."]
|
[
|
||||||
|
]
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -432,7 +472,7 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const blockChainSection = $el("div", { style: sectionStyle }, [
|
const blockChainSection = $el("div", { style: sectionStyle }, [
|
||||||
$el("label", { style: labelStyle }, ["7️⃣ Store on blockchain "]),
|
$el("label", { style: labelStyle }, ["8️⃣ Store on blockchain "]),
|
||||||
$el(
|
$el(
|
||||||
"label",
|
"label",
|
||||||
{
|
{
|
||||||
@@ -463,6 +503,139 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
this.ratingRadioButtonsCheck0 = $el("input", {
|
||||||
|
type: "radio",
|
||||||
|
name: "content_rating",
|
||||||
|
value: "0",
|
||||||
|
id: "content_rating0",
|
||||||
|
});
|
||||||
|
this.ratingRadioButtonsCheck1 = $el("input", {
|
||||||
|
type: "radio",
|
||||||
|
name: "content_rating",
|
||||||
|
value: "1",
|
||||||
|
id: "content_rating1",
|
||||||
|
});
|
||||||
|
this.ratingRadioButtonsCheck2 = $el("input", {
|
||||||
|
type: "radio",
|
||||||
|
name: "content_rating",
|
||||||
|
value: "2",
|
||||||
|
id: "content_rating2",
|
||||||
|
});
|
||||||
|
this.ratingRadioButtonsCheck_1 = $el("input", {
|
||||||
|
type: "radio",
|
||||||
|
name: "content_rating",
|
||||||
|
value: "-1",
|
||||||
|
id: "content_rating_1",
|
||||||
|
checked: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// content rating
|
||||||
|
const contentRatingSection = $el("div", { style: sectionStyle }, [
|
||||||
|
$el("label", { style: labelStyle }, ["7️⃣ Content rating "]),
|
||||||
|
$el(
|
||||||
|
"label",
|
||||||
|
{
|
||||||
|
style: {
|
||||||
|
marginTop: "10px",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
cursor: "pointer",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
[
|
||||||
|
this.ratingRadioButtonsCheck0,
|
||||||
|
$el("img", {
|
||||||
|
style: {
|
||||||
|
width: "12px",
|
||||||
|
height: "12px",
|
||||||
|
marginLeft: "5px",
|
||||||
|
},
|
||||||
|
src: "https://static.copus.io/images/client/202507/test/b9f17da83b054d53cd0cb4508c2c30dc.png",
|
||||||
|
}),
|
||||||
|
$el("span", { style: { marginLeft: "5px", color: "#fff" } }, [
|
||||||
|
"All ages",
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
$el(
|
||||||
|
"p",
|
||||||
|
{ style: { fontSize: "10px", color: "#fff", marginLeft: "20px" } },
|
||||||
|
["Safe for all viewers; no profanity, violence, or mature themes."]
|
||||||
|
),
|
||||||
|
$el(
|
||||||
|
"label",
|
||||||
|
{ style: { display: "flex", alignItems: "center", cursor: "pointer" } },
|
||||||
|
[
|
||||||
|
this.ratingRadioButtonsCheck1,
|
||||||
|
$el("img", {
|
||||||
|
style: {
|
||||||
|
width: "12px",
|
||||||
|
height: "12px",
|
||||||
|
marginLeft: "5px",
|
||||||
|
},
|
||||||
|
src: "https://static.copus.io/images/client/202507/test/7848bc0d3690671df21c7cf00c4cfc81.png",
|
||||||
|
}),
|
||||||
|
$el("span", { style: { marginLeft: "5px", color: "#fff" } }, [
|
||||||
|
"13+ (Teen)",
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
$el(
|
||||||
|
"p",
|
||||||
|
{ style: { fontSize: "10px", color: "#fff", marginLeft: "20px" } },
|
||||||
|
[
|
||||||
|
"Mild language, light themes, or cartoon violence; no explicit content. ",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
$el(
|
||||||
|
"label",
|
||||||
|
{ style: { display: "flex", alignItems: "center", cursor: "pointer" } },
|
||||||
|
[
|
||||||
|
this.ratingRadioButtonsCheck2,
|
||||||
|
$el("img", {
|
||||||
|
style: {
|
||||||
|
width: "12px",
|
||||||
|
height: "12px",
|
||||||
|
marginLeft: "5px",
|
||||||
|
},
|
||||||
|
src: "https://static.copus.io/images/client/202507/test/bc51839c208d68d91173e43c23bff039.png",
|
||||||
|
}),
|
||||||
|
$el("span", { style: { marginLeft: "5px", color: "#fff" } }, [
|
||||||
|
"18+ (Explicit)",
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
$el(
|
||||||
|
"p",
|
||||||
|
{ style: { fontSize: "10px", color: "#fff", marginLeft: "20px" } },
|
||||||
|
[
|
||||||
|
"Explicit content, including sexual content, strong violence, or intense themes. ",
|
||||||
|
]
|
||||||
|
),
|
||||||
|
$el(
|
||||||
|
"label",
|
||||||
|
{ style: { display: "flex", alignItems: "center", cursor: "pointer" } },
|
||||||
|
[
|
||||||
|
this.ratingRadioButtonsCheck_1,
|
||||||
|
$el("img", {
|
||||||
|
style: {
|
||||||
|
width: "12px",
|
||||||
|
height: "12px",
|
||||||
|
marginLeft: "5px",
|
||||||
|
},
|
||||||
|
src: "https://static.copus.io/images/client/202507/test/5c802fdcaaea4e7bbed37393eec0d5ba.png",
|
||||||
|
}),
|
||||||
|
$el("span", { style: { marginLeft: "5px", color: "#fff" } }, [
|
||||||
|
"Not Rated",
|
||||||
|
]),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
$el(
|
||||||
|
"p",
|
||||||
|
{ style: { fontSize: "10px", color: "#fff", marginLeft: "20px" } },
|
||||||
|
["No age rating provided."]
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
// Message Section
|
// Message Section
|
||||||
this.message = $el(
|
this.message = $el(
|
||||||
@@ -526,6 +699,7 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
DescriptionSection,
|
DescriptionSection,
|
||||||
// contestSection,
|
// contestSection,
|
||||||
blockChainSection_lock,
|
blockChainSection_lock,
|
||||||
|
contentRatingSection,
|
||||||
blockChainSection,
|
blockChainSection,
|
||||||
this.message,
|
this.message,
|
||||||
buttonsSection,
|
buttonsSection,
|
||||||
@@ -587,7 +761,9 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
url: data,
|
url: data,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error("make sure your API key is correct and try again later");
|
throw new Error(
|
||||||
|
"make sure your API key is correct and try again later"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e?.response?.status === 413) {
|
if (e?.response?.status === 413) {
|
||||||
@@ -628,8 +804,15 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
subTitle: this.SubTitleInput.value,
|
subTitle: this.SubTitleInput.value,
|
||||||
content: this.descriptionInput.value,
|
content: this.descriptionInput.value,
|
||||||
storeOnChain: this.radioButtonsCheck.checked ? true : false,
|
storeOnChain: this.radioButtonsCheck.checked ? true : false,
|
||||||
lockState:this.radioButtonsCheck_lock.checked ? 2 : 0,
|
lockState: this.radioButtonsCheck_lock.checked ? 2 : 0,
|
||||||
unlockPrice:this.LockInput.value,
|
unlockPrice: this.LockInput.value,
|
||||||
|
rating: this.ratingRadioButtonsCheck0.checked
|
||||||
|
? 0
|
||||||
|
: this.ratingRadioButtonsCheck1.checked
|
||||||
|
? 1
|
||||||
|
: this.ratingRadioButtonsCheck2.checked
|
||||||
|
? 2
|
||||||
|
: -1,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!this.keyInput.value) {
|
if (!this.keyInput.value) {
|
||||||
@@ -644,8 +827,8 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
throw new Error("Title is required");
|
throw new Error("Title is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.radioButtonsCheck_lock.checked){
|
if (this.radioButtonsCheck_lock.checked) {
|
||||||
if (!this.LockInput.value){
|
if (!this.LockInput.value) {
|
||||||
throw new Error("Price is required");
|
throw new Error("Price is required");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -696,7 +879,7 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (res.status && res.data.status && res.data) {
|
if (res.status && res.data.status && res.data) {
|
||||||
localStorage.setItem("copus_token",this.keyInput.value);
|
localStorage.setItem("copus_token", this.keyInput.value);
|
||||||
const { data } = res.data;
|
const { data } = res.data;
|
||||||
if (data) {
|
if (data) {
|
||||||
const url = `${DEFAULT_HOMEPAGE_URL}/work/${data}`;
|
const url = `${DEFAULT_HOMEPAGE_URL}/work/${data}`;
|
||||||
@@ -757,7 +940,7 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
this.element.style.display = "block";
|
this.element.style.display = "block";
|
||||||
this.previewImage.src = "";
|
this.previewImage.src = "";
|
||||||
this.previewImage.style.display = "none";
|
this.previewImage.style.display = "none";
|
||||||
this.keyInput.value = apiToken!=null?apiToken:"";
|
this.keyInput.value = apiToken != null ? apiToken : "";
|
||||||
this.uploadedImages = [];
|
this.uploadedImages = [];
|
||||||
this.allFilesImages = [];
|
this.allFilesImages = [];
|
||||||
this.allFiles = [];
|
this.allFiles = [];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
.cn-manager {
|
.cn-manager {
|
||||||
--grid-font: -apple-system, BlinkMacSystemFont, "Segue UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
--grid-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||||
z-index: 1099;
|
z-index: 1099;
|
||||||
width: 80%;
|
width: 80%;
|
||||||
height: 80%;
|
height: 80%;
|
||||||
|
|||||||
@@ -714,6 +714,7 @@ export class CustomNodesManager {
|
|||||||
link.href = rowItem.reference;
|
link.href = rowItem.reference;
|
||||||
link.target = '_blank';
|
link.target = '_blank';
|
||||||
link.innerHTML = `<b>${title}</b>`;
|
link.innerHTML = `<b>${title}</b>`;
|
||||||
|
link.title = rowItem.originalData.id;
|
||||||
container.appendChild(link);
|
container.appendChild(link);
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
@@ -1625,17 +1626,35 @@ export class CustomNodesManager {
|
|||||||
getNodesInWorkflow() {
|
getNodesInWorkflow() {
|
||||||
let usedGroupNodes = new Set();
|
let usedGroupNodes = new Set();
|
||||||
let allUsedNodes = {};
|
let allUsedNodes = {};
|
||||||
|
const visitedGraphs = new Set();
|
||||||
|
|
||||||
for(let k in app.graph._nodes) {
|
const visitGraph = (graph) => {
|
||||||
let node = app.graph._nodes[k];
|
if (!graph || visitedGraphs.has(graph)) return;
|
||||||
|
visitedGraphs.add(graph);
|
||||||
|
|
||||||
if(node.type.startsWith('workflow>')) {
|
const nodes = graph._nodes || graph.nodes || [];
|
||||||
|
for(let k in nodes) {
|
||||||
|
let node = nodes[k];
|
||||||
|
if (!node) continue;
|
||||||
|
|
||||||
|
// If it's a SubgraphNode, recurse into its graph and continue searching
|
||||||
|
if (node.isSubgraphNode?.() && node.subgraph) {
|
||||||
|
visitGraph(node.subgraph);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node.type) continue;
|
||||||
|
|
||||||
|
// Group nodes / components
|
||||||
|
if(typeof node.type === 'string' && node.type.startsWith('workflow>')) {
|
||||||
usedGroupNodes.add(node.type.slice(9));
|
usedGroupNodes.add(node.type.slice(9));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
allUsedNodes[node.type] = node;
|
allUsedNodes[node.type] = node;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
visitGraph(app.graph);
|
||||||
|
|
||||||
for(let k of usedGroupNodes) {
|
for(let k of usedGroupNodes) {
|
||||||
let subnodes = app.graph.extra.groupNodes[k]?.nodes;
|
let subnodes = app.graph.extra.groupNodes[k]?.nodes;
|
||||||
|
|||||||
@@ -41,11 +41,12 @@ from ..common.enums import NetworkMode, SecurityLevel, DBMode
|
|||||||
from ..common import context
|
from ..common import context
|
||||||
|
|
||||||
|
|
||||||
version_code = [4, 0]
|
version_code = [4, 0, 3]
|
||||||
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
version_str = f"V{version_code[0]}.{version_code[1]}" + (f'.{version_code[2]}' if len(version_code) > 2 else '')
|
||||||
|
|
||||||
|
|
||||||
DEFAULT_CHANNEL = "https://raw.githubusercontent.com/Comfy-Org/ComfyUI-Manager/main"
|
DEFAULT_CHANNEL = "https://raw.githubusercontent.com/Comfy-Org/ComfyUI-Manager/main"
|
||||||
|
DEFAULT_CHANNEL_LEGACY = "https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main"
|
||||||
|
|
||||||
|
|
||||||
default_custom_nodes_path = None
|
default_custom_nodes_path = None
|
||||||
@@ -160,7 +161,7 @@ comfy_ui_revision = "Unknown"
|
|||||||
comfy_ui_commit_datetime = datetime(1900, 1, 1, 0, 0, 0)
|
comfy_ui_commit_datetime = datetime(1900, 1, 1, 0, 0, 0)
|
||||||
|
|
||||||
channel_dict = None
|
channel_dict = None
|
||||||
valid_channels = {'default', 'local'}
|
valid_channels = {'default', 'local', DEFAULT_CHANNEL, DEFAULT_CHANNEL_LEGACY}
|
||||||
channel_list = None
|
channel_list = None
|
||||||
|
|
||||||
|
|
||||||
@@ -304,16 +305,84 @@ class ManagedResult:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class NormalizedKeyDict:
|
||||||
|
def __init__(self):
|
||||||
|
self._store = {}
|
||||||
|
self._key_map = {}
|
||||||
|
|
||||||
|
def _normalize_key(self, key):
|
||||||
|
if isinstance(key, str):
|
||||||
|
return key.strip().lower()
|
||||||
|
return key
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
norm_key = self._normalize_key(key)
|
||||||
|
self._key_map[norm_key] = key
|
||||||
|
self._store[key] = value
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
norm_key = self._normalize_key(key)
|
||||||
|
original_key = self._key_map[norm_key]
|
||||||
|
return self._store[original_key]
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
norm_key = self._normalize_key(key)
|
||||||
|
original_key = self._key_map.pop(norm_key)
|
||||||
|
del self._store[original_key]
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return self._normalize_key(key) in self._key_map
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return self[key] if key in self else default
|
||||||
|
|
||||||
|
def setdefault(self, key, default=None):
|
||||||
|
if key in self:
|
||||||
|
return self[key]
|
||||||
|
self[key] = default
|
||||||
|
return default
|
||||||
|
|
||||||
|
def pop(self, key, default=None):
|
||||||
|
if key in self:
|
||||||
|
val = self[key]
|
||||||
|
del self[key]
|
||||||
|
return val
|
||||||
|
if default is not None:
|
||||||
|
return default
|
||||||
|
raise KeyError(key)
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self._store.keys()
|
||||||
|
|
||||||
|
def values(self):
|
||||||
|
return self._store.values()
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return self._store.items()
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self._store)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._store)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return repr(self._store)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return dict(self._store)
|
||||||
|
|
||||||
|
|
||||||
class UnifiedManager:
|
class UnifiedManager:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.installed_node_packages: dict[str, InstalledNodePackage] = {}
|
self.installed_node_packages: dict[str, InstalledNodePackage] = {}
|
||||||
|
|
||||||
self.cnr_inactive_nodes = {} # node_id -> node_version -> fullpath
|
self.cnr_inactive_nodes = NormalizedKeyDict() # node_id -> node_version -> fullpath
|
||||||
self.nightly_inactive_nodes = {} # node_id -> fullpath
|
self.nightly_inactive_nodes = NormalizedKeyDict() # node_id -> fullpath
|
||||||
self.unknown_inactive_nodes = {} # node_id -> repo url * fullpath
|
self.unknown_inactive_nodes = {} # node_id -> repo url * fullpath
|
||||||
self.active_nodes = {} # node_id -> node_version * fullpath
|
self.active_nodes = NormalizedKeyDict() # node_id -> node_version * fullpath
|
||||||
self.unknown_active_nodes = {} # node_id -> repo url * fullpath
|
self.unknown_active_nodes = {} # node_id -> repo url * fullpath
|
||||||
self.cnr_map = {} # node_id -> cnr info
|
self.cnr_map = NormalizedKeyDict() # node_id -> cnr info
|
||||||
self.repo_cnr_map = {} # repo_url -> cnr info
|
self.repo_cnr_map = {} # repo_url -> cnr info
|
||||||
self.custom_node_map_cache = {} # (channel, mode) -> augmented custom node list json
|
self.custom_node_map_cache = {} # (channel, mode) -> augmented custom node list json
|
||||||
self.processed_install = set()
|
self.processed_install = set()
|
||||||
@@ -721,7 +790,7 @@ class UnifiedManager:
|
|||||||
channel = normalize_channel(channel)
|
channel = normalize_channel(channel)
|
||||||
nodes = await self.load_nightly(channel, mode)
|
nodes = await self.load_nightly(channel, mode)
|
||||||
|
|
||||||
res = {}
|
res = NormalizedKeyDict()
|
||||||
added_cnr = set()
|
added_cnr = set()
|
||||||
for v in nodes.values():
|
for v in nodes.values():
|
||||||
v = v[0]
|
v = v[0]
|
||||||
@@ -1322,6 +1391,7 @@ class UnifiedManager:
|
|||||||
return ManagedResult('skip')
|
return ManagedResult('skip')
|
||||||
elif self.is_disabled(node_id):
|
elif self.is_disabled(node_id):
|
||||||
return self.unified_enable(node_id)
|
return self.unified_enable(node_id)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
version_spec = self.resolve_unspecified_version(node_id)
|
version_spec = self.resolve_unspecified_version(node_id)
|
||||||
|
|
||||||
@@ -1557,16 +1627,18 @@ def read_config():
|
|||||||
config = configparser.ConfigParser(strict=False)
|
config = configparser.ConfigParser(strict=False)
|
||||||
config.read(context.manager_config_path)
|
config.read(context.manager_config_path)
|
||||||
default_conf = config['default']
|
default_conf = config['default']
|
||||||
manager_util.use_uv = default_conf['use_uv'].lower() == 'true' if 'use_uv' in default_conf else False
|
|
||||||
|
|
||||||
def get_bool(key, default_value):
|
def get_bool(key, default_value):
|
||||||
return default_conf[key].lower() == 'true' if key in default_conf else False
|
return default_conf[key].lower() == 'true' if key in default_conf else False
|
||||||
|
|
||||||
|
manager_util.use_uv = default_conf['use_uv'].lower() == 'true' if 'use_uv' in default_conf else False
|
||||||
|
manager_util.bypass_ssl = get_bool('bypass_ssl', False)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'http_channel_enabled': get_bool('http_channel_enabled', False),
|
'http_channel_enabled': get_bool('http_channel_enabled', False),
|
||||||
'preview_method': default_conf.get('preview_method', manager_funcs.get_current_preview_method()).lower(),
|
'preview_method': default_conf.get('preview_method', manager_funcs.get_current_preview_method()).lower(),
|
||||||
'git_exe': default_conf.get('git_exe', ''),
|
'git_exe': default_conf.get('git_exe', ''),
|
||||||
'use_uv': get_bool('use_uv', False),
|
'use_uv': get_bool('use_uv', True),
|
||||||
'channel_url': default_conf.get('channel_url', DEFAULT_CHANNEL),
|
'channel_url': default_conf.get('channel_url', DEFAULT_CHANNEL),
|
||||||
'default_cache_as_channel_url': get_bool('default_cache_as_channel_url', False),
|
'default_cache_as_channel_url': get_bool('default_cache_as_channel_url', False),
|
||||||
'share_option': default_conf.get('share_option', 'all').lower(),
|
'share_option': default_conf.get('share_option', 'all').lower(),
|
||||||
@@ -1585,15 +1657,17 @@ def read_config():
|
|||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
manager_util.use_uv = False
|
manager_util.use_uv = False
|
||||||
|
manager_util.bypass_ssl = False
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'http_channel_enabled': False,
|
'http_channel_enabled': False,
|
||||||
'preview_method': manager_funcs.get_current_preview_method(),
|
'preview_method': manager_funcs.get_current_preview_method(),
|
||||||
'git_exe': '',
|
'git_exe': '',
|
||||||
'use_uv': False,
|
'use_uv': True,
|
||||||
'channel_url': DEFAULT_CHANNEL,
|
'channel_url': DEFAULT_CHANNEL,
|
||||||
'default_cache_as_channel_url': False,
|
'default_cache_as_channel_url': False,
|
||||||
'share_option': 'all',
|
'share_option': 'all',
|
||||||
'bypass_ssl': False,
|
'bypass_ssl': manager_util.bypass_ssl,
|
||||||
'file_logging': True,
|
'file_logging': True,
|
||||||
'component_policy': 'workflow',
|
'component_policy': 'workflow',
|
||||||
'update_policy': 'stable-comfyui',
|
'update_policy': 'stable-comfyui',
|
||||||
@@ -1839,6 +1913,27 @@ def execute_install_script(url, repo_path, lazy_mode=False, instant_execution=Fa
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def install_manager_requirements(repo_path):
|
||||||
|
"""
|
||||||
|
Install packages from manager_requirements.txt if it exists.
|
||||||
|
This is specifically for ComfyUI's manager_requirements.txt.
|
||||||
|
"""
|
||||||
|
manager_requirements_path = os.path.join(repo_path, "manager_requirements.txt")
|
||||||
|
if not os.path.exists(manager_requirements_path):
|
||||||
|
return
|
||||||
|
|
||||||
|
logging.info("[ComfyUI-Manager] Installing manager_requirements.txt")
|
||||||
|
with open(manager_requirements_path, "r") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith('#'):
|
||||||
|
if '#' in line:
|
||||||
|
line = line.split('#')[0].strip()
|
||||||
|
if line:
|
||||||
|
install_cmd = manager_util.make_pip_cmd(["install", line])
|
||||||
|
subprocess.run(install_cmd)
|
||||||
|
|
||||||
|
|
||||||
def git_repo_update_check_with(path, do_fetch=False, do_update=False, no_deps=False):
|
def git_repo_update_check_with(path, do_fetch=False, do_update=False, no_deps=False):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -2358,6 +2453,7 @@ def update_to_stable_comfyui(repo_path):
|
|||||||
else:
|
else:
|
||||||
logging.info(f"[ComfyUI-Manager] Updating ComfyUI: {current_tag} -> {latest_tag}")
|
logging.info(f"[ComfyUI-Manager] Updating ComfyUI: {current_tag} -> {latest_tag}")
|
||||||
repo.git.checkout(latest_tag)
|
repo.git.checkout(latest_tag)
|
||||||
|
execute_install_script("ComfyUI", repo_path, instant_execution=False, no_deps=False)
|
||||||
return 'updated', latest_tag
|
return 'updated', latest_tag
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@@ -2489,9 +2585,13 @@ def check_state_of_git_node_pack_single(item, do_fetch=False, do_update_check=Tr
|
|||||||
|
|
||||||
|
|
||||||
def get_installed_pip_packages():
|
def get_installed_pip_packages():
|
||||||
|
try:
|
||||||
# extract pip package infos
|
# extract pip package infos
|
||||||
cmd = manager_util.make_pip_cmd(['freeze'])
|
cmd = manager_util.make_pip_cmd(['freeze'])
|
||||||
pips = subprocess.check_output(cmd, text=True).split('\n')
|
pips = subprocess.check_output(cmd, text=True).split('\n')
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning("[ComfyUI-Manager] Could not enumerate pip packages for snapshot: %s", e)
|
||||||
|
return {}
|
||||||
|
|
||||||
res = {}
|
res = {}
|
||||||
for x in pips:
|
for x in pips:
|
||||||
@@ -2776,7 +2876,7 @@ async def get_unified_total_nodes(channel, mode, regsitry_cache_mode='cache'):
|
|||||||
|
|
||||||
if cnr_id is not None:
|
if cnr_id is not None:
|
||||||
# cnr or nightly version
|
# cnr or nightly version
|
||||||
cnr_ids.remove(cnr_id)
|
cnr_ids.discard(cnr_id)
|
||||||
updatable = False
|
updatable = False
|
||||||
cnr = unified_manager.cnr_map[cnr_id]
|
cnr = unified_manager.cnr_map[cnr_id]
|
||||||
|
|
||||||
@@ -2940,6 +3040,11 @@ async def restore_snapshot(snapshot_path, git_helper_extras=None):
|
|||||||
info = yaml.load(snapshot_file, Loader=yaml.SafeLoader)
|
info = yaml.load(snapshot_file, Loader=yaml.SafeLoader)
|
||||||
info = info['custom_nodes']
|
info = info['custom_nodes']
|
||||||
|
|
||||||
|
if 'pips' in info and info['pips']:
|
||||||
|
pips = info['pips']
|
||||||
|
else:
|
||||||
|
pips = {}
|
||||||
|
|
||||||
# for cnr restore
|
# for cnr restore
|
||||||
cnr_info = info.get('cnr_custom_nodes')
|
cnr_info = info.get('cnr_custom_nodes')
|
||||||
if cnr_info is not None:
|
if cnr_info is not None:
|
||||||
@@ -3146,6 +3251,8 @@ async def restore_snapshot(snapshot_path, git_helper_extras=None):
|
|||||||
unified_manager.repo_install(repo_url, to_path, instant_execution=True, no_deps=False, return_postinstall=False)
|
unified_manager.repo_install(repo_url, to_path, instant_execution=True, no_deps=False, return_postinstall=False)
|
||||||
cloned_repos.append(repo_name)
|
cloned_repos.append(repo_name)
|
||||||
|
|
||||||
|
manager_util.restore_pip_snapshot(pips, git_helper_extras)
|
||||||
|
|
||||||
# print summary
|
# print summary
|
||||||
for x in cloned_repos:
|
for x in cloned_repos:
|
||||||
print(f"[ INSTALLED ] {x}")
|
print(f"[ INSTALLED ] {x}")
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from ..common import manager_util
|
|||||||
from ..common import cm_global
|
from ..common import cm_global
|
||||||
from ..common import manager_downloader
|
from ..common import manager_downloader
|
||||||
from ..common import context
|
from ..common import context
|
||||||
|
from ..common import manager_security
|
||||||
|
|
||||||
|
|
||||||
logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})")
|
logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})")
|
||||||
@@ -36,7 +37,8 @@ logging.info("[ComfyUI-Manager] network_mode: " + network_mode_description)
|
|||||||
comfy_ui_hash = "-"
|
comfy_ui_hash = "-"
|
||||||
comfyui_tag = None
|
comfyui_tag = None
|
||||||
|
|
||||||
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/Comfy-Org/ComfyUI-Manager#security-policy"
|
SECURITY_MESSAGE_MIDDLE = "ERROR: To use this action, a security_level of `normal or below` is required. Please contact the administrator.\nReference: https://github.com/Comfy-Org/ComfyUI-Manager#security-policy"
|
||||||
|
SECURITY_MESSAGE_MIDDLE_P = "ERROR: To use this action, security_level must be `normal or below`, and network_mode must be set to `personal_cloud`. 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/Comfy-Org/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/Comfy-Org/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/Comfy-Org/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/Comfy-Org/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."
|
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."
|
||||||
@@ -93,13 +95,27 @@ model_dir_name_map = {
|
|||||||
|
|
||||||
|
|
||||||
def is_allowed_security_level(level):
|
def is_allowed_security_level(level):
|
||||||
|
is_personal_cloud = core.get_config()['network_mode'].lower() == 'personal_cloud'
|
||||||
|
|
||||||
if level == 'block':
|
if level == 'block':
|
||||||
return False
|
return False
|
||||||
|
elif level == 'high+':
|
||||||
|
if is_local_mode:
|
||||||
|
return core.get_config()['security_level'] in ['weak', 'normal-']
|
||||||
|
elif is_personal_cloud:
|
||||||
|
return core.get_config()['security_level'] == 'weak'
|
||||||
|
else:
|
||||||
|
return False
|
||||||
elif level == 'high':
|
elif level == 'high':
|
||||||
if is_local_mode:
|
if is_local_mode:
|
||||||
return core.get_config()['security_level'] in ['weak', 'normal-']
|
return core.get_config()['security_level'] in ['weak', 'normal-']
|
||||||
else:
|
else:
|
||||||
return core.get_config()['security_level'] == 'weak'
|
return core.get_config()['security_level'] == 'weak'
|
||||||
|
elif level == 'middle+':
|
||||||
|
if is_local_mode or is_personal_cloud:
|
||||||
|
return core.get_config()['security_level'] in ['weak', 'normal', 'normal-']
|
||||||
|
else:
|
||||||
|
return False
|
||||||
elif level == 'middle':
|
elif level == 'middle':
|
||||||
return core.get_config()['security_level'] in ['weak', 'normal', 'normal-']
|
return core.get_config()['security_level'] in ['weak', 'normal', 'normal-']
|
||||||
else:
|
else:
|
||||||
@@ -116,7 +132,7 @@ async def get_risky_level(files, pip_packages):
|
|||||||
|
|
||||||
for x in files:
|
for x in files:
|
||||||
if x not in all_urls:
|
if x not in all_urls:
|
||||||
return "high"
|
return "high+"
|
||||||
|
|
||||||
all_pip_packages = set()
|
all_pip_packages = set()
|
||||||
for x in json_data1['custom_nodes'] + json_data2['custom_nodes']:
|
for x in json_data1['custom_nodes'] + json_data2['custom_nodes']:
|
||||||
@@ -126,7 +142,7 @@ async def get_risky_level(files, pip_packages):
|
|||||||
if p not in all_pip_packages:
|
if p not in all_pip_packages:
|
||||||
return "block"
|
return "block"
|
||||||
|
|
||||||
return "middle"
|
return "middle+"
|
||||||
|
|
||||||
|
|
||||||
class ManagerFuncsInComfyUI(core.ManagerFuncs):
|
class ManagerFuncsInComfyUI(core.ManagerFuncs):
|
||||||
@@ -545,6 +561,8 @@ async def task_worker():
|
|||||||
logging.error("ComfyUI update failed")
|
logging.error("ComfyUI update failed")
|
||||||
return "fail"
|
return "fail"
|
||||||
elif res == "updated":
|
elif res == "updated":
|
||||||
|
core.install_manager_requirements(repo_path)
|
||||||
|
|
||||||
if is_stable:
|
if is_stable:
|
||||||
logging.info("ComfyUI is updated to latest stable version.")
|
logging.info("ComfyUI is updated to latest stable version.")
|
||||||
return "success-stable-"+latest_tag
|
return "success-stable-"+latest_tag
|
||||||
@@ -650,7 +668,7 @@ async def task_worker():
|
|||||||
return 'success'
|
return 'success'
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"[ComfyUI-Manager] ERROR: {e}", file=sys.stderr)
|
logging.error(f"[ComfyUI-Manager] ERROR: {e}")
|
||||||
|
|
||||||
return f"Model installation error: {model_url}"
|
return f"Model installation error: {model_url}"
|
||||||
|
|
||||||
@@ -758,29 +776,29 @@ async def queue_batch(request):
|
|||||||
for x in v:
|
for x in v:
|
||||||
res = await _uninstall_custom_node(x)
|
res = await _uninstall_custom_node(x)
|
||||||
if res.status != 200:
|
if res.status != 200:
|
||||||
failed.add(x[0])
|
failed.add(x['id'])
|
||||||
else:
|
else:
|
||||||
res = await _install_custom_node(x)
|
res = await _install_custom_node(x)
|
||||||
if res.status != 200:
|
if res.status != 200:
|
||||||
failed.add(x[0])
|
failed.add(x['id'])
|
||||||
|
|
||||||
elif k == 'install':
|
elif k == 'install':
|
||||||
for x in v:
|
for x in v:
|
||||||
res = await _install_custom_node(x)
|
res = await _install_custom_node(x)
|
||||||
if res.status != 200:
|
if res.status != 200:
|
||||||
failed.add(x[0])
|
failed.add(x['id'])
|
||||||
|
|
||||||
elif k == 'uninstall':
|
elif k == 'uninstall':
|
||||||
for x in v:
|
for x in v:
|
||||||
res = await _uninstall_custom_node(x)
|
res = await _uninstall_custom_node(x)
|
||||||
if res.status != 200:
|
if res.status != 200:
|
||||||
failed.add(x[0])
|
failed.add(x['id'])
|
||||||
|
|
||||||
elif k == 'update':
|
elif k == 'update':
|
||||||
for x in v:
|
for x in v:
|
||||||
res = await _update_custom_node(x)
|
res = await _update_custom_node(x)
|
||||||
if res.status != 200:
|
if res.status != 200:
|
||||||
failed.add(x[0])
|
failed.add(x['id'])
|
||||||
|
|
||||||
elif k == 'update_comfyui':
|
elif k == 'update_comfyui':
|
||||||
await update_comfyui(None)
|
await update_comfyui(None)
|
||||||
@@ -793,13 +811,13 @@ async def queue_batch(request):
|
|||||||
for x in v:
|
for x in v:
|
||||||
res = await _install_model(x)
|
res = await _install_model(x)
|
||||||
if res.status != 200:
|
if res.status != 200:
|
||||||
failed.add(x[0])
|
failed.add(x['id'])
|
||||||
|
|
||||||
elif k == 'fix':
|
elif k == 'fix':
|
||||||
for x in v:
|
for x in v:
|
||||||
res = await _fix_custom_node(x)
|
res = await _fix_custom_node(x)
|
||||||
if res.status != 200:
|
if res.status != 200:
|
||||||
failed.add(x[0])
|
failed.add(x['id'])
|
||||||
|
|
||||||
with task_worker_lock:
|
with task_worker_lock:
|
||||||
finalize_temp_queue_batch(json_data, failed)
|
finalize_temp_queue_batch(json_data, failed)
|
||||||
@@ -910,8 +928,8 @@ async def update_all(request):
|
|||||||
|
|
||||||
|
|
||||||
async def _update_all(json_data):
|
async def _update_all(json_data):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle+'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE_P)
|
||||||
return web.Response(status=403)
|
return web.Response(status=403)
|
||||||
|
|
||||||
with task_worker_lock:
|
with task_worker_lock:
|
||||||
@@ -1056,6 +1074,9 @@ async def fetch_customnode_list(request):
|
|||||||
if channel != 'local':
|
if channel != 'local':
|
||||||
found = 'custom'
|
found = 'custom'
|
||||||
|
|
||||||
|
if channel == core.DEFAULT_CHANNEL or channel == core.DEFAULT_CHANNEL_LEGACY:
|
||||||
|
channel = 'default'
|
||||||
|
else:
|
||||||
for name, url in core.get_channel_dict().items():
|
for name, url in core.get_channel_dict().items():
|
||||||
if url == channel:
|
if url == channel:
|
||||||
found = name
|
found = name
|
||||||
@@ -1063,7 +1084,7 @@ async def fetch_customnode_list(request):
|
|||||||
|
|
||||||
channel = found
|
channel = found
|
||||||
|
|
||||||
result = dict(channel=channel, node_packs=node_packs)
|
result = dict(channel=channel, node_packs=node_packs.to_dict())
|
||||||
|
|
||||||
return web.json_response(result, content_type='application/json')
|
return web.json_response(result, content_type='application/json')
|
||||||
|
|
||||||
@@ -1162,7 +1183,7 @@ async def get_snapshot_list(request):
|
|||||||
@routes.get("/v2/snapshot/remove")
|
@routes.get("/v2/snapshot/remove")
|
||||||
async def remove_snapshot(request):
|
async def remove_snapshot(request):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE)
|
||||||
return web.Response(status=403)
|
return web.Response(status=403)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1179,8 +1200,8 @@ async def remove_snapshot(request):
|
|||||||
|
|
||||||
@routes.get("/v2/snapshot/restore")
|
@routes.get("/v2/snapshot/restore")
|
||||||
async def restore_snapshot(request):
|
async def restore_snapshot(request):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle+'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE_P)
|
||||||
return web.Response(status=403)
|
return web.Response(status=403)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1292,6 +1313,65 @@ async def import_fail_info(request):
|
|||||||
return web.Response(status=400)
|
return web.Response(status=400)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.post("/v2/customnode/import_fail_info_bulk")
|
||||||
|
async def import_fail_info_bulk(request):
|
||||||
|
try:
|
||||||
|
json_data = await request.json()
|
||||||
|
|
||||||
|
# Basic validation - ensure we have either cnr_ids or urls
|
||||||
|
if not isinstance(json_data, dict):
|
||||||
|
return web.Response(status=400, text="Request body must be a JSON object")
|
||||||
|
|
||||||
|
if "cnr_ids" not in json_data and "urls" not in json_data:
|
||||||
|
return web.Response(
|
||||||
|
status=400, text="Either 'cnr_ids' or 'urls' field is required"
|
||||||
|
)
|
||||||
|
|
||||||
|
await core.unified_manager.reload('cache')
|
||||||
|
await core.unified_manager.get_custom_nodes('default', 'cache')
|
||||||
|
|
||||||
|
results = {}
|
||||||
|
|
||||||
|
if "cnr_ids" in json_data:
|
||||||
|
if not isinstance(json_data["cnr_ids"], list):
|
||||||
|
return web.Response(status=400, text="'cnr_ids' must be an array")
|
||||||
|
for cnr_id in json_data["cnr_ids"]:
|
||||||
|
if not isinstance(cnr_id, str):
|
||||||
|
results[cnr_id] = {"error": "cnr_id must be a string"}
|
||||||
|
continue
|
||||||
|
module_name = core.unified_manager.get_module_name(cnr_id)
|
||||||
|
if module_name is not None:
|
||||||
|
info = cm_global.error_dict.get(module_name)
|
||||||
|
if info is not None:
|
||||||
|
results[cnr_id] = info
|
||||||
|
else:
|
||||||
|
results[cnr_id] = None
|
||||||
|
else:
|
||||||
|
results[cnr_id] = None
|
||||||
|
|
||||||
|
if "urls" in json_data:
|
||||||
|
if not isinstance(json_data["urls"], list):
|
||||||
|
return web.Response(status=400, text="'urls' must be an array")
|
||||||
|
for url in json_data["urls"]:
|
||||||
|
if not isinstance(url, str):
|
||||||
|
results[url] = {"error": "url must be a string"}
|
||||||
|
continue
|
||||||
|
module_name = core.unified_manager.get_module_name(url)
|
||||||
|
if module_name is not None:
|
||||||
|
info = cm_global.error_dict.get(module_name)
|
||||||
|
if info is not None:
|
||||||
|
results[url] = info
|
||||||
|
else:
|
||||||
|
results[url] = None
|
||||||
|
else:
|
||||||
|
results[url] = None
|
||||||
|
|
||||||
|
return web.json_response(results)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"[ComfyUI-Manager] Error processing bulk import fail info: {e}")
|
||||||
|
return web.Response(status=500, text="Internal server error")
|
||||||
|
|
||||||
|
|
||||||
@routes.post("/v2/manager/queue/reinstall")
|
@routes.post("/v2/manager/queue/reinstall")
|
||||||
async def reinstall_custom_node(request):
|
async def reinstall_custom_node(request):
|
||||||
await uninstall_custom_node(request)
|
await uninstall_custom_node(request)
|
||||||
@@ -1356,8 +1436,8 @@ async def install_custom_node(request):
|
|||||||
|
|
||||||
|
|
||||||
async def _install_custom_node(json_data):
|
async def _install_custom_node(json_data):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle+'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE_P)
|
||||||
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
|
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
|
||||||
|
|
||||||
# non-nightly cnr is safe
|
# non-nightly cnr is safe
|
||||||
@@ -1462,7 +1542,7 @@ async def _fix_custom_node(json_data):
|
|||||||
|
|
||||||
@routes.post("/v2/customnode/install/git_url")
|
@routes.post("/v2/customnode/install/git_url")
|
||||||
async def install_custom_node_git_url(request):
|
async def install_custom_node_git_url(request):
|
||||||
if not is_allowed_security_level('high'):
|
if not is_allowed_security_level('high+'):
|
||||||
logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
|
logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
|
||||||
return web.Response(status=403)
|
return web.Response(status=403)
|
||||||
|
|
||||||
@@ -1482,7 +1562,7 @@ async def install_custom_node_git_url(request):
|
|||||||
|
|
||||||
@routes.post("/v2/customnode/install/pip")
|
@routes.post("/v2/customnode/install/pip")
|
||||||
async def install_custom_node_pip(request):
|
async def install_custom_node_pip(request):
|
||||||
if not is_allowed_security_level('high'):
|
if not is_allowed_security_level('high+'):
|
||||||
logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
|
logging.error(SECURITY_MESSAGE_NORMAL_MINUS)
|
||||||
return web.Response(status=403)
|
return web.Response(status=403)
|
||||||
|
|
||||||
@@ -1500,7 +1580,7 @@ async def uninstall_custom_node(request):
|
|||||||
|
|
||||||
async def _uninstall_custom_node(json_data):
|
async def _uninstall_custom_node(json_data):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE)
|
||||||
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
|
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
|
||||||
|
|
||||||
node_id = json_data.get('id')
|
node_id = json_data.get('id')
|
||||||
@@ -1526,7 +1606,7 @@ async def update_custom_node(request):
|
|||||||
|
|
||||||
async def _update_custom_node(json_data):
|
async def _update_custom_node(json_data):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE)
|
||||||
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
|
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
|
||||||
|
|
||||||
node_id = json_data.get('id')
|
node_id = json_data.get('id')
|
||||||
@@ -1617,8 +1697,8 @@ async def install_model(request):
|
|||||||
|
|
||||||
|
|
||||||
async def _install_model(json_data):
|
async def _install_model(json_data):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle+'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE_P)
|
||||||
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
|
return web.Response(status=403, text="A security error has occurred. Please check the terminal logs")
|
||||||
|
|
||||||
# validate request
|
# validate request
|
||||||
@@ -1626,7 +1706,7 @@ async def _install_model(json_data):
|
|||||||
logging.error(f"[ComfyUI-Manager] Invalid model install request is detected: {json_data}")
|
logging.error(f"[ComfyUI-Manager] Invalid model install request is detected: {json_data}")
|
||||||
return web.Response(status=400, text="Invalid model install request is detected")
|
return web.Response(status=400, text="Invalid model install request is detected")
|
||||||
|
|
||||||
if not json_data['filename'].endswith('.safetensors') and not is_allowed_security_level('high'):
|
if not json_data['filename'].endswith('.safetensors') and not is_allowed_security_level('high+'):
|
||||||
models_json = await core.get_data_by_mode('cache', 'model-list.json', 'default')
|
models_json = await core.get_data_by_mode('cache', 'model-list.json', 'default')
|
||||||
|
|
||||||
is_belongs_to_whitelist = False
|
is_belongs_to_whitelist = False
|
||||||
@@ -1783,7 +1863,7 @@ async def get_notice_legacy(request):
|
|||||||
@routes.get("/v2/manager/reboot")
|
@routes.get("/v2/manager/reboot")
|
||||||
def restart(self):
|
def restart(self):
|
||||||
if not is_allowed_security_level('middle'):
|
if not is_allowed_security_level('middle'):
|
||||||
logging.error(SECURITY_MESSAGE_MIDDLE_OR_BELOW)
|
logging.error(SECURITY_MESSAGE_MIDDLE)
|
||||||
return web.Response(status=403)
|
return web.Response(status=403)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1949,9 +2029,10 @@ if not os.path.exists(context.manager_config_path):
|
|||||||
core.write_config()
|
core.write_config()
|
||||||
|
|
||||||
|
|
||||||
cm_global.register_extension('ComfyUI-Manager',
|
# policy setup
|
||||||
{'version': core.version,
|
manager_security.add_handler_policy(reinstall_custom_node, manager_security.HANDLER_POLICY.MULTIPLE_REMOTE_BAN_NOT_PERSONAL_CLOUD)
|
||||||
'name': 'ComfyUI Manager',
|
manager_security.add_handler_policy(install_custom_node, manager_security.HANDLER_POLICY.MULTIPLE_REMOTE_BAN_NOT_PERSONAL_CLOUD)
|
||||||
'nodes': {},
|
manager_security.add_handler_policy(fix_custom_node, manager_security.HANDLER_POLICY.MULTIPLE_REMOTE_BAN_NOT_PERSONAL_CLOUD)
|
||||||
'description': 'This extension provides the ability to manage custom nodes in ComfyUI.', })
|
manager_security.add_handler_policy(install_custom_node_git_url, manager_security.HANDLER_POLICY.MULTIPLE_REMOTE_BAN_NOT_PERSONAL_CLOUD)
|
||||||
|
manager_security.add_handler_policy(install_custom_node_pip, manager_security.HANDLER_POLICY.MULTIPLE_REMOTE_BAN_NOT_PERSONAL_CLOUD)
|
||||||
|
manager_security.add_handler_policy(install_model, manager_security.HANDLER_POLICY.MULTIPLE_REMOTE_BAN_NOT_PERSONAL_CLOUD)
|
||||||
|
|||||||
@@ -10,6 +10,16 @@ import hashlib
|
|||||||
|
|
||||||
import folder_paths
|
import folder_paths
|
||||||
from server import PromptServer
|
from server import PromptServer
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from nio import AsyncClient, LoginResponse, UploadResponse
|
||||||
|
matrix_nio_is_available = True
|
||||||
|
except Exception:
|
||||||
|
logging.warning(f"[ComfyUI-Manager] The matrix sharing feature has been disabled because the `matrix-nio` dependency is not installed.\n\tTo use this feature, please run the following command:\n\t{sys.executable} -m pip install matrix-nio\n")
|
||||||
|
matrix_nio_is_available = False
|
||||||
|
|
||||||
|
|
||||||
def extract_model_file_names(json_data):
|
def extract_model_file_names(json_data):
|
||||||
@@ -192,6 +202,14 @@ async def get_esheep_workflow_and_images(request):
|
|||||||
return web.Response(status=200, text=json.dumps(data))
|
return web.Response(status=200, text=json.dumps(data))
|
||||||
|
|
||||||
|
|
||||||
|
@PromptServer.instance.routes.get("/v2/manager/get_matrix_dep_status")
|
||||||
|
async def get_matrix_dep_status(request):
|
||||||
|
if matrix_nio_is_available:
|
||||||
|
return web.Response(status=200, text='available')
|
||||||
|
else:
|
||||||
|
return web.Response(status=200, text='unavailable')
|
||||||
|
|
||||||
|
|
||||||
def set_matrix_auth(json_data):
|
def set_matrix_auth(json_data):
|
||||||
homeserver = json_data['homeserver']
|
homeserver = json_data['homeserver']
|
||||||
username = json_data['username']
|
username = json_data['username']
|
||||||
@@ -331,15 +349,12 @@ async def share_art(request):
|
|||||||
workflowId = upload_workflow_json["workflowId"]
|
workflowId = upload_workflow_json["workflowId"]
|
||||||
|
|
||||||
# check if the user has provided Matrix credentials
|
# check if the user has provided Matrix credentials
|
||||||
if "matrix" in share_destinations:
|
if matrix_nio_is_available and "matrix" in share_destinations:
|
||||||
comfyui_share_room_id = '!LGYSoacpJPhIfBqVfb:matrix.org'
|
comfyui_share_room_id = '!LGYSoacpJPhIfBqVfb:matrix.org'
|
||||||
filename = os.path.basename(asset_filepath)
|
filename = os.path.basename(asset_filepath)
|
||||||
content_type = assetFileType
|
content_type = assetFileType
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from matrix_client.api import MatrixHttpApi
|
|
||||||
from matrix_client.client import MatrixClient
|
|
||||||
|
|
||||||
homeserver = 'matrix.org'
|
homeserver = 'matrix.org'
|
||||||
if matrix_auth:
|
if matrix_auth:
|
||||||
homeserver = matrix_auth.get('homeserver', 'matrix.org')
|
homeserver = matrix_auth.get('homeserver', 'matrix.org')
|
||||||
@@ -347,20 +362,35 @@ async def share_art(request):
|
|||||||
if not homeserver.startswith("https://"):
|
if not homeserver.startswith("https://"):
|
||||||
homeserver = "https://" + homeserver
|
homeserver = "https://" + homeserver
|
||||||
|
|
||||||
client = MatrixClient(homeserver)
|
client = AsyncClient(homeserver, matrix_auth['username'])
|
||||||
try:
|
|
||||||
token = client.login(username=matrix_auth['username'], password=matrix_auth['password'])
|
# Login
|
||||||
if not token:
|
login_resp = await client.login(matrix_auth['password'])
|
||||||
return web.json_response({"error": "Invalid Matrix credentials."}, content_type='application/json', status=400)
|
if not isinstance(login_resp, LoginResponse) or not login_resp.access_token:
|
||||||
except Exception:
|
await client.close()
|
||||||
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)
|
# Upload asset
|
||||||
with open(asset_filepath, 'rb') as f:
|
with open(asset_filepath, 'rb') as f:
|
||||||
mxc_url = matrix.media_upload(f.read(), content_type, filename=filename)['content_uri']
|
upload_resp, _maybe_keys = await client.upload(f, content_type=content_type, filename=filename)
|
||||||
|
asset_data = f.seek(0) or f.read() # get size for info below
|
||||||
|
if not isinstance(upload_resp, UploadResponse) or not upload_resp.content_uri:
|
||||||
|
await client.close()
|
||||||
|
return web.json_response({"error": "Failed to upload asset to Matrix."}, content_type='application/json', status=500)
|
||||||
|
mxc_url = upload_resp.content_uri
|
||||||
|
|
||||||
workflow_json_mxc_url = matrix.media_upload(prompt['workflow'], 'application/json', filename='workflow.json')['content_uri']
|
# Upload workflow JSON
|
||||||
|
import io
|
||||||
|
workflow_json_bytes = json.dumps(prompt['workflow']).encode('utf-8')
|
||||||
|
workflow_io = io.BytesIO(workflow_json_bytes)
|
||||||
|
upload_workflow_resp, _maybe_keys = await client.upload(workflow_io, content_type='application/json', filename='workflow.json')
|
||||||
|
workflow_io.seek(0)
|
||||||
|
if not isinstance(upload_workflow_resp, UploadResponse) or not upload_workflow_resp.content_uri:
|
||||||
|
await client.close()
|
||||||
|
return web.json_response({"error": "Failed to upload workflow to Matrix."}, content_type='application/json', status=500)
|
||||||
|
workflow_json_mxc_url = upload_workflow_resp.content_uri
|
||||||
|
|
||||||
|
# Send text message
|
||||||
text_content = ""
|
text_content = ""
|
||||||
if title:
|
if title:
|
||||||
text_content += f"{title}\n"
|
text_content += f"{title}\n"
|
||||||
@@ -368,10 +398,45 @@ async def share_art(request):
|
|||||||
text_content += f"{description}\n"
|
text_content += f"{description}\n"
|
||||||
if credits:
|
if credits:
|
||||||
text_content += f"\ncredits: {credits}\n"
|
text_content += f"\ncredits: {credits}\n"
|
||||||
matrix.send_message(comfyui_share_room_id, text_content)
|
await client.room_send(
|
||||||
matrix.send_content(comfyui_share_room_id, mxc_url, filename, 'm.image')
|
room_id=comfyui_share_room_id,
|
||||||
matrix.send_content(comfyui_share_room_id, workflow_json_mxc_url, 'workflow.json', 'm.file')
|
message_type="m.room.message",
|
||||||
except Exception:
|
content={"msgtype": "m.text", "body": text_content}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Send image
|
||||||
|
await client.room_send(
|
||||||
|
room_id=comfyui_share_room_id,
|
||||||
|
message_type="m.room.message",
|
||||||
|
content={
|
||||||
|
"msgtype": "m.image",
|
||||||
|
"body": filename,
|
||||||
|
"url": mxc_url,
|
||||||
|
"info": {
|
||||||
|
"mimetype": content_type,
|
||||||
|
"size": len(asset_data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Send workflow JSON file
|
||||||
|
await client.room_send(
|
||||||
|
room_id=comfyui_share_room_id,
|
||||||
|
message_type="m.room.message",
|
||||||
|
content={
|
||||||
|
"msgtype": "m.file",
|
||||||
|
"body": "workflow.json",
|
||||||
|
"url": workflow_json_mxc_url,
|
||||||
|
"info": {
|
||||||
|
"mimetype": "application/json",
|
||||||
|
"size": len(workflow_json_bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await client.close()
|
||||||
|
|
||||||
|
except:
|
||||||
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)
|
||||||
|
|||||||
@@ -1973,6 +1973,97 @@
|
|||||||
"url": "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth",
|
"url": "https://dl.fbaipublicfiles.com/segment_anything/sam_vit_b_01ec64.pth",
|
||||||
"size": "375.0MB"
|
"size": "375.0MB"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "sam2.1_hiera_tiny.pt",
|
||||||
|
"type": "sam2.1",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2.1 hiera model (tiny)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2.1_hiera_tiny.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_tiny.pt",
|
||||||
|
"size": "149.0MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sam2.1_hiera_small.pt",
|
||||||
|
"type": "sam2.1",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2.1 hiera model (small)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2.1_hiera_small.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_small.pt",
|
||||||
|
"size": "176.0MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sam2.1_hiera_base_plus.pt",
|
||||||
|
"type": "sam2.1",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2.1 hiera model (base+)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2.1_hiera_base_plus.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_base_plus.pt",
|
||||||
|
"size": "309.0MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sam2.1_hiera_large.pt",
|
||||||
|
"type": "sam2.1",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2.1 hiera model (large)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2.1_hiera_large.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_large.pt",
|
||||||
|
"size": "857.0MB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "sam2_hiera_tiny.pt",
|
||||||
|
"type": "sam2",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2 hiera model (tiny)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2_hiera_tiny.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_tiny.pt",
|
||||||
|
"size": "149.0MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sam2_hiera_small.pt",
|
||||||
|
"type": "sam2",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2 hiera model (small)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2_hiera_small.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_small.pt",
|
||||||
|
"size": "176.0MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sam2_hiera_base_plus.pt",
|
||||||
|
"type": "sam2",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2 hiera model (base+)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2_hiera_base_plus.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_base_plus.pt",
|
||||||
|
"size": "309.0MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sam2_hiera_large.pt",
|
||||||
|
"type": "sam2",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2 hiera model (large)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2_hiera_large.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_large.pt",
|
||||||
|
"size": "857.0MB"
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "seecoder v1.0",
|
"name": "seecoder v1.0",
|
||||||
"type": "seecoder",
|
"type": "seecoder",
|
||||||
@@ -4006,6 +4097,29 @@
|
|||||||
"size": "649MB"
|
"size": "649MB"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/omnigen2_fp16.safetensors",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "OmniGen2",
|
||||||
|
"save_path": "default",
|
||||||
|
"description": "OmniGen2 diffusion model. This is required for using OmniGen2.",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged",
|
||||||
|
"filename": "omnigen2_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/omnigen2_fp16.safetensors",
|
||||||
|
"size": "7.93GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/qwen_2.5_vl_fp16.safetensors",
|
||||||
|
"type": "clip",
|
||||||
|
"base": "qwen-2.5",
|
||||||
|
"save_path": "default",
|
||||||
|
"description": "text encoder for OmniGen2",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged",
|
||||||
|
"filename": "qwen_2.5_vl_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged/resolve/main/split_files/text_encoders/qwen_2.5_vl_fp16.safetensors",
|
||||||
|
"size": "7.51GB"
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "FLUX.1 [Schnell] Diffusion model",
|
"name": "FLUX.1 [Schnell] Diffusion model",
|
||||||
"type": "diffusion_model",
|
"type": "diffusion_model",
|
||||||
@@ -4023,7 +4137,7 @@
|
|||||||
"type": "VAE",
|
"type": "VAE",
|
||||||
"base": "FLUX.1",
|
"base": "FLUX.1",
|
||||||
"save_path": "vae/FLUX1",
|
"save_path": "vae/FLUX1",
|
||||||
"description": "FLUX.1 VAE model",
|
"description": "FLUX.1 VAE model\nNOTE: This VAE model can also be used for image generation with OmniGen2.",
|
||||||
"reference": "https://huggingface.co/black-forest-labs/FLUX.1-schnell",
|
"reference": "https://huggingface.co/black-forest-labs/FLUX.1-schnell",
|
||||||
"filename": "ae.safetensors",
|
"filename": "ae.safetensors",
|
||||||
"url": "https://huggingface.co/black-forest-labs/FLUX.1-schnell/resolve/main/ae.safetensors",
|
"url": "https://huggingface.co/black-forest-labs/FLUX.1-schnell/resolve/main/ae.safetensors",
|
||||||
@@ -4931,6 +5045,105 @@
|
|||||||
"size": "1.26GB"
|
"size": "1.26GB"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 i2v high noise 14B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for i2v high noise 14B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_i2v_high_noise_14B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_high_noise_14B_fp16.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 i2v high noise 14B (fp8_scaled)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for i2v high noise 14B (fp8_scaled)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"size": "14.3GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 i2v low noise 14B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for i2v low noise 14B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_i2v_low_noise_14B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_low_noise_14B_fp16.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 i2v low noise 14B (fp8_scaled)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for i2v low noise 14B (fp8_scaled)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_i2v_low_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_low_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"size": "14.3GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 t2v high noise 14B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for t2v high noise 14B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_t2v_high_noise_14B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_high_noise_14B_fp16.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 t2v high noise 14B (fp8_scaled)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for t2v high noise 14B (fp8_scaled)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_t2v_high_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_high_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"size": "14.3GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 t2v low noise 14B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for t2v low noise 14B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_t2v_low_noise_14B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_low_noise_14B_fp16.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 t2v low noise 14B (fp8_scaled)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for t2v low noise 14B (fp8_scaled)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_t2v_low_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_low_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"size": "14.3GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 ti2v 5B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for ti2v 5B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_ti2v_5B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_ti2v_5B_fp16.safetensors",
|
||||||
|
"size": "10.0GB"
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Comfy-Org/umt5_xxl_fp16.safetensors",
|
"name": "Comfy-Org/umt5_xxl_fp16.safetensors",
|
||||||
@@ -5033,6 +5246,50 @@
|
|||||||
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/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"
|
"size": "15.7GB"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 2B Distilled v0.9.8",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "LTX-Video 2B distilled model v0.9.8 with improved prompt understanding and detail generation.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-2b-0.9.8-distilled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-2b-0.9.8-distilled.safetensors",
|
||||||
|
"size": "6.34GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 2B Distilled FP8 v0.9.8",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "Quantized LTX-Video 2B distilled model v0.9.8 with improved prompt understanding and detail generation, optimized for lower VRAM usage.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-2b-0.9.8-distilled-fp8.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-2b-0.9.8-distilled-fp8.safetensors",
|
||||||
|
"size": "4.46GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 13B Distilled v0.9.8",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "LTX-Video 13B distilled model v0.9.8 with improved prompt understanding and detail generation.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-13b-0.9.8-distilled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.8-distilled.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video 13B Distilled FP8 v0.9.8",
|
||||||
|
"type": "checkpoint",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "checkpoints/LTXV",
|
||||||
|
"description": "Quantized LTX-Video 13B distilled model v0.9.8 with improved prompt understanding and detail generation, optimized for lower VRAM usage.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video",
|
||||||
|
"filename": "ltxv-13b-0.9.8-distilled-fp8.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltxv-13b-0.9.8-distilled-fp8.safetensors",
|
||||||
|
"size": "15.7GB"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "LTX-Video 13B Distilled LoRA v0.9.7",
|
"name": "LTX-Video 13B Distilled LoRA v0.9.7",
|
||||||
"type": "lora",
|
"type": "lora",
|
||||||
@@ -5044,6 +5301,50 @@
|
|||||||
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/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"
|
"size": "1.33GB"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video ICLoRA Depth 13B v0.9.7",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "loras",
|
||||||
|
"description": "In-Context LoRA (IC LoRA) for depth-controlled video-to-video generation with precise depth conditioning.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-depth-13b-0.9.7",
|
||||||
|
"filename": "ltxv-097-ic-lora-depth-control-comfyui.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-depth-13b-0.9.7/resolve/main/ltxv-097-ic-lora-depth-control-comfyui.safetensors",
|
||||||
|
"size": "81.9MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video ICLoRA Pose 13B v0.9.7",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "loras",
|
||||||
|
"description": "In-Context LoRA (IC LoRA) for pose-controlled video-to-video generation with precise pose conditioning.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-pose-13b-0.9.7",
|
||||||
|
"filename": "ltxv-097-ic-lora-pose-control-comfyui.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-pose-13b-0.9.7/resolve/main/ltxv-097-ic-lora-pose-control-comfyui.safetensors",
|
||||||
|
"size": "151MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video ICLoRA Canny 13B v0.9.7",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "loras",
|
||||||
|
"description": "In-Context LoRA (IC LoRA) for canny edge-controlled video-to-video generation with precise edge conditioning.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-canny-13b-0.9.7",
|
||||||
|
"filename": "ltxv-097-ic-lora-canny-control-comfyui.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-canny-13b-0.9.7/resolve/main/ltxv-097-ic-lora-canny-control-comfyui.safetensors",
|
||||||
|
"size": "81.9MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LTX-Video ICLoRA Detailer 13B v0.9.8",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "LTX-Video",
|
||||||
|
"save_path": "loras",
|
||||||
|
"description": "A video detailer model on top of LTXV_13B_098_DEV trained on custom data using In-Context LoRA (IC LoRA) method.",
|
||||||
|
"reference": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-detailer-13b-0.9.8",
|
||||||
|
"filename": "ltxv-098-ic-lora-detailer-comfyui.safetensors",
|
||||||
|
"url": "https://huggingface.co/Lightricks/LTX-Video-ICLoRA-detailer-13b-0.9.8/resolve/main/ltxv-098-ic-lora-detailer-comfyui.safetensors",
|
||||||
|
"size": "1.31GB"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Latent Bridge Matching for Image Relighting",
|
"name": "Latent Bridge Matching for Image Relighting",
|
||||||
"type": "diffusion_model",
|
"type": "diffusion_model",
|
||||||
@@ -5054,6 +5355,317 @@
|
|||||||
"filename": "LBM_relighting.safetensors",
|
"filename": "LBM_relighting.safetensors",
|
||||||
"url": "https://huggingface.co/jasperai/LBM_relighting/resolve/main/model.safetensors",
|
"url": "https://huggingface.co/jasperai/LBM_relighting/resolve/main/model.safetensors",
|
||||||
"size": "5.02GB"
|
"size": "5.02GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image VAE",
|
||||||
|
"type": "VAE",
|
||||||
|
"base": "Qwen-Image",
|
||||||
|
"save_path": "vae/qwen-image",
|
||||||
|
"description": "VAE model for Qwen-Image",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI",
|
||||||
|
"filename": "qwen_image_vae.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/vae/qwen_image_vae.safetensors",
|
||||||
|
"size": "335MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen 2.5 VL 7B Text Encoder (fp8_scaled)",
|
||||||
|
"type": "clip",
|
||||||
|
"base": "Qwen-2.5-VL",
|
||||||
|
"save_path": "text_encoders/qwen",
|
||||||
|
"description": "Qwen 2.5 VL 7B text encoder model (fp8_scaled)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI",
|
||||||
|
"filename": "qwen_2.5_vl_7b_fp8_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/text_encoders/qwen_2.5_vl_7b_fp8_scaled.safetensors",
|
||||||
|
"size": "3.75GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen 2.5 VL 7B Text Encoder",
|
||||||
|
"type": "clip",
|
||||||
|
"base": "Qwen-2.5-VL",
|
||||||
|
"save_path": "text_encoders/qwen",
|
||||||
|
"description": "Qwen 2.5 VL 7B text encoder model",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI",
|
||||||
|
"filename": "qwen_2.5_vl_7b.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/text_encoders/qwen_2.5_vl_7b.safetensors",
|
||||||
|
"size": "7.51GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image Diffusion Model (fp8_e4m3fn)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Qwen-Image",
|
||||||
|
"save_path": "diffusion_models/qwen-image",
|
||||||
|
"description": "Qwen-Image diffusion model (fp8_e4m3fn)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI",
|
||||||
|
"filename": "qwen_image_fp8_e4m3fn.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_fp8_e4m3fn.safetensors",
|
||||||
|
"size": "4.89GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image Diffusion Model (bf16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Qwen-Image",
|
||||||
|
"save_path": "diffusion_models/qwen-image",
|
||||||
|
"description": "Qwen-Image diffusion model (bf16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI",
|
||||||
|
"filename": "qwen_image_bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Qwen-Image_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_bf16.safetensors",
|
||||||
|
"size": "9.78GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Edit 2509 Diffusion Model (fp8_e4m3fn)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Qwen-Image-Edit",
|
||||||
|
"save_path": "diffusion_models/qwen-image-edit",
|
||||||
|
"description": "Qwen-Image-Edit 2509 diffusion model (fp8_e4m3fn)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI",
|
||||||
|
"filename": "qwen_image_edit_2509_fp8_e4m3fn.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_edit_2509_fp8_e4m3fn.safetensors",
|
||||||
|
"size": "4.89GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Edit 2509 Diffusion Model (bf16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Qwen-Image-Edit",
|
||||||
|
"save_path": "diffusion_models/qwen-image-edit",
|
||||||
|
"description": "Qwen-Image-Edit 2509 diffusion model (bf16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI",
|
||||||
|
"filename": "qwen_image_edit_2509_bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_edit_2509_bf16.safetensors",
|
||||||
|
"size": "9.78GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Edit Diffusion Model (fp8_e4m3fn)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Qwen-Image-Edit",
|
||||||
|
"save_path": "diffusion_models/qwen-image-edit",
|
||||||
|
"description": "Qwen-Image-Edit diffusion model (fp8_e4m3fn)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI",
|
||||||
|
"filename": "qwen_image_edit_fp8_e4m3fn.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_edit_fp8_e4m3fn.safetensors",
|
||||||
|
"size": "4.89GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Edit Diffusion Model (bf16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Qwen-Image-Edit",
|
||||||
|
"save_path": "diffusion_models/qwen-image-edit",
|
||||||
|
"description": "Qwen-Image-Edit diffusion model (bf16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI",
|
||||||
|
"filename": "qwen_image_edit_bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Qwen-Image-Edit_ComfyUI/resolve/main/split_files/diffusion_models/qwen_image_edit_bf16.safetensors",
|
||||||
|
"size": "9.78GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Lightning 8steps V1.0",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image",
|
||||||
|
"save_path": "loras/qwen-image-lightning",
|
||||||
|
"description": "Qwen-Image-Lightning 8-step LoRA model V1.0",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Lightning-8steps-V1.0.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-8steps-V1.0.safetensors",
|
||||||
|
"size": "9.78GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Lightning 4steps V1.0",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image",
|
||||||
|
"save_path": "loras/qwen-image-lightning",
|
||||||
|
"description": "Qwen-Image-Lightning 4-step LoRA model V1.0",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Lightning-4steps-V1.0.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-4steps-V1.0.safetensors",
|
||||||
|
"size": "9.78GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Lightning 4steps V1.0 (bf16)",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image",
|
||||||
|
"save_path": "loras/qwen-image-lightning",
|
||||||
|
"description": "Qwen-Image-Lightning 4-step LoRA model V1.0 (bf16)",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Lightning-4steps-V1.0-bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-4steps-V1.0-bf16.safetensors",
|
||||||
|
"size": "19.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Lightning 4steps V2.0",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image",
|
||||||
|
"save_path": "loras/qwen-image-lightning",
|
||||||
|
"description": "Qwen-Image-Lightning 4-step LoRA model V2.0",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Lightning-4steps-V2.0.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-4steps-V2.0.safetensors",
|
||||||
|
"size": "9.78GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Lightning 4steps V2.0 (bf16)",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image",
|
||||||
|
"save_path": "loras/qwen-image-lightning",
|
||||||
|
"description": "Qwen-Image-Lightning 4-step LoRA model V2.0 (bf16)",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Lightning-4steps-V2.0-bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-4steps-V2.0-bf16.safetensors",
|
||||||
|
"size": "19.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Lightning 8steps V1.1",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image",
|
||||||
|
"save_path": "loras/qwen-image-lightning",
|
||||||
|
"description": "Qwen-Image-Lightning 8-step LoRA model V1.1",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Lightning-8steps-V1.1.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-8steps-V1.1.safetensors",
|
||||||
|
"size": "9.78GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Lightning 8steps V1.1 (bf16)",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image",
|
||||||
|
"save_path": "loras/qwen-image-lightning",
|
||||||
|
"description": "Qwen-Image-Lightning 8-step LoRA model V1.1 (bf16)",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Lightning-8steps-V1.1-bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-8steps-V1.1-bf16.safetensors",
|
||||||
|
"size": "19.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Lightning 8steps V2.0",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image",
|
||||||
|
"save_path": "loras/qwen-image-lightning",
|
||||||
|
"description": "Qwen-Image-Lightning 8-step LoRA model V2.0",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Lightning-8steps-V2.0.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-8steps-V2.0.safetensors",
|
||||||
|
"size": "9.78GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Lightning 8steps V2.0 (bf16)",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image",
|
||||||
|
"save_path": "loras/qwen-image-lightning",
|
||||||
|
"description": "Qwen-Image-Lightning 8-step LoRA model V2.0 (bf16)",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Lightning-8steps-V2.0-bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Lightning-8steps-V2.0-bf16.safetensors",
|
||||||
|
"size": "19.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Edit-Lightning 4steps V1.0",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image-Edit",
|
||||||
|
"save_path": "loras/qwen-image-edit-lightning",
|
||||||
|
"description": "Qwen-Image-Edit-Lightning 4-step LoRA model V1.0",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Edit-Lightning-4steps-V1.0.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-Lightning-4steps-V1.0.safetensors",
|
||||||
|
"size": "9.78GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Edit-Lightning 4steps V1.0 (bf16)",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image-Edit",
|
||||||
|
"save_path": "loras/qwen-image-edit-lightning",
|
||||||
|
"description": "Qwen-Image-Edit-Lightning 4-step LoRA model V1.0 (bf16)",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Edit-Lightning-4steps-V1.0-bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-Lightning-4steps-V1.0-bf16.safetensors",
|
||||||
|
"size": "19.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Edit-Lightning 8steps V1.0",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image-Edit",
|
||||||
|
"save_path": "loras/qwen-image-edit-lightning",
|
||||||
|
"description": "Qwen-Image-Edit-Lightning 8-step LoRA model V1.0",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Edit-Lightning-8steps-V1.0.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-Lightning-8steps-V1.0.safetensors",
|
||||||
|
"size": "9.78GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Edit-Lightning 8steps V1.0 (bf16)",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image-Edit",
|
||||||
|
"save_path": "loras/qwen-image-edit-lightning",
|
||||||
|
"description": "Qwen-Image-Edit-Lightning 8-step LoRA model V1.0 (bf16)",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Edit-Lightning-8steps-V1.0-bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-Lightning-8steps-V1.0-bf16.safetensors",
|
||||||
|
"size": "19.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Edit-2509-Lightning 4steps V1.0 (bf16)",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image-Edit",
|
||||||
|
"save_path": "loras/qwen-image-edit-lightning",
|
||||||
|
"description": "Qwen-Image-Edit-2509-Lightning 4-step LoRA model V1.0 (bf16)",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Edit-2509-Lightning-4steps-V1.0-bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-2509/Qwen-Image-Edit-2509-Lightning-4steps-V1.0-bf16.safetensors",
|
||||||
|
"size": "19.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Edit-2509-Lightning 4steps V1.0 (fp32)",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image-Edit",
|
||||||
|
"save_path": "loras/qwen-image-edit-lightning",
|
||||||
|
"description": "Qwen-Image-Edit-2509-Lightning 4-step LoRA model V1.0 (fp32)",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Edit-2509-Lightning-4steps-V1.0-fp32.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-2509/Qwen-Image-Edit-2509-Lightning-4steps-V1.0-fp32.safetensors",
|
||||||
|
"size": "39.1GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Edit-2509-Lightning 8steps V1.0 (bf16)",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image-Edit",
|
||||||
|
"save_path": "loras/qwen-image-edit-lightning",
|
||||||
|
"description": "Qwen-Image-Edit-2509-Lightning 8-step LoRA model V1.0 (bf16)",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Edit-2509-Lightning-8steps-V1.0-bf16.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-2509/Qwen-Image-Edit-2509-Lightning-8steps-V1.0-bf16.safetensors",
|
||||||
|
"size": "19.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image-Edit-2509-Lightning 8steps V1.0 (fp32)",
|
||||||
|
"type": "lora",
|
||||||
|
"base": "Qwen-Image-Edit",
|
||||||
|
"save_path": "loras/qwen-image-edit-lightning",
|
||||||
|
"description": "Qwen-Image-Edit-2509-Lightning 8-step LoRA model V1.0 (fp32)",
|
||||||
|
"reference": "https://huggingface.co/lightx2v/Qwen-Image-Lightning",
|
||||||
|
"filename": "Qwen-Image-Edit-2509-Lightning-8steps-V1.0-fp32.safetensors",
|
||||||
|
"url": "https://huggingface.co/lightx2v/Qwen-Image-Lightning/resolve/main/Qwen-Image-Edit-2509/Qwen-Image-Edit-2509-Lightning-8steps-V1.0-fp32.safetensors",
|
||||||
|
"size": "39.1GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image InstantX ControlNet Union",
|
||||||
|
"type": "controlnet",
|
||||||
|
"base": "Qwen-Image",
|
||||||
|
"save_path": "controlnet/qwen-image/instantx",
|
||||||
|
"description": "Qwen-Image InstantX ControlNet Union model",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image-InstantX-ControlNets",
|
||||||
|
"filename": "Qwen-Image-InstantX-ControlNet-Union.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Qwen-Image-InstantX-ControlNets/resolve/main/split_files/controlnet/Qwen-Image-InstantX-ControlNet-Union.safetensors",
|
||||||
|
"size": "2.54GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Qwen-Image InstantX ControlNet Inpainting",
|
||||||
|
"type": "controlnet",
|
||||||
|
"base": "Qwen-Image",
|
||||||
|
"save_path": "controlnet/qwen-image/instantx",
|
||||||
|
"description": "Qwen-Image InstantX ControlNet Inpainting model",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Qwen-Image-InstantX-ControlNets",
|
||||||
|
"filename": "Qwen-Image-InstantX-ControlNet-Inpainting.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Qwen-Image-InstantX-ControlNets/resolve/main/split_files/controlnet/Qwen-Image-InstantX-ControlNet-Inpainting.safetensors",
|
||||||
|
"size": "2.54GB"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,6 @@ else:
|
|||||||
def current_timestamp():
|
def current_timestamp():
|
||||||
return str(time.time()).split('.')[0]
|
return str(time.time()).split('.')[0]
|
||||||
|
|
||||||
security_check.security_check()
|
|
||||||
|
|
||||||
cm_global.pip_blacklist = {'torch', 'torchaudio', 'torchsde', 'torchvision'}
|
cm_global.pip_blacklist = {'torch', 'torchaudio', 'torchsde', 'torchvision'}
|
||||||
cm_global.pip_downgrade_blacklist = ['torch', 'torchaudio', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
|
cm_global.pip_downgrade_blacklist = ['torch', 'torchaudio', 'torchsde', 'torchvision', 'transformers', 'safetensors', 'kornia']
|
||||||
@@ -81,7 +80,7 @@ cm_global.register_api('cm.is_import_failed_extension', is_import_failed_extensi
|
|||||||
comfyui_manager_path = os.path.abspath(os.path.dirname(__file__))
|
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 = folder_paths.get_system_user_directory("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")
|
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")
|
||||||
@@ -111,20 +110,15 @@ def check_file_logging():
|
|||||||
|
|
||||||
read_config()
|
read_config()
|
||||||
read_uv_mode()
|
read_uv_mode()
|
||||||
|
security_check.security_check()
|
||||||
check_file_logging()
|
check_file_logging()
|
||||||
|
|
||||||
if sys.version_info < (3, 13):
|
cm_global.pip_overrides = {}
|
||||||
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)
|
||||||
|
|
||||||
if sys.version_info < (3, 13):
|
|
||||||
cm_global.pip_overrides['numpy'] = 'numpy<2'
|
|
||||||
|
|
||||||
|
|
||||||
if os.path.exists(manager_pip_blacklist_path):
|
if os.path.exists(manager_pip_blacklist_path):
|
||||||
with open(manager_pip_blacklist_path, 'r', encoding="UTF-8", errors="ignore") as f:
|
with open(manager_pip_blacklist_path, 'r', encoding="UTF-8", errors="ignore") as f:
|
||||||
@@ -489,7 +483,7 @@ 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(manager_files_path, "startup-scripts", "install-scripts.txt")
|
||||||
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, manager_files_path)
|
pip_fixer = manager_util.PIPFixer(manager_util.get_installed_packages(), comfy_path, manager_files_path)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ You can set whether to use ComfyUI-Manager solely via CLI.
|
|||||||
`restore-dependencies`
|
`restore-dependencies`
|
||||||
|
|
||||||
* This command can be used if custom nodes are installed under the `ComfyUI/custom_nodes` path but their dependencies are not installed.
|
* This command can be used if custom nodes are installed under the `ComfyUI/custom_nodes` path but their dependencies are not installed.
|
||||||
* It is useful when starting a new cloud instance, like colab, where dependencies need to be reinstalled and installation scripts re-executed.
|
* It is useful when starting a new cloud instance, like Colab, where dependencies need to be reinstalled and installation scripts re-executed.
|
||||||
* It can also be utilized if ComfyUI is reinstalled and only the custom_nodes path has been backed up and restored.
|
* It can also be utilized if ComfyUI is reinstalled and only the custom_nodes path has been backed up and restored.
|
||||||
|
|
||||||
### 7. Clear
|
### 7. Clear
|
||||||
|
|||||||
@@ -23,13 +23,13 @@ OPTIONS:
|
|||||||
## How To Use?
|
## How To Use?
|
||||||
* `python cm-cli.py` 를 통해서 실행 시킬 수 있습니다.
|
* `python cm-cli.py` 를 통해서 실행 시킬 수 있습니다.
|
||||||
* 예를 들어 custom node를 모두 업데이트 하고 싶다면
|
* 예를 들어 custom node를 모두 업데이트 하고 싶다면
|
||||||
* ComfyUI-Manager경로 에서 `python cm-cli.py update all` 를 command를 실행할 수 있습니다.
|
* ComfyUI-Manager 경로에서 `python cm-cli.py update all` 명령을 실행할 수 있습니다.
|
||||||
* ComfyUI 경로에서 실행한다면, `python custom_nodes/ComfyUI-Manager/cm-cli.py update all` 와 같이 cm-cli.py 의 경로를 지정할 수도 있습니다.
|
* ComfyUI 경로에서 실행한다면, `python custom_nodes/ComfyUI-Manager/cm-cli.py update all` 와 같이 cm-cli.py 의 경로를 지정할 수도 있습니다.
|
||||||
|
|
||||||
## Prerequisite
|
## Prerequisite
|
||||||
* ComfyUI 를 실행하는 python과 동일한 python 환경에서 실행해야 합니다.
|
* ComfyUI 를 실행하는 python과 동일한 python 환경에서 실행해야 합니다.
|
||||||
* venv를 사용할 경우 해당 venv를 activate 한 상태에서 실행해야 합니다.
|
* venv를 사용할 경우 해당 venv를 activate 한 상태에서 실행해야 합니다.
|
||||||
* portable 버전을 사용할 경우 run_nvidia_gpu.bat 파일이 있는 경로인 경우, 다음과 같은 방식으로 코맨드를 실행해야 합니다.
|
* portable 버전을 사용할 경우 run_nvidia_gpu.bat 파일이 있는 경로인 경우, 다음과 같은 방식으로 명령을 실행해야 합니다.
|
||||||
`.\python_embeded\python.exe ComfyUI\custom_nodes\ComfyUI-Manager\cm-cli.py update all`
|
`.\python_embeded\python.exe ComfyUI\custom_nodes\ComfyUI-Manager\cm-cli.py update all`
|
||||||
* ComfyUI 의 경로는 COMFYUI_PATH 환경 변수로 설정할 수 있습니다. 만약 생략할 경우 다음과 같은 경고 메시지가 나타나며, ComfyUI-Manager가 설치된 경로를 기준으로 상대 경로로 설정됩니다.
|
* ComfyUI 의 경로는 COMFYUI_PATH 환경 변수로 설정할 수 있습니다. 만약 생략할 경우 다음과 같은 경고 메시지가 나타나며, ComfyUI-Manager가 설치된 경로를 기준으로 상대 경로로 설정됩니다.
|
||||||
```
|
```
|
||||||
@@ -40,8 +40,8 @@ OPTIONS:
|
|||||||
|
|
||||||
### 1. --channel, --mode
|
### 1. --channel, --mode
|
||||||
* 정보 보기 기능과 커스텀 노드 관리 기능의 경우는 --channel과 --mode를 통해 정보 DB를 설정할 수 있습니다.
|
* 정보 보기 기능과 커스텀 노드 관리 기능의 경우는 --channel과 --mode를 통해 정보 DB를 설정할 수 있습니다.
|
||||||
* 예들 들어 `python cm-cli.py update all --channel recent --mode remote`와 같은 command를 실행할 경우, 현재 ComfyUI-Manager repo에 내장된 로컬의 정보가 아닌 remote의 최신 정보를 기준으로 동작하며, recent channel에 있는 목록을 대상으로만 동작합니다.
|
* 예를 들어 `python cm-cli.py update all --channel recent --mode remote`와 같은 명령을 실행할 경우, 현재 ComfyUI-Manager repo에 내장된 로컬의 정보가 아닌 remote의 최신 정보를 기준으로 동작하며, recent channel에 있는 목록을 대상으로만 동작합니다.
|
||||||
* --channel, --mode 는 `simple-show, show, install, uninstall, update, disable, enable, fix` command에서만 사용 가능합니다.
|
* --channel, --mode 는 `simple-show, show, install, uninstall, update, disable, enable, fix` 명령에서만 사용 가능합니다.
|
||||||
|
|
||||||
### 2. 관리 정보 보기
|
### 2. 관리 정보 보기
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ OPTIONS:
|
|||||||
* `[show|simple-show]` - `show`는 상세하게 정보를 보여주며, `simple-show`는 간단하게 정보를 보여줍니다.
|
* `[show|simple-show]` - `show`는 상세하게 정보를 보여주며, `simple-show`는 간단하게 정보를 보여줍니다.
|
||||||
|
|
||||||
|
|
||||||
`python cm-cli.py show installed` 와 같은 코맨드를 실행하면 설치된 커스텀 노드의 정보를 상세하게 보여줍니다.
|
`python cm-cli.py show installed` 와 같은 명령을 실행하면 설치된 커스텀 노드의 정보를 상세하게 보여줍니다.
|
||||||
```
|
```
|
||||||
-= ComfyUI-Manager CLI (V2.24) =-
|
-= ComfyUI-Manager CLI (V2.24) =-
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ FETCH DATA from: https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main
|
|||||||
[ DISABLED ] ComfyUI-Loopchain (author: Fannovel16)
|
[ DISABLED ] ComfyUI-Loopchain (author: Fannovel16)
|
||||||
```
|
```
|
||||||
|
|
||||||
`python cm-cli.py simple-show installed` 와 같은 코맨드를 이용해서 설치된 커스텀 노드의 정보를 간단하게 보여줍니다.
|
`python cm-cli.py simple-show installed` 와 같은 명령을 이용해서 설치된 커스텀 노드의 정보를 간단하게 보여줍니다.
|
||||||
|
|
||||||
```
|
```
|
||||||
-= ComfyUI-Manager CLI (V2.24) =-
|
-= ComfyUI-Manager CLI (V2.24) =-
|
||||||
@@ -89,7 +89,7 @@ ComfyUI-Loopchain
|
|||||||
* `installed`: enable, disable 여부와 상관없이 설치된 모든 노드를 보여줍니다
|
* `installed`: enable, disable 여부와 상관없이 설치된 모든 노드를 보여줍니다
|
||||||
* `not-installed`: 설치되지 않은 커스텀 노드의 목록을 보여줍니다.
|
* `not-installed`: 설치되지 않은 커스텀 노드의 목록을 보여줍니다.
|
||||||
* `all`: 모든 커스텀 노드의 목록을 보여줍니다.
|
* `all`: 모든 커스텀 노드의 목록을 보여줍니다.
|
||||||
* `snapshot`: 현재 설치된 커스텀 노드의 snapshot 정보를 보여줍니다. `show`롤 통해서 볼 경우는 json 출력 형태로 보여주며, `simple-show`를 통해서 볼 경우는 간단하게, 커밋 해시와 함께 보여줍니다.
|
* `snapshot`: 현재 설치된 커스텀 노드의 snapshot 정보를 보여줍니다. `show`를 통해서 볼 경우는 json 출력 형태로 보여주며, `simple-show`를 통해서 볼 경우는 간단하게, 커밋 해시와 함께 보여줍니다.
|
||||||
* `snapshot-list`: ComfyUI-Manager/snapshots 에 저장된 snapshot 파일의 목록을 보여줍니다.
|
* `snapshot-list`: ComfyUI-Manager/snapshots 에 저장된 snapshot 파일의 목록을 보여줍니다.
|
||||||
|
|
||||||
### 3. 커스텀 노드 관리 하기
|
### 3. 커스텀 노드 관리 하기
|
||||||
@@ -98,7 +98,7 @@ ComfyUI-Loopchain
|
|||||||
|
|
||||||
* `python cm-cli.py install ComfyUI-Impact-Pack ComfyUI-Inspire-Pack ComfyUI_experiments` 와 같이 커스텀 노드의 이름을 나열해서 관리 기능을 적용할 수 있습니다.
|
* `python cm-cli.py install ComfyUI-Impact-Pack ComfyUI-Inspire-Pack ComfyUI_experiments` 와 같이 커스텀 노드의 이름을 나열해서 관리 기능을 적용할 수 있습니다.
|
||||||
* 커스텀 노드의 이름은 `show`를 했을 때 보여주는 이름이며, git repository의 이름입니다.
|
* 커스텀 노드의 이름은 `show`를 했을 때 보여주는 이름이며, git repository의 이름입니다.
|
||||||
(추후 nickname 을 사용가능하돌고 업데이트 할 예정입니다.)
|
(추후 nickname을 사용 가능하도록 업데이트할 예정입니다.)
|
||||||
|
|
||||||
`[update|disable|enable|fix] all ?[--channel <channel name>] ?[--mode [remote|local|cache]]`
|
`[update|disable|enable|fix] all ?[--channel <channel name>] ?[--mode [remote|local|cache]]`
|
||||||
|
|
||||||
@@ -124,7 +124,7 @@ ComfyUI-Loopchain
|
|||||||
* `--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`: 사용자 디렉토리 설정
|
* `--user-directory`: 사용자 디렉토리 설정
|
||||||
* `--restore-to`: 복구될 커스텀 노드가 설치될 경로. (이 옵션을 적용할 경우 오직 대상 경로에 설치된 custom nodes 만 설치된 것으로 인식함.)
|
* `--restore-to`: 복구될 커스텀 노드가 설치될 경로. (이 옵션을 적용할 경우 오직 대상 경로에 설치된 custom nodes만 설치된 것으로 인식함.)
|
||||||
|
|
||||||
### 5. CLI only mode
|
### 5. CLI only mode
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ ComfyUI-Manager를 CLI로만 사용할 것인지를 설정할 수 있습니다.
|
|||||||
`cli-only-mode [enable|disable]`
|
`cli-only-mode [enable|disable]`
|
||||||
|
|
||||||
* security 혹은 policy 의 이유로 GUI 를 통한 ComfyUI-Manager 사용을 제한하고 싶은 경우 이 모드를 사용할 수 있습니다.
|
* security 혹은 policy 의 이유로 GUI 를 통한 ComfyUI-Manager 사용을 제한하고 싶은 경우 이 모드를 사용할 수 있습니다.
|
||||||
* CLI only mode를 적용할 경우 ComfyUI-Manager 가 매우 제한된 상태로 로드되어, 내부적으로 제공하는 web API가 비활성화 되며, 메인 메뉴에서도 Manager 버튼이 표시되지 않습니다.
|
* CLI only mode를 적용할 경우 ComfyUI-Manager 가 매우 제한된 상태로 로드되어, 내부적으로 제공하는 web API가 비활성화되며, 메인 메뉴에서도 Manager 버튼이 표시되지 않습니다.
|
||||||
|
|
||||||
|
|
||||||
### 6. 의존성 설치
|
### 6. 의존성 설치
|
||||||
@@ -141,10 +141,10 @@ ComfyUI-Manager를 CLI로만 사용할 것인지를 설정할 수 있습니다.
|
|||||||
`restore-dependencies`
|
`restore-dependencies`
|
||||||
|
|
||||||
* `ComfyUI/custom_nodes` 하위 경로에 커스텀 노드들이 설치되어 있긴 하지만, 의존성이 설치되지 않은 경우 사용할 수 있습니다.
|
* `ComfyUI/custom_nodes` 하위 경로에 커스텀 노드들이 설치되어 있긴 하지만, 의존성이 설치되지 않은 경우 사용할 수 있습니다.
|
||||||
* colab 과 같이 cloud instance를 새로 시작하는 경우 의존성 재설치 및 설치 스크립트가 재실행 되어야 하는 경우 사용합니다.
|
* Colab과 같이 cloud instance를 새로 시작하는 경우 의존성 재설치 및 설치 스크립트가 재실행되어야 하는 경우 사용합니다.
|
||||||
* ComfyUI을 재설치할 경우, custom_nodes 경로만 백업했다가 재설치 할 경우 활용 가능합니다.
|
* ComfyUI를 재설치할 경우, custom_nodes 경로만 백업했다가 재설치할 경우 활용 가능합니다.
|
||||||
|
|
||||||
|
|
||||||
### 7. clear
|
### 7. clear
|
||||||
|
|
||||||
GUI에서 install, update를 하거나 snapshot 을 restore하는 경우 예약을 통해서 다음번 ComfyUI를 실행할 경우 실행되는 구조입니다. `clear` 는 이런 예약 상태를 clear해서, 아무런 사전 실행이 적용되지 않도록 합니다.
|
GUI에서 install, update를 하거나 snapshot을 restore하는 경우 예약을 통해서 다음번 ComfyUI를 실행할 경우 실행되는 구조입니다. `clear` 는 이런 예약 상태를 clear해서, 아무런 사전 실행이 적용되지 않도록 합니다.
|
||||||
|
|||||||
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,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
rm ~/.tmp/dev/*.py > /dev/null 2>&1
|
rm ~/.tmp/dev/*.py > /dev/null 2>&1
|
||||||
python ../../scanner.py ~/.tmp/dev
|
python ../../scanner.py ~/.tmp/dev $*
|
||||||
@@ -1,5 +1,25 @@
|
|||||||
{
|
{
|
||||||
"custom_nodes": [
|
"custom_nodes": [
|
||||||
|
{
|
||||||
|
"author": "synchronicity-labs",
|
||||||
|
"title": "ComfyUI Sync Lipsync Node",
|
||||||
|
"reference": "https://github.com/synchronicity-labs/sync-comfyui",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/synchronicity-labs/sync-comfyui"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "This custom node allows you to perform audio-video lip synchronization inside ComfyUI using a simple interface."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "joaomede",
|
||||||
|
"title": "ComfyUI-Unload-Model-Fork",
|
||||||
|
"reference": "https://github.com/joaomede/ComfyUI-Unload-Model-Fork",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/joaomede/ComfyUI-Unload-Model-Fork"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "For unloading a model or all models, using the memory management that is already present in ComfyUI. Copied from [a/https://github.com/willblaschko/ComfyUI-Unload-Models](https://github.com/willblaschko/ComfyUI-Unload-Models) but without the unnecessary extra stuff."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"author": "SanDiegoDude",
|
"author": "SanDiegoDude",
|
||||||
"title": "ComfyUI-HiDream-Sampler [WIP]",
|
"title": "ComfyUI-HiDream-Sampler [WIP]",
|
||||||
@@ -149,6 +169,16 @@
|
|||||||
],
|
],
|
||||||
"install_type": "git-clone",
|
"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"
|
"description": "A fork of KJNodes for ComfyUI.\nVarious quality of life -nodes for ComfyUI, mostly just visual stuff to improve usability"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "huixingyun",
|
||||||
|
"title": "ComfyUI-SoundFlow",
|
||||||
|
"reference": "https://github.com/huixingyun/ComfyUI-SoundFlow",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/huixingyun/ComfyUI-SoundFlow"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "forked from https://github.com/fredconex/ComfyUI-SoundFlow (removed)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
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,219 @@
|
|||||||
{
|
{
|
||||||
"models": [
|
"models": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 i2v high noise 14B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for i2v high noise 14B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_i2v_high_noise_14B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_high_noise_14B_fp16.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 i2v high noise 14B (fp8_scaled)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for i2v high noise 14B (fp8_scaled)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_high_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"size": "14.3GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 i2v low noise 14B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for i2v low noise 14B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_i2v_low_noise_14B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_low_noise_14B_fp16.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 i2v low noise 14B (fp8_scaled)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for i2v low noise 14B (fp8_scaled)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_i2v_low_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_i2v_low_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"size": "14.3GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 t2v high noise 14B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for t2v high noise 14B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_t2v_high_noise_14B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_high_noise_14B_fp16.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 t2v high noise 14B (fp8_scaled)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for t2v high noise 14B (fp8_scaled)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_t2v_high_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_high_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"size": "14.3GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 t2v low noise 14B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for t2v low noise 14B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_t2v_low_noise_14B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_low_noise_14B_fp16.safetensors",
|
||||||
|
"size": "28.6GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 t2v low noise 14B (fp8_scaled)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for t2v low noise 14B (fp8_scaled)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_t2v_low_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_t2v_low_noise_14B_fp8_scaled.safetensors",
|
||||||
|
"size": "14.3GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/Wan2.2 ti2v 5B (fp16)",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "Wan2.2",
|
||||||
|
"save_path": "diffusion_models/Wan2.2",
|
||||||
|
"description": "Wan2.2 diffusion model for ti2v 5B (fp16)",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged",
|
||||||
|
"filename": "wan2.2_ti2v_5B_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Wan_2.2_ComfyUI_Repackaged/resolve/main/split_files/diffusion_models/wan2.2_ti2v_5B_fp16.safetensors",
|
||||||
|
"size": "10.0GB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "sam2.1_hiera_tiny.pt",
|
||||||
|
"type": "sam2.1",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2.1 hiera model (tiny)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2.1_hiera_tiny.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_tiny.pt",
|
||||||
|
"size": "149.0MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sam2.1_hiera_small.pt",
|
||||||
|
"type": "sam2.1",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2.1 hiera model (small)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2.1_hiera_small.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_small.pt",
|
||||||
|
"size": "176.0MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sam2.1_hiera_base_plus.pt",
|
||||||
|
"type": "sam2.1",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2.1 hiera model (base+)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2.1_hiera_base_plus.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_base_plus.pt",
|
||||||
|
"size": "309.0MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sam2.1_hiera_large.pt",
|
||||||
|
"type": "sam2.1",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2.1 hiera model (large)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2.1_hiera_large.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/092824/sam2.1_hiera_large.pt",
|
||||||
|
"size": "857.0MB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "sam2_hiera_tiny.pt",
|
||||||
|
"type": "sam2",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2 hiera model (tiny)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2_hiera_tiny.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_tiny.pt",
|
||||||
|
"size": "149.0MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sam2_hiera_small.pt",
|
||||||
|
"type": "sam2",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2 hiera model (small)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2_hiera_small.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_small.pt",
|
||||||
|
"size": "176.0MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sam2_hiera_base_plus.pt",
|
||||||
|
"type": "sam2",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2 hiera model (base+)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2_hiera_base_plus.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_base_plus.pt",
|
||||||
|
"size": "309.0MB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sam2_hiera_large.pt",
|
||||||
|
"type": "sam2",
|
||||||
|
"base": "SAM",
|
||||||
|
"save_path": "sams",
|
||||||
|
"description": "Segmenty Anything SAM 2 hiera model (large)",
|
||||||
|
"reference": "https://github.com/facebookresearch/sam2#model-description",
|
||||||
|
"filename": "sam2_hiera_large.pt",
|
||||||
|
"url": "https://dl.fbaipublicfiles.com/segment_anything_2/072824/sam2_hiera_large.pt",
|
||||||
|
"size": "857.0MB"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/omnigen2_fp16.safetensors",
|
||||||
|
"type": "diffusion_model",
|
||||||
|
"base": "OmniGen2",
|
||||||
|
"save_path": "default",
|
||||||
|
"description": "OmniGen2 diffusion model. This is required for using OmniGen2.",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged",
|
||||||
|
"filename": "omnigen2_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged/resolve/main/split_files/diffusion_models/omnigen2_fp16.safetensors",
|
||||||
|
"size": "7.93GB"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Comfy-Org/qwen_2.5_vl_fp16.safetensors",
|
||||||
|
"type": "clip",
|
||||||
|
"base": "qwen-2.5",
|
||||||
|
"save_path": "default",
|
||||||
|
"description": "text encoder for OmniGen2",
|
||||||
|
"reference": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged",
|
||||||
|
"filename": "qwen_2.5_vl_fp16.safetensors",
|
||||||
|
"url": "https://huggingface.co/Comfy-Org/Omnigen2_ComfyUI_repackaged/resolve/main/split_files/text_encoders/qwen_2.5_vl_fp16.safetensors",
|
||||||
|
"size": "7.51GB"
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "Latent Bridge Matching for Image Relighting",
|
"name": "Latent Bridge Matching for Image Relighting",
|
||||||
"type": "diffusion_model",
|
"type": "diffusion_model",
|
||||||
@@ -473,224 +687,6 @@
|
|||||||
"filename": "llava_llama3_fp16.safetensors",
|
"filename": "llava_llama3_fp16.safetensors",
|
||||||
"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": "PixArt-Sigma-XL-2-512-MS.safetensors (diffusion)",
|
|
||||||
"type": "diffusion_model",
|
|
||||||
"base": "pixart-sigma",
|
|
||||||
"save_path": "diffusion_models/PixArt-Sigma",
|
|
||||||
"description": "PixArt-Sigma Diffusion model",
|
|
||||||
"reference": "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-512-MS",
|
|
||||||
"filename": "PixArt-Sigma-XL-2-512-MS.safetensors",
|
|
||||||
"url": "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-512-MS/resolve/main/transformer/diffusion_pytorch_model.safetensors",
|
|
||||||
"size": "2.44GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "PixArt-Sigma-XL-2-1024-MS.safetensors (diffusion)",
|
|
||||||
"type": "diffusion_model",
|
|
||||||
"base": "pixart-sigma",
|
|
||||||
"save_path": "diffusion_models/PixArt-Sigma",
|
|
||||||
"description": "PixArt-Sigma Diffusion model",
|
|
||||||
"reference": "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS",
|
|
||||||
"filename": "PixArt-Sigma-XL-2-1024-MS.safetensors",
|
|
||||||
"url": "https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS/resolve/main/transformer/diffusion_pytorch_model.safetensors",
|
|
||||||
"size": "2.44GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "PixArt-XL-2-1024-MS.safetensors (diffusion)",
|
|
||||||
"type": "diffusion_model",
|
|
||||||
"base": "pixart-alpha",
|
|
||||||
"save_path": "diffusion_models/PixArt-Alpha",
|
|
||||||
"description": "PixArt-Alpha Diffusion model",
|
|
||||||
"reference": "https://huggingface.co/PixArt-alpha/PixArt-XL-2-1024-MS",
|
|
||||||
"filename": "PixArt-XL-2-1024-MS.safetensors",
|
|
||||||
"url": "https://huggingface.co/PixArt-alpha/PixArt-XL-2-1024-MS/resolve/main/transformer/diffusion_pytorch_model.safetensors",
|
|
||||||
"size": "2.45GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "Comfy-Org/hunyuan_video_t2v_720p_bf16.safetensors",
|
|
||||||
"type": "diffusion_model",
|
|
||||||
"base": "Hunyuan Video",
|
|
||||||
"save_path": "diffusion_models/hunyuan_video",
|
|
||||||
"description": "Huyuan Video diffusion model. repackaged version.",
|
|
||||||
"reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged",
|
|
||||||
"filename": "hunyuan_video_t2v_720p_bf16.safetensors",
|
|
||||||
"url": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged/resolve/main/split_files/diffusion_models/hunyuan_video_t2v_720p_bf16.safetensors",
|
|
||||||
"size": "25.6GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Comfy-Org/hunyuan_video_vae_bf16.safetensors",
|
|
||||||
"type": "VAE",
|
|
||||||
"base": "Hunyuan Video",
|
|
||||||
"save_path": "VAE",
|
|
||||||
"description": "Huyuan Video VAE model. repackaged version.",
|
|
||||||
"reference": "https://huggingface.co/Comfy-Org/HunyuanVideo_repackaged",
|
|
||||||
"filename": "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"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "LTX-Video 2B v0.9.1 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.1.safetensors",
|
|
||||||
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltx-video-2b-v0.9.1.safetensors",
|
|
||||||
"size": "5.72GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "XLabs-AI/flux-canny-controlnet-v3.safetensors",
|
|
||||||
"type": "controlnet",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "xlabs/controlnets",
|
|
||||||
"description": "ControlNet checkpoints for FLUX.1-dev model by Black Forest Labs.",
|
|
||||||
"reference": "https://huggingface.co/XLabs-AI/flux-controlnet-collections",
|
|
||||||
"filename": "flux-canny-controlnet-v3.safetensors",
|
|
||||||
"url": "https://huggingface.co/XLabs-AI/flux-controlnet-collections/resolve/main/flux-canny-controlnet-v3.safetensors",
|
|
||||||
"size": "1.49GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "XLabs-AI/flux-depth-controlnet-v3.safetensors",
|
|
||||||
"type": "controlnet",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "xlabs/controlnets",
|
|
||||||
"description": "ControlNet checkpoints for FLUX.1-dev model by Black Forest Labs.",
|
|
||||||
"reference": "https://huggingface.co/XLabs-AI/flux-controlnet-collections",
|
|
||||||
"filename": "flux-depth-controlnet-v3.safetensors",
|
|
||||||
"url": "https://huggingface.co/XLabs-AI/flux-controlnet-collections/resolve/main/flux-depth-controlnet-v3.safetensors",
|
|
||||||
"size": "1.49GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "XLabs-AI/flux-hed-controlnet-v3.safetensors",
|
|
||||||
"type": "controlnet",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "xlabs/controlnets",
|
|
||||||
"description": "ControlNet checkpoints for FLUX.1-dev model by Black Forest Labs.",
|
|
||||||
"reference": "https://huggingface.co/XLabs-AI/flux-controlnet-collections",
|
|
||||||
"filename": "flux-hed-controlnet-v3.safetensors",
|
|
||||||
"url": "https://huggingface.co/XLabs-AI/flux-controlnet-collections/resolve/main/flux-hed-controlnet-v3.safetensors",
|
|
||||||
"size": "1.49GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "XLabs-AI/realism_lora.safetensors",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "xlabs/loras",
|
|
||||||
"description": "A checkpoint with trained LoRAs for FLUX.1-dev model by Black Forest Labs",
|
|
||||||
"reference": "https://huggingface.co/XLabs-AI/flux-lora-collection",
|
|
||||||
"filename": "realism_lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/XLabs-AI/flux-lora-collection/resolve/main/realism_lora.safetensors",
|
|
||||||
"size": "44.8MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "XLabs-AI/art_lora.safetensors",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "xlabs/loras",
|
|
||||||
"description": "A checkpoint with trained LoRAs for FLUX.1-dev model by Black Forest Labs",
|
|
||||||
"reference": "https://huggingface.co/XLabs-AI/flux-lora-collection",
|
|
||||||
"filename": "art_lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/XLabs-AI/flux-lora-collection/resolve/main/scenery_lora.safetensors",
|
|
||||||
"size": "44.8MB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "XLabs-AI/mjv6_lora.safetensors",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "xlabs/loras",
|
|
||||||
"description": "A checkpoint with trained LoRAs for FLUX.1-dev model by Black Forest Labs",
|
|
||||||
"reference": "https://huggingface.co/XLabs-AI/flux-lora-collection",
|
|
||||||
"filename": "mjv6_lora.safetensors",
|
|
||||||
"url": "https://huggingface.co/XLabs-AI/flux-lora-collection/resolve/main/mjv6_lora.safetensors",
|
|
||||||
"size": "44.8MB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "XLabs-AI/flux-ip-adapter",
|
|
||||||
"type": "lora",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "xlabs/ipadapters",
|
|
||||||
"description": "A checkpoint with trained LoRAs for FLUX.1-dev model by Black Forest Labs",
|
|
||||||
"reference": "https://huggingface.co/XLabs-AI/flux-ip-adapter",
|
|
||||||
"filename": "ip_adapter.safetensors",
|
|
||||||
"url": "https://huggingface.co/XLabs-AI/flux-ip-adapter/resolve/main/ip_adapter.safetensors",
|
|
||||||
"size": "982MB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "stabilityai/SD3.5-Large-Controlnet-Blur",
|
|
||||||
"type": "controlnet",
|
|
||||||
"base": "SD3.5",
|
|
||||||
"save_path": "controlnet/SD3.5",
|
|
||||||
"description": "Blur Controlnet model for SD3.5 Large",
|
|
||||||
"reference": "https://huggingface.co/stabilityai/stable-diffusion-3.5-controlnets",
|
|
||||||
"filename": "sd3.5_large_controlnet_blur.safetensors",
|
|
||||||
"url": "https://huggingface.co/stabilityai/stable-diffusion-3.5-controlnets/resolve/main/sd3.5_large_controlnet_blur.safetensors",
|
|
||||||
"size": "8.65GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "stabilityai/SD3.5-Large-Controlnet-Canny",
|
|
||||||
"type": "controlnet",
|
|
||||||
"base": "SD3.5",
|
|
||||||
"save_path": "controlnet/SD3.5",
|
|
||||||
"description": "Canny Controlnet model for SD3.5 Large",
|
|
||||||
"reference": "https://huggingface.co/stabilityai/stable-diffusion-3.5-controlnets",
|
|
||||||
"filename": "sd3.5_large_controlnet_canny.safetensors",
|
|
||||||
"url": "https://huggingface.co/stabilityai/stable-diffusion-3.5-controlnets/resolve/main/sd3.5_large_controlnet_canny.safetensors",
|
|
||||||
"size": "8.65GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "stabilityai/SD3.5-Large-Controlnet-Depth",
|
|
||||||
"type": "controlnet",
|
|
||||||
"base": "SD3.5",
|
|
||||||
"save_path": "controlnet/SD3.5",
|
|
||||||
"description": "Depth Controlnet model for SD3.5 Large",
|
|
||||||
"reference": "https://huggingface.co/stabilityai/stable-diffusion-3.5-controlnets",
|
|
||||||
"filename": "sd3.5_large_controlnet_depth.safetensors",
|
|
||||||
"url": "https://huggingface.co/stabilityai/stable-diffusion-3.5-controlnets/resolve/main/sd3.5_large_controlnet_depth.safetensors",
|
|
||||||
"size": "8.65GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "LTX-Video 2B v0.9 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.safetensors",
|
|
||||||
"url": "https://huggingface.co/Lightricks/LTX-Video/resolve/main/ltx-video-2b-v0.9.safetensors",
|
|
||||||
"size": "9.37GB"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "InstantX/FLUX.1-dev-IP-Adapter",
|
|
||||||
"type": "IP-Adapter",
|
|
||||||
"base": "FLUX.1",
|
|
||||||
"save_path": "ipadapter-flux",
|
|
||||||
"description": "FLUX.1-dev-IP-Adapter",
|
|
||||||
"reference": "https://huggingface.co/InstantX/FLUX.1-dev-IP-Adapter",
|
|
||||||
"filename": "ip-adapter.bin",
|
|
||||||
"url": "https://huggingface.co/InstantX/FLUX.1-dev-IP-Adapter/resolve/main/ip-adapter.bin",
|
|
||||||
"size": "5.29GB"
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
"name": "Comfy-Org/sigclip_vision_384 (patch14_384)",
|
|
||||||
"type": "clip_vision",
|
|
||||||
"base": "sigclip",
|
|
||||||
"save_path": "clip_vision",
|
|
||||||
"description": "This clip vision model is required for FLUX.1 Redux.",
|
|
||||||
"reference": "https://huggingface.co/Comfy-Org/sigclip_vision_384/tree/main",
|
|
||||||
"filename": "sigclip_vision_patch14_384.safetensors",
|
|
||||||
"url": "https://huggingface.co/Comfy-Org/sigclip_vision_384/resolve/main/sigclip_vision_patch14_384.safetensors",
|
|
||||||
"size": "857MB"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,16 @@
|
|||||||
"install_type": "git-clone",
|
"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."
|
"description": "A minimal template for creating React/TypeScript frontend extensions for ComfyUI, with complete boilerplate setup including internationalization and unit testing."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"author": "comfyui-wiki",
|
||||||
|
"title": "ComfyUI-i18n-demo",
|
||||||
|
"reference": "https://github.com/comfyui-wiki/ComfyUI-i18n-demo",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/comfyui-wiki/ComfyUI-i18n-demo"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "ComfyUI custom node develop i18n support demo "
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"author": "Suzie1",
|
"author": "Suzie1",
|
||||||
"title": "Guide To Making Custom Nodes in ComfyUI",
|
"title": "Guide To Making Custom Nodes in ComfyUI",
|
||||||
@@ -331,6 +341,36 @@
|
|||||||
],
|
],
|
||||||
"description": "Dynamic Node examples for ComfyUI",
|
"description": "Dynamic Node examples for ComfyUI",
|
||||||
"install_type": "git-clone"
|
"install_type": "git-clone"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "Jonathon-Doran",
|
||||||
|
"title": "remote-combo-demo",
|
||||||
|
"reference": "https://github.com/Jonathon-Doran/remote-combo-demo",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/Jonathon-Doran/remote-combo-demo"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "A minimal test suite demonstrating how remote COMBO inputs behave in ComfyUI, with and without force_input"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "J1mB091",
|
||||||
|
"title": "ComfyUI-J1mB091 Custom Nodes",
|
||||||
|
"reference": "https://github.com/J1mB091/ComfyUI-J1mB091",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/J1mB091/ComfyUI-J1mB091"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Vibe Coded ComfyUI Custom Nodes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"author": "aiforhumans",
|
||||||
|
"title": "XDev Nodes - Complete Toolkit",
|
||||||
|
"reference": "https://github.com/aiforhumans/comfyui-xdev-nodes",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/aiforhumans/comfyui-xdev-nodes"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Complete ComfyUI development toolkit with 8 professional nodes including VAE tools, universal type testing, and comprehensive debugging infrastructure."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
179
openapi.yaml
179
openapi.yaml
@@ -18,6 +18,14 @@ security: []
|
|||||||
# Common API components
|
# Common API components
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
|
OperationType:
|
||||||
|
type: string
|
||||||
|
enum: [install, uninstall, update, update-comfyui, fix, disable, enable, install-model]
|
||||||
|
description: Type of operation or task being performed
|
||||||
|
OperationResult:
|
||||||
|
type: string
|
||||||
|
enum: [success, failed, skipped, error, skip]
|
||||||
|
description: Result status of an operation (failed/error and skipped/skip are aliases)
|
||||||
# Core Task Queue Models
|
# Core Task Queue Models
|
||||||
QueueTaskItem:
|
QueueTaskItem:
|
||||||
type: object
|
type: object
|
||||||
@@ -29,9 +37,7 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
description: Client identifier that initiated the task
|
description: Client identifier that initiated the task
|
||||||
kind:
|
kind:
|
||||||
type: string
|
$ref: '#/components/schemas/OperationType'
|
||||||
description: Type of task being performed
|
|
||||||
enum: [install, uninstall, update, update-all, update-comfyui, fix, disable, enable, install-model]
|
|
||||||
params:
|
params:
|
||||||
oneOf:
|
oneOf:
|
||||||
- $ref: '#/components/schemas/InstallPackParams'
|
- $ref: '#/components/schemas/InstallPackParams'
|
||||||
@@ -65,14 +71,19 @@ components:
|
|||||||
description: Task result message or details
|
description: Task result message or details
|
||||||
status:
|
status:
|
||||||
$ref: '#/components/schemas/TaskExecutionStatus'
|
$ref: '#/components/schemas/TaskExecutionStatus'
|
||||||
|
batch_id:
|
||||||
|
type: [string, 'null']
|
||||||
|
description: ID of the batch this task belongs to
|
||||||
|
end_time:
|
||||||
|
type: [string, 'null']
|
||||||
|
format: date-time
|
||||||
|
description: ISO timestamp when task execution ended
|
||||||
required: [ui_id, client_id, kind, timestamp, result]
|
required: [ui_id, client_id, kind, timestamp, result]
|
||||||
TaskExecutionStatus:
|
TaskExecutionStatus:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
status_str:
|
status_str:
|
||||||
type: string
|
$ref: '#/components/schemas/OperationResult'
|
||||||
enum: [success, error, skip]
|
|
||||||
description: Overall task execution status
|
|
||||||
completed:
|
completed:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: Whether the task completed
|
description: Whether the task completed
|
||||||
@@ -223,6 +234,14 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
enum: [git-clone, copy, cnr]
|
enum: [git-clone, copy, cnr]
|
||||||
description: Type of installation used for the pack
|
description: Type of installation used for the pack
|
||||||
|
SecurityLevel:
|
||||||
|
type: string
|
||||||
|
enum: [strong, normal, normal-, weak]
|
||||||
|
description: Security level configuration (from most to least restrictive)
|
||||||
|
RiskLevel:
|
||||||
|
type: string
|
||||||
|
enum: [block, high+, high, middle+, middle]
|
||||||
|
description: Risk classification for operations
|
||||||
ManagerPack:
|
ManagerPack:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: '#/components/schemas/ManagerPackInfo'
|
- $ref: '#/components/schemas/ManagerPackInfo'
|
||||||
@@ -235,7 +254,7 @@ components:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
description: Files included in the pack
|
description: Repository URLs for installation (typically contains one GitHub URL)
|
||||||
reference:
|
reference:
|
||||||
type: string
|
type: string
|
||||||
description: The type of installation reference
|
description: The type of installation reference
|
||||||
@@ -366,6 +385,46 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
description: ComfyUI Node Registry ID of the package to enable
|
description: ComfyUI Node Registry ID of the package to enable
|
||||||
required: [cnr_id]
|
required: [cnr_id]
|
||||||
|
# Query Parameter Models
|
||||||
|
UpdateAllQueryParams:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
client_id:
|
||||||
|
type: string
|
||||||
|
description: Client identifier that initiated the request
|
||||||
|
ui_id:
|
||||||
|
type: string
|
||||||
|
description: Base UI identifier for task tracking
|
||||||
|
mode:
|
||||||
|
$ref: '#/components/schemas/ManagerDatabaseSource'
|
||||||
|
required: [client_id, ui_id]
|
||||||
|
UpdateComfyUIQueryParams:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
client_id:
|
||||||
|
type: string
|
||||||
|
description: Client identifier that initiated the request
|
||||||
|
ui_id:
|
||||||
|
type: string
|
||||||
|
description: UI identifier for task tracking
|
||||||
|
stable:
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
description: Whether to update to stable version (true) or nightly (false)
|
||||||
|
required: [client_id, ui_id]
|
||||||
|
ComfyUISwitchVersionQueryParams:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
ver:
|
||||||
|
type: string
|
||||||
|
description: Version to switch to
|
||||||
|
client_id:
|
||||||
|
type: string
|
||||||
|
description: Client identifier that initiated the request
|
||||||
|
ui_id:
|
||||||
|
type: string
|
||||||
|
description: UI identifier for task tracking
|
||||||
|
required: [ver, client_id, ui_id]
|
||||||
# Queue Status Models
|
# Queue Status Models
|
||||||
QueueStatus:
|
QueueStatus:
|
||||||
type: object
|
type: object
|
||||||
@@ -580,9 +639,7 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
description: Unique operation identifier
|
description: Unique operation identifier
|
||||||
operation_type:
|
operation_type:
|
||||||
type: string
|
$ref: '#/components/schemas/OperationType'
|
||||||
description: Type of operation
|
|
||||||
enum: [install, update, uninstall, fix, disable, enable, install-model]
|
|
||||||
target:
|
target:
|
||||||
type: string
|
type: string
|
||||||
description: Target of the operation (node name, model name, etc.)
|
description: Target of the operation (node name, model name, etc.)
|
||||||
@@ -590,9 +647,7 @@ components:
|
|||||||
type: [string, 'null']
|
type: [string, 'null']
|
||||||
description: Target version for the operation
|
description: Target version for the operation
|
||||||
result:
|
result:
|
||||||
type: string
|
$ref: '#/components/schemas/OperationResult'
|
||||||
description: Operation result
|
|
||||||
enum: [success, failed, skipped]
|
|
||||||
error_message:
|
error_message:
|
||||||
type: [string, 'null']
|
type: [string, 'null']
|
||||||
description: Error message if operation failed
|
description: Error message if operation failed
|
||||||
@@ -640,6 +695,45 @@ components:
|
|||||||
type: object
|
type: object
|
||||||
additionalProperties: true
|
additionalProperties: true
|
||||||
description: ComfyUI Manager configuration settings
|
description: ComfyUI Manager configuration settings
|
||||||
|
comfyui_root_path:
|
||||||
|
type: [string, 'null']
|
||||||
|
description: ComfyUI root installation directory
|
||||||
|
model_paths:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: Map of model types to their configured paths
|
||||||
|
manager_version:
|
||||||
|
type: [string, 'null']
|
||||||
|
description: ComfyUI Manager version
|
||||||
|
security_level:
|
||||||
|
$ref: '#/components/schemas/SecurityLevel'
|
||||||
|
network_mode:
|
||||||
|
type: [string, 'null']
|
||||||
|
description: Network mode (online, offline, private)
|
||||||
|
cli_args:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
description: Selected ComfyUI CLI arguments
|
||||||
|
custom_nodes_count:
|
||||||
|
type: [integer, 'null']
|
||||||
|
description: Total number of custom node packages
|
||||||
|
minimum: 0
|
||||||
|
failed_imports:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: List of custom nodes that failed to import
|
||||||
|
pip_packages:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
description: Map of installed pip packages to their versions
|
||||||
|
embedded_python:
|
||||||
|
type: [boolean, 'null']
|
||||||
|
description: Whether ComfyUI is running from an embedded Python distribution
|
||||||
required: [snapshot_time, comfyui_version, python_version, platform_info]
|
required: [snapshot_time, comfyui_version, python_version, platform_info]
|
||||||
BatchExecutionRecord:
|
BatchExecutionRecord:
|
||||||
type: object
|
type: object
|
||||||
@@ -688,6 +782,39 @@ components:
|
|||||||
minimum: 0
|
minimum: 0
|
||||||
default: 0
|
default: 0
|
||||||
required: [batch_id, start_time, state_before]
|
required: [batch_id, start_time, state_before]
|
||||||
|
|
||||||
|
ImportFailInfoBulkRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
cnr_ids:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: A list of CNR IDs to check.
|
||||||
|
urls:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
description: A list of repository URLs to check.
|
||||||
|
|
||||||
|
ImportFailInfoBulkResponse:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
$ref: '#/components/schemas/ImportFailInfoItem'
|
||||||
|
description: >-
|
||||||
|
A dictionary where each key is a cnr_id or url from the request,
|
||||||
|
and the value is the corresponding error info.
|
||||||
|
|
||||||
|
ImportFailInfoItem:
|
||||||
|
oneOf:
|
||||||
|
- type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
traceback:
|
||||||
|
type: string
|
||||||
|
- type: "null"
|
||||||
|
|
||||||
securitySchemes:
|
securitySchemes:
|
||||||
securityLevel:
|
securityLevel:
|
||||||
type: apiKey
|
type: apiKey
|
||||||
@@ -923,6 +1050,32 @@ paths:
|
|||||||
description: Processing started
|
description: Processing started
|
||||||
'201':
|
'201':
|
||||||
description: Processing already in progress
|
description: Processing already in progress
|
||||||
|
|
||||||
|
/v2/customnode/import_fail_info_bulk:
|
||||||
|
post:
|
||||||
|
summary: Get import failure info for multiple nodes
|
||||||
|
description: Retrieves recorded import failure information for a list of custom nodes.
|
||||||
|
tags:
|
||||||
|
- customnode
|
||||||
|
requestBody:
|
||||||
|
description: A list of CNR IDs or repository URLs to check.
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ImportFailInfoBulkRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: A dictionary containing the import failure information.
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ImportFailInfoBulkResponse'
|
||||||
|
'400':
|
||||||
|
description: Bad Request. The request body is invalid.
|
||||||
|
'500':
|
||||||
|
description: Internal Server Error.
|
||||||
|
|
||||||
/v2/manager/queue/reset:
|
/v2/manager/queue/reset:
|
||||||
get:
|
get:
|
||||||
summary: Reset queue
|
summary: Reset queue
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
[project]
|
[project]
|
||||||
name = "comfyui-manager"
|
name = "comfyui-manager"
|
||||||
license = { text = "GPL-3.0-only" }
|
license = { text = "GPL-3.0-only" }
|
||||||
version = "4.0.0-beta.4"
|
version = "4.0.3b4"
|
||||||
requires-python = ">= 3.9"
|
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."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -19,7 +19,7 @@ maintainers = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 5 - Production/Stable",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
||||||
]
|
]
|
||||||
@@ -27,7 +27,7 @@ classifiers = [
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"GitPython",
|
"GitPython",
|
||||||
"PyGithub",
|
"PyGithub",
|
||||||
"matrix-client==0.4.0",
|
# "matrix-nio",
|
||||||
"transformers",
|
"transformers",
|
||||||
"huggingface-hub>0.20",
|
"huggingface-hub>0.20",
|
||||||
"typer",
|
"typer",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
GitPython
|
GitPython
|
||||||
PyGithub
|
PyGithub
|
||||||
matrix-client==0.4.0
|
# matrix-nio
|
||||||
transformers
|
transformers
|
||||||
huggingface-hub>0.20
|
huggingface-hub
|
||||||
typer
|
typer
|
||||||
rich
|
rich
|
||||||
typing-extensions
|
typing-extensions
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ lint.select = [
|
|||||||
"F",
|
"F",
|
||||||
]
|
]
|
||||||
|
|
||||||
exclude = ["*.ipynb"]
|
exclude = ["*.ipynb", "tests"]
|
||||||
|
|||||||
357
scanner.py
357
scanner.py
@@ -7,13 +7,15 @@ import concurrent
|
|||||||
import datetime
|
import datetime
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import requests
|
import requests
|
||||||
|
import warnings
|
||||||
|
import argparse
|
||||||
|
|
||||||
builtin_nodes = set()
|
builtin_nodes = set()
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
from github import Github
|
from github import Github, Auth
|
||||||
|
|
||||||
|
|
||||||
def download_url(url, dest_folder, filename=None):
|
def download_url(url, dest_folder, filename=None):
|
||||||
@@ -39,26 +41,51 @@ def download_url(url, dest_folder, filename=None):
|
|||||||
raise Exception(f"Failed to download file from {url}")
|
raise Exception(f"Failed to download file from {url}")
|
||||||
|
|
||||||
|
|
||||||
# prepare temp dir
|
def parse_arguments():
|
||||||
if len(sys.argv) > 1:
|
"""Parse command-line arguments"""
|
||||||
temp_dir = sys.argv[1]
|
parser = argparse.ArgumentParser(
|
||||||
else:
|
description='ComfyUI Manager Node Scanner',
|
||||||
temp_dir = os.path.join(os.getcwd(), ".tmp")
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||||
|
epilog='''
|
||||||
|
Examples:
|
||||||
|
# Standard mode
|
||||||
|
python3 scanner.py
|
||||||
|
python3 scanner.py --skip-update
|
||||||
|
|
||||||
if not os.path.exists(temp_dir):
|
# Scan-only mode
|
||||||
os.makedirs(temp_dir)
|
python3 scanner.py --scan-only temp-urls-clean.list
|
||||||
|
python3 scanner.py --scan-only urls.list --temp-dir /custom/temp
|
||||||
|
python3 scanner.py --scan-only urls.list --skip-update
|
||||||
|
'''
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument('--scan-only', type=str, metavar='URL_LIST_FILE',
|
||||||
|
help='Scan-only mode: provide URL list file (one URL per line)')
|
||||||
|
parser.add_argument('--temp-dir', type=str, metavar='DIR',
|
||||||
|
help='Temporary directory for cloned repositories')
|
||||||
|
parser.add_argument('--skip-update', action='store_true',
|
||||||
|
help='Skip git clone/pull operations')
|
||||||
|
parser.add_argument('--skip-stat-update', action='store_true',
|
||||||
|
help='Skip GitHub stats collection')
|
||||||
|
parser.add_argument('--skip-all', action='store_true',
|
||||||
|
help='Skip all update operations')
|
||||||
|
|
||||||
|
# Backward compatibility: positional argument for temp_dir
|
||||||
|
parser.add_argument('temp_dir_positional', nargs='?', metavar='TEMP_DIR',
|
||||||
|
help='(Legacy) Temporary directory path')
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
skip_update = '--skip-update' in sys.argv or '--skip-all' in sys.argv
|
# Module-level variables (will be set in main if running as script)
|
||||||
skip_stat_update = '--skip-stat-update' in sys.argv or '--skip-all' in sys.argv
|
args = None
|
||||||
|
scan_only_mode = False
|
||||||
if not skip_stat_update:
|
url_list_file = None
|
||||||
g = Github(os.environ.get('GITHUB_TOKEN'))
|
temp_dir = None
|
||||||
else:
|
skip_update = False
|
||||||
g = None
|
skip_stat_update = True
|
||||||
|
g = None
|
||||||
|
|
||||||
print(f"TEMP DIR: {temp_dir}")
|
|
||||||
|
|
||||||
|
|
||||||
parse_cnt = 0
|
parse_cnt = 0
|
||||||
@@ -73,12 +100,22 @@ def extract_nodes(code_text):
|
|||||||
parse_cnt += 1
|
parse_cnt += 1
|
||||||
|
|
||||||
code_text = re.sub(r'\\[^"\']', '', code_text)
|
code_text = re.sub(r'\\[^"\']', '', code_text)
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.filterwarnings('ignore', category=SyntaxWarning)
|
||||||
|
warnings.filterwarnings('ignore', category=DeprecationWarning)
|
||||||
parsed_code = ast.parse(code_text)
|
parsed_code = ast.parse(code_text)
|
||||||
|
|
||||||
assignments = (node for node in parsed_code.body if isinstance(node, ast.Assign))
|
# Support both ast.Assign and ast.AnnAssign (for type-annotated assignments)
|
||||||
|
assignments = (node for node in parsed_code.body if isinstance(node, (ast.Assign, ast.AnnAssign)))
|
||||||
|
|
||||||
for assignment in assignments:
|
for assignment in assignments:
|
||||||
if isinstance(assignment.targets[0], ast.Name) and assignment.targets[0].id in ['NODE_CONFIG', 'NODE_CLASS_MAPPINGS']:
|
# Handle ast.AnnAssign (e.g., NODE_CLASS_MAPPINGS: Type = {...})
|
||||||
|
if isinstance(assignment, ast.AnnAssign):
|
||||||
|
if isinstance(assignment.target, ast.Name) and assignment.target.id in ['NODE_CONFIG', 'NODE_CLASS_MAPPINGS']:
|
||||||
|
node_class_mappings = assignment.value
|
||||||
|
break
|
||||||
|
# Handle ast.Assign (e.g., NODE_CLASS_MAPPINGS = {...})
|
||||||
|
elif isinstance(assignment.targets[0], ast.Name) and assignment.targets[0].id in ['NODE_CONFIG', 'NODE_CLASS_MAPPINGS']:
|
||||||
node_class_mappings = assignment.value
|
node_class_mappings = assignment.value
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
@@ -98,6 +135,99 @@ def extract_nodes(code_text):
|
|||||||
return set()
|
return set()
|
||||||
|
|
||||||
|
|
||||||
|
def has_comfy_node_base(class_node):
|
||||||
|
"""Check if class inherits from io.ComfyNode or ComfyNode"""
|
||||||
|
for base in class_node.bases:
|
||||||
|
# Case 1: ComfyNode
|
||||||
|
if isinstance(base, ast.Name) and base.id == 'ComfyNode':
|
||||||
|
return True
|
||||||
|
# Case 2: io.ComfyNode
|
||||||
|
elif isinstance(base, ast.Attribute):
|
||||||
|
if base.attr == 'ComfyNode':
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def extract_keyword_value(call_node, keyword):
|
||||||
|
"""
|
||||||
|
Extract string value of keyword argument
|
||||||
|
Schema(node_id="MyNode") -> "MyNode"
|
||||||
|
"""
|
||||||
|
for kw in call_node.keywords:
|
||||||
|
if kw.arg == keyword:
|
||||||
|
# ast.Constant (Python 3.8+)
|
||||||
|
if isinstance(kw.value, ast.Constant):
|
||||||
|
if isinstance(kw.value.value, str):
|
||||||
|
return kw.value.value
|
||||||
|
# ast.Str (Python 3.7-) - suppress deprecation warning
|
||||||
|
else:
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.filterwarnings('ignore', category=DeprecationWarning)
|
||||||
|
if hasattr(ast, 'Str') and isinstance(kw.value, ast.Str):
|
||||||
|
return kw.value.s
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def is_schema_call(call_node):
|
||||||
|
"""Check if ast.Call is io.Schema() or Schema()"""
|
||||||
|
func = call_node.func
|
||||||
|
if isinstance(func, ast.Name) and func.id == 'Schema':
|
||||||
|
return True
|
||||||
|
elif isinstance(func, ast.Attribute) and func.attr == 'Schema':
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def extract_node_id_from_schema(class_node):
|
||||||
|
"""
|
||||||
|
Extract node_id from define_schema() method
|
||||||
|
"""
|
||||||
|
for item in class_node.body:
|
||||||
|
if isinstance(item, ast.FunctionDef) and item.name == 'define_schema':
|
||||||
|
# Walk through function body
|
||||||
|
for stmt in ast.walk(item):
|
||||||
|
if isinstance(stmt, ast.Call):
|
||||||
|
# Check if it's Schema() call
|
||||||
|
if is_schema_call(stmt):
|
||||||
|
node_id = extract_keyword_value(stmt, 'node_id')
|
||||||
|
if node_id:
|
||||||
|
return node_id
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def extract_v3_nodes(code_text):
|
||||||
|
"""
|
||||||
|
Extract V3 node IDs using AST parsing
|
||||||
|
Returns: set of node_id strings
|
||||||
|
"""
|
||||||
|
global parse_cnt
|
||||||
|
|
||||||
|
try:
|
||||||
|
if parse_cnt % 100 == 0:
|
||||||
|
print(".", end="", flush=True)
|
||||||
|
parse_cnt += 1
|
||||||
|
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.filterwarnings('ignore', category=SyntaxWarning)
|
||||||
|
warnings.filterwarnings('ignore', category=DeprecationWarning)
|
||||||
|
tree = ast.parse(code_text)
|
||||||
|
except (SyntaxError, UnicodeDecodeError):
|
||||||
|
return set()
|
||||||
|
|
||||||
|
nodes = set()
|
||||||
|
|
||||||
|
# Find io.ComfyNode subclasses
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if isinstance(node, ast.ClassDef):
|
||||||
|
# Check if inherits from ComfyNode
|
||||||
|
if has_comfy_node_base(node):
|
||||||
|
node_id = extract_node_id_from_schema(node)
|
||||||
|
if node_id:
|
||||||
|
nodes.add(node_id)
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
|
||||||
|
|
||||||
# scan
|
# scan
|
||||||
def scan_in_file(filename, is_builtin=False):
|
def scan_in_file(filename, is_builtin=False):
|
||||||
global builtin_nodes
|
global builtin_nodes
|
||||||
@@ -105,13 +235,18 @@ def scan_in_file(filename, is_builtin=False):
|
|||||||
with open(filename, encoding='utf-8', errors='ignore') as file:
|
with open(filename, encoding='utf-8', errors='ignore') as file:
|
||||||
code = file.read()
|
code = file.read()
|
||||||
|
|
||||||
pattern = r"_CLASS_MAPPINGS\s*=\s*{([^}]*)}"
|
# Support type annotations (e.g., NODE_CLASS_MAPPINGS: Type = {...}) and line continuations (\)
|
||||||
|
pattern = r"_CLASS_MAPPINGS\s*(?::\s*\w+\s*)?=\s*(?:\\\s*)?{([^}]*)}"
|
||||||
regex = re.compile(pattern, re.MULTILINE | re.DOTALL)
|
regex = re.compile(pattern, re.MULTILINE | re.DOTALL)
|
||||||
|
|
||||||
nodes = set()
|
nodes = set()
|
||||||
class_dict = {}
|
class_dict = {}
|
||||||
|
|
||||||
|
# V1 nodes detection
|
||||||
nodes |= extract_nodes(code)
|
nodes |= extract_nodes(code)
|
||||||
|
|
||||||
|
# V3 nodes detection
|
||||||
|
nodes |= extract_v3_nodes(code)
|
||||||
code = re.sub(r'^#.*?$', '', code, flags=re.MULTILINE)
|
code = re.sub(r'^#.*?$', '', code, flags=re.MULTILINE)
|
||||||
|
|
||||||
def extract_keys(pattern, code):
|
def extract_keys(pattern, code):
|
||||||
@@ -208,6 +343,53 @@ def get_nodes(target_dir):
|
|||||||
return py_files, directories
|
return py_files, directories
|
||||||
|
|
||||||
|
|
||||||
|
def get_urls_from_list_file(list_file):
|
||||||
|
"""
|
||||||
|
Read URLs from list file for scan-only mode
|
||||||
|
|
||||||
|
Args:
|
||||||
|
list_file (str): Path to URL list file (one URL per line)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list of tuples: [(url, "", None, None), ...]
|
||||||
|
Format: (url, title, preemptions, nodename_pattern)
|
||||||
|
- title: Empty string
|
||||||
|
- preemptions: None
|
||||||
|
- nodename_pattern: None
|
||||||
|
|
||||||
|
File format:
|
||||||
|
https://github.com/owner/repo1
|
||||||
|
https://github.com/owner/repo2
|
||||||
|
# Comments starting with # are ignored
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
FileNotFoundError: If list_file does not exist
|
||||||
|
"""
|
||||||
|
if not os.path.exists(list_file):
|
||||||
|
raise FileNotFoundError(f"URL list file not found: {list_file}")
|
||||||
|
|
||||||
|
urls = []
|
||||||
|
with open(list_file, 'r', encoding='utf-8') as f:
|
||||||
|
for line_num, line in enumerate(f, 1):
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
# Skip empty lines and comments
|
||||||
|
if not line or line.startswith('#'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Validate URL format (basic check)
|
||||||
|
if not (line.startswith('http://') or line.startswith('https://')):
|
||||||
|
print(f"WARNING: Line {line_num} is not a valid URL: {line}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Add URL with empty metadata
|
||||||
|
# (url, title, preemptions, nodename_pattern)
|
||||||
|
urls.append((line, "", None, None))
|
||||||
|
|
||||||
|
print(f"Loaded {len(urls)} URLs from {list_file}")
|
||||||
|
return urls
|
||||||
|
|
||||||
|
|
||||||
def get_git_urls_from_json(json_file):
|
def get_git_urls_from_json(json_file):
|
||||||
with open(json_file, encoding='utf-8') as file:
|
with open(json_file, encoding='utf-8') as file:
|
||||||
data = json.load(file)
|
data = json.load(file)
|
||||||
@@ -255,22 +437,52 @@ def clone_or_pull_git_repository(git_url):
|
|||||||
repo.git.submodule('update', '--init', '--recursive')
|
repo.git.submodule('update', '--init', '--recursive')
|
||||||
print(f"Pulling {repo_name}...")
|
print(f"Pulling {repo_name}...")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Pulling {repo_name} failed: {e}")
|
print(f"Failed to pull '{repo_name}': {e}")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
Repo.clone_from(git_url, repo_dir, recursive=True)
|
Repo.clone_from(git_url, repo_dir, recursive=True)
|
||||||
print(f"Cloning {repo_name}...")
|
print(f"Cloning {repo_name}...")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Cloning {repo_name} failed: {e}")
|
print(f"Failed to clone '{repo_name}': {e}")
|
||||||
|
|
||||||
|
|
||||||
def update_custom_nodes():
|
def update_custom_nodes(scan_only_mode=False, url_list_file=None):
|
||||||
|
"""
|
||||||
|
Update custom nodes by cloning/pulling repositories
|
||||||
|
|
||||||
|
Args:
|
||||||
|
scan_only_mode (bool): If True, use URL list file instead of custom-node-list.json
|
||||||
|
url_list_file (str): Path to URL list file (required if scan_only_mode=True)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: node_info mapping {repo_name: (url, title, preemptions, node_pattern)}
|
||||||
|
"""
|
||||||
if not os.path.exists(temp_dir):
|
if not os.path.exists(temp_dir):
|
||||||
os.makedirs(temp_dir)
|
os.makedirs(temp_dir)
|
||||||
|
|
||||||
node_info = {}
|
node_info = {}
|
||||||
|
|
||||||
|
# Select URL source based on mode
|
||||||
|
if scan_only_mode:
|
||||||
|
if not url_list_file:
|
||||||
|
raise ValueError("url_list_file is required in scan-only mode")
|
||||||
|
|
||||||
|
git_url_titles_preemptions = get_urls_from_list_file(url_list_file)
|
||||||
|
print("\n[Scan-Only Mode]")
|
||||||
|
print(f" - URL source: {url_list_file}")
|
||||||
|
print(" - GitHub stats: DISABLED")
|
||||||
|
print(f" - Git clone/pull: {'ENABLED' if not skip_update else 'DISABLED'}")
|
||||||
|
print(" - Metadata: EMPTY")
|
||||||
|
else:
|
||||||
|
if not os.path.exists('custom-node-list.json'):
|
||||||
|
raise FileNotFoundError("custom-node-list.json not found")
|
||||||
|
|
||||||
git_url_titles_preemptions = get_git_urls_from_json('custom-node-list.json')
|
git_url_titles_preemptions = get_git_urls_from_json('custom-node-list.json')
|
||||||
|
print("\n[Standard Mode]")
|
||||||
|
print(" - URL source: custom-node-list.json")
|
||||||
|
print(f" - GitHub stats: {'ENABLED' if not skip_stat_update else 'DISABLED'}")
|
||||||
|
print(f" - Git clone/pull: {'ENABLED' if not skip_update else 'DISABLED'}")
|
||||||
|
print(" - Metadata: FULL")
|
||||||
|
|
||||||
def process_git_url_title(url, title, preemptions, node_pattern):
|
def process_git_url_title(url, title, preemptions, node_pattern):
|
||||||
name = os.path.basename(url)
|
name = os.path.basename(url)
|
||||||
@@ -382,10 +594,13 @@ def update_custom_nodes():
|
|||||||
if not skip_stat_update:
|
if not skip_stat_update:
|
||||||
process_git_stats(git_url_titles_preemptions)
|
process_git_stats(git_url_titles_preemptions)
|
||||||
|
|
||||||
|
# Git clone/pull for all repositories
|
||||||
with concurrent.futures.ThreadPoolExecutor(11) as executor:
|
with concurrent.futures.ThreadPoolExecutor(11) as executor:
|
||||||
for url, title, preemptions, node_pattern in git_url_titles_preemptions:
|
for url, title, preemptions, node_pattern in git_url_titles_preemptions:
|
||||||
executor.submit(process_git_url_title, url, title, preemptions, node_pattern)
|
executor.submit(process_git_url_title, url, title, preemptions, node_pattern)
|
||||||
|
|
||||||
|
# .py file download (skip in scan-only mode - only process git repos)
|
||||||
|
if not scan_only_mode:
|
||||||
py_url_titles_and_pattern = get_py_urls_from_json('custom-node-list.json')
|
py_url_titles_and_pattern = get_py_urls_from_json('custom-node-list.json')
|
||||||
|
|
||||||
def download_and_store_info(url_title_preemptions_and_pattern):
|
def download_and_store_info(url_title_preemptions_and_pattern):
|
||||||
@@ -405,11 +620,20 @@ def update_custom_nodes():
|
|||||||
return node_info
|
return node_info
|
||||||
|
|
||||||
|
|
||||||
def gen_json(node_info):
|
def gen_json(node_info, scan_only_mode=False):
|
||||||
|
"""
|
||||||
|
Generate extension-node-map.json from scanned node information
|
||||||
|
|
||||||
|
Args:
|
||||||
|
node_info (dict): Repository metadata mapping
|
||||||
|
scan_only_mode (bool): If True, exclude metadata from output
|
||||||
|
"""
|
||||||
# scan from .py file
|
# scan from .py file
|
||||||
node_files, node_dirs = get_nodes(temp_dir)
|
node_files, node_dirs = get_nodes(temp_dir)
|
||||||
|
|
||||||
comfyui_path = os.path.abspath(os.path.join(temp_dir, "ComfyUI"))
|
comfyui_path = os.path.abspath(os.path.join(temp_dir, "ComfyUI"))
|
||||||
|
# Only reorder if ComfyUI exists in the list
|
||||||
|
if comfyui_path in node_dirs:
|
||||||
node_dirs.remove(comfyui_path)
|
node_dirs.remove(comfyui_path)
|
||||||
node_dirs = [comfyui_path] + node_dirs
|
node_dirs = [comfyui_path] + node_dirs
|
||||||
|
|
||||||
@@ -422,6 +646,7 @@ def gen_json(node_info):
|
|||||||
for py in py_files:
|
for py in py_files:
|
||||||
nodes_in_file, metadata_in_file = scan_in_file(py, dirname == "ComfyUI")
|
nodes_in_file, metadata_in_file = scan_in_file(py, dirname == "ComfyUI")
|
||||||
nodes.update(nodes_in_file)
|
nodes.update(nodes_in_file)
|
||||||
|
# Include metadata from .py files in both modes
|
||||||
metadata.update(metadata_in_file)
|
metadata.update(metadata_in_file)
|
||||||
|
|
||||||
dirname = os.path.basename(dirname)
|
dirname = os.path.basename(dirname)
|
||||||
@@ -436,6 +661,9 @@ def gen_json(node_info):
|
|||||||
if dirname in node_info:
|
if dirname in node_info:
|
||||||
git_url, title, preemptions, node_pattern = node_info[dirname]
|
git_url, title, preemptions, node_pattern = node_info[dirname]
|
||||||
|
|
||||||
|
# Conditionally add metadata based on mode
|
||||||
|
if not scan_only_mode:
|
||||||
|
# Standard mode: include all metadata
|
||||||
metadata['title_aux'] = title
|
metadata['title_aux'] = title
|
||||||
|
|
||||||
if preemptions is not None:
|
if preemptions is not None:
|
||||||
@@ -443,7 +671,15 @@ def gen_json(node_info):
|
|||||||
|
|
||||||
if node_pattern is not None:
|
if node_pattern is not None:
|
||||||
metadata['nodename_pattern'] = node_pattern
|
metadata['nodename_pattern'] = node_pattern
|
||||||
|
# Scan-only mode: metadata remains empty
|
||||||
|
|
||||||
|
data[git_url] = (nodes, metadata)
|
||||||
|
else:
|
||||||
|
# Scan-only mode: Repository not in node_info (expected behavior)
|
||||||
|
# Construct URL from dirname (author_repo format)
|
||||||
|
if '_' in dirname:
|
||||||
|
parts = dirname.split('_', 1)
|
||||||
|
git_url = f"https://github.com/{parts[0]}/{parts[1]}"
|
||||||
data[git_url] = (nodes, metadata)
|
data[git_url] = (nodes, metadata)
|
||||||
else:
|
else:
|
||||||
print(f"WARN: {dirname} is removed from custom-node-list.json")
|
print(f"WARN: {dirname} is removed from custom-node-list.json")
|
||||||
@@ -459,6 +695,9 @@ def gen_json(node_info):
|
|||||||
|
|
||||||
if file in node_info:
|
if file in node_info:
|
||||||
url, title, preemptions, node_pattern = node_info[file]
|
url, title, preemptions, node_pattern = node_info[file]
|
||||||
|
|
||||||
|
# Conditionally add metadata based on mode
|
||||||
|
if not scan_only_mode:
|
||||||
metadata['title_aux'] = title
|
metadata['title_aux'] = title
|
||||||
|
|
||||||
if preemptions is not None:
|
if preemptions is not None:
|
||||||
@@ -477,6 +716,10 @@ def gen_json(node_info):
|
|||||||
for extension in extensions:
|
for extension in extensions:
|
||||||
node_list_json_path = os.path.join(temp_dir, extension, 'node_list.json')
|
node_list_json_path = os.path.join(temp_dir, extension, 'node_list.json')
|
||||||
if os.path.exists(node_list_json_path):
|
if os.path.exists(node_list_json_path):
|
||||||
|
# Skip if extension not in node_info (scan-only mode with limited URLs)
|
||||||
|
if extension not in node_info:
|
||||||
|
continue
|
||||||
|
|
||||||
git_url, title, preemptions, node_pattern = node_info[extension]
|
git_url, title, preemptions, node_pattern = node_info[extension]
|
||||||
|
|
||||||
with open(node_list_json_path, 'r', encoding='utf-8') as f:
|
with open(node_list_json_path, 'r', encoding='utf-8') as f:
|
||||||
@@ -496,13 +739,22 @@ def gen_json(node_info):
|
|||||||
nodes_in_url, metadata_in_url = data[git_url]
|
nodes_in_url, metadata_in_url = data[git_url]
|
||||||
nodes = set(nodes_in_url)
|
nodes = set(nodes_in_url)
|
||||||
|
|
||||||
|
try:
|
||||||
for x, desc in node_list_json.items():
|
for x, desc in node_list_json.items():
|
||||||
nodes.add(x.strip())
|
nodes.add(x.strip())
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nERROR: Invalid json format '{node_list_json_path}'")
|
||||||
|
print("------------------------------------------------------")
|
||||||
|
print(e)
|
||||||
|
print("------------------------------------------------------")
|
||||||
|
node_list_json = {}
|
||||||
|
|
||||||
|
# Conditionally add metadata based on mode
|
||||||
|
if not scan_only_mode:
|
||||||
metadata_in_url['title_aux'] = title
|
metadata_in_url['title_aux'] = title
|
||||||
|
|
||||||
if preemptions is not None:
|
if preemptions is not None:
|
||||||
metadata['preemptions'] = preemptions
|
metadata_in_url['preemptions'] = preemptions
|
||||||
|
|
||||||
if node_pattern is not None:
|
if node_pattern is not None:
|
||||||
metadata_in_url['nodename_pattern'] = node_pattern
|
metadata_in_url['nodename_pattern'] = node_pattern
|
||||||
@@ -516,12 +768,53 @@ def gen_json(node_info):
|
|||||||
json.dump(data, file, indent=4, sort_keys=True)
|
json.dump(data, file, indent=4, sort_keys=True)
|
||||||
|
|
||||||
|
|
||||||
print("### ComfyUI Manager Node Scanner ###")
|
if __name__ == "__main__":
|
||||||
|
# Parse arguments
|
||||||
|
args = parse_arguments()
|
||||||
|
|
||||||
print("\n# Updating extensions\n")
|
# Determine mode
|
||||||
updated_node_info = update_custom_nodes()
|
scan_only_mode = args.scan_only is not None
|
||||||
|
url_list_file = args.scan_only if scan_only_mode else None
|
||||||
|
|
||||||
print("\n# 'extension-node-map.json' file is generated.\n")
|
# Determine temp_dir
|
||||||
gen_json(updated_node_info)
|
if args.temp_dir:
|
||||||
|
temp_dir = args.temp_dir
|
||||||
|
elif args.temp_dir_positional:
|
||||||
|
temp_dir = args.temp_dir_positional
|
||||||
|
else:
|
||||||
|
temp_dir = os.path.join(os.getcwd(), ".tmp")
|
||||||
|
|
||||||
print("\nDONE.\n")
|
if not os.path.exists(temp_dir):
|
||||||
|
os.makedirs(temp_dir)
|
||||||
|
|
||||||
|
# Determine skip flags
|
||||||
|
skip_update = args.skip_update or args.skip_all
|
||||||
|
skip_stat_update = args.skip_stat_update or args.skip_all or scan_only_mode
|
||||||
|
|
||||||
|
if not skip_stat_update:
|
||||||
|
auth = Auth.Token(os.environ.get('GITHUB_TOKEN'))
|
||||||
|
g = Github(auth=auth)
|
||||||
|
else:
|
||||||
|
g = None
|
||||||
|
|
||||||
|
print("### ComfyUI Manager Node Scanner ###")
|
||||||
|
|
||||||
|
if scan_only_mode:
|
||||||
|
print(f"\n# [Scan-Only Mode] Processing URL list: {url_list_file}\n")
|
||||||
|
else:
|
||||||
|
print("\n# [Standard Mode] Updating extensions\n")
|
||||||
|
|
||||||
|
# Update/clone repositories and collect node info
|
||||||
|
updated_node_info = update_custom_nodes(scan_only_mode, url_list_file)
|
||||||
|
|
||||||
|
print("\n# Generating 'extension-node-map.json'...\n")
|
||||||
|
|
||||||
|
# Generate extension-node-map.json
|
||||||
|
gen_json(updated_node_info, scan_only_mode)
|
||||||
|
|
||||||
|
print("\n✅ DONE.\n")
|
||||||
|
|
||||||
|
if scan_only_mode:
|
||||||
|
print("Output: extension-node-map.json (node mappings only)")
|
||||||
|
else:
|
||||||
|
print("Output: extension-node-map.json (full metadata)")
|
||||||
Reference in New Issue
Block a user