mirror of
https://github.com/Comfy-Org/ComfyUI-Manager.git
synced 2025-12-22 21:00:49 +08:00
Compare commits
577 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80e5c8a987 | ||
|
|
e0e4886e63 | ||
|
|
c0947f4192 | ||
|
|
7706b047ce | ||
|
|
a44c6ff27c | ||
|
|
f4fdd51ce9 | ||
|
|
ae6c7dd673 | ||
|
|
0cbc773126 | ||
|
|
45bd3473fa | ||
|
|
02175844da | ||
|
|
fd60f7ee70 | ||
|
|
9eb4c3ab23 | ||
|
|
72d1aa7d97 | ||
|
|
57628ead80 | ||
|
|
9733c2328b | ||
|
|
70663cecc3 | ||
|
|
7c77942a92 | ||
|
|
04cf18e149 | ||
|
|
1825edda7e | ||
|
|
045f91c411 | ||
|
|
96d24f548c | ||
|
|
c7f03ad64e | ||
|
|
1232989d7d | ||
|
|
8f66a7997f | ||
|
|
f32dd80c24 | ||
|
|
a06ba343de | ||
|
|
bba55d4d5a | ||
|
|
87111bd889 | ||
|
|
3661ffd3ab | ||
|
|
d8f111a5e3 | ||
|
|
ae5565ce68 | ||
|
|
e4c370a7d9 | ||
|
|
891005bcd3 | ||
|
|
d3a4a7a0fa | ||
|
|
10211d1a93 | ||
|
|
7f019a932b | ||
|
|
fae909de2f | ||
|
|
d8455ef6e5 | ||
|
|
934c994783 | ||
|
|
d0961d596d | ||
|
|
382df24764 | ||
|
|
bfcfa42125 | ||
|
|
2333886c34 | ||
|
|
0cdad3c886 | ||
|
|
eee23c543b | ||
|
|
f0a8812f5e | ||
|
|
a8d603f753 | ||
|
|
22acaa1d2c | ||
|
|
fe791ccee9 | ||
|
|
414557eee0 | ||
|
|
97d2741360 | ||
|
|
b95e5f1eae | ||
|
|
43b200dc91 | ||
|
|
29014699bb | ||
|
|
5576672957 | ||
|
|
5002606861 | ||
|
|
ba0fb343ff | ||
|
|
17e5ae6bc2 | ||
|
|
7a0186efc8 | ||
|
|
de64af4a68 | ||
|
|
4a852ac8a8 | ||
|
|
6784bfb98c | ||
|
|
c8f246d344 | ||
|
|
8b3d31a936 | ||
|
|
5e88d6445b | ||
|
|
fd7dff88df | ||
|
|
8cfee1f483 | ||
|
|
cf4d8e6125 | ||
|
|
c0e8a41d2a | ||
|
|
a02c27b1af | ||
|
|
712e1bac0d | ||
|
|
513ea46cbe | ||
|
|
b1919b6f95 | ||
|
|
43561d209b | ||
|
|
16dcbc5412 | ||
|
|
c8dd2d5cad | ||
|
|
4b37777066 | ||
|
|
95ecd85a12 | ||
|
|
5c475e3c15 | ||
|
|
f705ee6863 | ||
|
|
1f67c18989 | ||
|
|
de6d451c5b | ||
|
|
580296d6f3 | ||
|
|
a9e28fbce3 | ||
|
|
311779cb20 | ||
|
|
d2f8a89e87 | ||
|
|
84c95bf322 | ||
|
|
f75c801955 | ||
|
|
faa2f54371 | ||
|
|
4249ac193a | ||
|
|
c709274a28 | ||
|
|
c8f05e79db | ||
|
|
4d2887e99f | ||
|
|
29256a5154 | ||
|
|
82d42e4094 | ||
|
|
53850fb627 | ||
|
|
34b4c8ce46 | ||
|
|
e944841054 | ||
|
|
f6a5ff5552 | ||
|
|
01763b59d4 | ||
|
|
044173b2a1 | ||
|
|
99e7a88dbd | ||
|
|
01cd9fbb0e | ||
|
|
aaed1dc3d5 | ||
|
|
c8dce94c03 | ||
|
|
06496d07b3 | ||
|
|
a97f98c9cc | ||
|
|
8d0406f74f | ||
|
|
c64d14701d | ||
|
|
00332ae444 | ||
|
|
e8deb3d8fe | ||
|
|
8b234c99cf | ||
|
|
1f986d9c45 | ||
|
|
bacb8fb3cd | ||
|
|
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 | ||
|
|
be95396a57 | ||
|
|
59cbed429f | ||
|
|
d49df7aebb | ||
|
|
0aa9faad2e | ||
|
|
1337def888 | ||
|
|
4b100c558b | ||
|
|
1425a71ece | ||
|
|
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 | ||
|
|
3a377300e1 | ||
|
|
33a07e3a86 | ||
|
|
212cafc1d7 | ||
|
|
2643b3cbcc | ||
|
|
d445229b6d | ||
|
|
dab5c451b0 | ||
|
|
7bdf06131a | ||
|
|
854648d5af | ||
|
|
c5f7b97359 | ||
|
|
dd8a727ad6 | ||
|
|
6c627fe422 | ||
|
|
ee980e1caf | ||
|
|
22bfaf6527 | ||
|
|
48ab48cc30 | ||
|
|
a0b14d4127 | ||
|
|
03f9fe1a70 | ||
|
|
8915b8d796 | ||
|
|
c77ffeeec0 | ||
|
|
4acf5660b2 | ||
|
|
2d9f0a668c | ||
|
|
9e6cb246cc | ||
|
|
14544ca63d | ||
|
|
26b347c04c | ||
|
|
36f75d1811 | ||
|
|
27fc787294 | ||
|
|
d23286d390 | ||
|
|
7c3ccc76c3 | ||
|
|
892dc5d4f3 | ||
|
|
e278692749 | ||
|
|
8d77dd2246 | ||
|
|
14ede2a585 | ||
|
|
5b525622f1 | ||
|
|
a24b11905c | ||
|
|
5d70858341 | ||
|
|
3daa006741 | ||
|
|
0bcc0c2101 | ||
|
|
b8850c808c | ||
|
|
f4f2c01ac1 | ||
|
|
7072e82dff | ||
|
|
53dc36c4cf | ||
|
|
5aadc3af00 | ||
|
|
8c28a698ed | ||
|
|
5ed6d8b202 | ||
|
|
b73dc7bf5e | ||
|
|
71d0f4ab63 | ||
|
|
d479dcde81 | ||
|
|
ae536017d5 | ||
|
|
67ddfce279 | ||
|
|
b1f39b34d7 | ||
|
|
6cf958ccce | ||
|
|
eaed3677d3 | ||
|
|
b9c88da54d | ||
|
|
104ae77f7a | ||
|
|
bfcb2ce61b | ||
|
|
63ba5fed09 | ||
|
|
98a8464933 | ||
|
|
7e3e6726e0 | ||
|
|
09567b2bb2 | ||
|
|
f3bd116184 | ||
|
|
7509737563 | ||
|
|
cfb815d879 | ||
|
|
44241fb967 | ||
|
|
c4b45129bd | ||
|
|
70741008ca | ||
|
|
6c2d2cae2a | ||
|
|
28f13d3311 | ||
|
|
4e31aaa8fb | ||
|
|
ba99f0c2cc | ||
|
|
e0a96b4937 | ||
|
|
82c055f527 | ||
|
|
f94008192c | ||
|
|
3895d5279e | ||
|
|
3d85ecc525 | ||
|
|
7da00796e5 | ||
|
|
6086419cb6 | ||
|
|
5bc1f2f2c0 | ||
|
|
32a83b211e | ||
|
|
bead7b3a7f | ||
|
|
815d6d6572 | ||
|
|
95ce812992 | ||
|
|
9a36f4748c | ||
|
|
50b7849a35 | ||
|
|
6f1245b27c | ||
|
|
cc87ed3899 | ||
|
|
1d9037fefe | ||
|
|
03016e2d16 | ||
|
|
3d41617f4e | ||
|
|
35151ffdd1 | ||
|
|
4527d41a7a | ||
|
|
553cba12f3 | ||
|
|
116e068ac3 | ||
|
|
1010dd2d28 | ||
|
|
7354242906 | ||
|
|
e7d0b158e9 | ||
|
|
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 | ||
|
|
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 | ||
|
|
281319d2da | ||
|
|
5cb203685c | ||
|
|
01fa37900b | ||
|
|
edbe744e17 | ||
|
|
2a32a1a4a8 | ||
|
|
404bdb21e6 | ||
|
|
b260c9a512 | ||
|
|
4b941adb6a | ||
|
|
bd752550a8 | ||
|
|
b8b71bb961 | ||
|
|
5aaf7a4092 | ||
|
|
030e02ffb8 | ||
|
|
d962aa03f4 | ||
|
|
9e4a2aae43 | ||
|
|
ee6eb685e7 | ||
|
|
09a38a32ce | ||
|
|
d13b19d43d | ||
|
|
e730dca1ad | ||
|
|
8da30640bb | ||
|
|
6f4eb88e07 | ||
|
|
d9592b9dab | ||
|
|
b87ada72aa | ||
|
|
83363ba1f0 | ||
|
|
23ebe7f718 | ||
|
|
e04264cfa3 | ||
|
|
8d29e5037f | ||
|
|
6926ed45b0 | ||
|
|
736b85b8bb | ||
|
|
9e3361bc31 | ||
|
|
6e10381020 | ||
|
|
a1d37d379c | ||
|
|
07d87db7a2 | ||
|
|
4e556673d2 | ||
|
|
f421304fc1 | ||
|
|
c9271b1686 | ||
|
|
12eb6863da | ||
|
|
4834874091 |
58
.github/workflows/publish-to-pypi.yml
vendored
Normal file
58
.github/workflows/publish-to-pypi.yml
vendored
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
name: Publish to PyPI
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- draft-v4
|
||||||
|
paths:
|
||||||
|
- "pyproject.toml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.repository_owner == 'ltdrdata' || github.repository_owner == 'Comfy-Org' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v4
|
||||||
|
with:
|
||||||
|
python-version: '3.9'
|
||||||
|
|
||||||
|
- name: Install build dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
python -m pip install build twine
|
||||||
|
|
||||||
|
- name: Get current version
|
||||||
|
id: current_version
|
||||||
|
run: |
|
||||||
|
CURRENT_VERSION=$(grep -oP 'version = "\K[^"]+' pyproject.toml)
|
||||||
|
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
||||||
|
echo "Current version: $CURRENT_VERSION"
|
||||||
|
|
||||||
|
- name: Build package
|
||||||
|
run: python -m build
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
id: create_release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
files: dist/*
|
||||||
|
tag_name: v${{ steps.current_version.outputs.version }}
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
generate_release_notes: true
|
||||||
|
|
||||||
|
- name: Publish to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
password: ${{ secrets.PYPI_TOKEN }}
|
||||||
|
skip-existing: true
|
||||||
|
verbose: true
|
||||||
52
README.md
52
README.md
@ -5,6 +5,7 @@
|
|||||||

|

|
||||||
|
|
||||||
## NOTICE
|
## NOTICE
|
||||||
|
* V3.38: **Security patch** - Manager data migrated to protected path. See [Migration Guide](docs/en/v3.38-userdata-security-migration.md).
|
||||||
* V3.16: Support for `uv` has been added. Set `use_uv` in `config.ini`.
|
* V3.16: Support for `uv` has been added. Set `use_uv` in `config.ini`.
|
||||||
* V3.10: `double-click feature` is removed
|
* V3.10: `double-click feature` is removed
|
||||||
* This feature has been moved to https://github.com/ltdrdata/comfyui-connection-helper
|
* This feature has been moved to https://github.com/ltdrdata/comfyui-connection-helper
|
||||||
@ -17,7 +18,7 @@
|
|||||||
|
|
||||||
To install ComfyUI-Manager in addition to an existing installation of ComfyUI, you can follow the following steps:
|
To install ComfyUI-Manager in addition to an existing installation of ComfyUI, you can follow the following steps:
|
||||||
|
|
||||||
1. goto `ComfyUI/custom_nodes` dir in terminal(cmd)
|
1. Go to `ComfyUI/custom_nodes` dir in terminal (cmd)
|
||||||
2. `git clone https://github.com/ltdrdata/ComfyUI-Manager comfyui-manager`
|
2. `git clone https://github.com/ltdrdata/ComfyUI-Manager comfyui-manager`
|
||||||
3. Restart ComfyUI
|
3. Restart ComfyUI
|
||||||
|
|
||||||
@ -28,8 +29,8 @@ To install ComfyUI-Manager in addition to an existing installation of ComfyUI, y
|
|||||||
- standalone version
|
- standalone version
|
||||||
- select option: use windows default console window
|
- select option: use windows default console window
|
||||||
2. Download [scripts/install-manager-for-portable-version.bat](https://github.com/ltdrdata/ComfyUI-Manager/raw/main/scripts/install-manager-for-portable-version.bat) into installed `"ComfyUI_windows_portable"` directory
|
2. Download [scripts/install-manager-for-portable-version.bat](https://github.com/ltdrdata/ComfyUI-Manager/raw/main/scripts/install-manager-for-portable-version.bat) into installed `"ComfyUI_windows_portable"` directory
|
||||||
- Don't click. Right click the link and use save as...
|
- Don't click. Right-click the link and choose 'Save As...'
|
||||||
3. double click `install-manager-for-portable-version.bat` batch file
|
3. Double-click `install-manager-for-portable-version.bat` batch file
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -47,7 +48,7 @@ pip install comfy-cli
|
|||||||
comfy install
|
comfy install
|
||||||
```
|
```
|
||||||
|
|
||||||
Linux/OSX:
|
Linux/macOS:
|
||||||
```commandline
|
```commandline
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
. venv/bin/activate
|
. venv/bin/activate
|
||||||
@ -57,13 +58,13 @@ comfy install
|
|||||||
* See also: https://github.com/Comfy-Org/comfy-cli
|
* See also: https://github.com/Comfy-Org/comfy-cli
|
||||||
|
|
||||||
|
|
||||||
### Installation[method4] (Installation for linux+venv: ComfyUI + ComfyUI-Manager)
|
### Installation[method4] (Installation for Linux+venv: ComfyUI + ComfyUI-Manager)
|
||||||
|
|
||||||
To install ComfyUI with ComfyUI-Manager on Linux using a venv environment, you can follow these steps:
|
To install ComfyUI with ComfyUI-Manager on Linux using a venv environment, you can follow these steps:
|
||||||
* **prerequisite: python-is-python3, python3-venv, git**
|
* **prerequisite: python-is-python3, python3-venv, git**
|
||||||
|
|
||||||
1. Download [scripts/install-comfyui-venv-linux.sh](https://github.com/ltdrdata/ComfyUI-Manager/raw/main/scripts/install-comfyui-venv-linux.sh) into empty install directory
|
1. Download [scripts/install-comfyui-venv-linux.sh](https://github.com/ltdrdata/ComfyUI-Manager/raw/main/scripts/install-comfyui-venv-linux.sh) into empty install directory
|
||||||
- Don't click. Right click the link and use save as...
|
- Don't click. Right-click the link and choose 'Save As...'
|
||||||
- ComfyUI will be installed in the subdirectory of the specified directory, and the directory will contain the generated executable script.
|
- ComfyUI will be installed in the subdirectory of the specified directory, and the directory will contain the generated executable script.
|
||||||
2. `chmod +x install-comfyui-venv-linux.sh`
|
2. `chmod +x install-comfyui-venv-linux.sh`
|
||||||
3. `./install-comfyui-venv-linux.sh`
|
3. `./install-comfyui-venv-linux.sh`
|
||||||
@ -140,20 +141,27 @@ This repository provides Colab notebooks that allow you to install and use Comfy
|
|||||||
|
|
||||||
|
|
||||||
## Paths
|
## Paths
|
||||||
In `ComfyUI-Manager` V3.0 and later, configuration files and dynamically generated files are located under `<USER_DIRECTORY>/default/ComfyUI-Manager/`.
|
Starting from V3.38, Manager uses a protected system path for enhanced security.
|
||||||
|
|
||||||
* <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`
|
| ComfyUI Version | Manager Path |
|
||||||
* Configurable channel lists: `<USER_DIRECTORY>/default/ComfyUI-Manager/channels.ini`
|
|-----------------|--------------|
|
||||||
* Configurable pip overrides: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_overrides.json`
|
| v0.3.76+ (with System User API) | `<USER_DIRECTORY>/__manager/` |
|
||||||
* Configurable pip blacklist: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_blacklist.list`
|
| Older versions | `<USER_DIRECTORY>/default/ComfyUI-Manager/` |
|
||||||
* Configurable pip auto fix: `<USER_DIRECTORY>/default/ComfyUI-Manager/pip_auto_fix.list`
|
|
||||||
* Saved snapshot files: `<USER_DIRECTORY>/default/ComfyUI-Manager/snapshots`
|
* Basic config files: `config.ini`
|
||||||
* Startup script files: `<USER_DIRECTORY>/default/ComfyUI-Manager/startup-scripts`
|
* Configurable channel lists: `channels.list`
|
||||||
* Component files: `<USER_DIRECTORY>/default/ComfyUI-Manager/components`
|
* Configurable pip overrides: `pip_overrides.json`
|
||||||
|
* Configurable pip blacklist: `pip_blacklist.list`
|
||||||
|
* Configurable pip auto fix: `pip_auto_fix.list`
|
||||||
|
* Saved snapshot files: `snapshots/`
|
||||||
|
* Startup script files: `startup-scripts/`
|
||||||
|
* Component files: `components/`
|
||||||
|
|
||||||
|
> **Note**: See [Migration Guide](docs/en/v3.38-userdata-security-migration.md) for upgrade details.
|
||||||
|
|
||||||
|
|
||||||
## `extra_model_paths.yaml` Configuration
|
## `extra_model_paths.yaml` Configuration
|
||||||
@ -176,7 +184,7 @@ The following settings are applied based on the section marked as `is_default`.
|
|||||||

|

|
||||||
|
|
||||||
|
|
||||||
## 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).
|
||||||
|
|
||||||
@ -222,7 +230,7 @@ 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>/default/ComfyUI-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>/default/ComfyUI-Manager/components`.
|
||||||
@ -240,7 +248,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
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@ -282,7 +290,7 @@ The following settings are applied based on the section marked as `is_default`.
|
|||||||
* 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.
|
||||||
@ -348,7 +356,7 @@ 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`.
|
||||||
|
|
||||||
@ -356,9 +364,9 @@ When you run the `scan.sh` script:
|
|||||||
## 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>/default/ComfyUI-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`
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
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
|
||||||
|
|||||||
10687
custom-node-list.json
Executable file → Normal file
10687
custom-node-list.json
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
@ -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
|
||||||
|
|||||||
230
docs/en/v3.38-userdata-security-migration.md
Normal file
230
docs/en/v3.38-userdata-security-migration.md
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
# ComfyUI-Manager V3.38: Userdata Security Migration Guide
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
ComfyUI-Manager V3.38 introduces a **security patch** that migrates Manager's configuration and data to a protected system path. This change leverages ComfyUI's new System User Protection API (PR #10966) to provide enhanced security isolation.
|
||||||
|
|
||||||
|
This guide explains what happens during the migration and how to handle various situations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Changed
|
||||||
|
|
||||||
|
### Finding Your Paths
|
||||||
|
|
||||||
|
When ComfyUI starts, it displays the full paths in the terminal:
|
||||||
|
|
||||||
|
```
|
||||||
|
** User directory: /path/to/ComfyUI/user
|
||||||
|
** ComfyUI-Manager config path: /path/to/ComfyUI/user/__manager/config.ini
|
||||||
|
```
|
||||||
|
|
||||||
|
Look for these lines in your startup log to find the exact location on your system. In this guide, paths are shown relative to the `user` directory.
|
||||||
|
|
||||||
|
### Path Migration
|
||||||
|
|
||||||
|
| Data | Legacy Path | New Path |
|
||||||
|
|------|-------------|----------|
|
||||||
|
| Configuration | `user/default/ComfyUI-Manager/` | `user/__manager/` |
|
||||||
|
| Snapshots | `user/default/ComfyUI-Manager/snapshots/` | `user/__manager/snapshots/` |
|
||||||
|
|
||||||
|
### Why This Change
|
||||||
|
|
||||||
|
In older ComfyUI versions, the `default/` directory was **unprotected** and accessible via web APIs. If you ran ComfyUI with `--listen 0.0.0.0` or similar options to allow external connections, this data **may have been tampered with** by malicious actors.
|
||||||
|
|
||||||
|
**Note:** If you only used ComfyUI locally (without `--listen` or with `--listen 127.0.0.1`), your data was not exposed to this vulnerability.
|
||||||
|
|
||||||
|
The new `__manager` path uses ComfyUI's protected system directory, which:
|
||||||
|
- **Cannot be accessed** from outside (protected by ComfyUI)
|
||||||
|
- Isolates system settings from user data
|
||||||
|
- Enables stricter security for remote access
|
||||||
|
|
||||||
|
**This is why only `config.ini` is automatically migrated** - other files (snapshots) may have been compromised and should be manually verified before copying.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Automatic Migration
|
||||||
|
|
||||||
|
When you start ComfyUI with the new System User Protection API, Manager automatically handles the migration:
|
||||||
|
|
||||||
|
### Step 1: Configuration Migration
|
||||||
|
|
||||||
|
Only `config.ini` is migrated automatically.
|
||||||
|
|
||||||
|
**Important**: Snapshots are **NOT** automatically migrated. You must copy them manually if needed.
|
||||||
|
|
||||||
|
### Step 2: Security Level Check
|
||||||
|
|
||||||
|
During migration, if your security level is below `normal` (i.e., `weak` or `normal-`), it will be automatically raised to `normal`. This is a safety measure because the security level setting itself may have been tampered with in the old version.
|
||||||
|
|
||||||
|
```
|
||||||
|
======================================================================
|
||||||
|
[ComfyUI-Manager] WARNING: Security level adjusted
|
||||||
|
- Previous: 'weak' → New: 'normal'
|
||||||
|
- Raised to prevent unauthorized remote access.
|
||||||
|
======================================================================
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need a lower security level, you can manually edit the config after migration.
|
||||||
|
|
||||||
|
### Step 3: Legacy Backup
|
||||||
|
|
||||||
|
Your entire legacy directory is moved to a backup location:
|
||||||
|
```
|
||||||
|
user/__manager/.legacy-manager-backup/
|
||||||
|
```
|
||||||
|
|
||||||
|
This backup is preserved until you manually delete it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Persistent Backup Notification
|
||||||
|
|
||||||
|
As long as the backup exists, Manager will remind you on **every startup**:
|
||||||
|
|
||||||
|
```
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
[ComfyUI-Manager] NOTICE: Legacy backup exists
|
||||||
|
- Your old Manager data was backed up to:
|
||||||
|
/path/to/ComfyUI/user/__manager/.legacy-manager-backup
|
||||||
|
- Please verify and remove it when no longer needed.
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
```
|
||||||
|
|
||||||
|
**To stop this notification**: Delete the `.legacy-manager-backup` folder inside `user/__manager/` after confirming you don't need any data from it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recovering Old Data
|
||||||
|
|
||||||
|
### Snapshots
|
||||||
|
|
||||||
|
If you need your old snapshots, copy the contents of `.legacy-manager-backup/snapshots/` to `user/__manager/snapshots/`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Outdated ComfyUI Warning
|
||||||
|
|
||||||
|
If you're running an older version of ComfyUI without the System User Protection API, Manager will:
|
||||||
|
|
||||||
|
1. **Force security level to `strong`** - All installations are blocked
|
||||||
|
2. **Display warning message**:
|
||||||
|
|
||||||
|
```
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
[ComfyUI-Manager] ERROR: ComfyUI version is outdated!
|
||||||
|
- Most operations are blocked for security.
|
||||||
|
- ComfyUI update is still allowed.
|
||||||
|
- Please update ComfyUI to use Manager normally.
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution**: Update ComfyUI to v0.3.76 or later.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Levels
|
||||||
|
|
||||||
|
| Level | What's Allowed |
|
||||||
|
|-------|----------------|
|
||||||
|
| `strong` | ComfyUI update only. All other installations blocked. |
|
||||||
|
| `normal` | Install/update/remove registered custom nodes and models. |
|
||||||
|
| `normal-` | Above + Install via Git URL or pip (localhost only). |
|
||||||
|
| `weak` | All operations allowed, including from remote connections. |
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- `strong` is forced on outdated ComfyUI versions.
|
||||||
|
- `normal` is the default and recommended for most users.
|
||||||
|
- `normal-` is for developers who need to install unregistered nodes locally.
|
||||||
|
- `weak` should only be used in isolated development environments.
|
||||||
|
|
||||||
|
### Changing Security Level
|
||||||
|
|
||||||
|
Edit `user/__manager/config.ini`:
|
||||||
|
```ini
|
||||||
|
[default]
|
||||||
|
security_level = normal
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Messages
|
||||||
|
|
||||||
|
### "comfyui_outdated" (HTTP 403)
|
||||||
|
|
||||||
|
This error appears when:
|
||||||
|
- Your ComfyUI doesn't have the System User Protection API
|
||||||
|
- All installations are blocked until you update ComfyUI
|
||||||
|
|
||||||
|
**Solution**: Update ComfyUI to the latest version.
|
||||||
|
|
||||||
|
### "security_level" (HTTP 403)
|
||||||
|
|
||||||
|
This error appears when:
|
||||||
|
- Your security level blocks the requested operation
|
||||||
|
- For example, `strong` level blocks all installations
|
||||||
|
|
||||||
|
**Solution**: Lower your security level in config.ini if appropriate for your use case.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Warning: Suspicious Path
|
||||||
|
|
||||||
|
If you see this error on an **older** ComfyUI:
|
||||||
|
|
||||||
|
```
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
[ComfyUI-Manager] ERROR: Suspicious path detected!
|
||||||
|
- '__manager' exists with low security level: 'weak'
|
||||||
|
- Please verify manually:
|
||||||
|
/path/to/ComfyUI/user/__manager/config.ini
|
||||||
|
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||||
|
```
|
||||||
|
|
||||||
|
On older ComfyUI versions, the `__manager` directory is not normally created. If this directory exists, it may have been created externally. For safety, manually verify the contents of this directory before updating ComfyUI.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### All my installations are blocked
|
||||||
|
|
||||||
|
**Check 1**: Is your ComfyUI updated?
|
||||||
|
- Old ComfyUI forces `security_level = strong`
|
||||||
|
- Update ComfyUI to resolve
|
||||||
|
|
||||||
|
**Check 2**: What's your security level?
|
||||||
|
- Check `user/__manager/config.ini`
|
||||||
|
- `security_level = strong` blocks all installations
|
||||||
|
|
||||||
|
### My snapshots are missing
|
||||||
|
|
||||||
|
Snapshots are not automatically migrated. You need to manually copy the `snapshots` folder from inside `.legacy-manager-backup` to the `user/__manager/` directory.
|
||||||
|
|
||||||
|
### I keep seeing the backup notification
|
||||||
|
|
||||||
|
Delete the `.legacy-manager-backup` folder inside `user/__manager/` after confirming you don't need any data from it.
|
||||||
|
|
||||||
|
### Snapshot restore is blocked
|
||||||
|
|
||||||
|
On old ComfyUI (without System User API), snapshot restore is blocked because security is forced to `strong`. Update ComfyUI to enable snapshot restore.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## File Structure Reference
|
||||||
|
|
||||||
|
```
|
||||||
|
user/
|
||||||
|
└── __manager/
|
||||||
|
├── config.ini # Manager configuration
|
||||||
|
├── channels.list # Custom node channels
|
||||||
|
├── snapshots/ # Environment snapshots
|
||||||
|
└── .legacy-manager-backup/ # Backup of old Manager data (temporary)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- **ComfyUI**: v0.3.76 or later (with System User Protection API)
|
||||||
|
- **ComfyUI-Manager**: V3.38 or later
|
||||||
@ -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]]`
|
||||||
|
|
||||||
@ -141,8 +141,8 @@ 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
|
||||||
|
|||||||
17498
extension-node-map.json
17498
extension-node-map.json
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import traceback
|
import traceback
|
||||||
|
import time
|
||||||
|
|
||||||
import git
|
import git
|
||||||
import json
|
import json
|
||||||
@ -219,7 +220,14 @@ def gitpull(path):
|
|||||||
repo.close()
|
repo.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
remote.pull()
|
try:
|
||||||
|
repo.git.pull('--ff-only')
|
||||||
|
except git.GitCommandError:
|
||||||
|
backup_name = f'backup_{time.strftime("%Y%m%d_%H%M%S")}'
|
||||||
|
repo.create_head(backup_name)
|
||||||
|
print(f"[ComfyUI-Manager] Cannot fast-forward. Backup created: {backup_name}")
|
||||||
|
repo.git.reset('--hard', f'{remote_name}/{branch_name}')
|
||||||
|
print(f"[ComfyUI-Manager] Reset to {remote_name}/{branch_name}")
|
||||||
|
|
||||||
repo.git.submodule('update', '--init', '--recursive')
|
repo.git.submodule('update', '--init', '--recursive')
|
||||||
new_commit_hash = repo.head.commit.hexsha
|
new_commit_hash = repo.head.commit.hexsha
|
||||||
|
|||||||
22700
github-stats-cache.json
Normal file
22700
github-stats-cache.json
Normal file
File diff suppressed because it is too large
Load Diff
15481
github-stats.json
15481
github-stats.json
File diff suppressed because it is too large
Load Diff
@ -40,10 +40,11 @@ import cnr_utils
|
|||||||
import manager_util
|
import manager_util
|
||||||
import git_utils
|
import git_utils
|
||||||
import manager_downloader
|
import manager_downloader
|
||||||
|
import manager_migration
|
||||||
from node_package import InstalledNodePackage
|
from node_package import InstalledNodePackage
|
||||||
|
|
||||||
|
|
||||||
version_code = [3, 35]
|
version_code = [3, 39]
|
||||||
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 '')
|
||||||
|
|
||||||
|
|
||||||
@ -214,9 +215,10 @@ def update_user_directory(user_dir):
|
|||||||
global manager_pip_blacklist_path
|
global manager_pip_blacklist_path
|
||||||
global manager_components_path
|
global manager_components_path
|
||||||
|
|
||||||
manager_files_path = os.path.abspath(os.path.join(user_dir, 'default', 'ComfyUI-Manager'))
|
manager_files_path = manager_migration.get_manager_path(user_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)
|
||||||
|
manager_migration.run_migration_checks(user_dir, manager_files_path)
|
||||||
|
|
||||||
manager_snapshot_path = os.path.join(manager_files_path, "snapshots")
|
manager_snapshot_path = os.path.join(manager_files_path, "snapshots")
|
||||||
if not os.path.exists(manager_snapshot_path):
|
if not os.path.exists(manager_snapshot_path):
|
||||||
@ -1484,6 +1486,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)
|
||||||
|
|
||||||
@ -1718,7 +1721,7 @@ def read_config():
|
|||||||
manager_util.use_uv = default_conf['use_uv'].lower() == 'true' if 'use_uv' 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)
|
manager_util.bypass_ssl = get_bool('bypass_ssl', False)
|
||||||
|
|
||||||
return {
|
result = {
|
||||||
'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', ''),
|
||||||
@ -1738,6 +1741,8 @@ def read_config():
|
|||||||
'security_level': default_conf.get('security_level', 'normal').lower(),
|
'security_level': default_conf.get('security_level', 'normal').lower(),
|
||||||
'db_mode': default_conf.get('db_mode', 'cache').lower(),
|
'db_mode': default_conf.get('db_mode', 'cache').lower(),
|
||||||
}
|
}
|
||||||
|
manager_migration.force_security_level_if_needed(result)
|
||||||
|
return result
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
import importlib.util
|
import importlib.util
|
||||||
@ -1745,7 +1750,7 @@ def read_config():
|
|||||||
manager_util.use_uv = importlib.util.find_spec("uv") is not None and platform.system() != "Windows"
|
manager_util.use_uv = importlib.util.find_spec("uv") is not None and platform.system() != "Windows"
|
||||||
manager_util.bypass_ssl = False
|
manager_util.bypass_ssl = False
|
||||||
|
|
||||||
return {
|
result = {
|
||||||
'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': '',
|
||||||
@ -1765,6 +1770,8 @@ def read_config():
|
|||||||
'security_level': 'normal', # strong | normal | normal- | weak
|
'security_level': 'normal', # strong | normal | normal- | weak
|
||||||
'db_mode': 'cache', # local | cache | remote
|
'db_mode': 'cache', # local | cache | remote
|
||||||
}
|
}
|
||||||
|
manager_migration.force_security_level_if_needed(result)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_config():
|
def get_config():
|
||||||
@ -2246,9 +2253,17 @@ def git_pull(path):
|
|||||||
|
|
||||||
current_branch = repo.active_branch
|
current_branch = repo.active_branch
|
||||||
remote_name = current_branch.tracking_branch().remote_name
|
remote_name = current_branch.tracking_branch().remote_name
|
||||||
remote = repo.remote(name=remote_name)
|
|
||||||
|
|
||||||
remote.pull()
|
try:
|
||||||
|
repo.git.pull('--ff-only')
|
||||||
|
except git.GitCommandError:
|
||||||
|
branch_name = current_branch.name
|
||||||
|
backup_name = f'backup_{time.strftime("%Y%m%d_%H%M%S")}'
|
||||||
|
repo.create_head(backup_name)
|
||||||
|
logging.info(f"[ComfyUI-Manager] Cannot fast-forward. Backup created: {backup_name}")
|
||||||
|
repo.git.reset('--hard', f'{remote_name}/{branch_name}')
|
||||||
|
logging.info(f"[ComfyUI-Manager] Reset to {remote_name}/{branch_name}")
|
||||||
|
|
||||||
repo.git.submodule('update', '--init', '--recursive')
|
repo.git.submodule('update', '--init', '--recursive')
|
||||||
|
|
||||||
repo.close()
|
repo.close()
|
||||||
@ -2516,22 +2531,23 @@ def update_to_stable_comfyui(repo_path):
|
|||||||
logging.error('\t'+branch.name)
|
logging.error('\t'+branch.name)
|
||||||
return "fail", None
|
return "fail", None
|
||||||
|
|
||||||
versions, current_tag, _ = get_comfyui_versions(repo)
|
versions, current_tag, latest_tag = get_comfyui_versions(repo)
|
||||||
|
|
||||||
if len(versions) == 0 or (len(versions) == 1 and versions[0] == 'nightly'):
|
if latest_tag is None:
|
||||||
logging.info("[ComfyUI-Manager] Unable to update to the stable ComfyUI version.")
|
logging.info("[ComfyUI-Manager] Unable to update to the stable ComfyUI version.")
|
||||||
return "fail", None
|
return "fail", None
|
||||||
|
|
||||||
if versions[0] == 'nightly':
|
tag_ref = next((t for t in repo.tags if t.name == latest_tag), None)
|
||||||
latest_tag = versions[1]
|
if tag_ref is None:
|
||||||
else:
|
logging.info(f"[ComfyUI-Manager] Unable to locate tag '{latest_tag}' in repository.")
|
||||||
latest_tag = versions[0]
|
return "fail", None
|
||||||
|
|
||||||
if current_tag == latest_tag:
|
if repo.head.commit == tag_ref.commit:
|
||||||
return "skip", None
|
return "skip", None
|
||||||
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(tag_ref.name)
|
||||||
|
execute_install_script("ComfyUI", repo_path, instant_execution=False, no_deps=False)
|
||||||
return 'updated', latest_tag
|
return 'updated', latest_tag
|
||||||
except:
|
except:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@ -2663,9 +2679,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:
|
||||||
@ -3350,36 +3370,80 @@ async def restore_snapshot(snapshot_path, git_helper_extras=None):
|
|||||||
|
|
||||||
|
|
||||||
def get_comfyui_versions(repo=None):
|
def get_comfyui_versions(repo=None):
|
||||||
if repo is None:
|
repo = repo or git.Repo(comfy_path)
|
||||||
repo = git.Repo(comfy_path)
|
|
||||||
|
|
||||||
|
remote_name = None
|
||||||
try:
|
try:
|
||||||
remote = get_remote_name(repo)
|
remote_name = get_remote_name(repo)
|
||||||
repo.remotes[remote].fetch()
|
repo.remotes[remote_name].fetch()
|
||||||
except:
|
except:
|
||||||
logging.error("[ComfyUI-Manager] Failed to fetch ComfyUI")
|
logging.error("[ComfyUI-Manager] Failed to fetch ComfyUI")
|
||||||
|
|
||||||
versions = [x.name for x in repo.tags if x.name.startswith('v')]
|
def parse_semver(tag_name):
|
||||||
|
match = re.match(r'^v(\d+)\.(\d+)\.(\d+)$', tag_name)
|
||||||
|
return tuple(int(x) for x in match.groups()) if match else None
|
||||||
|
|
||||||
# nearest tag
|
def normalize_describe(tag_name):
|
||||||
versions = sorted(versions, key=lambda v: repo.git.log('-1', '--format=%ct', v), reverse=True)
|
if not tag_name:
|
||||||
versions = versions[:4]
|
return None
|
||||||
|
base = tag_name.split('-', 1)[0]
|
||||||
|
return base if parse_semver(base) else None
|
||||||
|
|
||||||
current_tag = repo.git.describe('--tags')
|
# Collect semver tags and sort descending (highest first)
|
||||||
|
semver_tags = []
|
||||||
|
for tag in repo.tags:
|
||||||
|
semver = parse_semver(tag.name)
|
||||||
|
if semver:
|
||||||
|
semver_tags.append((semver, tag.name))
|
||||||
|
semver_tags.sort(key=lambda x: x[0], reverse=True)
|
||||||
|
semver_tags = [name for _, name in semver_tags]
|
||||||
|
|
||||||
if current_tag not in versions:
|
latest_tag = semver_tags[0] if semver_tags else None
|
||||||
versions = sorted(versions + [current_tag], key=lambda v: repo.git.log('-1', '--format=%ct', v), reverse=True)
|
|
||||||
versions = versions[:4]
|
|
||||||
|
|
||||||
main_branch = repo.heads.master
|
try:
|
||||||
latest_commit = main_branch.commit
|
described = repo.git.describe('--tags')
|
||||||
latest_tag = repo.git.describe('--tags', latest_commit.hexsha)
|
except Exception:
|
||||||
|
described = ''
|
||||||
|
|
||||||
if latest_tag != versions[0]:
|
try:
|
||||||
versions.insert(0, 'nightly')
|
exact_tag = repo.git.describe('--tags', '--exact-match')
|
||||||
else:
|
except Exception:
|
||||||
versions[0] = 'nightly'
|
exact_tag = ''
|
||||||
|
|
||||||
|
head_is_default = False
|
||||||
|
if remote_name:
|
||||||
|
try:
|
||||||
|
default_head_ref = repo.refs[f'{remote_name}/HEAD']
|
||||||
|
default_commit = default_head_ref.reference.commit
|
||||||
|
head_is_default = repo.head.commit == default_commit
|
||||||
|
except Exception:
|
||||||
|
head_is_default = False
|
||||||
|
|
||||||
|
nearest_semver = normalize_describe(described)
|
||||||
|
exact_semver = exact_tag if parse_semver(exact_tag) else None
|
||||||
|
|
||||||
|
if head_is_default and not exact_tag:
|
||||||
current_tag = 'nightly'
|
current_tag = 'nightly'
|
||||||
|
else:
|
||||||
|
current_tag = exact_tag or described or 'nightly'
|
||||||
|
|
||||||
|
# Prepare semver list for display: top 4 plus the current/nearest semver if missing
|
||||||
|
display_semver_tags = semver_tags[:4]
|
||||||
|
if exact_semver and exact_semver not in display_semver_tags:
|
||||||
|
display_semver_tags.append(exact_semver)
|
||||||
|
elif nearest_semver and nearest_semver not in display_semver_tags:
|
||||||
|
display_semver_tags.append(nearest_semver)
|
||||||
|
|
||||||
|
versions = ['nightly']
|
||||||
|
|
||||||
|
if current_tag and not exact_semver and current_tag not in versions and current_tag not in display_semver_tags:
|
||||||
|
versions.append(current_tag)
|
||||||
|
|
||||||
|
for tag in display_semver_tags:
|
||||||
|
if tag not in versions:
|
||||||
|
versions.append(tag)
|
||||||
|
|
||||||
|
versions = versions[:6]
|
||||||
|
|
||||||
return versions, current_tag, latest_tag
|
return versions, current_tag, latest_tag
|
||||||
|
|
||||||
|
|||||||
356
glob/manager_migration.py
Normal file
356
glob/manager_migration.py
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
"""
|
||||||
|
ComfyUI-Manager migration module.
|
||||||
|
Handles migration from legacy paths to new __manager path structure.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import configparser
|
||||||
|
|
||||||
|
# Startup notices for notice board
|
||||||
|
startup_notices = [] # List of (message, level) tuples
|
||||||
|
|
||||||
|
|
||||||
|
def add_startup_notice(message, level='warning'):
|
||||||
|
"""Add a notice to be displayed on Manager notice board.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
message: HTML-formatted message string
|
||||||
|
level: 'warning', 'error', 'info'
|
||||||
|
"""
|
||||||
|
global startup_notices
|
||||||
|
startup_notices.append((message, level))
|
||||||
|
|
||||||
|
|
||||||
|
# Cache for API check (computed once per session)
|
||||||
|
_cached_has_system_user_api = None
|
||||||
|
|
||||||
|
|
||||||
|
def has_system_user_api():
|
||||||
|
"""Check if ComfyUI has the System User Protection API (PR #10966).
|
||||||
|
|
||||||
|
Result is cached for performance.
|
||||||
|
"""
|
||||||
|
global _cached_has_system_user_api
|
||||||
|
if _cached_has_system_user_api is None:
|
||||||
|
try:
|
||||||
|
import folder_paths
|
||||||
|
_cached_has_system_user_api = hasattr(folder_paths, 'get_system_user_directory')
|
||||||
|
except Exception:
|
||||||
|
_cached_has_system_user_api = False
|
||||||
|
return _cached_has_system_user_api
|
||||||
|
|
||||||
|
|
||||||
|
def get_manager_path(user_dir):
|
||||||
|
"""Get the appropriate manager files path based on ComfyUI version.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: manager_files_path
|
||||||
|
"""
|
||||||
|
if has_system_user_api():
|
||||||
|
return os.path.abspath(os.path.join(user_dir, '__manager'))
|
||||||
|
else:
|
||||||
|
return os.path.abspath(os.path.join(user_dir, 'default', 'ComfyUI-Manager'))
|
||||||
|
|
||||||
|
|
||||||
|
def run_migration_checks(user_dir, manager_files_path):
|
||||||
|
"""Run all migration and security checks.
|
||||||
|
|
||||||
|
Call this after get_manager_path() to handle:
|
||||||
|
- Legacy config migration (new ComfyUI)
|
||||||
|
- Legacy backup notification (every startup)
|
||||||
|
- Suspicious directory detection (old ComfyUI)
|
||||||
|
- Outdated ComfyUI warning (old ComfyUI)
|
||||||
|
"""
|
||||||
|
if has_system_user_api():
|
||||||
|
migrated = migrate_legacy_config(user_dir, manager_files_path)
|
||||||
|
# Only check for legacy backup if migration didn't just happen
|
||||||
|
# (migration already shows backup location in its message)
|
||||||
|
if not migrated:
|
||||||
|
check_legacy_backup(manager_files_path)
|
||||||
|
else:
|
||||||
|
check_suspicious_manager(user_dir)
|
||||||
|
warn_outdated_comfyui()
|
||||||
|
|
||||||
|
|
||||||
|
def check_legacy_backup(manager_files_path):
|
||||||
|
"""Check for legacy backup and notify user to verify and remove it.
|
||||||
|
|
||||||
|
This runs on every startup to remind users about pending legacy backup.
|
||||||
|
"""
|
||||||
|
backup_dir = os.path.join(manager_files_path, '.legacy-manager-backup')
|
||||||
|
if not os.path.exists(backup_dir):
|
||||||
|
return
|
||||||
|
|
||||||
|
# Terminal output
|
||||||
|
print("\n" + "-"*70)
|
||||||
|
print("[ComfyUI-Manager] NOTICE: Legacy backup exists")
|
||||||
|
print(" - Your old Manager data was backed up to:")
|
||||||
|
print(f" {backup_dir}")
|
||||||
|
print(" - Please verify and remove it when no longer needed.")
|
||||||
|
print("-"*70 + "\n")
|
||||||
|
|
||||||
|
# Notice board output
|
||||||
|
add_startup_notice(
|
||||||
|
"Legacy ComfyUI-Manager data backup exists. Please verify and remove when no longer needed. See terminal for details.",
|
||||||
|
level='info'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_suspicious_manager(user_dir):
|
||||||
|
"""Check for suspicious __manager directory on old ComfyUI.
|
||||||
|
|
||||||
|
On old ComfyUI without System User API, if __manager exists with low security,
|
||||||
|
warn the user to verify manually.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if suspicious setup detected
|
||||||
|
"""
|
||||||
|
if has_system_user_api():
|
||||||
|
return False # Not suspicious on new ComfyUI
|
||||||
|
|
||||||
|
suspicious_path = os.path.abspath(os.path.join(user_dir, '__manager'))
|
||||||
|
if not os.path.exists(suspicious_path):
|
||||||
|
return False
|
||||||
|
|
||||||
|
config_path = os.path.join(suspicious_path, 'config.ini')
|
||||||
|
if not os.path.exists(config_path):
|
||||||
|
return False
|
||||||
|
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read(config_path)
|
||||||
|
sec_level = config.get('default', 'security_level', fallback='normal').lower()
|
||||||
|
|
||||||
|
if sec_level in ['weak', 'normal-']:
|
||||||
|
# Terminal output
|
||||||
|
print("\n" + "!"*70)
|
||||||
|
print("[ComfyUI-Manager] ERROR: Suspicious path detected!")
|
||||||
|
print(f" - '__manager' exists with low security level: '{sec_level}'")
|
||||||
|
print(" - Please verify manually:")
|
||||||
|
print(f" {config_path}")
|
||||||
|
print("!"*70 + "\n")
|
||||||
|
|
||||||
|
# Notice board output
|
||||||
|
add_startup_notice(
|
||||||
|
"[Security Alert] Suspicious path detected. See terminal log for details.",
|
||||||
|
level='error'
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def warn_outdated_comfyui():
|
||||||
|
"""Warn user about outdated ComfyUI without System User API."""
|
||||||
|
if has_system_user_api():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Terminal output
|
||||||
|
print("\n" + "!"*70)
|
||||||
|
print("[ComfyUI-Manager] ERROR: ComfyUI version is outdated!")
|
||||||
|
print(" - Most operations are blocked for security.")
|
||||||
|
print(" - ComfyUI update is still allowed.")
|
||||||
|
print(" - Please update ComfyUI to use Manager normally.")
|
||||||
|
print("!"*70 + "\n")
|
||||||
|
|
||||||
|
# Notice board output
|
||||||
|
add_startup_notice(
|
||||||
|
"[Security Alert] ComfyUI outdated. Installations blocked (update allowed).<BR>"
|
||||||
|
"Update ComfyUI for normal operation.",
|
||||||
|
level='error'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_legacy_config(user_dir, manager_files_path):
|
||||||
|
"""Migrate ONLY config.ini to new __manager path if needed.
|
||||||
|
|
||||||
|
IMPORTANT: Only config.ini is migrated. Other files (snapshots, cache, etc.)
|
||||||
|
are NOT migrated - users must recreate them.
|
||||||
|
|
||||||
|
Scenarios:
|
||||||
|
1. Legacy exists, New doesn't exist → Migrate config.ini
|
||||||
|
2. Legacy exists, New exists → First update after upgrade
|
||||||
|
- Run ComfyUI dependency installation
|
||||||
|
- Rename legacy to .backup
|
||||||
|
3. Legacy doesn't exist → No migration needed
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if migration was performed
|
||||||
|
"""
|
||||||
|
if not has_system_user_api():
|
||||||
|
return False
|
||||||
|
|
||||||
|
legacy_dir = os.path.join(user_dir, 'default', 'ComfyUI-Manager')
|
||||||
|
legacy_config = os.path.join(legacy_dir, 'config.ini')
|
||||||
|
new_config = os.path.join(manager_files_path, 'config.ini')
|
||||||
|
|
||||||
|
if not os.path.exists(legacy_dir):
|
||||||
|
return False # No legacy directory, nothing to migrate
|
||||||
|
|
||||||
|
# IMPORTANT: Check for config.ini existence, not just directory
|
||||||
|
# (because makedirs() creates __manager before this function is called)
|
||||||
|
|
||||||
|
# Case: Both configs exist (first update after ComfyUI upgrade)
|
||||||
|
# This means user ran new ComfyUI at least once, creating __manager/config.ini
|
||||||
|
if os.path.exists(legacy_config) and os.path.exists(new_config):
|
||||||
|
_handle_first_update_migration(user_dir, legacy_dir, manager_files_path)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Case: Legacy config exists but new config doesn't (normal migration)
|
||||||
|
# This is the first run after ComfyUI upgrade
|
||||||
|
if os.path.exists(legacy_config) and not os.path.exists(new_config):
|
||||||
|
pass # Continue with normal migration below
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Terminal output
|
||||||
|
print("\n" + "-"*70)
|
||||||
|
print("[ComfyUI-Manager] NOTICE: Legacy config.ini detected")
|
||||||
|
print(f" - Old: {legacy_config}")
|
||||||
|
print(f" - New: {new_config}")
|
||||||
|
print(" - Migrating config.ini only (other files are NOT migrated).")
|
||||||
|
print(" - Security level below 'normal' will be raised.")
|
||||||
|
print("-"*70 + "\n")
|
||||||
|
|
||||||
|
_migrate_config_with_security_check(legacy_config, new_config)
|
||||||
|
|
||||||
|
# Move legacy directory to backup
|
||||||
|
_move_legacy_to_backup(legacy_dir, manager_files_path)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def _handle_first_update_migration(user_dir, legacy_dir, manager_files_path):
|
||||||
|
"""Handle first ComfyUI update when both legacy and new directories exist.
|
||||||
|
|
||||||
|
This scenario happens when:
|
||||||
|
- User was on old ComfyUI (using default/ComfyUI-Manager)
|
||||||
|
- ComfyUI was updated (now has System User API)
|
||||||
|
- Manager already created __manager on first new run
|
||||||
|
- But legacy directory still exists
|
||||||
|
|
||||||
|
Actions:
|
||||||
|
1. Run ComfyUI dependency installation
|
||||||
|
2. Move legacy to __manager/.legacy-manager-backup
|
||||||
|
"""
|
||||||
|
# Terminal output
|
||||||
|
print("\n" + "-"*70)
|
||||||
|
print("[ComfyUI-Manager] NOTICE: First update after ComfyUI upgrade detected")
|
||||||
|
print(" - Both legacy and new directories exist.")
|
||||||
|
print(" - Running ComfyUI dependency installation...")
|
||||||
|
print("-"*70 + "\n")
|
||||||
|
|
||||||
|
# Run ComfyUI dependency installation
|
||||||
|
# Path: glob/manager_migration.py → glob → comfyui-manager → custom_nodes → ComfyUI
|
||||||
|
try:
|
||||||
|
comfyui_path = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
|
||||||
|
requirements_path = os.path.join(comfyui_path, 'requirements.txt')
|
||||||
|
if os.path.exists(requirements_path):
|
||||||
|
subprocess.run([sys.executable, '-m', 'pip', 'install', '-r', requirements_path],
|
||||||
|
capture_output=True, check=False)
|
||||||
|
print("[ComfyUI-Manager] ComfyUI dependencies installation completed.")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ComfyUI-Manager] WARNING: Failed to install ComfyUI dependencies: {e}")
|
||||||
|
|
||||||
|
# Move legacy to backup inside __manager
|
||||||
|
_move_legacy_to_backup(legacy_dir, manager_files_path)
|
||||||
|
|
||||||
|
|
||||||
|
def _move_legacy_to_backup(legacy_dir, manager_files_path):
|
||||||
|
"""Move legacy directory to backup inside __manager.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: Path to backup directory if successful, None if failed
|
||||||
|
"""
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
backup_dir = os.path.join(manager_files_path, '.legacy-manager-backup')
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.path.exists(backup_dir):
|
||||||
|
shutil.rmtree(backup_dir) # Remove old backup if exists
|
||||||
|
shutil.move(legacy_dir, backup_dir)
|
||||||
|
|
||||||
|
# Terminal output (full paths shown here only)
|
||||||
|
print("\n" + "-"*70)
|
||||||
|
print("[ComfyUI-Manager] NOTICE: Legacy settings migrated")
|
||||||
|
print(f" - Old location: {legacy_dir}")
|
||||||
|
print(f" - Backed up to: {backup_dir}")
|
||||||
|
print(" - Please verify and remove the backup when no longer needed.")
|
||||||
|
print("-"*70 + "\n")
|
||||||
|
|
||||||
|
# Notice board output (no full paths for security)
|
||||||
|
add_startup_notice(
|
||||||
|
"Legacy ComfyUI-Manager data migrated. See terminal for details.",
|
||||||
|
level='info'
|
||||||
|
)
|
||||||
|
return backup_dir
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ComfyUI-Manager] WARNING: Failed to backup legacy directory: {e}")
|
||||||
|
add_startup_notice(
|
||||||
|
f"[MIGRATION] Failed to backup legacy directory: {e}",
|
||||||
|
level='warning'
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _migrate_config_with_security_check(legacy_path, new_path):
|
||||||
|
"""Migrate legacy config, raising security level only if below default."""
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
try:
|
||||||
|
config.read(legacy_path)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"[ComfyUI-Manager] WARNING: Failed to parse config.ini: {e}")
|
||||||
|
print(" - Creating fresh config with default settings.")
|
||||||
|
add_startup_notice(
|
||||||
|
"[MIGRATION] Failed to parse legacy config. Using defaults.",
|
||||||
|
level='warning'
|
||||||
|
)
|
||||||
|
return # Skip migration, let Manager create fresh config
|
||||||
|
|
||||||
|
# Security level hierarchy: strong > normal > normal- > weak
|
||||||
|
# Default is 'normal', only raise if below default
|
||||||
|
if 'default' in config:
|
||||||
|
current_level = config['default'].get('security_level', 'normal').lower()
|
||||||
|
below_default_levels = ['weak', 'normal-']
|
||||||
|
|
||||||
|
if current_level in below_default_levels:
|
||||||
|
config['default']['security_level'] = 'normal'
|
||||||
|
|
||||||
|
# Terminal output
|
||||||
|
print("\n" + "="*70)
|
||||||
|
print("[ComfyUI-Manager] WARNING: Security level adjusted")
|
||||||
|
print(f" - Previous: '{current_level}' → New: 'normal'")
|
||||||
|
print(" - Raised to prevent unauthorized remote access.")
|
||||||
|
print("="*70 + "\n")
|
||||||
|
|
||||||
|
# Notice board output
|
||||||
|
add_startup_notice(
|
||||||
|
f"[MIGRATION] Security level raised: '{current_level}' → 'normal'.<BR>"
|
||||||
|
"To prevent unauthorized remote access.",
|
||||||
|
level='warning'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(f" - Security level: '{current_level}' (no change needed)")
|
||||||
|
|
||||||
|
# Ensure directory exists
|
||||||
|
os.makedirs(os.path.dirname(new_path), exist_ok=True)
|
||||||
|
|
||||||
|
with open(new_path, 'w') as f:
|
||||||
|
config.write(f)
|
||||||
|
|
||||||
|
|
||||||
|
def force_security_level_if_needed(config_dict):
|
||||||
|
"""Force security level to 'strong' if on old ComfyUI.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config_dict: Configuration dictionary to modify in-place
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if security level was forced
|
||||||
|
"""
|
||||||
|
if not has_system_user_api():
|
||||||
|
config_dict['security_level'] = 'strong'
|
||||||
|
return True
|
||||||
|
return False
|
||||||
@ -22,6 +22,7 @@ import asyncio
|
|||||||
import queue
|
import queue
|
||||||
|
|
||||||
import manager_downloader
|
import manager_downloader
|
||||||
|
import manager_migration
|
||||||
|
|
||||||
|
|
||||||
logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})")
|
logging.info(f"### Loading: ComfyUI-Manager ({core.version_str})")
|
||||||
@ -37,6 +38,25 @@ SECURITY_MESSAGE_NORMAL_MINUS_MODEL = "ERROR: Downloading models that are not in
|
|||||||
|
|
||||||
routes = PromptServer.instance.routes
|
routes = PromptServer.instance.routes
|
||||||
|
|
||||||
|
|
||||||
|
def has_per_queue_preview():
|
||||||
|
"""
|
||||||
|
Check if ComfyUI PR #11261 (per-queue live preview override) is merged
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if ComfyUI has per-queue preview feature
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import latent_preview
|
||||||
|
return hasattr(latent_preview, 'set_preview_method')
|
||||||
|
except ImportError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
# Detect ComfyUI per-queue preview override feature (PR #11261)
|
||||||
|
COMFYUI_HAS_PER_QUEUE_PREVIEW = has_per_queue_preview()
|
||||||
|
|
||||||
|
|
||||||
def handle_stream(stream, prefix):
|
def handle_stream(stream, prefix):
|
||||||
stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
|
stream.reconfigure(encoding=locale.getpreferredencoding(), errors='replace')
|
||||||
for msg in stream:
|
for msg in stream:
|
||||||
@ -181,10 +201,19 @@ def set_preview_method(method):
|
|||||||
core.get_config()['preview_method'] = method
|
core.get_config()['preview_method'] = method
|
||||||
|
|
||||||
|
|
||||||
if args.preview_method == latent_preview.LatentPreviewMethod.NoPreviews:
|
if COMFYUI_HAS_PER_QUEUE_PREVIEW:
|
||||||
|
logging.info(
|
||||||
|
"[ComfyUI-Manager] ComfyUI per-queue preview override detected (PR #11261). "
|
||||||
|
"Manager's preview method feature is disabled. "
|
||||||
|
"Use ComfyUI's --preview-method CLI option or 'Settings > Execution > Live preview method'."
|
||||||
|
)
|
||||||
|
elif args.preview_method == latent_preview.LatentPreviewMethod.NoPreviews:
|
||||||
set_preview_method(core.get_config()['preview_method'])
|
set_preview_method(core.get_config()['preview_method'])
|
||||||
else:
|
else:
|
||||||
logging.warning("[ComfyUI-Manager] Since --preview-method is set, ComfyUI-Manager's preview method feature will be ignored.")
|
logging.warning(
|
||||||
|
"[ComfyUI-Manager] Since --preview-method is set, "
|
||||||
|
"ComfyUI-Manager's preview method feature will be ignored."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def set_component_policy(mode):
|
def set_component_policy(mode):
|
||||||
@ -276,6 +305,13 @@ import zipfile
|
|||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
|
|
||||||
|
def security_403_response():
|
||||||
|
"""Return appropriate 403 response based on ComfyUI version."""
|
||||||
|
if not manager_migration.has_system_user_api():
|
||||||
|
return web.json_response({"error": "comfyui_outdated"}, status=403)
|
||||||
|
return web.json_response({"error": "security_level"}, status=403)
|
||||||
|
|
||||||
|
|
||||||
def get_model_dir(data, show_log=False):
|
def get_model_dir(data, show_log=False):
|
||||||
if 'download_model_base' in folder_paths.folder_names_and_paths:
|
if 'download_model_base' in folder_paths.folder_names_and_paths:
|
||||||
models_base = folder_paths.folder_names_and_paths['download_model_base'][0][0]
|
models_base = folder_paths.folder_names_and_paths['download_model_base'][0][0]
|
||||||
@ -732,7 +768,7 @@ async def fetch_updates(request):
|
|||||||
async def update_all(request):
|
async def update_all(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_OR_BELOW)
|
||||||
return web.Response(status=403)
|
return security_403_response()
|
||||||
|
|
||||||
with task_worker_lock:
|
with task_worker_lock:
|
||||||
is_processing = task_worker_thread is not None and task_worker_thread.is_alive()
|
is_processing = task_worker_thread is not None and task_worker_thread.is_alive()
|
||||||
@ -965,7 +1001,7 @@ async def get_snapshot_list(request):
|
|||||||
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_OR_BELOW)
|
||||||
return web.Response(status=403)
|
return security_403_response()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
target = request.rel_url.query["target"]
|
target = request.rel_url.query["target"]
|
||||||
@ -983,7 +1019,7 @@ async def remove_snapshot(request):
|
|||||||
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_OR_BELOW)
|
||||||
return web.Response(status=403)
|
return security_403_response()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
target = request.rel_url.query["target"]
|
target = request.rel_url.query["target"]
|
||||||
@ -1302,7 +1338,7 @@ async def fix_custom_node(request):
|
|||||||
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 security_403_response()
|
||||||
|
|
||||||
url = await request.text()
|
url = await request.text()
|
||||||
res = await core.gitclone_install(url)
|
res = await core.gitclone_install(url)
|
||||||
@ -1322,7 +1358,7 @@ async def install_custom_node_git_url(request):
|
|||||||
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 security_403_response()
|
||||||
|
|
||||||
packages = await request.text()
|
packages = await request.text()
|
||||||
core.pip_install(packages.split(' '))
|
core.pip_install(packages.split(' '))
|
||||||
@ -1474,14 +1510,26 @@ async def install_model(request):
|
|||||||
|
|
||||||
@routes.get("/manager/preview_method")
|
@routes.get("/manager/preview_method")
|
||||||
async def preview_method(request):
|
async def preview_method(request):
|
||||||
|
# Setting change request
|
||||||
if "value" in request.rel_url.query:
|
if "value" in request.rel_url.query:
|
||||||
|
# Reject setting change if per-queue preview feature is available
|
||||||
|
if COMFYUI_HAS_PER_QUEUE_PREVIEW:
|
||||||
|
return web.Response(text="DISABLED", status=403)
|
||||||
|
|
||||||
|
# Process normally if not available
|
||||||
set_preview_method(request.rel_url.query['value'])
|
set_preview_method(request.rel_url.query['value'])
|
||||||
core.write_config()
|
core.write_config()
|
||||||
else:
|
|
||||||
return web.Response(text=core.manager_funcs.get_current_preview_method(), status=200)
|
|
||||||
|
|
||||||
return web.Response(status=200)
|
return web.Response(status=200)
|
||||||
|
|
||||||
|
# Status query request
|
||||||
|
else:
|
||||||
|
# Return DISABLED if per-queue preview feature is available
|
||||||
|
if COMFYUI_HAS_PER_QUEUE_PREVIEW:
|
||||||
|
return web.Response(text="DISABLED", status=200)
|
||||||
|
|
||||||
|
# Return current value if not available
|
||||||
|
return web.Response(text=core.manager_funcs.get_current_preview_method(), status=200)
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/manager/db_mode")
|
@routes.get("/manager/db_mode")
|
||||||
async def db_mode(request):
|
async def db_mode(request):
|
||||||
@ -1594,6 +1642,16 @@ async def get_notice(request):
|
|||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Prepend startup notices from manager_migration
|
||||||
|
for message, level in reversed(manager_migration.startup_notices):
|
||||||
|
if level == 'error':
|
||||||
|
style = 'color:red; background-color:white; font-weight:bold'
|
||||||
|
elif level == 'warning':
|
||||||
|
style = 'color:orange; background-color:white; font-weight:bold'
|
||||||
|
else:
|
||||||
|
style = 'color:blue; background-color:white'
|
||||||
|
markdown_content = f'<P style="{style}">{message}</P>' + markdown_content
|
||||||
|
|
||||||
return web.Response(text=markdown_content, status=200)
|
return web.Response(text=markdown_content, status=200)
|
||||||
else:
|
else:
|
||||||
return web.Response(text="Unable to retrieve Notice", status=200)
|
return web.Response(text="Unable to retrieve Notice", status=200)
|
||||||
@ -1601,11 +1659,35 @@ async def get_notice(request):
|
|||||||
return web.Response(text="Unable to retrieve Notice", status=200)
|
return web.Response(text="Unable to retrieve Notice", status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@routes.get("/manager/startup_alerts")
|
||||||
|
async def get_startup_alerts(request):
|
||||||
|
"""Return startup alerts for customAlert display on page load.
|
||||||
|
|
||||||
|
Returns JSON array of alerts that should be shown to user immediately.
|
||||||
|
All startup notices (error, warning, info) are returned.
|
||||||
|
"""
|
||||||
|
alerts = []
|
||||||
|
|
||||||
|
# Return all startup notices for alert display
|
||||||
|
for message, level in manager_migration.startup_notices:
|
||||||
|
# Convert HTML BR to newlines for customAlert
|
||||||
|
text = message.replace('<BR>', '\n').replace('<br>', '\n')
|
||||||
|
# Add [ComfyUI-Manager] prefix for customAlert (notice board shows in Manager UI anyway)
|
||||||
|
text = text.replace('[Security Alert]', '[ComfyUI-Manager] Security Alert:')
|
||||||
|
text = text.replace('[MIGRATION]', '[ComfyUI-Manager] Migration:')
|
||||||
|
alerts.append({
|
||||||
|
'message': text,
|
||||||
|
'level': level
|
||||||
|
})
|
||||||
|
|
||||||
|
return web.json_response(alerts)
|
||||||
|
|
||||||
|
|
||||||
@routes.get("/manager/reboot")
|
@routes.get("/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_OR_BELOW)
|
||||||
return web.Response(status=403)
|
return security_403_response()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sys.stdout.close_log()
|
sys.stdout.close_log()
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import re
|
|||||||
import logging
|
import logging
|
||||||
import platform
|
import platform
|
||||||
import shlex
|
import shlex
|
||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
|
||||||
cache_lock = threading.Lock()
|
cache_lock = threading.Lock()
|
||||||
@ -34,18 +35,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:
|
||||||
|
|||||||
@ -335,8 +335,7 @@ async def share_art(request):
|
|||||||
content_type = assetFileType
|
content_type = assetFileType
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from nio import AsyncClient, LoginResponse, RoomSendResponse, UploadResponse, RoomMessageText, RoomMessageMedia
|
from nio import AsyncClient, LoginResponse, UploadResponse
|
||||||
import asyncio
|
|
||||||
|
|
||||||
homeserver = 'matrix.org'
|
homeserver = 'matrix.org'
|
||||||
if matrix_auth:
|
if matrix_auth:
|
||||||
|
|||||||
@ -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.
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { api } from "../../scripts/api.js";
|
import { api } from "../../scripts/api.js";
|
||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { sleep, customConfirm, customAlert } from "./common.js";
|
import { sleep, customConfirm, customAlert, handle403Response, show_message } from "./common.js";
|
||||||
|
|
||||||
async function tryInstallCustomNode(event) {
|
async function tryInstallCustomNode(event) {
|
||||||
let msg = '-= [ComfyUI Manager] extension installation request =-\n\n';
|
let msg = '-= [ComfyUI Manager] extension installation request =-\n\n';
|
||||||
@ -42,7 +42,7 @@ async function tryInstallCustomNode(event) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if(response.status == 403) {
|
if(response.status == 403) {
|
||||||
show_message('This action is not allowed with this security level configuration.');
|
await handle403Response(response);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
else if(response.status == 400) {
|
else if(response.status == 400) {
|
||||||
@ -54,7 +54,7 @@ async function tryInstallCustomNode(event) {
|
|||||||
|
|
||||||
let response = await api.fetchApi("/manager/reboot");
|
let response = await api.fetchApi("/manager/reboot");
|
||||||
if(response.status == 403) {
|
if(response.status == 403) {
|
||||||
show_message('This action is not allowed with this security level configuration.');
|
await handle403Response(response);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
227
js/comfyui-gui-builder.js
Normal file
227
js/comfyui-gui-builder.js
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
import { $el } from "../../scripts/ui.js";
|
||||||
|
|
||||||
|
function normalizeContent(content) {
|
||||||
|
const tmp = document.createElement('div');
|
||||||
|
if (typeof content === 'string') {
|
||||||
|
tmp.innerHTML = content;
|
||||||
|
return Array.from(tmp.childNodes);
|
||||||
|
}
|
||||||
|
if (content instanceof Node) {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createSettingsCombo(label, content) {
|
||||||
|
const settingItem = $el("div.setting-item", {}, [
|
||||||
|
$el("div.flex.flex-row.items-center.gap-2",[
|
||||||
|
$el("div.form-label.flex.grow.items-center", [
|
||||||
|
$el("span.text-muted", { textContent: label },)
|
||||||
|
]),
|
||||||
|
$el("div.form-input.flex.justify-end",
|
||||||
|
[content]
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]);
|
||||||
|
return settingItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildGuiFrame(dialogId, title, iconClass, content, owner) {
|
||||||
|
const dialog_mask = $el("div.p-dialog-mask.p-overlay-mask.p-overlay-mask-enter", {
|
||||||
|
parent: document.body,
|
||||||
|
style: {
|
||||||
|
position: "fixed",
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
left: "0px",
|
||||||
|
top: "0px",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
pointerEvents: "auto",
|
||||||
|
zIndex: "1000"
|
||||||
|
},
|
||||||
|
onclick: (e) => {
|
||||||
|
if (e.target === dialog_mask) {
|
||||||
|
owner.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// data-pc-section="mask"
|
||||||
|
});
|
||||||
|
|
||||||
|
const header_actions = $el("div.p-dialog-header-actions", {
|
||||||
|
// [TODO]
|
||||||
|
// data-pc-section="headeractions"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const close_button = $el("button.p-button.p-component.p-button-icon-only.p-button-secondary.p-button-rounded.p-button-text.p-dialog-close-button", {
|
||||||
|
parent: header_actions,
|
||||||
|
type: "button",
|
||||||
|
ariaLabel: "Close",
|
||||||
|
onclick: () => owner.close(),
|
||||||
|
// "data-pc-name": "pcclosebutton",
|
||||||
|
// "data-p-disabled": "false",
|
||||||
|
// "data-p-severity": "secondary",
|
||||||
|
// "data-pc-group-section": "headericon",
|
||||||
|
// "data-pc-extend": "button",
|
||||||
|
// "data-pc-section": "root",
|
||||||
|
// [FIXME] Not sure how to do most of the SVG using $el
|
||||||
|
innerHTML: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" class="p-icon p-button-icon" aria-hidden="true"><path d="M8.01186 7.00933L12.27 2.75116C12.341 2.68501 12.398 2.60524 12.4375 2.51661C12.4769 2.42798 12.4982 2.3323 12.4999 2.23529C12.5016 2.13827 12.4838 2.0419 12.4474 1.95194C12.4111 1.86197 12.357 1.78024 12.2884 1.71163C12.2198 1.64302 12.138 1.58893 12.0481 1.55259C11.9581 1.51625 11.8617 1.4984 11.7647 1.50011C11.6677 1.50182 11.572 1.52306 11.4834 1.56255C11.3948 1.60204 11.315 1.65898 11.2488 1.72997L6.99067 5.98814L2.7325 1.72997C2.59553 1.60234 2.41437 1.53286 2.22718 1.53616C2.03999 1.53946 1.8614 1.61529 1.72901 1.74767C1.59663 1.88006 1.5208 2.05865 1.5175 2.24584C1.5142 2.43303 1.58368 2.61419 1.71131 2.75116L5.96948 7.00933L1.71131 11.2675C1.576 11.403 1.5 11.5866 1.5 11.7781C1.5 11.9696 1.576 12.1532 1.71131 12.2887C1.84679 12.424 2.03043 12.5 2.2219 12.5C2.41338 12.5 2.59702 12.424 2.7325 12.2887L6.99067 8.03052L11.2488 12.2887C11.3843 12.424 11.568 12.5 11.7594 12.5C11.9509 12.5 12.1346 12.424 12.27 12.2887C12.4053 12.1532 12.4813 11.9696 12.4813 11.7781C12.4813 11.5866 12.4053 11.403 12.27 11.2675L8.01186 7.00933Z" fill="currentColor"></path></svg><span class="p-button-label" data-pc-section="label"> </span><!---->'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const dialog_header = $el("div.p-dialog-header",
|
||||||
|
[
|
||||||
|
$el("div", [
|
||||||
|
$el("div",
|
||||||
|
{
|
||||||
|
id: "frame-title-container",
|
||||||
|
},
|
||||||
|
[
|
||||||
|
$el("h2.px-4", [
|
||||||
|
$el(iconClass, {
|
||||||
|
style: {
|
||||||
|
"font-size": "1.25rem",
|
||||||
|
"margin-right": ".5rem"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
$el("span", { textContent: title })
|
||||||
|
])
|
||||||
|
]
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
header_actions
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const contentFrame = $el("div.p-dialog-content", {}, normalizeContent(content));
|
||||||
|
const manager_dialog = $el("div.p-dialog.p-component.global-dialog", {
|
||||||
|
id: dialogId,
|
||||||
|
parent: dialog_mask,
|
||||||
|
style: {
|
||||||
|
'display': 'flex',
|
||||||
|
'flex-direction': 'column',
|
||||||
|
'pointer-events': 'auto',
|
||||||
|
'margin': '0px',
|
||||||
|
},
|
||||||
|
role: 'dialog',
|
||||||
|
ariaModal: 'true',
|
||||||
|
// [TODO]
|
||||||
|
// ariaLabbelledby: 'cm-title',
|
||||||
|
// maximized: 'false',
|
||||||
|
// data-pc-name: 'dialog',
|
||||||
|
// data-pc-section: 'root',
|
||||||
|
// data-pd-focustrap: 'true'
|
||||||
|
},
|
||||||
|
[ dialog_header, contentFrame ]
|
||||||
|
);
|
||||||
|
|
||||||
|
const hidden_accessible = $el("span.p-hidden-accessible.p-hidden-focusable", {
|
||||||
|
parent: manager_dialog,
|
||||||
|
tabindex: "0",
|
||||||
|
role: "presentation",
|
||||||
|
ariaHidden: "true",
|
||||||
|
"data-p-hidden-accessible": "true",
|
||||||
|
"data-p-hidden-focusable": "true",
|
||||||
|
"data-pc-section": "firstfocusableelement"
|
||||||
|
});
|
||||||
|
|
||||||
|
return dialog_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildGuiFrameCustomHeader(dialogId, customHeader, content, owner) {
|
||||||
|
const dialog_mask = $el("div.p-dialog-mask.p-overlay-mask.p-overlay-mask-enter", {
|
||||||
|
parent: document.body,
|
||||||
|
style: {
|
||||||
|
position: "fixed",
|
||||||
|
height: "100%",
|
||||||
|
width: "100%",
|
||||||
|
left: "0px",
|
||||||
|
top: "0px",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
pointerEvents: "auto",
|
||||||
|
zIndex: "1000"
|
||||||
|
},
|
||||||
|
onclick: (e) => {
|
||||||
|
if (e.target === dialog_mask) {
|
||||||
|
owner.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// data-pc-section="mask"
|
||||||
|
});
|
||||||
|
|
||||||
|
const header_actions = $el("div.p-dialog-header-actions", {
|
||||||
|
// [TODO]
|
||||||
|
// data-pc-section="headeractions"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const close_button = $el("button.p-button.p-component.p-button-icon-only.p-button-secondary.p-button-rounded.p-button-text.p-dialog-close-button", {
|
||||||
|
parent: header_actions,
|
||||||
|
type: "button",
|
||||||
|
ariaLabel: "Close",
|
||||||
|
onclick: () => owner.close(),
|
||||||
|
// "data-pc-name": "pcclosebutton",
|
||||||
|
// "data-p-disabled": "false",
|
||||||
|
// "data-p-severity": "secondary",
|
||||||
|
// "data-pc-group-section": "headericon",
|
||||||
|
// "data-pc-extend": "button",
|
||||||
|
// "data-pc-section": "root",
|
||||||
|
// [FIXME] Not sure how to do most of the SVG using $el
|
||||||
|
innerHTML: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" class="p-icon p-button-icon" aria-hidden="true"><path d="M8.01186 7.00933L12.27 2.75116C12.341 2.68501 12.398 2.60524 12.4375 2.51661C12.4769 2.42798 12.4982 2.3323 12.4999 2.23529C12.5016 2.13827 12.4838 2.0419 12.4474 1.95194C12.4111 1.86197 12.357 1.78024 12.2884 1.71163C12.2198 1.64302 12.138 1.58893 12.0481 1.55259C11.9581 1.51625 11.8617 1.4984 11.7647 1.50011C11.6677 1.50182 11.572 1.52306 11.4834 1.56255C11.3948 1.60204 11.315 1.65898 11.2488 1.72997L6.99067 5.98814L2.7325 1.72997C2.59553 1.60234 2.41437 1.53286 2.22718 1.53616C2.03999 1.53946 1.8614 1.61529 1.72901 1.74767C1.59663 1.88006 1.5208 2.05865 1.5175 2.24584C1.5142 2.43303 1.58368 2.61419 1.71131 2.75116L5.96948 7.00933L1.71131 11.2675C1.576 11.403 1.5 11.5866 1.5 11.7781C1.5 11.9696 1.576 12.1532 1.71131 12.2887C1.84679 12.424 2.03043 12.5 2.2219 12.5C2.41338 12.5 2.59702 12.424 2.7325 12.2887L6.99067 8.03052L11.2488 12.2887C11.3843 12.424 11.568 12.5 11.7594 12.5C11.9509 12.5 12.1346 12.424 12.27 12.2887C12.4053 12.1532 12.4813 11.9696 12.4813 11.7781C12.4813 11.5866 12.4053 11.403 12.27 11.2675L8.01186 7.00933Z" fill="currentColor"></path></svg><span class="p-button-label" data-pc-section="label"> </span><!---->'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const _customHeader = normalizeContent(customHeader);
|
||||||
|
const dialog_header = $el("div.p-dialog-header",
|
||||||
|
[
|
||||||
|
$el("div", [
|
||||||
|
$el("div",
|
||||||
|
{
|
||||||
|
id: "frame-title-container",
|
||||||
|
},
|
||||||
|
Array.isArray(_customHeader) ? _customHeader : [_customHeader]
|
||||||
|
)
|
||||||
|
]),
|
||||||
|
header_actions
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const contentFrame = $el("div.p-dialog-content", {}, normalizeContent(content));
|
||||||
|
const manager_dialog = $el("div.p-dialog.p-component.global-dialog", {
|
||||||
|
id: dialogId,
|
||||||
|
parent: dialog_mask,
|
||||||
|
style: {
|
||||||
|
'display': 'flex',
|
||||||
|
'flex-direction': 'column',
|
||||||
|
'pointer-events': 'auto',
|
||||||
|
'margin': '0px',
|
||||||
|
},
|
||||||
|
role: 'dialog',
|
||||||
|
ariaModal: 'true',
|
||||||
|
// [TODO]
|
||||||
|
// ariaLabbelledby: 'cm-title',
|
||||||
|
// maximized: 'false',
|
||||||
|
// data-pc-name: 'dialog',
|
||||||
|
// data-pc-section: 'root',
|
||||||
|
// data-pd-focustrap: 'true'
|
||||||
|
},
|
||||||
|
[ dialog_header, contentFrame ]
|
||||||
|
);
|
||||||
|
|
||||||
|
const hidden_accessible = $el("span.p-hidden-accessible.p-hidden-focusable", {
|
||||||
|
parent: manager_dialog,
|
||||||
|
tabindex: "0",
|
||||||
|
role: "presentation",
|
||||||
|
ariaHidden: "true",
|
||||||
|
"data-p-hidden-accessible": "true",
|
||||||
|
"data-p-hidden-focusable": "true",
|
||||||
|
"data-pc-section": "firstfocusableelement"
|
||||||
|
});
|
||||||
|
|
||||||
|
return dialog_mask;
|
||||||
|
}
|
||||||
@ -14,12 +14,13 @@ import { OpenArtShareDialog } from "./comfyui-share-openart.js";
|
|||||||
import {
|
import {
|
||||||
free_models, install_pip, install_via_git_url, manager_instance,
|
free_models, install_pip, install_via_git_url, manager_instance,
|
||||||
rebootAPI, setManagerInstance, show_message, customAlert, customPrompt,
|
rebootAPI, setManagerInstance, show_message, customAlert, customPrompt,
|
||||||
infoToast, showTerminal, setNeedRestart
|
infoToast, showTerminal, setNeedRestart, handle403Response
|
||||||
} from "./common.js";
|
} from "./common.js";
|
||||||
import { ComponentBuilderDialog, getPureName, load_components, set_component_policy } from "./components-manager.js";
|
import { ComponentBuilderDialog, getPureName, load_components, set_component_policy } from "./components-manager.js";
|
||||||
import { CustomNodesManager } from "./custom-nodes-manager.js";
|
import { CustomNodesManager } from "./custom-nodes-manager.js";
|
||||||
import { ModelManager } from "./model-manager.js";
|
import { ModelManager } from "./model-manager.js";
|
||||||
import { SnapshotManager } from "./snapshot.js";
|
import { SnapshotManager } from "./snapshot.js";
|
||||||
|
import { buildGuiFrame, createSettingsCombo } from "./comfyui-gui-builder.js";
|
||||||
|
|
||||||
let manager_version = await getVersion();
|
let manager_version = await getVersion();
|
||||||
|
|
||||||
@ -44,12 +45,16 @@ docStyle.innerHTML = `
|
|||||||
|
|
||||||
#cm-manager-dialog {
|
#cm-manager-dialog {
|
||||||
width: 1000px;
|
width: 1000px;
|
||||||
height: 455px;
|
height: auto;
|
||||||
box-sizing: content-box;
|
box-sizing: content-box;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#cm-manager-dialog br {
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.cb-widget {
|
.cb-widget {
|
||||||
width: 400px;
|
width: 400px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
@ -80,6 +85,7 @@ docStyle.innerHTML = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cm-menu-container {
|
.cm-menu-container {
|
||||||
|
padding : calc(var(--spacing)*2);
|
||||||
column-gap: 20px;
|
column-gap: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@ -140,8 +146,8 @@ docStyle.innerHTML = `
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cm-notice-board {
|
.cm-notice-board {
|
||||||
width: 290px;
|
width: auto;
|
||||||
height: 230px;
|
height: 280px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
color: var(--input-text);
|
color: var(--input-text);
|
||||||
border: 1px solid var(--descrip-text);
|
border: 1px solid var(--descrip-text);
|
||||||
@ -238,68 +244,50 @@ var is_updating = false;
|
|||||||
// copied style from https://github.com/pythongosssss/ComfyUI-Custom-Scripts
|
// copied style from https://github.com/pythongosssss/ComfyUI-Custom-Scripts
|
||||||
const style = `
|
const style = `
|
||||||
#workflowgallery-button {
|
#workflowgallery-button {
|
||||||
width: 310px;
|
height: 50px;
|
||||||
height: 27px;
|
|
||||||
padding: 0px !important;
|
padding: 0px !important;
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: 17px !important;
|
|
||||||
}
|
}
|
||||||
#cm-nodeinfo-button {
|
#cm-nodeinfo-button {
|
||||||
width: 310px;
|
|
||||||
height: 27px;
|
|
||||||
padding: 0px !important;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: 17px !important;
|
|
||||||
}
|
}
|
||||||
#cm-manual-button {
|
#cm-manual-button {
|
||||||
width: 310px;
|
|
||||||
height: 27px;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-button {
|
.cm-button {
|
||||||
width: 310px;
|
width: auto;
|
||||||
height: 30px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-size: 17px !important;
|
background-color: var(--comfy-menu-secondary-bg);
|
||||||
|
border-color: var(--border-color);
|
||||||
|
color: var(--input-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-button:hover {
|
||||||
|
filter: brightness(125%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-button-red {
|
.cm-button-red {
|
||||||
width: 310px;
|
|
||||||
height: 30px;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: 17px !important;
|
|
||||||
background-color: #500000 !important;
|
background-color: #500000 !important;
|
||||||
|
border-color: #88181b !important;
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cm-button-red:hover {
|
||||||
|
background-color: #88181b !important;
|
||||||
|
}
|
||||||
|
|
||||||
.cm-button-orange {
|
.cm-button-orange {
|
||||||
width: 310px;
|
|
||||||
height: 30px;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: 17px !important;
|
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background-color: orange !important;
|
background-color: orange !important;
|
||||||
color: black !important;
|
color: black !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-experimental-button {
|
.cm-experimental-button {
|
||||||
width: 290px;
|
width: 100%;
|
||||||
height: 30px;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: 17px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-experimental {
|
.cm-experimental {
|
||||||
width: 310px;
|
|
||||||
border: 1px solid #555;
|
border: 1px solid #555;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@ -326,8 +314,14 @@ const style = `
|
|||||||
|
|
||||||
.cm-menu-combo {
|
.cm-menu-combo {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
width: 310px;
|
padding: 0.5em 0.5em;
|
||||||
box-sizing: border-box;
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--comfy-menu-secondary-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cm-menu-combo:hover {
|
||||||
|
filter: brightness(125%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cm-small-button {
|
.cm-small-button {
|
||||||
@ -753,9 +747,9 @@ async function onQueueStatus(event) {
|
|||||||
|
|
||||||
const rebootButton = document.getElementById('cm-reboot-button5');
|
const rebootButton = document.getElementById('cm-reboot-button5');
|
||||||
rebootButton?.addEventListener("click",
|
rebootButton?.addEventListener("click",
|
||||||
function() {
|
async function() {
|
||||||
if(rebootAPI()) {
|
if(await rebootAPI()) {
|
||||||
manager_dialog.close();
|
manager_instance.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -780,8 +774,13 @@ async function updateAll(update_comfyui) {
|
|||||||
|
|
||||||
const response = await api.fetchApi(`/manager/queue/update_all?mode=${mode}`);
|
const response = await api.fetchApi(`/manager/queue/update_all?mode=${mode}`);
|
||||||
|
|
||||||
if (response.status == 401) {
|
if (response.status == 403) {
|
||||||
|
await handle403Response(response);
|
||||||
|
reset_action_buttons();
|
||||||
|
}
|
||||||
|
else if (response.status == 401) {
|
||||||
customAlert('Another task is already in progress. Please stop the ongoing task first.');
|
customAlert('Another task is already in progress. Please stop the ongoing task first.');
|
||||||
|
reset_action_buttons();
|
||||||
}
|
}
|
||||||
else if(response.status == 200) {
|
else if(response.status == 200) {
|
||||||
is_updating = true;
|
is_updating = true;
|
||||||
@ -826,7 +825,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
const isElectron = 'electronAPI' in window;
|
const isElectron = 'electronAPI' in window;
|
||||||
|
|
||||||
update_comfyui_button =
|
update_comfyui_button =
|
||||||
$el("button.cm-button", {
|
$el("button.p-button.p-component.cm-button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Update ComfyUI",
|
textContent: "Update ComfyUI",
|
||||||
style: {
|
style: {
|
||||||
@ -837,7 +836,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
});
|
});
|
||||||
|
|
||||||
switch_comfyui_button =
|
switch_comfyui_button =
|
||||||
$el("button.cm-button", {
|
$el("button.p-button.p-component.cm-button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Switch ComfyUI",
|
textContent: "Switch ComfyUI",
|
||||||
style: {
|
style: {
|
||||||
@ -848,7 +847,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
});
|
});
|
||||||
|
|
||||||
restart_stop_button =
|
restart_stop_button =
|
||||||
$el("button.cm-button-red", {
|
$el("button.p-button.p-component.cm-button-red", {
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Restart",
|
textContent: "Restart",
|
||||||
onclick: () => restartOrStop()
|
onclick: () => restartOrStop()
|
||||||
@ -856,7 +855,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
|
|
||||||
if(isElectron) {
|
if(isElectron) {
|
||||||
update_all_button =
|
update_all_button =
|
||||||
$el("button.cm-button", {
|
$el("button.p-button.p-component.cm-button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Update All Custom Nodes",
|
textContent: "Update All Custom Nodes",
|
||||||
onclick:
|
onclick:
|
||||||
@ -865,7 +864,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
update_all_button =
|
update_all_button =
|
||||||
$el("button.cm-button", {
|
$el("button.p-button.p-component.cm-button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Update All",
|
textContent: "Update All",
|
||||||
onclick:
|
onclick:
|
||||||
@ -875,7 +874,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
|
|
||||||
const res =
|
const res =
|
||||||
[
|
[
|
||||||
$el("button.cm-button", {
|
$el("button.p-button.p-component.cm-button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Custom Nodes Manager",
|
textContent: "Custom Nodes Manager",
|
||||||
onclick:
|
onclick:
|
||||||
@ -887,7 +886,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
$el("button.cm-button", {
|
$el("button.p-button.p-component.cm-button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Install Missing Custom Nodes",
|
textContent: "Install Missing Custom Nodes",
|
||||||
onclick:
|
onclick:
|
||||||
@ -899,7 +898,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
$el("button.cm-button", {
|
$el("button.p-button.p-component.cm-button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Custom Nodes In Workflow",
|
textContent: "Custom Nodes In Workflow",
|
||||||
onclick:
|
onclick:
|
||||||
@ -911,8 +910,8 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
$el("br", {}, []),
|
$el("div", {}, []),
|
||||||
$el("button.cm-button", {
|
$el("button.p-button.p-component.cm-button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Model Manager",
|
textContent: "Model Manager",
|
||||||
onclick:
|
onclick:
|
||||||
@ -924,7 +923,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
$el("button.cm-button", {
|
$el("button.p-button.p-component.cm-button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Install via Git URL",
|
textContent: "Install via Git URL",
|
||||||
onclick: async () => {
|
onclick: async () => {
|
||||||
@ -936,13 +935,13 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
$el("br", {}, []),
|
$el("div", {}, []),
|
||||||
update_all_button,
|
update_all_button,
|
||||||
update_comfyui_button,
|
update_comfyui_button,
|
||||||
switch_comfyui_button,
|
switch_comfyui_button,
|
||||||
// fetch_updates_button,
|
// fetch_updates_button,
|
||||||
|
|
||||||
$el("br", {}, []),
|
$el("div", {}, []),
|
||||||
restart_stop_button,
|
restart_stop_button,
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -955,12 +954,13 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
let self = this;
|
let self = this;
|
||||||
|
|
||||||
// db mode
|
// db mode
|
||||||
|
|
||||||
this.datasrc_combo = document.createElement("select");
|
this.datasrc_combo = document.createElement("select");
|
||||||
this.datasrc_combo.setAttribute("title", "Configure where to retrieve node/model information. If set to 'local,' the channel is ignored, and if set to 'channel (remote),' it fetches the latest information each time the list is opened.");
|
this.datasrc_combo.setAttribute("title", "Configure where to retrieve node/model information. If set to 'local,' the channel is ignored, and if set to 'channel (remote),' it fetches the latest information each time the list is opened.");
|
||||||
this.datasrc_combo.className = "cm-menu-combo";
|
this.datasrc_combo.className = "cm-menu-combo p-select p-component p-inputwrapper p-inputwrapper-filled ";
|
||||||
this.datasrc_combo.appendChild($el('option', { value: 'cache', text: 'DB: Channel (1day cache)' }, []));
|
this.datasrc_combo.appendChild($el('option', { value: 'cache', text: 'Channel (1day cache)' }, []));
|
||||||
this.datasrc_combo.appendChild($el('option', { value: 'local', text: 'DB: Local' }, []));
|
this.datasrc_combo.appendChild($el('option', { value: 'local', text: 'Local' }, []));
|
||||||
this.datasrc_combo.appendChild($el('option', { value: 'remote', text: 'DB: Channel (remote)' }, []));
|
this.datasrc_combo.appendChild($el('option', { value: 'remote', text: 'Channel (remote)' }, []));
|
||||||
|
|
||||||
api.fetchApi('/manager/db_mode')
|
api.fetchApi('/manager/db_mode')
|
||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
@ -970,27 +970,110 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
api.fetchApi(`/manager/db_mode?value=${event.target.value}`);
|
api.fetchApi(`/manager/db_mode?value=${event.target.value}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const dbRetrievalSetttingItem = createSettingsCombo("DB", this.datasrc_combo);
|
||||||
|
|
||||||
// preview method
|
// preview method
|
||||||
let preview_combo = document.createElement("select");
|
let preview_combo = document.createElement("select");
|
||||||
preview_combo.setAttribute("title", "Configure how latent variables will be decoded during preview in the sampling process.");
|
preview_combo.setAttribute("title", "Configure how latent variables will be decoded during preview in the sampling process.");
|
||||||
preview_combo.className = "cm-menu-combo";
|
preview_combo.className = "cm-menu-combo p-select p-component p-inputwrapper p-inputwrapper-filled";
|
||||||
preview_combo.appendChild($el('option', { value: 'auto', text: 'Preview method: Auto' }, []));
|
|
||||||
preview_combo.appendChild($el('option', { value: 'taesd', text: 'Preview method: TAESD (slow)' }, []));
|
|
||||||
preview_combo.appendChild($el('option', { value: 'latent2rgb', text: 'Preview method: Latent2RGB (fast)' }, []));
|
|
||||||
preview_combo.appendChild($el('option', { value: 'none', text: 'Preview method: None (very fast)' }, []));
|
|
||||||
|
|
||||||
|
// Loading state to prevent flash of enabled state
|
||||||
|
preview_combo.appendChild($el('option', { value: '', text: 'Loading...', disabled: true }, []));
|
||||||
|
preview_combo.appendChild($el('option', { value: 'auto', text: 'Auto' }, []));
|
||||||
|
preview_combo.appendChild($el('option', { value: 'taesd', text: 'TAESD (slow)' }, []));
|
||||||
|
preview_combo.appendChild($el('option', { value: 'latent2rgb', text: 'Latent2RGB (fast)' }, []));
|
||||||
|
preview_combo.appendChild($el('option', { value: 'none', text: 'None (very fast)' }, []));
|
||||||
|
|
||||||
|
// Start disabled to prevent flash
|
||||||
|
preview_combo.disabled = true;
|
||||||
|
preview_combo.value = '';
|
||||||
|
|
||||||
|
// Fetch current state
|
||||||
api.fetchApi('/manager/preview_method')
|
api.fetchApi('/manager/preview_method')
|
||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
.then(data => { preview_combo.value = data; });
|
.then(data => {
|
||||||
|
// Remove loading option
|
||||||
|
preview_combo.querySelector('option[value=""]')?.remove();
|
||||||
|
|
||||||
|
if (data === "DISABLED") {
|
||||||
|
// ComfyUI per-queue preview feature is active
|
||||||
|
preview_combo.disabled = true;
|
||||||
|
preview_combo.value = 'auto';
|
||||||
|
|
||||||
|
// Accessibility attributes
|
||||||
|
preview_combo.setAttribute("aria-disabled", "true");
|
||||||
|
preview_combo.setAttribute("aria-label",
|
||||||
|
"Preview method setting (disabled - managed by ComfyUI). " +
|
||||||
|
"Use Settings > Execution > Live preview method instead."
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tooltip for mouse users
|
||||||
|
preview_combo.setAttribute("title",
|
||||||
|
"This feature is now provided natively by ComfyUI. " +
|
||||||
|
"Please use 'Settings > Execution > Live preview method' instead."
|
||||||
|
);
|
||||||
|
|
||||||
|
// Visual feedback
|
||||||
|
preview_combo.style.opacity = '0.6';
|
||||||
|
preview_combo.style.cursor = 'not-allowed';
|
||||||
|
} else {
|
||||||
|
// Manager feature is active
|
||||||
|
preview_combo.disabled = false;
|
||||||
|
preview_combo.value = data;
|
||||||
|
|
||||||
|
// Accessibility for enabled state
|
||||||
|
preview_combo.setAttribute("aria-label",
|
||||||
|
"Preview method setting. Select how latent variables are decoded during preview."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('[ComfyUI-Manager] Failed to fetch preview method status:', error);
|
||||||
|
// Error recovery: fallback to enabled
|
||||||
|
preview_combo.querySelector('option[value=""]')?.remove();
|
||||||
|
preview_combo.disabled = false;
|
||||||
|
preview_combo.value = 'auto';
|
||||||
|
});
|
||||||
|
|
||||||
preview_combo.addEventListener('change', function (event) {
|
preview_combo.addEventListener('change', function (event) {
|
||||||
api.fetchApi(`/manager/preview_method?value=${event.target.value}`);
|
// Ignore if disabled
|
||||||
|
if (preview_combo.disabled) {
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normal operation
|
||||||
|
api.fetchApi(`/manager/preview_method?value=${event.target.value}`)
|
||||||
|
.then(response => {
|
||||||
|
if (response.status === 403) {
|
||||||
|
// Feature transitioned to native
|
||||||
|
alert(
|
||||||
|
'This feature is now provided natively by ComfyUI.\n' +
|
||||||
|
'Please use \'Settings > Execution > Live preview method\' instead.'
|
||||||
|
);
|
||||||
|
preview_combo.disabled = true;
|
||||||
|
preview_combo.style.opacity = '0.6';
|
||||||
|
preview_combo.style.cursor = 'not-allowed';
|
||||||
|
|
||||||
|
// Update aria attributes
|
||||||
|
preview_combo.setAttribute("aria-disabled", "true");
|
||||||
|
preview_combo.setAttribute("aria-label",
|
||||||
|
"Preview method setting (disabled - managed by ComfyUI). " +
|
||||||
|
"Use Settings > Execution > Live preview method instead."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('[ComfyUI-Manager] Preview method update failed:', error);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const previewSetttingItem = createSettingsCombo("Preview method", preview_combo);
|
||||||
|
|
||||||
// channel
|
// channel
|
||||||
let channel_combo = document.createElement("select");
|
let channel_combo = document.createElement("select");
|
||||||
channel_combo.setAttribute("title", "Configure the channel for retrieving data from the Custom Node list (including missing nodes) or the Model list.");
|
channel_combo.setAttribute("title", "Configure the channel for retrieving data from the Custom Node list (including missing nodes) or the Model list.");
|
||||||
channel_combo.className = "cm-menu-combo";
|
channel_combo.className = "cm-menu-combo p-select p-component p-inputwrapper p-inputwrapper-filled";
|
||||||
api.fetchApi('/manager/channel_url_list')
|
api.fetchApi('/manager/channel_url_list')
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(async data => {
|
.then(async data => {
|
||||||
@ -999,7 +1082,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
for (let i in urls) {
|
for (let i in urls) {
|
||||||
if (urls[i] != '') {
|
if (urls[i] != '') {
|
||||||
let name_url = urls[i].split('::');
|
let name_url = urls[i].split('::');
|
||||||
channel_combo.appendChild($el('option', { value: name_url[0], text: `Channel: ${name_url[0]}` }, []));
|
channel_combo.appendChild($el('option', { value: name_url[0], text: `${name_url[0]}` }, []));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1014,11 +1097,13 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const channelSetttingItem = createSettingsCombo("Channel", channel_combo);
|
||||||
|
|
||||||
|
|
||||||
// share
|
// share
|
||||||
let share_combo = document.createElement("select");
|
let share_combo = document.createElement("select");
|
||||||
share_combo.setAttribute("title", "Hide the share button in the main menu or set the default action upon clicking it. Additionally, configure the default share site when sharing via the context menu's share button.");
|
share_combo.setAttribute("title", "Hide the share button in the main menu or set the default action upon clicking it. Additionally, configure the default share site when sharing via the context menu's share button.");
|
||||||
share_combo.className = "cm-menu-combo";
|
share_combo.className = "cm-menu-combo p-select p-component p-inputwrapper p-inputwrapper-filled";
|
||||||
const share_options = [
|
const share_options = [
|
||||||
['none', 'None'],
|
['none', 'None'],
|
||||||
['openart', 'OpenArt AI'],
|
['openart', 'OpenArt AI'],
|
||||||
@ -1029,7 +1114,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
['all', 'All'],
|
['all', 'All'],
|
||||||
];
|
];
|
||||||
for (const option of share_options) {
|
for (const option of share_options) {
|
||||||
share_combo.appendChild($el('option', { value: option[0], text: `Share: ${option[1]}` }, []));
|
share_combo.appendChild($el('option', { value: option[0], text: `${option[1]}` }, []));
|
||||||
}
|
}
|
||||||
|
|
||||||
api.fetchApi('/manager/share_option')
|
api.fetchApi('/manager/share_option')
|
||||||
@ -1051,12 +1136,14 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const shareSetttingItem = createSettingsCombo("Share", share_combo);
|
||||||
|
|
||||||
let component_policy_combo = document.createElement("select");
|
let component_policy_combo = document.createElement("select");
|
||||||
component_policy_combo.setAttribute("title", "When loading the workflow, configure which version of the component to use.");
|
component_policy_combo.setAttribute("title", "When loading the workflow, configure which version of the component to use.");
|
||||||
component_policy_combo.className = "cm-menu-combo";
|
component_policy_combo.className = "cm-menu-combo p-select p-component p-inputwrapper p-inputwrapper-filled";
|
||||||
component_policy_combo.appendChild($el('option', { value: 'workflow', text: 'Component: Use workflow version' }, []));
|
component_policy_combo.appendChild($el('option', { value: 'workflow', text: 'Use workflow version' }, []));
|
||||||
component_policy_combo.appendChild($el('option', { value: 'higher', text: 'Component: Use higher version' }, []));
|
component_policy_combo.appendChild($el('option', { value: 'higher', text: 'Use higher version' }, []));
|
||||||
component_policy_combo.appendChild($el('option', { value: 'mine', text: 'Component: Use my version' }, []));
|
component_policy_combo.appendChild($el('option', { value: 'mine', text: 'Use my version' }, []));
|
||||||
api.fetchApi('/manager/policy/component')
|
api.fetchApi('/manager/policy/component')
|
||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@ -1069,15 +1156,14 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
set_component_policy(event.target.value);
|
set_component_policy(event.target.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const componentSetttingItem = createSettingsCombo("Component", component_policy_combo);
|
||||||
|
|
||||||
update_policy_combo = document.createElement("select");
|
update_policy_combo = document.createElement("select");
|
||||||
|
|
||||||
if(isElectron)
|
|
||||||
update_policy_combo.style.display = 'none';
|
|
||||||
|
|
||||||
update_policy_combo.setAttribute("title", "Sets the policy to be applied when performing an update.");
|
update_policy_combo.setAttribute("title", "Sets the policy to be applied when performing an update.");
|
||||||
update_policy_combo.className = "cm-menu-combo";
|
update_policy_combo.className = "cm-menu-combo p-select p-component p-inputwrapper p-inputwrapper-filled";
|
||||||
update_policy_combo.appendChild($el('option', { value: 'stable-comfyui', text: 'Update: ComfyUI Stable Version' }, []));
|
update_policy_combo.appendChild($el('option', { value: 'stable-comfyui', text: 'ComfyUI Stable Version' }, []));
|
||||||
update_policy_combo.appendChild($el('option', { value: 'nightly-comfyui', text: 'Update: ComfyUI Nightly Version' }, []));
|
update_policy_combo.appendChild($el('option', { value: 'nightly-comfyui', text: 'ComfyUI Nightly Version' }, []));
|
||||||
api.fetchApi('/manager/policy/update')
|
api.fetchApi('/manager/policy/update')
|
||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
.then(data => {
|
.then(data => {
|
||||||
@ -1088,20 +1174,22 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
api.fetchApi(`/manager/policy/update?value=${event.target.value}`);
|
api.fetchApi(`/manager/policy/update?value=${event.target.value}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
return [
|
const updateSetttingItem = createSettingsCombo("Update", update_policy_combo);
|
||||||
$el("br", {}, []),
|
|
||||||
this.datasrc_combo,
|
|
||||||
channel_combo,
|
|
||||||
preview_combo,
|
|
||||||
share_combo,
|
|
||||||
component_policy_combo,
|
|
||||||
update_policy_combo,
|
|
||||||
$el("br", {}, []),
|
|
||||||
|
|
||||||
$el("br", {}, []),
|
if(isElectron)
|
||||||
$el("filedset.cm-experimental", {}, [
|
updateSetttingItem.style.display = 'none';
|
||||||
|
|
||||||
|
return [
|
||||||
|
dbRetrievalSetttingItem,
|
||||||
|
channelSetttingItem,
|
||||||
|
previewSetttingItem,
|
||||||
|
shareSetttingItem,
|
||||||
|
componentSetttingItem,
|
||||||
|
updateSetttingItem,
|
||||||
|
//[TODO] replace mt-2 with wrapper div with flex column gap
|
||||||
|
$el("filedset.cm-experimental.mt-auto", {}, [
|
||||||
$el("legend.cm-experimental-legend", {}, ["EXPERIMENTAL"]),
|
$el("legend.cm-experimental-legend", {}, ["EXPERIMENTAL"]),
|
||||||
$el("button.cm-experimental-button", {
|
$el("button.p-button.p-component.cm-button.cm-experimental-button", {
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Snapshot Manager",
|
textContent: "Snapshot Manager",
|
||||||
onclick:
|
onclick:
|
||||||
@ -1111,7 +1199,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
SnapshotManager.instance.show();
|
SnapshotManager.instance.show();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
$el("button.cm-experimental-button", {
|
$el("button.p-button.p-component.cm-button.cm-experimental-button.mt-2", {
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Install PIP packages",
|
textContent: "Install PIP packages",
|
||||||
onclick:
|
onclick:
|
||||||
@ -1129,7 +1217,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
|
|
||||||
createControlsRight() {
|
createControlsRight() {
|
||||||
const elts = [
|
const elts = [
|
||||||
$el("button.cm-button", {
|
$el("button.p-button.p-component.cm-button", {
|
||||||
id: 'cm-manual-button',
|
id: 'cm-manual-button',
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Community Manual",
|
textContent: "Community Manual",
|
||||||
@ -1180,11 +1268,11 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
|
|
||||||
$el("button", {
|
$el("button.p-button.p-component.cm-button", {
|
||||||
id: 'workflowgallery-button',
|
id: 'workflowgallery-button',
|
||||||
type: "button",
|
type: "button",
|
||||||
style: {
|
style: {
|
||||||
...(localStorage.getItem("wg_last_visited") ? {height: '50px'} : {})
|
// ...(localStorage.getItem("wg_last_visited") ? {height: '50px'} : {})
|
||||||
},
|
},
|
||||||
onclick: (e) => {
|
onclick: (e) => {
|
||||||
const last_visited_site = localStorage.getItem("wg_last_visited")
|
const last_visited_site = localStorage.getItem("wg_last_visited")
|
||||||
@ -1207,7 +1295,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
}, [
|
}, [
|
||||||
$el("p", {
|
$el("p", {
|
||||||
id: 'workflowgallery-button-last-visited-label',
|
id: 'workflowgallery-button-last-visited-label',
|
||||||
textContent: `(${localStorage.getItem("wg_last_visited") ? localStorage.getItem("wg_last_visited").split('/')[2] : ''})`,
|
textContent: `(${localStorage.getItem("wg_last_visited") ? localStorage.getItem("wg_last_visited").split('/')[2] : 'none selected'})`,
|
||||||
style: {
|
style: {
|
||||||
'text-align': 'center',
|
'text-align': 'center',
|
||||||
'color': 'var(--input-text)',
|
'color': 'var(--input-text)',
|
||||||
@ -1223,13 +1311,12 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
|
|
||||||
$el("button.cm-button", {
|
$el("button.p-button.p-component.cm-button", {
|
||||||
id: 'cm-nodeinfo-button',
|
id: 'cm-nodeinfo-button',
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Nodes Info",
|
textContent: "Nodes Info",
|
||||||
onclick: () => { window.open("https://ltdrdata.github.io/", "comfyui-node-info"); }
|
onclick: () => { window.open("https://ltdrdata.github.io/", "comfyui-node-info"); }
|
||||||
}),
|
}),
|
||||||
$el("br", {}, []),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
var textarea = document.createElement("div");
|
var textarea = document.createElement("div");
|
||||||
@ -1244,31 +1331,23 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
const close_button = $el("button", { id: "cm-close-button", type: "button", textContent: "Close", onclick: () => this.close() });
|
const content = $el("div.cm-menu-container",
|
||||||
|
|
||||||
const content =
|
|
||||||
$el("div.comfy-modal-content",
|
|
||||||
[
|
[
|
||||||
$el("tr.cm-title", {}, [
|
$el("div.cm-menu-column.gap-2", [...this.createControlsLeft()]),
|
||||||
$el("font", {size:6, color:"white"}, [`ComfyUI Manager ${manager_version}`])]
|
$el("div.cm-menu-column.gap-2", [...this.createControlsMid()]),
|
||||||
),
|
$el("div.cm-menu-column.gap-2", [...this.createControlsRight()])
|
||||||
$el("br", {}, []),
|
|
||||||
$el("div.cm-menu-container",
|
|
||||||
[
|
|
||||||
$el("div.cm-menu-column", [...this.createControlsLeft()]),
|
|
||||||
$el("div.cm-menu-column", [...this.createControlsMid()]),
|
|
||||||
$el("div.cm-menu-column", [...this.createControlsRight()])
|
|
||||||
]),
|
|
||||||
|
|
||||||
$el("br", {}, []),
|
|
||||||
close_button,
|
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
content.style.width = '100%';
|
const frame = buildGuiFrame(
|
||||||
content.style.height = '100%';
|
'cm-manager-dialog', // dialog id
|
||||||
|
`ComfyUI Manager ${manager_version}`, // dialog title
|
||||||
|
"i.mdi.mdi-puzzle", // dialog icon class to show before title
|
||||||
|
content, // dialog content element
|
||||||
|
this
|
||||||
|
); // send this so we can attach close functions
|
||||||
|
|
||||||
this.element = $el("div.comfy-modal", { id:'cm-manager-dialog', parent: document.body }, [ content ]);
|
this.element = frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isVisible() {
|
get isVisible() {
|
||||||
@ -1276,7 +1355,7 @@ class ManagerMenuDialog extends ComfyDialog {
|
|||||||
}
|
}
|
||||||
|
|
||||||
show() {
|
show() {
|
||||||
this.element.style.display = "block";
|
this.element.style.display = "flex";
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleVisibility() {
|
toggleVisibility() {
|
||||||
@ -1453,6 +1532,31 @@ app.registerExtension({
|
|||||||
|
|
||||||
load_components();
|
load_components();
|
||||||
|
|
||||||
|
// Fetch and show startup alerts (critical errors like outdated ComfyUI)
|
||||||
|
// Poll until extensionManager.toast is ready (set in Vue onMounted)
|
||||||
|
const showStartupAlerts = async () => {
|
||||||
|
let toastWaitCount = 0;
|
||||||
|
const waitForToast = () => {
|
||||||
|
if (window['app']?.extensionManager?.toast) {
|
||||||
|
fetch('/manager/startup_alerts')
|
||||||
|
.then(response => response.ok ? response.json() : [])
|
||||||
|
.then(alerts => {
|
||||||
|
for (const alert of alerts) {
|
||||||
|
customAlert(alert.message);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(e => console.warn('[ComfyUI-Manager] Failed to fetch startup alerts:', e));
|
||||||
|
} else if (toastWaitCount < 300) { // Max 30 seconds (300 * 100ms)
|
||||||
|
toastWaitCount++;
|
||||||
|
setTimeout(waitForToast, 100);
|
||||||
|
} else {
|
||||||
|
console.warn('[ComfyUI-Manager] Timeout waiting for toast. Startup alerts skipped.');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
waitForToast();
|
||||||
|
};
|
||||||
|
showStartupAlerts();
|
||||||
|
|
||||||
const menu = document.querySelector(".comfy-menu");
|
const menu = document.querySelector(".comfy-menu");
|
||||||
const separator = document.createElement("hr");
|
const separator = document.createElement("hr");
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
@ -375,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",
|
||||||
{
|
{
|
||||||
@ -395,6 +397,7 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
marginLeft: "5px",
|
marginLeft: "5px",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
|
position: "relative",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@ -408,8 +411,18 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
color: "#fff",
|
color: "#fff",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
["Price US$"]
|
["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,
|
this.LockInput,
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
@ -429,9 +442,7 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[
|
[$el("span", { style: { marginLeft: "5px" } }, ["OFF"])]
|
||||||
$el("span", { style: { marginLeft: "5px" } }, ["OFF"]),
|
|
||||||
]
|
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
@ -440,7 +451,6 @@ export class CopusShareDialog extends ComfyDialog {
|
|||||||
"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.",
|
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|||||||
34
js/common.js
34
js/common.js
@ -100,6 +100,19 @@ export function show_message(msg) {
|
|||||||
app.ui.dialog.element.style.zIndex = 1100;
|
app.ui.dialog.element.style.zIndex = 1100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function handle403Response(res, defaultMessage) {
|
||||||
|
try {
|
||||||
|
const data = await res.json();
|
||||||
|
if(data.error === 'comfyui_outdated') {
|
||||||
|
show_message('ComfyUI version is outdated.<BR>Please update ComfyUI to use Manager normally.');
|
||||||
|
} else {
|
||||||
|
show_message(defaultMessage || 'This action is not allowed with this security level configuration.');
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
show_message(defaultMessage || 'This action is not allowed with this security level configuration.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function sleep(ms) {
|
export async function sleep(ms) {
|
||||||
return new Promise(resolve => setTimeout(resolve, ms));
|
return new Promise(resolve => setTimeout(resolve, ms));
|
||||||
}
|
}
|
||||||
@ -163,20 +176,23 @@ export async function customPrompt(title, message) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function rebootAPI() {
|
export async function rebootAPI() {
|
||||||
if ('electronAPI' in window) {
|
if ('electronAPI' in window) {
|
||||||
window.electronAPI.restartApp();
|
window.electronAPI.restartApp();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
customConfirm("Are you sure you'd like to reboot the server?").then((isConfirmed) => {
|
const isConfirmed = await customConfirm("Are you sure you'd like to reboot the server?");
|
||||||
if (isConfirmed) {
|
if (isConfirmed) {
|
||||||
try {
|
try {
|
||||||
api.fetchApi("/manager/reboot");
|
const response = await api.fetchApi("/manager/reboot");
|
||||||
|
if (response.status == 403) {
|
||||||
|
await handle403Response(response);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(exception) {}
|
catch(exception) {}
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -216,7 +232,7 @@ export async function install_pip(packages) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if(res.status == 403) {
|
if(res.status == 403) {
|
||||||
show_message('This action is not allowed with this security level configuration.');
|
await handle403Response(res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,7 +267,7 @@ export async function install_via_git_url(url, manager_dialog) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if(res.status == 403) {
|
if(res.status == 403) {
|
||||||
show_message('This action is not allowed with this security level configuration.');
|
await handle403Response(res);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -262,9 +278,9 @@ export async function install_via_git_url(url, manager_dialog) {
|
|||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
rebootButton.addEventListener("click",
|
rebootButton.addEventListener("click",
|
||||||
function() {
|
async function() {
|
||||||
if(rebootAPI()) {
|
if(await rebootAPI()) {
|
||||||
manager_dialog.close();
|
manager_instance.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
.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: 80vw;
|
||||||
height: 80%;
|
height: 75vh;
|
||||||
|
min-height: 30em;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
@ -10,6 +11,7 @@
|
|||||||
font-family: arial, sans-serif;
|
font-family: arial, sans-serif;
|
||||||
text-underline-offset: 3px;
|
text-underline-offset: 3px;
|
||||||
outline: none;
|
outline: none;
|
||||||
|
margin: calc(var(--spacing)*2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager .cn-flex-auto {
|
.cn-manager .cn-flex-auto {
|
||||||
@ -17,17 +19,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager button {
|
.cn-manager button {
|
||||||
|
width: auto;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: var(--input-text);
|
color: var(--input-text);
|
||||||
background-color: var(--comfy-input-bg);
|
background-color: var(--comfy-input-bg);
|
||||||
border-radius: 8px;
|
|
||||||
border-color: var(--border-color);
|
border-color: var(--border-color);
|
||||||
border-style: solid;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 4px 8px;
|
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cn-manager button:hover {
|
||||||
|
filter: brightness(125%);
|
||||||
|
}
|
||||||
|
|
||||||
.cn-manager button:disabled,
|
.cn-manager button:disabled,
|
||||||
.cn-manager input:disabled,
|
.cn-manager input:disabled,
|
||||||
.cn-manager select:disabled {
|
.cn-manager select:disabled {
|
||||||
@ -40,8 +46,13 @@
|
|||||||
|
|
||||||
.cn-manager .cn-manager-restart {
|
.cn-manager .cn-manager-restart {
|
||||||
display: none;
|
display: none;
|
||||||
background-color: #500000;
|
background-color: #500000 !important;
|
||||||
color: white;
|
border-color: #88181b !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager .cn-manager-restart:hover {
|
||||||
|
background-color: #88181b !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager .cn-manager-stop {
|
.cn-manager .cn-manager-stop {
|
||||||
@ -79,7 +90,6 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager-header label {
|
.cn-manager-header label {
|
||||||
@ -91,16 +101,32 @@
|
|||||||
.cn-manager-filter {
|
.cn-manager-filter {
|
||||||
height: 28px;
|
height: 28px;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5em 0.5em;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--comfy-input-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cn-manager-filter:hover {
|
||||||
|
filter: brightness(125%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager-keywords {
|
.cn-manager-keywords {
|
||||||
height: 28px;
|
height: 28px;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
padding: 0 5px 0 26px;
|
padding: 0 5px 0 26px;
|
||||||
|
background: var(--comfy-input-bg);
|
||||||
background-size: 16px;
|
background-size: 16px;
|
||||||
background-position: 5px center;
|
background-position: 5px center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20pointer-events%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23888%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m21%2021-4.486-4.494M19%2010.5a8.5%208.5%200%201%201-17%200%208.5%208.5%200%200%201%2017%200%22%2F%3E%3C%2Fsvg%3E");
|
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20pointer-events%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23888%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m21%2021-4.486-4.494M19%2010.5a8.5%208.5%200%201%201-17%200%208.5%208.5%200%200%201%2017%200%22%2F%3E%3C%2Fsvg%3E");
|
||||||
|
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
outline-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cn-manager-status {
|
.cn-manager-status {
|
||||||
@ -588,6 +614,10 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cn-install-buttons button {
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.cn-selected-buttons {
|
.cn-selected-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
||||||
import { api } from "../../scripts/api.js";
|
import { api } from "../../scripts/api.js";
|
||||||
|
import { buildGuiFrameCustomHeader, createSettingsCombo } from "./comfyui-gui-builder.js";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
manager_instance, rebootAPI, install_via_git_url,
|
manager_instance, rebootAPI, install_via_git_url,
|
||||||
fetchData, md5, icons, show_message, customConfirm, customAlert, customPrompt,
|
fetchData, md5, icons, show_message, customConfirm, customAlert, customPrompt,
|
||||||
sanitizeHTML, infoToast, showTerminal, setNeedRestart,
|
sanitizeHTML, infoToast, showTerminal, setNeedRestart,
|
||||||
storeColumnWidth, restoreColumnWidth, getTimeAgo, copyText, loadCss,
|
storeColumnWidth, restoreColumnWidth, getTimeAgo, copyText, loadCss,
|
||||||
showPopover, hidePopover
|
showPopover, hidePopover, handle403Response
|
||||||
} from "./common.js";
|
} from "./common.js";
|
||||||
|
|
||||||
// https://cenfun.github.io/turbogrid/api.html
|
// https://cenfun.github.io/turbogrid/api.html
|
||||||
@ -18,32 +19,19 @@ loadCss("./custom-nodes-manager.css");
|
|||||||
const gridId = "node";
|
const gridId = "node";
|
||||||
|
|
||||||
const pageHtml = `
|
const pageHtml = `
|
||||||
<div class="cn-manager-header">
|
<div class="cn-manager cn-manager-dark">
|
||||||
<label>Filter
|
|
||||||
<select class="cn-manager-filter"></select>
|
|
||||||
</label>
|
|
||||||
<input class="cn-manager-keywords" type="search" placeholder="Search" />
|
|
||||||
<div class="cn-manager-status"></div>
|
|
||||||
<div class="cn-flex-auto"></div>
|
|
||||||
<div class="cn-manager-channel"></div>
|
|
||||||
</div>
|
|
||||||
<div class="cn-manager-grid"></div>
|
<div class="cn-manager-grid"></div>
|
||||||
<div class="cn-manager-selection"></div>
|
<div class="cn-manager-selection"></div>
|
||||||
<div class="cn-manager-message"></div>
|
<div class="cn-manager-message"></div>
|
||||||
<div class="cn-manager-footer">
|
<div class="cn-manager-footer">
|
||||||
<button class="cn-manager-back">
|
<button class="cn-manager-restart p-button p-component">Restart</button>
|
||||||
<svg class="arrow-icon" width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<button class="cn-manager-stop p-button p-component">Stop</button>
|
||||||
<path d="M2 8H18M2 8L8 2M2 8L8 14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
Back
|
|
||||||
</button>
|
|
||||||
<button class="cn-manager-restart">Restart</button>
|
|
||||||
<button class="cn-manager-stop">Stop</button>
|
|
||||||
<div class="cn-flex-auto"></div>
|
<div class="cn-flex-auto"></div>
|
||||||
<button class="cn-manager-used-in-workflow">Used In Workflow</button>
|
<button class="cn-manager-used-in-workflow p-button p-component">Used In Workflow</button>
|
||||||
<button class="cn-manager-check-update">Check Update</button>
|
<button class="cn-manager-check-update p-button p-component">Check Update</button>
|
||||||
<button class="cn-manager-check-missing">Check Missing</button>
|
<button class="cn-manager-check-missing p-button p-component">Check Missing</button>
|
||||||
<button class="cn-manager-install-url">Install via Git URL</button>
|
<button class="cn-manager-install-url p-button p-component">Install via Git URL</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -89,11 +77,26 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.element = $el("div", {
|
const header = $el("div.cn-manager-header.px-2", {}, [
|
||||||
parent: document.body,
|
// $el("label", {}, [
|
||||||
className: "comfy-modal cn-manager"
|
// $el("span", { textContent: "Filter" }),
|
||||||
});
|
// $el("select.cn-manager-filter")
|
||||||
this.element.innerHTML = pageHtml;
|
// ]),
|
||||||
|
createSettingsCombo("Filter", $el("select.cn-manager-filter")),
|
||||||
|
$el("input.cn-manager-keywords.p-inputtext.p-component", { type: "search", placeholder: "Search" }),
|
||||||
|
$el("div.cn-manager-status"),
|
||||||
|
$el("div.cn-flex-auto"),
|
||||||
|
$el("div.cn-manager-channel")
|
||||||
|
]);
|
||||||
|
|
||||||
|
const frame = buildGuiFrameCustomHeader(
|
||||||
|
'cn-manager-dialog', // dialog id
|
||||||
|
header, // custom header element
|
||||||
|
pageHtml, // dialog content element
|
||||||
|
this
|
||||||
|
); // send this so we can attach close functions
|
||||||
|
|
||||||
|
this.element = frame;
|
||||||
this.element.setAttribute("tabindex", 0);
|
this.element.setAttribute("tabindex", 0);
|
||||||
this.element.focus();
|
this.element.focus();
|
||||||
|
|
||||||
@ -372,7 +375,7 @@ export class CustomNodesManager {
|
|||||||
|
|
||||||
return list.map(id => {
|
return list.map(id => {
|
||||||
const bt = buttons[id];
|
const bt = buttons[id];
|
||||||
return `<button class="cn-btn-${id}" group="${action}" mode="${bt.mode}">${bt.label}</button>`;
|
return `<button class="cn-btn-${id} p-button p-component" group="${action}" mode="${bt.mode}">${bt.label}</button>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -655,7 +658,6 @@ export class CustomNodesManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderGrid() {
|
renderGrid() {
|
||||||
|
|
||||||
// update theme
|
// update theme
|
||||||
const globalStyle = window.getComputedStyle(document.body);
|
const globalStyle = window.getComputedStyle(document.body);
|
||||||
this.colorVars = {
|
this.colorVars = {
|
||||||
@ -1528,7 +1530,16 @@ export class CustomNodesManager {
|
|||||||
errorMsg = `'${item.title}': `;
|
errorMsg = `'${item.title}': `;
|
||||||
|
|
||||||
if(res.status == 403) {
|
if(res.status == 403) {
|
||||||
|
try {
|
||||||
|
const data = await res.json();
|
||||||
|
if(data.error === 'comfyui_outdated') {
|
||||||
|
errorMsg += `ComfyUI version is outdated. Please update ComfyUI to use Manager normally.\n`;
|
||||||
|
} else {
|
||||||
errorMsg += `This action is not allowed with this security level configuration.\n`;
|
errorMsg += `This action is not allowed with this security level configuration.\n`;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
errorMsg += `This action is not allowed with this security level configuration.\n`;
|
||||||
|
}
|
||||||
} else if(res.status == 404) {
|
} else if(res.status == 404) {
|
||||||
errorMsg += `With the current security level configuration, only custom nodes from the <B>"default channel"</B> can be installed.\n`;
|
errorMsg += `With the current security level configuration, only custom nodes from the <B>"default channel"</B> can be installed.\n`;
|
||||||
} else {
|
} else {
|
||||||
@ -1625,17 +1636,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;
|
||||||
|
|||||||
@ -1,13 +1,15 @@
|
|||||||
.cmm-manager {
|
.cmm-manager {
|
||||||
--grid-font: -apple-system, BlinkMacSystemFont, "Segoe 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: 80vw;
|
||||||
height: 80%;
|
height: 75vh;
|
||||||
|
min-height: 30em;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
color: var(--fg-color);
|
color: var(--fg-color);
|
||||||
font-family: arial, sans-serif;
|
font-family: arial, sans-serif;
|
||||||
|
margin: calc(var(--spacing)*2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cmm-manager .cmm-flex-auto {
|
.cmm-manager .cmm-flex-auto {
|
||||||
@ -18,14 +20,15 @@
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
color: var(--input-text);
|
color: var(--input-text);
|
||||||
background-color: var(--comfy-input-bg);
|
background-color: var(--comfy-input-bg);
|
||||||
border-radius: 8px;
|
|
||||||
border-color: var(--border-color);
|
border-color: var(--border-color);
|
||||||
border-style: solid;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 4px 8px;
|
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cmm-manager button:hover {
|
||||||
|
filter: brightness(125%);
|
||||||
|
}
|
||||||
|
|
||||||
.cmm-manager button:disabled,
|
.cmm-manager button:disabled,
|
||||||
.cmm-manager input:disabled,
|
.cmm-manager input:disabled,
|
||||||
.cmm-manager select:disabled {
|
.cmm-manager select:disabled {
|
||||||
@ -38,7 +41,7 @@
|
|||||||
|
|
||||||
.cmm-manager .cmm-manager-refresh {
|
.cmm-manager .cmm-manager-refresh {
|
||||||
display: none;
|
display: none;
|
||||||
background-color: #000080;
|
background-color: #000080 !important;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,7 +56,6 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cmm-manager-header label {
|
.cmm-manager-header label {
|
||||||
@ -67,16 +69,34 @@
|
|||||||
.cmm-manager-filter {
|
.cmm-manager-filter {
|
||||||
height: 28px;
|
height: 28px;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0.5em 0.5em;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--comfy-input-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cmm-manager-type:hover,
|
||||||
|
.cmm-manager-base:hover,
|
||||||
|
.cmm-manager-filter:hover {
|
||||||
|
filter: brightness(125%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cmm-manager-keywords {
|
.cmm-manager-keywords {
|
||||||
height: 28px;
|
height: 28px;
|
||||||
line-height: 28px;
|
line-height: 28px;
|
||||||
padding: 0 5px 0 26px;
|
padding: 0 5px 0 26px;
|
||||||
|
background: var(--comfy-input-bg);
|
||||||
background-size: 16px;
|
background-size: 16px;
|
||||||
background-position: 5px center;
|
background-position: 5px center;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20pointer-events%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23888%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m21%2021-4.486-4.494M19%2010.5a8.5%208.5%200%201%201-17%200%208.5%208.5%200%200%201%2017%200%22%2F%3E%3C%2Fsvg%3E");
|
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%220%200%2024%2024%22%20width%3D%22100%25%22%20height%3D%22100%25%22%20pointer-events%3D%22none%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20fill%3D%22none%22%20stroke%3D%22%23888%22%20stroke-linecap%3D%22round%22%20stroke-linejoin%3D%22round%22%20stroke-width%3D%222%22%20d%3D%22m21%2021-4.486-4.494M19%2010.5a8.5%208.5%200%201%201-17%200%208.5%208.5%200%200%201%2017%200%22%2F%3E%3C%2Fsvg%3E");
|
||||||
|
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
outline-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cmm-manager-status {
|
.cmm-manager-status {
|
||||||
@ -148,6 +168,10 @@
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cmm-btn-install {
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
.cmm-btn-download {
|
.cmm-btn-download {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
|
|||||||
@ -3,46 +3,29 @@ import { $el } from "../../scripts/ui.js";
|
|||||||
import {
|
import {
|
||||||
manager_instance, rebootAPI,
|
manager_instance, rebootAPI,
|
||||||
fetchData, md5, icons, show_message, customAlert, infoToast, showTerminal,
|
fetchData, md5, icons, show_message, customAlert, infoToast, showTerminal,
|
||||||
storeColumnWidth, restoreColumnWidth, loadCss
|
storeColumnWidth, restoreColumnWidth, loadCss, handle403Response
|
||||||
} from "./common.js";
|
} from "./common.js";
|
||||||
import { api } from "../../scripts/api.js";
|
import { api } from "../../scripts/api.js";
|
||||||
|
|
||||||
// https://cenfun.github.io/turbogrid/api.html
|
// https://cenfun.github.io/turbogrid/api.html
|
||||||
import TG from "./turbogrid.esm.js";
|
import TG from "./turbogrid.esm.js";
|
||||||
|
import { buildGuiFrameCustomHeader, createSettingsCombo } from "./comfyui-gui-builder.js";
|
||||||
|
|
||||||
loadCss("./model-manager.css");
|
loadCss("./model-manager.css");
|
||||||
|
|
||||||
const gridId = "model";
|
const gridId = "model";
|
||||||
|
|
||||||
const pageHtml = `
|
const pageHtml = `
|
||||||
<div class="cmm-manager-header">
|
<div class="cmm-manager cmm-manager-dark">
|
||||||
<label>Filter
|
|
||||||
<select class="cmm-manager-filter"></select>
|
|
||||||
</label>
|
|
||||||
<label>Type
|
|
||||||
<select class="cmm-manager-type"></select>
|
|
||||||
</label>
|
|
||||||
<label>Base
|
|
||||||
<select class="cmm-manager-base"></select>
|
|
||||||
</label>
|
|
||||||
<input class="cmm-manager-keywords" type="search" placeholder="Search" />
|
|
||||||
<div class="cmm-manager-status"></div>
|
|
||||||
<div class="cmm-flex-auto"></div>
|
|
||||||
</div>
|
|
||||||
<div class="cmm-manager-grid"></div>
|
<div class="cmm-manager-grid"></div>
|
||||||
<div class="cmm-manager-selection"></div>
|
<div class="cmm-manager-selection"></div>
|
||||||
<div class="cmm-manager-message"></div>
|
<div class="cmm-manager-message"></div>
|
||||||
<div class="cmm-manager-footer">
|
<div class="cmm-manager-footer">
|
||||||
<button class="cmm-manager-back">
|
<button class="cmm-manager-refresh p-button p-component">Refresh</button>
|
||||||
<svg class="arrow-icon" width="14" height="14" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<button class="cmm-manager-stop p-button p-component">Stop</button>
|
||||||
<path d="M2 8H18M2 8L8 2M2 8L8 14" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
||||||
</svg>
|
|
||||||
Back
|
|
||||||
</button>
|
|
||||||
<button class="cmm-manager-refresh">Refresh</button>
|
|
||||||
<button class="cmm-manager-stop">Stop</button>
|
|
||||||
<div class="cmm-flex-auto"></div>
|
<div class="cmm-flex-auto"></div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export class ModelManager {
|
export class ModelManager {
|
||||||
@ -64,11 +47,23 @@ export class ModelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.element = $el("div", {
|
const header = $el("div.cmm-manager-header", {}, [
|
||||||
parent: document.body,
|
createSettingsCombo("Filter", $el("select.cmm-manager-filter")),
|
||||||
className: "comfy-modal cmm-manager"
|
createSettingsCombo("Type", $el("select.cmm-manager-type")),
|
||||||
});
|
createSettingsCombo("Base", $el("select.cmm-manager-base")),
|
||||||
this.element.innerHTML = pageHtml;
|
$el("input.cmm-manager-keywords.p-inputtext.p-component", { type: "search", placeholder: "Search" }),
|
||||||
|
$el("div.cmm-manager-status"),
|
||||||
|
$el("div.cmm-flex-auto")
|
||||||
|
]);
|
||||||
|
|
||||||
|
const frame = buildGuiFrameCustomHeader(
|
||||||
|
'cmm-manager-dialog', // dialog id
|
||||||
|
header, // custom header element
|
||||||
|
pageHtml, // dialog content element
|
||||||
|
this
|
||||||
|
); // send this so we can attach close functions
|
||||||
|
|
||||||
|
this.element = frame;
|
||||||
this.initFilter();
|
this.initFilter();
|
||||||
this.bindEvents();
|
this.bindEvents();
|
||||||
this.initGrid();
|
this.initGrid();
|
||||||
@ -347,7 +342,7 @@ export class ModelManager {
|
|||||||
if (installed === "True") {
|
if (installed === "True") {
|
||||||
return `<div class="cmm-icon-passed">${icons.passed}</div>`;
|
return `<div class="cmm-icon-passed">${icons.passed}</div>`;
|
||||||
}
|
}
|
||||||
return `<button class="cmm-btn-install" mode="install">Install</button>`;
|
return `<button class="cmm-btn-install p-button p-component" mode="install">Install</button>`;
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
id: 'url',
|
id: 'url',
|
||||||
@ -420,7 +415,7 @@ export class ModelManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.selectedModels = selectedList;
|
this.selectedModels = selectedList;
|
||||||
this.showSelection(`<span>Selected <b>${selectedList.length}</b> models <button class="cmm-btn-install" mode="install">Install</button>`);
|
this.showSelection(`<span>Selected <b>${selectedList.length}</b> models <button class="cmm-btn-install p-button p-component" mode="install">Install</button>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
focusInstall(item) {
|
focusInstall(item) {
|
||||||
@ -477,7 +472,16 @@ export class ModelManager {
|
|||||||
errorMsg = `'${item.name}': `;
|
errorMsg = `'${item.name}': `;
|
||||||
|
|
||||||
if(res.status == 403) {
|
if(res.status == 403) {
|
||||||
|
try {
|
||||||
|
const data = await res.json();
|
||||||
|
if(data.error === 'comfyui_outdated') {
|
||||||
|
errorMsg += `ComfyUI version is outdated. Please update ComfyUI to use Manager normally.\n`;
|
||||||
|
} else {
|
||||||
errorMsg += `This action is not allowed with this security level configuration.\n`;
|
errorMsg += `This action is not allowed with this security level configuration.\n`;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
errorMsg += `This action is not allowed with this security level configuration.\n`;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
errorMsg += await res.text() + '\n';
|
errorMsg += await res.text() + '\n';
|
||||||
}
|
}
|
||||||
|
|||||||
65
js/snapshot.css
Normal file
65
js/snapshot.css
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
.snapshot-manager {
|
||||||
|
--grid-font: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
|
||||||
|
z-index: 1099;
|
||||||
|
width: 80vw;
|
||||||
|
height: 75vh;
|
||||||
|
min-height: 30em;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
color: var(--fg-color);
|
||||||
|
font-family: arial, sans-serif;
|
||||||
|
text-underline-offset: 3px;
|
||||||
|
outline: none;
|
||||||
|
margin: calc(var(--spacing)*2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.snapshot-manager button {
|
||||||
|
width: auto;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--input-text);
|
||||||
|
background-color: var(--comfy-input-bg);
|
||||||
|
border-color: var(--border-color);
|
||||||
|
margin: 0;
|
||||||
|
min-width: 100px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snapshot-manager .snapshot-restore-btn {
|
||||||
|
background-color: #00158f !important;
|
||||||
|
border-color: #2025b9 !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snapshot-manager .snapshot-remove-btn {
|
||||||
|
background-color: #970000 !important;
|
||||||
|
border-color: #be2127 !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snapshot-manager button:hover {
|
||||||
|
filter: brightness(125%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.snapshot-manager .data-btns {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: calc(var(--spacing)*2);
|
||||||
|
padding: calc(var(--spacing)*2);
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snapshot-footer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snapshot-manager .cn-flex-auto {
|
||||||
|
flex: auto;
|
||||||
|
}
|
||||||
@ -1,8 +1,10 @@
|
|||||||
import { app } from "../../scripts/app.js";
|
import { app } from "../../scripts/app.js";
|
||||||
import { api } from "../../scripts/api.js"
|
import { api } from "../../scripts/api.js"
|
||||||
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
import { ComfyDialog, $el } from "../../scripts/ui.js";
|
||||||
import { manager_instance, rebootAPI, show_message } from "./common.js";
|
import { manager_instance, rebootAPI, show_message, handle403Response, loadCss } from "./common.js";
|
||||||
|
import { buildGuiFrame } from "./comfyui-gui-builder.js";
|
||||||
|
|
||||||
|
loadCss("./snapshot.css");
|
||||||
|
|
||||||
async function restore_snapshot(target) {
|
async function restore_snapshot(target) {
|
||||||
if(SnapshotManager.instance) {
|
if(SnapshotManager.instance) {
|
||||||
@ -10,7 +12,7 @@ async function restore_snapshot(target) {
|
|||||||
const response = await api.fetchApi(`/snapshot/restore?target=${target}`, { cache: "no-store" });
|
const response = await api.fetchApi(`/snapshot/restore?target=${target}`, { cache: "no-store" });
|
||||||
|
|
||||||
if(response.status == 403) {
|
if(response.status == 403) {
|
||||||
show_message('This action is not allowed with this security level configuration.');
|
await handle403Response(response);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -27,7 +29,7 @@ async function restore_snapshot(target) {
|
|||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
await SnapshotManager.instance.invalidateControl();
|
await SnapshotManager.instance.invalidateControl();
|
||||||
SnapshotManager.instance.updateMessage("<BR>To apply the snapshot, please <button id='cm-reboot-button2' class='cm-small-button'>RESTART</button> ComfyUI. And refresh browser.", 'cm-reboot-button2');
|
SnapshotManager.instance.updateMessage("<BR>To apply the snapshot, please <button id='cm-reboot-button2' class='p-button p-component'>RESTART</button> ComfyUI. And refresh browser.", 'cm-reboot-button2');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,7 +40,7 @@ async function remove_snapshot(target) {
|
|||||||
const response = await api.fetchApi(`/snapshot/remove?target=${target}`, { cache: "no-store" });
|
const response = await api.fetchApi(`/snapshot/remove?target=${target}`, { cache: "no-store" });
|
||||||
|
|
||||||
if(response.status == 403) {
|
if(response.status == 403) {
|
||||||
show_message('This action is not allowed with this security level configuration.');
|
await handle403Response(response);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,6 +90,8 @@ export class SnapshotManager extends ComfyDialog {
|
|||||||
message_box = null;
|
message_box = null;
|
||||||
data = null;
|
data = null;
|
||||||
|
|
||||||
|
content = $el("div.snapshot-manager");
|
||||||
|
|
||||||
clear() {
|
clear() {
|
||||||
this.restore_buttons = [];
|
this.restore_buttons = [];
|
||||||
this.message_box = null;
|
this.message_box = null;
|
||||||
@ -96,9 +100,18 @@ export class SnapshotManager extends ComfyDialog {
|
|||||||
|
|
||||||
constructor(app, manager_dialog) {
|
constructor(app, manager_dialog) {
|
||||||
super();
|
super();
|
||||||
this.manager_dialog = manager_dialog;
|
// this.manager_dialog = manager_dialog;
|
||||||
this.search_keyword = '';
|
this.search_keyword = '';
|
||||||
this.element = $el("div.comfy-modal", { parent: document.body }, []);
|
|
||||||
|
const frame = buildGuiFrame(
|
||||||
|
'snapshot-manager-dialog', // dialog id
|
||||||
|
'Snapshot Manager', // title
|
||||||
|
'i.mdi.mdi-puzzle', // icon class
|
||||||
|
this.content, // dialog content element
|
||||||
|
this
|
||||||
|
); // send this so we can attach close functions
|
||||||
|
|
||||||
|
this.element = frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
async remove_item() {
|
async remove_item() {
|
||||||
@ -109,7 +122,7 @@ export class SnapshotManager extends ComfyDialog {
|
|||||||
|
|
||||||
createControls() {
|
createControls() {
|
||||||
return [
|
return [
|
||||||
$el("button.cm-small-button", {
|
$el("button.p-button.p-component", {
|
||||||
type: "button",
|
type: "button",
|
||||||
textContent: "Close",
|
textContent: "Close",
|
||||||
onclick: () => { this.close(); }
|
onclick: () => { this.close(); }
|
||||||
@ -132,8 +145,8 @@ export class SnapshotManager extends ComfyDialog {
|
|||||||
this.clear();
|
this.clear();
|
||||||
this.data = (await getSnapshotList()).items;
|
this.data = (await getSnapshotList()).items;
|
||||||
|
|
||||||
while (this.element.children.length) {
|
while (this.content.children.length) {
|
||||||
this.element.removeChild(this.element.children[0]);
|
this.content.removeChild(this.content.children[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.createGrid();
|
await this.createGrid();
|
||||||
@ -145,8 +158,8 @@ export class SnapshotManager extends ComfyDialog {
|
|||||||
if(btn_id) {
|
if(btn_id) {
|
||||||
const rebootButton = document.getElementById(btn_id);
|
const rebootButton = document.getElementById(btn_id);
|
||||||
const self = this;
|
const self = this;
|
||||||
rebootButton.onclick = function() {
|
rebootButton.onclick = async function() {
|
||||||
if(rebootAPI()) {
|
if(await rebootAPI()) {
|
||||||
self.close();
|
self.close();
|
||||||
self.manager_dialog.close();
|
self.manager_dialog.close();
|
||||||
}
|
}
|
||||||
@ -204,20 +217,21 @@ export class SnapshotManager extends ComfyDialog {
|
|||||||
data2.innerHTML = ` ${data}`;
|
data2.innerHTML = ` ${data}`;
|
||||||
var data_button = document.createElement('td');
|
var data_button = document.createElement('td');
|
||||||
data_button.style.textAlign = "center";
|
data_button.style.textAlign = "center";
|
||||||
|
data_button.className = "data-btns";
|
||||||
|
|
||||||
var restoreBtn = document.createElement('button');
|
var restoreBtn = document.createElement('button');
|
||||||
|
restoreBtn.className = "snapshot-restore-btn p-button p-component";
|
||||||
restoreBtn.innerHTML = 'Restore';
|
restoreBtn.innerHTML = 'Restore';
|
||||||
restoreBtn.style.width = "100px";
|
restoreBtn.style.width = "100px";
|
||||||
restoreBtn.style.backgroundColor = 'blue';
|
|
||||||
|
|
||||||
restoreBtn.addEventListener('click', function() {
|
restoreBtn.addEventListener('click', function() {
|
||||||
restore_snapshot(data);
|
restore_snapshot(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
var removeBtn = document.createElement('button');
|
var removeBtn = document.createElement('button');
|
||||||
|
removeBtn.className = "snapshot-remove-btn p-button p-component";
|
||||||
removeBtn.innerHTML = 'Remove';
|
removeBtn.innerHTML = 'Remove';
|
||||||
removeBtn.style.width = "100px";
|
removeBtn.style.width = "100px";
|
||||||
removeBtn.style.backgroundColor = 'red';
|
|
||||||
|
|
||||||
removeBtn.addEventListener('click', function() {
|
removeBtn.addEventListener('click', function() {
|
||||||
remove_snapshot(data);
|
remove_snapshot(data);
|
||||||
@ -241,13 +255,14 @@ export class SnapshotManager extends ComfyDialog {
|
|||||||
let self = this;
|
let self = this;
|
||||||
const panel = document.createElement('div');
|
const panel = document.createElement('div');
|
||||||
panel.style.width = "100%";
|
panel.style.width = "100%";
|
||||||
|
panel.style.height = "100%";
|
||||||
panel.appendChild(grid);
|
panel.appendChild(grid);
|
||||||
|
|
||||||
function handleResize() {
|
function handleResize() {
|
||||||
const parentHeight = self.element.clientHeight;
|
const parentHeight = self.element.clientHeight;
|
||||||
const gridHeight = parentHeight - 200;
|
const gridHeight = parentHeight - 200;
|
||||||
|
|
||||||
grid.style.height = gridHeight + "px";
|
// grid.style.height = gridHeight + "px";
|
||||||
}
|
}
|
||||||
window.addEventListener("resize", handleResize);
|
window.addEventListener("resize", handleResize);
|
||||||
|
|
||||||
@ -256,25 +271,17 @@ export class SnapshotManager extends ComfyDialog {
|
|||||||
grid.style.width = "100%";
|
grid.style.width = "100%";
|
||||||
grid.style.height = "100%";
|
grid.style.height = "100%";
|
||||||
grid.style.overflowY = "scroll";
|
grid.style.overflowY = "scroll";
|
||||||
this.element.style.height = "85%";
|
|
||||||
this.element.style.width = "80%";
|
this.content.appendChild(panel);
|
||||||
this.element.appendChild(panel);
|
|
||||||
|
|
||||||
handleResize();
|
handleResize();
|
||||||
}
|
}
|
||||||
|
|
||||||
async createBottomControls() {
|
async createBottomControls() {
|
||||||
var close_button = document.createElement("button");
|
|
||||||
close_button.className = "cm-small-button";
|
|
||||||
close_button.innerHTML = "Close";
|
|
||||||
close_button.onclick = () => { this.close(); }
|
|
||||||
close_button.style.display = "inline-block";
|
|
||||||
|
|
||||||
var save_button = document.createElement("button");
|
var save_button = document.createElement("button");
|
||||||
save_button.className = "cm-small-button";
|
save_button.className = "p-button p-component";
|
||||||
save_button.innerHTML = "Save snapshot";
|
save_button.innerHTML = "Save snapshot";
|
||||||
save_button.onclick = () => { save_current_snapshot(); }
|
save_button.onclick = () => { save_current_snapshot(); }
|
||||||
save_button.style.display = "inline-block";
|
|
||||||
save_button.style.horizontalAlign = "right";
|
save_button.style.horizontalAlign = "right";
|
||||||
save_button.style.width = "170px";
|
save_button.style.width = "170px";
|
||||||
|
|
||||||
@ -282,15 +289,19 @@ export class SnapshotManager extends ComfyDialog {
|
|||||||
this.message_box.style.height = '60px';
|
this.message_box.style.height = '60px';
|
||||||
this.message_box.style.verticalAlign = 'middle';
|
this.message_box.style.verticalAlign = 'middle';
|
||||||
|
|
||||||
this.element.appendChild(this.message_box);
|
const footer = $el("div.snapshot-footer");
|
||||||
this.element.appendChild(close_button);
|
const spacer = $el("div.cn-flex-auto");
|
||||||
this.element.appendChild(save_button);
|
footer.appendChild(spacer);
|
||||||
|
footer.appendChild(save_button);
|
||||||
|
|
||||||
|
this.content.appendChild(this.message_box);
|
||||||
|
this.content.appendChild(footer);
|
||||||
}
|
}
|
||||||
|
|
||||||
async show() {
|
async show() {
|
||||||
try {
|
try {
|
||||||
this.invalidateControl();
|
this.invalidateControl();
|
||||||
this.element.style.display = "block";
|
this.element.style.display = "flex";
|
||||||
this.element.style.zIndex = 1099;
|
this.element.style.zIndex = 1099;
|
||||||
}
|
}
|
||||||
catch(exception) {
|
catch(exception) {
|
||||||
|
|||||||
273
json-checker.py
273
json-checker.py
@ -1,25 +1,264 @@
|
|||||||
import json
|
#!/usr/bin/env python3
|
||||||
import argparse
|
"""JSON Entry Validator
|
||||||
|
|
||||||
def check_json_syntax(file_path):
|
Validates JSON entries based on content structure.
|
||||||
|
|
||||||
|
Validation rules based on JSON content:
|
||||||
|
- {"custom_nodes": [...]}: Validates required fields (author, title, reference, files, install_type, description)
|
||||||
|
- {"models": [...]}: Validates JSON syntax only (no required fields)
|
||||||
|
- Other JSON structures: Validates JSON syntax only
|
||||||
|
|
||||||
|
Git repository URL validation (for custom_nodes):
|
||||||
|
1. URLs must NOT end with .git
|
||||||
|
2. URLs must follow format: https://github.com/{author}/{reponame}
|
||||||
|
3. .py and .js files are exempt from this check
|
||||||
|
|
||||||
|
Supported formats:
|
||||||
|
- Array format: [{...}, {...}]
|
||||||
|
- Object format: {"custom_nodes": [...]} or {"models": [...]}
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
# Required fields for each entry type
|
||||||
|
REQUIRED_FIELDS_CUSTOM_NODE = ['author', 'title', 'reference', 'files', 'install_type', 'description']
|
||||||
|
REQUIRED_FIELDS_MODEL = [] # model-list.json doesn't require field validation
|
||||||
|
|
||||||
|
# Pattern for valid GitHub repository URL (without .git suffix)
|
||||||
|
GITHUB_REPO_PATTERN = re.compile(r'^https://github\.com/[^/]+/[^/]+$')
|
||||||
|
|
||||||
|
|
||||||
|
def get_entry_context(entry: Dict) -> str:
|
||||||
|
"""Get identifying information from entry for error messages
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entry: JSON entry
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
String with author and reference info
|
||||||
|
"""
|
||||||
|
parts = []
|
||||||
|
if 'author' in entry:
|
||||||
|
parts.append(f"author={entry['author']}")
|
||||||
|
if 'reference' in entry:
|
||||||
|
parts.append(f"ref={entry['reference']}")
|
||||||
|
if 'title' in entry:
|
||||||
|
parts.append(f"title={entry['title']}")
|
||||||
|
|
||||||
|
if parts:
|
||||||
|
return " | ".join(parts)
|
||||||
|
else:
|
||||||
|
# No identifying info - show actual entry content (truncated)
|
||||||
|
import json
|
||||||
|
entry_str = json.dumps(entry, ensure_ascii=False)
|
||||||
|
if len(entry_str) > 100:
|
||||||
|
entry_str = entry_str[:100] + "..."
|
||||||
|
return f"content={entry_str}"
|
||||||
|
|
||||||
|
|
||||||
|
def validate_required_fields(entry: Dict, entry_index: int, required_fields: List[str]) -> List[str]:
|
||||||
|
"""Validate that all required fields are present
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entry: JSON entry to validate
|
||||||
|
entry_index: Index of entry in array (for error reporting)
|
||||||
|
required_fields: List of required field names
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of error descriptions (without entry prefix/context)
|
||||||
|
"""
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
for field in required_fields:
|
||||||
|
if field not in entry:
|
||||||
|
errors.append(f"Missing required field '{field}'")
|
||||||
|
elif entry[field] is None:
|
||||||
|
errors.append(f"Field '{field}' is null")
|
||||||
|
elif isinstance(entry[field], str) and not entry[field].strip():
|
||||||
|
errors.append(f"Field '{field}' is empty")
|
||||||
|
elif field == 'files' and not entry[field]: # Empty array
|
||||||
|
errors.append("Field 'files' is empty array")
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
def validate_git_repo_urls(entry: Dict, entry_index: int) -> List[str]:
|
||||||
|
"""Validate git repository URLs in 'files' array
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- Git repo URLs must NOT end with .git
|
||||||
|
- Must follow format: https://github.com/{author}/{reponame}
|
||||||
|
- .py and .js files are exempt
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entry: JSON entry to validate
|
||||||
|
entry_index: Index of entry in array (for error reporting)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of error descriptions (without entry prefix/context)
|
||||||
|
"""
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
if 'files' not in entry or not isinstance(entry['files'], list):
|
||||||
|
return errors
|
||||||
|
|
||||||
|
for file_url in entry['files']:
|
||||||
|
if not isinstance(file_url, str):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Skip .py and .js files - they're exempt from git repo validation
|
||||||
|
if file_url.endswith('.py') or file_url.endswith('.js'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Check if it's a GitHub URL (likely a git repo)
|
||||||
|
if 'github.com' in file_url:
|
||||||
|
# Error if URL ends with .git
|
||||||
|
if file_url.endswith('.git'):
|
||||||
|
errors.append(f"Git repo URL must NOT end with .git: {file_url}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Validate format: https://github.com/{author}/{reponame}
|
||||||
|
if not GITHUB_REPO_PATTERN.match(file_url):
|
||||||
|
errors.append(f"Invalid git repo URL format (expected https://github.com/author/reponame): {file_url}")
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
def validate_entry(entry: Dict, entry_index: int, required_fields: List[str]) -> List[str]:
|
||||||
|
"""Validate a single JSON entry
|
||||||
|
|
||||||
|
Args:
|
||||||
|
entry: JSON entry to validate
|
||||||
|
entry_index: Index of entry in array (for error reporting)
|
||||||
|
required_fields: List of required field names
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of error messages (empty if valid)
|
||||||
|
"""
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
# Check required fields
|
||||||
|
errors.extend(validate_required_fields(entry, entry_index, required_fields))
|
||||||
|
|
||||||
|
# Check git repository URLs
|
||||||
|
errors.extend(validate_git_repo_urls(entry, entry_index))
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
def validate_json_file(file_path: str) -> Tuple[bool, List[str]]:
|
||||||
|
"""Validate JSON file containing entries
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: Path to JSON file
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Tuple of (is_valid, error_messages)
|
||||||
|
"""
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
# Check file exists
|
||||||
|
path = Path(file_path)
|
||||||
|
if not path.exists():
|
||||||
|
return False, [f"File not found: {file_path}"]
|
||||||
|
|
||||||
|
# Load JSON
|
||||||
try:
|
try:
|
||||||
with open(file_path, 'r', encoding='utf-8') as file:
|
with open(path, 'r', encoding='utf-8') as f:
|
||||||
json_str = file.read()
|
data = json.load(f)
|
||||||
json.loads(json_str)
|
|
||||||
print(f"[ OK ] {file_path}")
|
|
||||||
except UnicodeDecodeError as e:
|
|
||||||
print(f"Unicode decode error: {e}")
|
|
||||||
except json.JSONDecodeError as e:
|
except json.JSONDecodeError as e:
|
||||||
print(f"[FAIL] {file_path}\n\n {e}\n")
|
return False, [f"Invalid JSON: {e}"]
|
||||||
except FileNotFoundError:
|
except Exception as e:
|
||||||
print(f"[FAIL] {file_path}\n\n File not found\n")
|
return False, [f"Error reading file: {e}"]
|
||||||
|
|
||||||
|
# Determine required fields based on JSON content
|
||||||
|
required_fields = []
|
||||||
|
|
||||||
|
# Validate structure - support both array and object formats
|
||||||
|
entries_to_validate = []
|
||||||
|
|
||||||
|
if isinstance(data, list):
|
||||||
|
# Direct array format: [{...}, {...}]
|
||||||
|
entries_to_validate = data
|
||||||
|
elif isinstance(data, dict):
|
||||||
|
# Object format: {"custom_nodes": [...]} or {"models": [...]}
|
||||||
|
# Determine validation based on keys
|
||||||
|
if 'custom_nodes' in data and isinstance(data['custom_nodes'], list):
|
||||||
|
required_fields = REQUIRED_FIELDS_CUSTOM_NODE
|
||||||
|
entries_to_validate = data['custom_nodes']
|
||||||
|
elif 'models' in data and isinstance(data['models'], list):
|
||||||
|
required_fields = REQUIRED_FIELDS_MODEL
|
||||||
|
entries_to_validate = data['models']
|
||||||
|
else:
|
||||||
|
# Other JSON structures (extension-node-map.json, etc.) - just validate JSON syntax
|
||||||
|
return True, []
|
||||||
|
else:
|
||||||
|
return False, ["JSON root must be either an array or an object containing arrays"]
|
||||||
|
|
||||||
|
# Validate each entry
|
||||||
|
for idx, entry in enumerate(entries_to_validate, start=1):
|
||||||
|
if not isinstance(entry, dict):
|
||||||
|
# Show actual value for type errors
|
||||||
|
entry_str = json.dumps(entry, ensure_ascii=False) if not isinstance(entry, str) else repr(entry)
|
||||||
|
if len(entry_str) > 150:
|
||||||
|
entry_str = entry_str[:150] + "..."
|
||||||
|
errors.append(f"\n❌ Entry #{idx}: Must be an object, got {type(entry).__name__}")
|
||||||
|
errors.append(f" Actual value: {entry_str}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
entry_errors = validate_entry(entry, idx, required_fields)
|
||||||
|
if entry_errors:
|
||||||
|
# Group errors by entry with context
|
||||||
|
context = get_entry_context(entry)
|
||||||
|
errors.append(f"\n❌ Entry #{idx} ({context}):")
|
||||||
|
for error in entry_errors:
|
||||||
|
errors.append(f" - {error}")
|
||||||
|
|
||||||
|
is_valid = len(errors) == 0
|
||||||
|
return is_valid, errors
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(description="JSON File Syntax Checker")
|
"""Main entry point"""
|
||||||
parser.add_argument("file_path", type=str, help="Path to the JSON file for syntax checking")
|
if len(sys.argv) < 2:
|
||||||
|
print("Usage: python json-checker.py <json-file>")
|
||||||
|
print("\nValidates JSON entries based on content:")
|
||||||
|
print(" - {\"custom_nodes\": [...]}: Validates required fields (author, title, reference, files, install_type, description)")
|
||||||
|
print(" - {\"models\": [...]}: Validates JSON syntax only (no required fields)")
|
||||||
|
print(" - Other JSON structures: Validates JSON syntax only")
|
||||||
|
print("\nGit repo URL validation (for custom_nodes):")
|
||||||
|
print(" - URLs must NOT end with .git")
|
||||||
|
print(" - URLs must follow: https://github.com/{author}/{reponame}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
args = parser.parse_args()
|
file_path = sys.argv[1]
|
||||||
check_json_syntax(args.file_path)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
is_valid, errors = validate_json_file(file_path)
|
||||||
|
|
||||||
|
if is_valid:
|
||||||
|
print(f"✅ {file_path}: Validation passed")
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print(f"Validating: {file_path}")
|
||||||
|
print("=" * 60)
|
||||||
|
print("❌ Validation failed!\n")
|
||||||
|
print("Errors:")
|
||||||
|
# Count actual errors (lines starting with " -")
|
||||||
|
error_count = sum(1 for e in errors if e.strip().startswith('-'))
|
||||||
|
for error in errors:
|
||||||
|
# Don't add ❌ prefix to grouped entries (they already have it)
|
||||||
|
if error.strip().startswith('❌'):
|
||||||
|
print(error)
|
||||||
|
else:
|
||||||
|
print(error)
|
||||||
|
print(f"\nTotal errors: {error_count}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||
410
model-list.json
410
model-list.json
@ -5045,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",
|
||||||
@ -5256,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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
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": "Fossiel",
|
||||||
|
"title": "ComfyUI-MultiGPU-Patched",
|
||||||
|
"reference": "https://github.com/Fossiel/ComfyUI-MultiGPU-Patched",
|
||||||
|
"files": [
|
||||||
|
"https://github.com/Fossiel/ComfyUI-MultiGPU-Patched"
|
||||||
|
],
|
||||||
|
"install_type": "git-clone",
|
||||||
|
"description": "Patched fork of ComfyUI-MultiGPU providing universal .safetensors and GGUF multi-GPU distribution with DisTorch 2.0 engine, model-driven allocation options (bytes/ratio modes), WanVideoWrapper integration, and up to 10% faster GGUF inference. (Description by CC)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"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",
|
"author": "joaomede",
|
||||||
"title": "ComfyUI-Unload-Model-Fork",
|
"title": "ComfyUI-Unload-Model-Fork",
|
||||||
@ -159,6 +179,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,106 @@
|
|||||||
{
|
{
|
||||||
"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",
|
"name": "sam2.1_hiera_tiny.pt",
|
||||||
"type": "sam2.1",
|
"type": "sam2.1",
|
||||||
@ -586,109 +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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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",
|
||||||
@ -341,6 +351,26 @@
|
|||||||
],
|
],
|
||||||
"install_type": "git-clone",
|
"install_type": "git-clone",
|
||||||
"description": "A minimal test suite demonstrating how remote COMBO inputs behave in ComfyUI, with and without force_input"
|
"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."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
57
openapi.yaml
57
openapi.yaml
@ -104,6 +104,38 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
description: Whether the queue is currently processing
|
description: Whether the queue is currently processing
|
||||||
|
|
||||||
|
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
|
||||||
@ -306,6 +338,31 @@ paths:
|
|||||||
'400':
|
'400':
|
||||||
description: No information available
|
description: No information available
|
||||||
|
|
||||||
|
/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.
|
||||||
|
|
||||||
/customnode/install/git_url:
|
/customnode/install/git_url:
|
||||||
post:
|
post:
|
||||||
summary: Install custom node via Git URL
|
summary: Install custom node via Git URL
|
||||||
|
|||||||
@ -85,7 +85,15 @@ 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]
|
||||||
|
|
||||||
|
# Check for System User API availability (PR #10966)
|
||||||
|
_has_system_user_api = hasattr(folder_paths, 'get_system_user_directory')
|
||||||
|
|
||||||
|
if _has_system_user_api:
|
||||||
|
manager_files_path = os.path.abspath(os.path.join(folder_paths.get_user_directory(), '__manager'))
|
||||||
|
else:
|
||||||
manager_files_path = os.path.abspath(os.path.join(folder_paths.get_user_directory(), 'default', 'ComfyUI-Manager'))
|
manager_files_path = os.path.abspath(os.path.join(folder_paths.get_user_directory(), 'default', 'ComfyUI-Manager'))
|
||||||
|
|
||||||
manager_pip_overrides_path = os.path.join(manager_files_path, "pip_overrides.json")
|
manager_pip_overrides_path = os.path.join(manager_files_path, "pip_overrides.json")
|
||||||
manager_pip_blacklist_path = os.path.join(manager_files_path, "pip_blacklist.list")
|
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")
|
||||||
@ -516,7 +524,8 @@ 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")
|
# Use manager_files_path for consistency (fixes path inconsistency bug)
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
@ -793,7 +802,11 @@ def execute_startup_script():
|
|||||||
|
|
||||||
|
|
||||||
# Check if script_list_path exists
|
# Check if script_list_path exists
|
||||||
|
# Block startup-scripts on old ComfyUI (security measure)
|
||||||
|
if not _has_system_user_api:
|
||||||
if os.path.exists(script_list_path):
|
if os.path.exists(script_list_path):
|
||||||
|
print("[ComfyUI-Manager] Startup scripts blocked on old ComfyUI version.")
|
||||||
|
elif os.path.exists(script_list_path):
|
||||||
execute_startup_script()
|
execute_startup_script()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "comfyui-manager"
|
name = "comfyui-manager"
|
||||||
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
|
description = "ComfyUI-Manager provides features to install and manage custom nodes for ComfyUI, as well as various functionalities to assist with ComfyUI."
|
||||||
version = "3.35"
|
version = "3.39"
|
||||||
license = { file = "LICENSE.txt" }
|
license = { file = "LICENSE.txt" }
|
||||||
dependencies = ["GitPython", "PyGithub", "matrix-nio", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions", "toml", "uv", "chardet"]
|
dependencies = ["GitPython", "PyGithub", "matrix-nio", "transformers", "huggingface-hub>0.20", "typer", "rich", "typing-extensions", "toml", "uv", "chardet"]
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ GitPython
|
|||||||
PyGithub
|
PyGithub
|
||||||
matrix-nio
|
matrix-nio
|
||||||
transformers
|
transformers
|
||||||
huggingface-hub>0.20
|
huggingface-hub
|
||||||
typer
|
typer
|
||||||
rich
|
rich
|
||||||
typing-extensions
|
typing-extensions
|
||||||
|
|||||||
934
scanner.py
934
scanner.py
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user