Skip to content

Commit a0160c9

Browse files
authored
Merge branch 'comfyanonymous:master' into master
2 parents 9ead17a + 5e68a4c commit a0160c9

86 files changed

Lines changed: 33032 additions & 31369 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/stable-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ on:
1212
description: 'CUDA version'
1313
required: true
1414
type: string
15-
default: "121"
15+
default: "124"
1616
python_minor:
1717
description: 'Python minor version'
1818
required: true

.github/workflows/test-unit.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Unit Tests
2+
3+
on:
4+
push:
5+
branches: [ main, master ]
6+
pull_request:
7+
branches: [ main, master ]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
- uses: actions/setup-python@v4
15+
with:
16+
python-version: '3.10'
17+
- name: Install requirements
18+
run: |
19+
python -m pip install --upgrade pip
20+
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
21+
pip install -r requirements.txt
22+
- name: Run Unit Tests
23+
run: |
24+
pip install -r tests-unit/requirements.txt
25+
python -m pytest tests-unit

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ extra_model_paths.yaml
1212
.vscode/
1313
.idea/
1414
venv/
15+
.venv/
1516
/web/extensions/*
1617
!/web/extensions/logging.js.example
1718
!/web/extensions/core/

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ Workflow examples can be found on the [Examples page](https://comfyanonymous.git
9494
| Alt + `+` | Canvas Zoom in |
9595
| Alt + `-` | Canvas Zoom out |
9696
| Ctrl + Shift + LMB + Vertical drag | Canvas Zoom in/out |
97+
| P | Pin/Unpin selected nodes |
98+
| Ctrl + G | Group selected nodes |
9799
| Q | Toggle visibility of the queue |
98100
| H | Toggle visibility of history |
99101
| R | Refresh graph |

app/user_manager.py

Lines changed: 57 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,17 @@
55
import glob
66
import shutil
77
from aiohttp import web
8+
from urllib import parse
89
from comfy.cli_args import args
9-
from folder_paths import user_directory
10+
import folder_paths
1011
from .app_settings import AppSettings
1112

1213
default_user = "default"
13-
users_file = os.path.join(user_directory, "users.json")
1414

1515

1616
class UserManager():
1717
def __init__(self):
18-
global user_directory
18+
user_directory = folder_paths.get_user_directory()
1919

2020
self.settings = AppSettings(self)
2121
if not os.path.exists(user_directory):
@@ -25,14 +25,17 @@ def __init__(self):
2525
print("****** For multi-user setups add the --multi-user CLI argument to enable multiple user profiles. ******")
2626

2727
if args.multi_user:
28-
if os.path.isfile(users_file):
29-
with open(users_file) as f:
28+
if os.path.isfile(self.get_users_file()):
29+
with open(self.get_users_file()) as f:
3030
self.users = json.load(f)
3131
else:
3232
self.users = {}
3333
else:
3434
self.users = {"default": "default"}
3535

36+
def get_users_file(self):
37+
return os.path.join(folder_paths.get_user_directory(), "users.json")
38+
3639
def get_request_user_id(self, request):
3740
user = "default"
3841
if args.multi_user and "comfy-user" in request.headers:
@@ -44,7 +47,7 @@ def get_request_user_id(self, request):
4447
return user
4548

4649
def get_request_user_filepath(self, request, file, type="userdata", create_dir=True):
47-
global user_directory
50+
user_directory = folder_paths.get_user_directory()
4851

4952
if type == "userdata":
5053
root_dir = user_directory
@@ -59,6 +62,10 @@ def get_request_user_filepath(self, request, file, type="userdata", create_dir=T
5962
return None
6063

6164
if file is not None:
65+
# Check if filename is url encoded
66+
if "%" in file:
67+
file = parse.unquote(file)
68+
6269
# prevent leaving /{type}/{user}
6370
path = os.path.abspath(os.path.join(user_root, file))
6471
if os.path.commonpath((user_root, path)) != user_root:
@@ -80,8 +87,7 @@ def add_user(self, name):
8087

8188
self.users[user_id] = name
8289

83-
global users_file
84-
with open(users_file, "w") as f:
90+
with open(self.get_users_file(), "w") as f:
8591
json.dump(self.users, f)
8692

8793
return user_id
@@ -114,54 +120,75 @@ async def post_users(request):
114120
async def listuserdata(request):
115121
directory = request.rel_url.query.get('dir', '')
116122
if not directory:
117-
return web.Response(status=400)
118-
123+
return web.Response(status=400, text="Directory not provided")
124+
119125
path = self.get_request_user_filepath(request, directory)
120126
if not path:
121-
return web.Response(status=403)
122-
127+
return web.Response(status=403, text="Invalid directory")
128+
123129
if not os.path.exists(path):
124-
return web.Response(status=404)
125-
130+
return web.Response(status=404, text="Directory not found")
131+
126132
recurse = request.rel_url.query.get('recurse', '').lower() == "true"
127-
results = glob.glob(os.path.join(
128-
glob.escape(path), '**/*'), recursive=recurse)
129-
results = [os.path.relpath(x, path) for x in results if os.path.isfile(x)]
130-
133+
full_info = request.rel_url.query.get('full_info', '').lower() == "true"
134+
135+
# Use different patterns based on whether we're recursing or not
136+
if recurse:
137+
pattern = os.path.join(glob.escape(path), '**', '*')
138+
else:
139+
pattern = os.path.join(glob.escape(path), '*')
140+
141+
results = glob.glob(pattern, recursive=recurse)
142+
143+
if full_info:
144+
results = [
145+
{
146+
'path': os.path.relpath(x, path).replace(os.sep, '/'),
147+
'size': os.path.getsize(x),
148+
'modified': os.path.getmtime(x)
149+
} for x in results if os.path.isfile(x)
150+
]
151+
else:
152+
results = [
153+
os.path.relpath(x, path).replace(os.sep, '/')
154+
for x in results
155+
if os.path.isfile(x)
156+
]
157+
131158
split_path = request.rel_url.query.get('split', '').lower() == "true"
132-
if split_path:
133-
results = [[x] + x.split(os.sep) for x in results]
159+
if split_path and not full_info:
160+
results = [[x] + x.split('/') for x in results]
134161

