-
Notifications
You must be signed in to change notification settings - Fork 210
Task #5: One-Click Report Bundle Generator with Frontend Integration #84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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: | ||
| ''' | ||
|
|
@@ -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 = {} | ||
|
|
@@ -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) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. security (python.lang.maintainability.useless-inner-function): function 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 | ||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): Potential issue with reading CSV from ZIP file. Using
Suggested change
Comment on lines
+22
to
+24
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (code-quality): Avoid loops in tests. ( ExplanationAvoid complex code, like loops, in test functions.Google's software engineering guidelines says:
Some ways to fix this:
Software Engineering at Google / Don't Put Logic in Tests |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| print("✅ All report.zip checks passed.") | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||||||||||
| test_report_zip() | ||||||||||||||||||||||||
| 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" | ||
| ] | ||
| } |
| 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} | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainImprove 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 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:
ComfyUI workflows should be both maintainable and immediately executable without missing dependencies.
🤖 Prompt for AI Agents |
||
| 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||||||||||||
|
|
||||||||||||||||
| print(f"Report generated at: {REPORT_ZIP.resolve()}") | ||||||||||||||||
|
|
||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||
| create_report() | ||||||||||||||||
| 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 |
| 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 |
There was a problem hiding this comment.
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.