mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-01-30 16:20:17 +08:00
Compare commits
15 Commits
ecfe67ec5c
...
6d35230e7f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d35230e7f | ||
|
|
1a72bf2046 | ||
|
|
f030d2c425 | ||
|
|
019eaab4c9 | ||
|
|
f330220f66 | ||
|
|
d35e0fcdd7 | ||
|
|
3d0331813d | ||
|
|
afd4b725db | ||
|
|
149506beea | ||
|
|
dfca61be7f | ||
|
|
39a5c5621e | ||
|
|
0d20e44618 | ||
|
|
5f415089fc | ||
|
|
6d23bfde7f | ||
|
|
0eff10fd21 |
@ -108,7 +108,7 @@ See what ComfyUI can do with the [example workflows](https://comfyanonymous.gith
|
||||
- [LCM models and Loras](https://comfyanonymous.github.io/ComfyUI_examples/lcm/)
|
||||
- Latent previews with [TAESD](#how-to-show-high-quality-previews)
|
||||
- Works fully offline: core will never download anything unless you want to.
|
||||
- Optional API nodes to use paid models from external providers through the online [Comfy API](https://docs.comfy.org/tutorials/api-nodes/overview).
|
||||
- Optional API nodes to use paid models from external providers through the online [Comfy API](https://docs.comfy.org/tutorials/api-nodes/overview) disable with: `--disable-api-nodes`
|
||||
- [Config file](extra_model_paths.yaml.example) to set the search paths for models.
|
||||
|
||||
Workflow examples can be found on the [Examples page](https://comfyanonymous.github.io/ComfyUI_examples/)
|
||||
@ -212,7 +212,7 @@ Python 3.14 works but you may encounter issues with the torch compile node. The
|
||||
|
||||
Python 3.13 is very well supported. If you have trouble with some custom node dependencies on 3.13 you can try 3.12
|
||||
|
||||
torch 2.4 and above is supported but some features might only work on newer versions. We generally recommend using the latest major version of pytorch with the latest cuda version unless it is less than 2 weeks old.
|
||||
torch 2.4 and above is supported but some features and optimizations might only work on newer versions. We generally recommend using the latest major version of pytorch with the latest cuda version unless it is less than 2 weeks old.
|
||||
|
||||
### Instructions:
|
||||
|
||||
@ -229,7 +229,7 @@ AMD users can install rocm and pytorch with pip if you don't have it already ins
|
||||
|
||||
```pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm6.4```
|
||||
|
||||
This is the command to install the nightly with ROCm 7.0 which might have some performance improvements:
|
||||
This is the command to install the nightly with ROCm 7.1 which might have some performance improvements:
|
||||
|
||||
```pip install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/rocm7.1```
|
||||
|
||||
|
||||
@ -377,8 +377,22 @@ class UserManager():
|
||||
try:
|
||||
body = await request.read()
|
||||
|
||||
with open(path, "wb") as f:
|
||||
f.write(body)
|
||||
# Pretty print JSON files for better source control
|
||||
if path.lower().endswith('.json'):
|
||||
try:
|
||||
# Parse JSON and re-serialize with indentation
|
||||
json_data = json.loads(body.decode('utf-8'))
|
||||
formatted_json = json.dumps(json_data, indent=2)
|
||||
with open(path, "w", encoding='utf-8') as f:
|
||||
f.write(formatted_json)
|
||||
except (json.JSONDecodeError, UnicodeDecodeError):
|
||||
# If JSON parsing fails, save as-is
|
||||
with open(path, "wb") as f:
|
||||
f.write(body)
|
||||
else:
|
||||
# Non-JSON files are saved as-is
|
||||
with open(path, "wb") as f:
|
||||
f.write(body)
|
||||
except OSError as e:
|
||||
logging.warning(f"Error saving file '{path}': {e}")
|
||||
return web.Response(
|
||||
|
||||
@ -521,7 +521,7 @@ class PromptServer():
|
||||
buffer.seek(0)
|
||||
|
||||
return web.Response(body=buffer.read(), content_type=f'image/{image_format}',
|
||||
headers={"Content-Disposition": f"filename=\"{filename}\""})
|
||||
headers={"Content-Disposition": f"attachment; filename=\"{filename}\""})
|
||||
|
||||
if 'channel' not in request.rel_url.query:
|
||||
channel = 'rgba'
|
||||
@ -541,7 +541,7 @@ class PromptServer():
|
||||
buffer.seek(0)
|
||||
|
||||
return web.Response(body=buffer.read(), content_type='image/png',
|
||||
headers={"Content-Disposition": f"filename=\"{filename}\""})
|
||||
headers={"Content-Disposition": f"attachment; filename=\"{filename}\""})
|
||||
|
||||
elif channel == 'a':
|
||||
with Image.open(file) as img:
|
||||
@ -558,7 +558,7 @@ class PromptServer():
|
||||
alpha_buffer.seek(0)
|
||||
|
||||
return web.Response(body=alpha_buffer.read(), content_type='image/png',
|
||||
headers={"Content-Disposition": f"filename=\"{filename}\""})
|
||||
headers={"Content-Disposition": f"attachment; filename=\"{filename}\""})
|
||||
else:
|
||||
# Get content type from mimetype, defaulting to 'application/octet-stream'
|
||||
content_type = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
|
||||
@ -570,7 +570,7 @@ class PromptServer():
|
||||
return web.FileResponse(
|
||||
file,
|
||||
headers={
|
||||
"Content-Disposition": f"filename=\"{filename}\"",
|
||||
"Content-Disposition": f"attachment; filename=\"{filename}\"",
|
||||
"Content-Type": content_type
|
||||
}
|
||||
)
|
||||
|
||||
@ -287,3 +287,72 @@ async def test_listuserdata_v2_url_encoded_path(aiohttp_client, app, tmp_path):
|
||||
assert entry["name"] == "file.txt"
|
||||
# Ensure the path is correctly decoded and uses forward slash
|
||||
assert entry["path"] == "my dir/file.txt"
|
||||
|
||||
|
||||
async def test_post_userdata_json_pretty_print(aiohttp_client, app, tmp_path):
|
||||
"""Test that JSON files are saved with pretty printing (indentation)"""
|
||||
import json
|
||||
|
||||
client = await aiohttp_client(app)
|
||||
|
||||
# Create a compact JSON workflow
|
||||
workflow_data = {
|
||||
"nodes": [
|
||||
{"id": "1", "type": "LoadImage", "inputs": {"image": "test.png"}},
|
||||
{"id": "2", "type": "SaveImage", "inputs": {"images": ["1", 0]}}
|
||||
],
|
||||
"metadata": {"version": "1.0", "author": "test"}
|
||||
}
|
||||
compact_json = json.dumps(workflow_data).encode('utf-8')
|
||||
|
||||
# Save as JSON file
|
||||
resp = await client.post("/userdata/workflow.json", data=compact_json)
|
||||
assert resp.status == 200
|
||||
|
||||
# Read the saved file and verify it's pretty-printed
|
||||
with open(tmp_path / "workflow.json", "r", encoding='utf-8') as f:
|
||||
saved_content = f.read()
|
||||
|
||||
# Verify the file contains indentation (pretty-printed)
|
||||
assert " " in saved_content # Should have 2-space indentation
|
||||
assert "\n" in saved_content # Should have newlines
|
||||
|
||||
# Verify the content is still valid JSON and matches original data
|
||||
saved_data = json.loads(saved_content)
|
||||
assert saved_data == workflow_data
|
||||
|
||||
# Verify it's actually formatted (not compact)
|
||||
# Compact JSON would be much shorter
|
||||
assert len(saved_content) > len(compact_json)
|
||||
|
||||
|
||||
async def test_post_userdata_json_invalid_fallback(aiohttp_client, app, tmp_path):
|
||||
"""Test that invalid JSON is saved as-is without error"""
|
||||
client = await aiohttp_client(app)
|
||||
|
||||
# Create invalid JSON content
|
||||
invalid_json = b'{"invalid": json content}'
|
||||
|
||||
# Save as JSON file - should not fail
|
||||
resp = await client.post("/userdata/invalid.json", data=invalid_json)
|
||||
assert resp.status == 200
|
||||
|
||||
# Verify file was saved as-is
|
||||
with open(tmp_path / "invalid.json", "rb") as f:
|
||||
assert f.read() == invalid_json
|
||||
|
||||
|
||||
async def test_post_userdata_non_json_unchanged(aiohttp_client, app, tmp_path):
|
||||
"""Test that non-JSON files are saved unchanged"""
|
||||
client = await aiohttp_client(app)
|
||||
|
||||
# Create binary content
|
||||
binary_content = b'\x00\x01\x02\x03\x04\x05'
|
||||
|
||||
# Save as non-JSON file
|
||||
resp = await client.post("/userdata/test.bin", data=binary_content)
|
||||
assert resp.status == 200
|
||||
|
||||
# Verify file was saved exactly as-is
|
||||
with open(tmp_path / "test.bin", "rb") as f:
|
||||
assert f.read() == binary_content
|
||||
|
||||
Loading…
Reference in New Issue
Block a user