Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions ComfyUI/api_server/routes/internal/internal_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from api_server.services.terminal_service import TerminalService
import app.logger
import os
from pathlib import Path
import subprocess

class InternalRoutes:
'''
Expand Down Expand Up @@ -43,7 +45,6 @@ async def subscribe_logs(request):

return web.Response(status=200)


@self.routes.get('/folder_paths')
async def get_folder_paths(request):
response = {}
Expand All @@ -64,10 +65,27 @@ async def get_files(request: web.Request) -> web.Response:
)
return web.json_response([entry.name for entry in sorted_files], status=200)

# ✅ NEW: Generate Report Route
@self.routes.post('/generate_report')
async def generate_report(request):
try:
script_path = Path(__file__).parent.parent.parent / "utils" / "generate_report.py"
result = subprocess.run(["python", str(script_path)], capture_output=True, text=True, check=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Subprocess call may not use the correct Python interpreter.

Consider replacing 'python' with sys.executable to ensure the subprocess uses the current Python interpreter.

Suggested change
result = subprocess.run(["python", str(script_path)], capture_output=True, text=True, check=True)
import sys
result = subprocess.run([sys.executable, str(script_path)], capture_output=True, text=True, check=True)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

Source: opengrep

return web.json_response({
"status": "success",
"message": "Report generated successfully.",
"output": result.stdout
})
except subprocess.CalledProcessError as e:
return web.json_response({
"status": "error",
"message": "Report generation failed.",
"error": e.stderr
}, status=500)
Comment on lines +69 to +84
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (python.lang.maintainability.useless-inner-function): function generate_report is defined inside a function but never used

Source: opengrep


def get_app(self):
if self._app is None:
self._app = web.Application()
self.setup_routes()
self._app.add_routes(self.routes)
return self._app
return self._app
29 changes: 29 additions & 0 deletions ComfyUI/tests-unit/report_bundle_test/test_generate_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import zipfile
import csv
from pathlib import Path

def test_report_zip():
base_dir = Path(__file__).resolve().parent.parent / "utils"
report_zip_path = base_dir / "report" / "report.zip"

assert report_zip_path.exists(), "report.zip was not created."

with zipfile.ZipFile(report_zip_path, 'r') as zipf:
names = zipf.namelist()

# Check required files
assert "config.json" in names, "Missing config.json in ZIP"
assert "results.csv" in names, "Missing results.csv in ZIP"
assert "README.txt" in names, "Missing README.txt in ZIP"

# Check image references in CSV exist in ZIP
with zipf.open("results.csv") as csvfile:
reader = csv.DictReader(line.decode('utf-8') for line in csvfile)
for row in reader:
image_name = f"grids/{row['image_file']}"
assert image_name in names, f"Missing image file {image_name} in ZIP"
Comment on lines +20 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Potential issue with reading CSV from ZIP file.

Using io.TextIOWrapper to wrap the file object ensures proper handling of newlines and encodings, making CSV reading more reliable.

Suggested change
with zipf.open("results.csv") as csvfile:
reader = csv.DictReader(line.decode('utf-8') for line in csvfile)
for row in reader:
image_name = f"grids/{row['image_file']}"
assert image_name in names, f"Missing image file {image_name} in ZIP"
with zipf.open("results.csv") as csvfile:
with io.TextIOWrapper(csvfile, encoding='utf-8', newline='') as textfile:
reader = csv.DictReader(textfile)
for row in reader:
image_name = f"grids/{row['image_file']}"
assert image_name in names, f"Missing image file {image_name} in ZIP"

Comment on lines +22 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Avoid loops in tests. (no-loop-in-tests)

ExplanationAvoid complex code, like loops, in test functions.

Google's software engineering guidelines says:
"Clear tests are trivially correct upon inspection"
To reach that avoid complex code in tests:

  • loops
  • conditionals

Some ways to fix this:

  • Use parametrized tests to get rid of the loop.
  • Move the complex logic into helpers.
  • Move the complex part into pytest fixtures.

Complexity is most often introduced in the form of logic. Logic is defined via the imperative parts of programming languages such as operators, loops, and conditionals. When a piece of code contains logic, you need to do a bit of mental computation to determine its result instead of just reading it off of the screen. It doesn't take much logic to make a test more difficult to reason about.

Software Engineering at Google / Don't Put Logic in Tests


print("✅ All report.zip checks passed.")

if __name__ == "__main__":
test_report_zip()
8 changes: 7 additions & 1 deletion ComfyUI/user/default/comfy.settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
{
"Comfy.TutorialCompleted": true
"Comfy.TutorialCompleted": true,
"Comfy.ColorPalette": "dark",
"Comfy.Release.Version": "0.3.49",
"Comfy.Release.Timestamp": 1754554707284,
"Comfy.NodeLibrary.Bookmarks.V2": [
"KSamplerAdvanced"
]
}
1 change: 1 addition & 0 deletions ComfyUI/user/default/workflows/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"id":"cc1b2b54-eec0-4305-ae97-16299b26e39a","revision":0,"last_node_id":12,"last_link_id":12,"nodes":[{"id":8,"type":"VAEDecode","pos":[1385.1756591796875,225.75196838378906],"size":[210,46],"flags":{},"order":5,"mode":0,"inputs":[{"localized_name":"samples","name":"samples","type":"LATENT","link":7},{"localized_name":"vae","name":"vae","type":"VAE","link":11}],"outputs":[{"localized_name":"IMAGE","name":"IMAGE","type":"IMAGE","slot_index":0,"links":[9]}],"properties":{"Node name for S&R":"VAEDecode"},"widgets_values":[]},{"id":5,"type":"EmptyLatentImage","pos":[488.48797607421875,706.76806640625],"size":[315,106],"flags":{},"order":0,"mode":0,"inputs":[{"localized_name":"width","name":"width","type":"INT","widget":{"name":"width"},"link":null},{"localized_name":"height","name":"height","type":"INT","widget":{"name":"height"},"link":null},{"localized_name":"batch_size","name":"batch_size","type":"INT","widget":{"name":"batch_size"},"link":null}],"outputs":[{"localized_name":"LATENT","name":"LATENT","type":"LATENT","slot_index":0,"links":[2]}],"properties":{"Node name for S&R":"EmptyLatentImage"},"widgets_values":[512,512,1]},{"id":7,"type":"CLIPTextEncode","pos":[413.4464416503906,393.6659240722656],"size":[425.27801513671875,180.6060791015625],"flags":{},"order":3,"mode":0,"inputs":[{"localized_name":"clip","name":"clip","type":"CLIP","link":10},{"localized_name":"text","name":"text","type":"STRING","widget":{"name":"text"},"link":null}],"outputs":[{"localized_name":"CONDITIONING","name":"CONDITIONING","type":"CONDITIONING","slot_index":0,"links":[6]}],"properties":{"Node name for S&R":"CLIPTextEncode"},"widgets_values":["text, watermark"],"color":"#323","bgcolor":"#535"},{"id":4,"type":"CheckpointLoaderSimple","pos":[12.355875015258789,538.53125],"size":[315,98],"flags":{},"order":1,"mode":0,"inputs":[{"localized_name":"ckpt_name","name":"ckpt_name","type":"COMBO","widget":{"name":"ckpt_name"},"link":null}],"outputs":[{"localized_name":"MODEL","name":"MODEL","type":"MODEL","slot_index":0,"links":[12]},{"localized_name":"CLIP","name":"CLIP","type":"CLIP","slot_index":1,"links":[3,10]},{"localized_name":"VAE","name":"VAE","type":"VAE","slot_index":2,"links":[11]}],"properties":{"Node name for S&R":"CheckpointLoaderSimple"},"widgets_values":["v1-5-pruned-emaonly.safetensors"]},{"id":9,"type":"SaveImage","pos":[1456.6739501953125,425.198486328125],"size":[210,270],"flags":{},"order":6,"mode":0,"inputs":[{"localized_name":"images","name":"images","type":"IMAGE","link":9},{"localized_name":"filename_prefix","name":"filename_prefix","type":"STRING","widget":{"name":"filename_prefix"},"link":null}],"outputs":[],"properties":{},"widgets_values":["ComfyUI"]},{"id":3,"type":"KSampler","pos":[1028.920166015625,474.3313293457031],"size":[315,262],"flags":{},"order":4,"mode":0,"inputs":[{"localized_name":"model","name":"model","type":"MODEL","link":12},{"localized_name":"positive","name":"positive","type":"CONDITIONING","link":4},{"localized_name":"negative","name":"negative","type":"CONDITIONING","link":6},{"localized_name":"latent_image","name":"latent_image","type":"LATENT","link":2},{"localized_name":"seed","name":"seed","type":"INT","widget":{"name":"seed"},"link":null},{"localized_name":"steps","name":"steps","type":"INT","widget":{"name":"steps"},"link":null},{"localized_name":"cfg","name":"cfg","type":"FLOAT","widget":{"name":"cfg"},"link":null},{"localized_name":"sampler_name","name":"sampler_name","type":"COMBO","widget":{"name":"sampler_name"},"link":null},{"localized_name":"scheduler","name":"scheduler","type":"COMBO","widget":{"name":"scheduler"},"link":null},{"localized_name":"denoise","name":"denoise","type":"FLOAT","widget":{"name":"denoise"},"link":null}],"outputs":[{"localized_name":"LATENT","name":"LATENT","type":"LATENT","slot_index":0,"links":[7]}],"properties":{"Node name for S&R":"KSampler"},"widgets_values":[157166686672613,"randomize",20,8,"euler","normal",1]},{"id":6,"type":"CLIPTextEncode","pos":[436.2958984375,132.760009765625],"size":[422.84503173828125,164.31304931640625],"flags":{},"order":2,"mode":0,"inputs":[{"localized_name":"clip","name":"clip","type":"CLIP","link":3},{"localized_name":"text","name":"text","type":"STRING","widget":{"name":"text"},"link":null}],"outputs":[{"localized_name":"CONDITIONING","name":"CONDITIONING","type":"CONDITIONING","slot_index":0,"links":[4]}],"properties":{"Node name for S&R":"CLIPTextEncode"},"widgets_values":["cute little golden retriever puppies"],"color":"#223","bgcolor":"#335"}],"links":[[2,5,0,3,3,"LATENT"],[3,4,1,6,0,"CLIP"],[4,6,0,3,1,"CONDITIONING"],[6,7,0,3,2,"CONDITIONING"],[7,3,0,8,0,"LATENT"],[9,8,0,9,0,"IMAGE"],[10,4,1,7,0,"CLIP"],[11,4,2,8,1,"VAE"],[12,4,0,3,0,"MODEL"]],"groups":[],"config":{},"extra":{"ds":{"scale":0.6115909044841489,"offset":[522.8425740808559,15.336086631059548]}},"version":0.4}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Improve JSON formatting and validate hardcoded references.

This minified JSON is difficult to read and maintain. Consider formatting it for better readability, and verify that hardcoded references like "v1-5-pruned-emaonly.safetensors" correspond to actual available models.

Apply this formatting for better readability:

-{"id":"cc1b2b54-eec0-4305-ae97-16299b26e39a","revision":0,"last_node_id":12,"last_link_id":12,"nodes":[...
+{
+  "id": "cc1b2b54-eec0-4305-ae97-16299b26e39a",
+  "revision": 0,
+  "last_node_id": 12,
+  "last_link_id": 12,
+  "nodes": [
+    ...
+  ],
+  ...
+}

Verify that the referenced model and workflow components exist:


🏁 Script executed:

#!/bin/bash
# Verify model file and workflow validity
echo "Checking for referenced model file..."
fd "v1-5-pruned-emaonly.safetensors" --type f

echo "Validating JSON structure..."
python3 -c "
import json
import sys
try:
    with open('ComfyUI/user/default/workflows/test.json', 'r') as f:
        workflow = json.load(f)
    print(f'Valid JSON with {len(workflow.get(\"nodes\", []))} nodes')
    
    # Check for required node types
    node_types = [node.get('type') for node in workflow.get('nodes', [])]
    required_types = ['CheckpointLoaderSimple', 'CLIPTextEncode', 'KSampler', 'VAEDecode', 'SaveImage']
    missing = [t for t in required_types if t not in node_types]
    if missing:
        print(f'Missing node types: {missing}')
    else:
        print('All required node types present')
except Exception as e:
    print(f'JSON validation failed: {e}')
    sys.exit(1)
"

Length of output: 1034


Ensure JSON readability and model availability

The workflow JSON is valid and includes all required nodes, but it’s still minified and references a model file that isn’t present in the repo. Please:

  • Format ComfyUI/user/default/workflows/test.json for readability:

    -{"id":"cc1b2b54-eec0-4305-ae97-16299b26e39a","revision":0,"last_node_id":12,"last_link_id":12,"nodes":[...],"version":0.4}
    +{
    +  "id": "cc1b2b54-eec0-4305-ae97-16299b26e39a",
    +  "revision": 0,
    +  "last_node_id": 12,
    +  "last_link_id": 12,
    +  "nodes": [
    +    {
    +      "id": 4,
    +      "type": "CheckpointLoaderSimple",
    +      "widgets_values": ["v1-5-pruned-emaonly.safetensors"],
    +      ...
    +    },
    +    ...
    +  ],
    +  "version": 0.4
    +}
  • Verify or include the referenced model:

    • The script fd "v1-5-pruned-emaonly.safetensors" --type f returned no matches.
    • Either add v1-5-pruned-emaonly.safetensors to your model directory, update the filename to one that exists, or mock the checkpoint for testing.

ComfyUI workflows should be both maintainable and immediately executable without missing dependencies.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In ComfyUI/user/default/workflows/test.json at line 1, the JSON content is
minified and references a model file "v1-5-pruned-emaonly.safetensors" that is
missing from the repository. To fix this, pretty-print the JSON to improve
readability by formatting it with proper indentation and line breaks.
Additionally, verify the presence of the referenced model file in the model
directory; if it is missing, either add the actual
"v1-5-pruned-emaonly.safetensors" file, update the JSON to reference an existing
model file, or create a mock checkpoint file for testing purposes to ensure the
workflow can run without missing dependencies.

49 changes: 49 additions & 0 deletions ComfyUI/utils/generate_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import os
import csv
import json
import zipfile
from pathlib import Path

# Define paths
BASE_DIR = Path(__file__).parent
DATA_DIR = BASE_DIR / "sample_data"
REPORT_DIR = BASE_DIR / "report"
REPORT_ZIP = REPORT_DIR / "report.zip"
README_CONTENT = """# DreamLayer Inference Report

This bundle contains:
- Inference results (results.csv)
- Configuration used (config.json)
- Grid images with seeds and parameters
"""

def create_report():
REPORT_DIR.mkdir(exist_ok=True)

with zipfile.ZipFile(REPORT_ZIP, 'w', zipfile.ZIP_DEFLATED) as zipf:
# Add config.json
config_path = DATA_DIR / "config.json"
if config_path.exists():
zipf.write(config_path, arcname="config.json")

# Add results.csv and referenced images
results_path = DATA_DIR / "results.csv"
if results_path.exists():
zipf.write(results_path, arcname="results.csv")
with open(results_path, newline='') as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
image_file = DATA_DIR / row["image_file"]
if image_file.exists():
zipf.write(image_file, arcname=f"grids/{image_file.name}")

# Add readme
readme_path = REPORT_DIR / "README.txt"
with open(readme_path, 'w') as f:
f.write(README_CONTENT)
zipf.write(readme_path, arcname="README.txt")
Comment on lines +40 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Temporary README.txt file is left in the report directory.

Consider writing the README directly to the zip with ZipFile.writestr, or deleting the temporary file after archiving to avoid clutter in REPORT_DIR.

Suggested change
# Add readme
readme_path = REPORT_DIR / "README.txt"
with open(readme_path, 'w') as f:
f.write(README_CONTENT)
zipf.write(readme_path, arcname="README.txt")
# Add readme directly to the zip
zipf.writestr("README.txt", README_CONTENT)


print(f"Report generated at: {REPORT_ZIP.resolve()}")

if __name__ == "__main__":
create_report()
6 changes: 6 additions & 0 deletions ComfyUI/utils/report/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# DreamLayer Inference Report

This bundle contains:
- Inference results (results.csv)
- Configuration used (config.json)
- Grid images with seeds and parameters
Binary file added ComfyUI/utils/report/report.zip
Binary file not shown.
6 changes: 6 additions & 0 deletions ComfyUI/utils/report/report/README.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# DreamLayer Inference Report

This bundle contains:
- Inference results (results.csv)
- Configuration used (config.json)
- Grid images with seeds and parameters
Loading