135162
return web.json_response(results)
136163

137164
def get_user_data_path(request, check_exists = False, param = "file"):
138165
file = request.match_info.get(param, None)
139166
if not file:
140167
return web.Response(status=400)
141-
168+
142169
path = self.get_request_user_filepath(request, file)
143170
if not path:
144171
return web.Response(status=403)
145-
172+
146173
if check_exists and not os.path.exists(path):
147174
return web.Response(status=404)
148-
175+
149176
return path
150177

151178
@routes.get("/userdata/{file}")
152179
async def getuserdata(request):
153180
path = get_user_data_path(request, check_exists=True)
154181
if not isinstance(path, str):
155182
return path
156-
183+
157184
return web.FileResponse(path)
158185

159186
@routes.post("/userdata/{file}")
160187
async def post_userdata(request):
161188
path = get_user_data_path(request)
162189
if not isinstance(path, str):
163190
return path
164-
191+
165192
overwrite = request.query["overwrite"] != "false"
166193
if not overwrite and os.path.exists(path):
167194
return web.Response(status=409)
@@ -170,7 +197,7 @@ async def post_userdata(request):
170197

171198
with open(path, "wb") as f:
172199
f.write(body)
173-
200+
174201
resp = os.path.relpath(path, self.get_request_user_filepath(request, None))
175202
return web.json_response(resp)
176203

@@ -181,25 +208,25 @@ async def delete_userdata(request):
181208
return path
182209

183210
os.remove(path)
184-
211+
185212
return web.Response(status=204)
186213

187214
@routes.post("/userdata/{file}/move/{dest}")
188215
async def move_userdata(request):
189216
source = get_user_data_path(request, check_exists=True)
190217
if not isinstance(source, str):
191218
return source
192-
219+
193220
dest = get_user_data_path(request, check_exists=False, param="dest")
194221
if not isinstance(source, str):
195222
return dest
196-
223+
197224
overwrite = request.query["overwrite"] != "false"
198225
if not overwrite and os.path.exists(dest):
199226
return web.Response(status=409)
200227

201228
print(f"moving '{source}' -> '{dest}'")
202229
shutil.move(source, dest)
203-
230+
204231
resp = os.path.relpath(dest, self.get_request_user_filepath(request, None))
205232
return web.json_response(resp)

comfy/cldm/mmdit.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class ControlNet(comfy.ldm.modules.diffusionmodules.mmdit.MMDiT):
66
def __init__(
77
self,
88
num_blocks = None,
9+
control_latent_channels = None,
910
dtype = None,
1011
device = None,
1112
operations = None,
@@ -17,10 +18,13 @@ def __init__(
1718
for _ in range(len(self.joint_blocks)):
1819
self.controlnet_blocks.append(operations.Linear(self.hidden_size, self.hidden_size, device=device, dtype=dtype))
1920

21+
if control_latent_channels is None:
22+
control_latent_channels = self.in_channels
23+
2024
self.pos_embed_input = comfy.ldm.modules.diffusionmodules.mmdit.PatchEmbed(
2125
None,
2226
self.patch_size,
23-
self.in_channels,
27+
control_latent_channels,
2428
self.hidden_size,
2529
bias=True,
2630
strict_img_size=False,

comfy/cli_args.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ def is_valid_directory(path: Optional[str]) -> Optional[str]:
171171
help="The local filesystem path to the directory where the frontend is located. Overrides --front-end-version.",
172172
)
173173

174+
parser.add_argument("--user-directory", type=is_valid_directory, default=None, help="Set the ComfyUI user directory with an absolute path.")
175+
174176
if comfy.options.args_parsing:
175177
args = parser.parse_args()
176178
else:
File renamed without changes.

0 commit comments

Comments
 (0)