Compare commits

...

15 Commits

Author SHA1 Message Date
Sam Pullara
6d35230e7f
Merge f030d2c425 into 1a72bf2046 2026-01-19 12:13:02 +03:00
comfyanonymous
1a72bf2046
Readme update. (#11957)
Some checks are pending
Python Linting / Run Ruff (push) Waiting to run
Python Linting / Run Pylint (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.10, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.11, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-stable (12.1, , linux, 3.12, [self-hosted Linux], stable) (push) Waiting to run
Full Comfy CI Workflow Runs / test-unix-nightly (12.1, , linux, 3.11, [self-hosted Linux], nightly) (push) Waiting to run
Execution Tests / test (macos-latest) (push) Waiting to run
Execution Tests / test (ubuntu-latest) (push) Waiting to run
Execution Tests / test (windows-latest) (push) Waiting to run
Test server launches without errors / test (push) Waiting to run
Unit Tests / test (macos-latest) (push) Waiting to run
Unit Tests / test (ubuntu-latest) (push) Waiting to run
Unit Tests / test (windows-2022) (push) Waiting to run
2026-01-18 19:53:43 -08:00
Sam Pullara
f030d2c425
Merge branch 'Comfy-Org:master' into master 2026-01-14 09:29:13 -08:00
Sam Pullara
019eaab4c9
Merge branch 'comfyanonymous:master' into master 2026-01-06 14:24:31 -08:00
Sam Pullara
f330220f66
Merge branch 'comfyanonymous:master' into master 2025-12-15 09:37:39 -08:00
Sam Pullara
d35e0fcdd7
Merge branch 'comfyanonymous:master' into master 2025-12-01 12:41:36 -08:00
Sam Pullara
3d0331813d
Merge branch 'comfyanonymous:master' into master 2025-11-26 15:18:24 -08:00
Sam Pullara
afd4b725db
Merge pull request #1 from spullara/claude/fix-download-file-extension-01KxyJS9CQduLtV75QhZiPsT
Fix file download issue - add attachment disposition type to Content-…
2025-11-19 16:46:45 -08:00
Claude
149506beea
Fix file download issue - add attachment disposition type to Content-Disposition headers
Files were downloading with filename "view" instead of the actual filename because
the Content-Disposition header was missing the disposition type (attachment/inline).
Changed from `filename="..."` to `attachment; filename="..."` in all 4 locations
in the /view endpoint to ensure proper filename handling by browsers.

This fixes downloads for videos, audio, and other file types served through the
/view endpoint.
2025-11-20 00:40:19 +00:00
Sam Pullara
dfca61be7f
Merge branch 'comfyanonymous:master' into master 2025-11-19 13:33:33 -08:00
Sam Pullara
39a5c5621e
Merge branch 'comfyanonymous:master' into master 2025-11-12 15:06:53 -08:00
Sam Pullara
0d20e44618
Merge branch 'comfyanonymous:master' into master 2025-10-31 13:41:24 -07:00
Sam Pullara
5f415089fc
Merge branch 'comfyanonymous:master' into master 2025-10-30 15:19:16 -07:00
Sam Pullara
6d23bfde7f add tests for saving json files formatted nicely 2025-10-29 13:26:49 -07:00
Sam Pullara
0eff10fd21 store json files pretty printed for better source control compatibiility 2025-10-29 13:17:56 -07:00
4 changed files with 92 additions and 9 deletions

View File

@ -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```

View File

@ -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(

View File

@ -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
}
)

View File

@ -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