-
Notifications
You must be signed in to change notification settings - Fork 215
Matrix runner - Task 2 #92
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?
Conversation
Reviewer's GuideImplements Task #2 by adding a full matrix‐generation workflow in the Txt2Img UI: a MatrixSettings panel, a persisted job store and runner with pause/resume, a client‐side Canvas grid builder that labels and stitches sub-images, and matching backend endpoints to save and serve both single and grid images permanently. Sequence diagram for matrix job execution and grid savingsequenceDiagram
actor User
participant UI as Frontend UI
participant Store as MatrixStore
participant Backend
participant Canvas as Client Canvas
User->>UI: Configure matrix settings and click Generate
UI->>Store: Expand settings into job list, persist jobs
loop For each job
Store->>UI: Provide next job
UI->>Backend: Request image generation
Backend-->>UI: Return generated image
UI->>Store: Mark job as completed
end
UI->>Canvas: Build grid image from completed jobs
Canvas->>Backend: Save grid image via /api/save-matrix-grid
Backend-->>UI: Return persistent grid image URL
User->>UI: Download grid PNG or use 'Show in Folder'
Class diagram for Matrix job and settings typesclassDiagram
class MatrixSettingsType {
xAxis: MatrixParameter
yAxis: MatrixParameter
zAxis: MatrixParameter
drawLegend: boolean
includeSubImages: boolean
includeSubgrids: boolean
keepSeedsForRows: boolean
}
class MatrixParameter {
name: string
type: "list" | "range"
values: string
enabled: boolean
}
class MatrixJob {
id: string
settings: Txt2ImgCoreSettings
xValue: any
yValue: any
zValue: any
status: "pending" | "running" | "completed" | "failed"
result: ImageResult[]
}
class ImageResult {
id: string
url: string
prompt: string
negativePrompt: string
timestamp: number
settings: Txt2ImgCoreSettings
matrixInfo: any
}
MatrixSettingsType "1" -- "1" MatrixParameter : xAxis
MatrixSettingsType "1" -- "1" MatrixParameter : yAxis
MatrixSettingsType "1" -- "1" MatrixParameter : zAxis
MatrixJob "*" -- "1" Txt2ImgCoreSettings : settings
MatrixJob "*" -- "*" ImageResult : result
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughRemoved Run Registry, Report Bundle, and Img2Txt services/pages/tests and multiple Gemini/Stability workflow artifacts; added matrix-grid generation, storage, and frontend matrix job management/persistence; improved image serving, logging, API-key injection, and various refactors across backend and frontend. Type/annotation and small formatting tweaks applied in utility and API code. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Frontend
participant MatrixStore
participant Backend
User->>Frontend: Configure matrix (X/Y/Z) and click Generate
Frontend->>MatrixStore: setInitialState(jobs)
loop for each job
Frontend->>Backend: POST /generation (job settings)
Backend-->>Frontend: image result URL/blob
Frontend->>MatrixStore: updateJobStatus(index, 'completed', result)
end
Frontend->>Frontend: generateMatrixGrid(completedJobs)
Frontend->>Backend: POST /api/save-matrix-grid (base64 grid image)
Backend-->>Frontend: saved image URL + metadata
Frontend->>User: show grid in gallery
Estimated code review effort🎯 5 (Critical) | ⏱️ ~90+ minutes Possibly related PRs
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
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.
Hey @gihyuness - I've reviewed your changes - here's some feedback:
- Consider extracting the matrix‐generation and job‐runner logic out of Txt2ImgPage into custom hooks or utility modules to keep the component more focused and easier to test.
- Avoid hardcoding backend URLs (e.g. http://localhost:5001/5002) inside the component—pull those into environment‐driven config or a central API client to make the code more portable.
- There are a lot of console.log debug statements scattered through the new logic—consider gating them behind a debug flag or replacing them with a proper logging utility before merging.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider extracting the matrix‐generation and job‐runner logic out of Txt2ImgPage into custom hooks or utility modules to keep the component more focused and easier to test.
- Avoid hardcoding backend URLs (e.g. http://localhost:5001/5002) inside the component—pull those into environment‐driven config or a central API client to make the code more portable.
- There are a lot of console.log debug statements scattered through the new logic—consider gating them behind a debug flag or replacing them with a proper logging utility before merging.
## Individual Comments
### Comment 1
<location> `dream_layer_frontend/src/features/Txt2Img/Txt2ImgPage.tsx:97` </location>
<code_context>
+ const applyParameterToSettings = (settings: Txt2ImgCoreSettings, paramName: string, value: any) => {
</code_context>
<issue_to_address>
Unrecognized matrix parameter names are silently ignored.
Consider logging a warning or explicitly handling unknown parameter names to aid in identifying configuration errors and to support future extensibility.
</issue_to_address>
### Comment 2
<location> `dream_layer_frontend/src/components/tabs/txt2img/ImagePreview.tsx:50` </location>
<code_context>
+ setImageError(null);
+ };
+
const handleDownload = async (format: 'png' | 'zip') => {
if (!currentImage) return;
if (format === 'png') {
- // Download single PNG
- const link = document.createElement('a');
- link.href = currentImage.url;
- link.target = '_blank';
- link.download = `generated-image-${currentImage.id}.png`;
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
+ try {
+ const response = await fetch(currentImage.url);
+ const blob = await response.blob();
+
+ const link = document.createElement('a');
+ link.href = URL.createObjectURL(blob);
+ link.download = `generated-image-${currentImage.id}.png`;
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+
+ URL.revokeObjectURL(link.href);
+ } catch (error) {
+ console.error('Download failed:', error);
</code_context>
<issue_to_address>
URL.revokeObjectURL is called on a possibly already removed link.
Store the blob URL in a variable and revoke it after the download to avoid potential issues if the link's href is not a valid blob URL or has already been revoked.
</issue_to_address>
### Comment 3
<location> `dream_layer_backend/shared_utils.py:225` </location>
<code_context>
+
+ image_path = None
+
+ if filename.startswith('matrix-grid'):
+ matrix_filepath = os.path.join(MATRIX_GRIDS_DIR, filename)
+ if os.path.exists(matrix_filepath):
</code_context>
<issue_to_address>
Matrix grid images are now served from a dedicated directory.
Please ensure the directory path is configurable and validate filenames to prevent directory traversal vulnerabilities.
</issue_to_address>
<suggested_fix>
<<<<<<< SEARCH
if filename.startswith('matrix-grid'):
matrix_filepath = os.path.join(MATRIX_GRIDS_DIR, filename)
print(f"📁 Checking matrix_grids: {matrix_filepath}")
print(f"📁 Matrix grids directory exists: {os.path.exists(MATRIX_GRIDS_DIR)}")
if os.path.exists(MATRIX_GRIDS_DIR):
print(f"📁 Matrix grids directory contents: {os.listdir(MATRIX_GRIDS_DIR)}")
if os.path.exists(matrix_filepath):
print(f"✅ Found matrix grid: {matrix_filepath}")
print(f"📏 File size: {os.path.getsize(matrix_filepath)} bytes")
return send_file(matrix_filepath, mimetype='image/png')
=======
import os
# Make matrix grids directory configurable
MATRIX_GRIDS_DIR = os.environ.get("MATRIX_GRIDS_DIR", MATRIX_GRIDS_DIR)
# Validate filename to prevent directory traversal
def is_safe_filename(fname):
return (
".." not in fname
and "/" not in fname
and "\\" not in fname
and fname == os.path.basename(fname)
)
if filename.startswith('matrix-grid'):
if not is_safe_filename(filename):
print(f"❌ Unsafe filename detected: {filename}")
return jsonify({"error": "Invalid filename"}), 400
matrix_filepath = os.path.join(MATRIX_GRIDS_DIR, filename)
print(f"📁 Checking matrix_grids: {matrix_filepath}")
print(f"📁 Matrix grids directory exists: {os.path.exists(MATRIX_GRIDS_DIR)}")
if os.path.exists(MATRIX_GRIDS_DIR):
print(f"📁 Matrix grids directory contents: {os.listdir(MATRIX_GRIDS_DIR)}")
if os.path.exists(matrix_filepath):
print(f"✅ Found matrix grid: {matrix_filepath}")
print(f"📏 File size: {os.path.getsize(matrix_filepath)} bytes")
return send_file(matrix_filepath, mimetype='image/png')
>>>>>>> REPLACE
</suggested_fix>
### Comment 4
<location> `dream_layer_backend/shared_utils.py:265` </location>
<code_context>
- print(f" Checked: {served_filepath}")
- print(f" Checked: {input_filepath}")
- print(f" Checked: {output_filepath}")
+ if not filename.startswith('matrix-grid'):
+ matrix_filepath = os.path.join(MATRIX_GRIDS_DIR, filename)
+ if os.path.exists(matrix_filepath):
+ print(f"✅ Found in matrix_grids (fallback): {matrix_filepath}")
+ return send_file(matrix_filepath, mimetype='image/png')
+ print(f"❌ Image not found in any directory: {filename}")
</code_context>
<issue_to_address>
Fallback to matrix_grids directory for non-matrix-grid filenames.
This fallback could cause issues if filenames in matrix_grids overlap with others. Please consider limiting the fallback or clarifying its intended purpose.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const applyParameterToSettings = (settings: Txt2ImgCoreSettings, paramName: string, value: any) => { | ||
| console.log(`Applying parameter ${paramName} = ${value} to settings`); | ||
|
|
||
| switch (paramName) { | ||
| case 'Seed': | ||
| if (matrixSettings.keepSeedsForRows) { | ||
| console.log('Keep -1 for seeds is enabled, keeping original seed'); | ||
| settings.seed = -1; | ||
| settings.random_seed = true; | ||
| } else { |
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): Unrecognized matrix parameter names are silently ignored.
Consider logging a warning or explicitly handling unknown parameter names to aid in identifying configuration errors and to support future extensibility.
| const handleDownload = async (format: 'png' | 'zip') => { | ||
| if (!currentImage) return; | ||
|
|
||
| if (format === 'png') { | ||
| // Download single PNG | ||
| const link = document.createElement('a'); | ||
| link.href = currentImage.url; | ||
| link.target = '_blank'; | ||
| link.download = `generated-image-${currentImage.id}.png`; | ||
| document.body.appendChild(link); | ||
| link.click(); | ||
| document.body.removeChild(link); | ||
| try { | ||
| const response = await fetch(currentImage.url); | ||
| const blob = await response.blob(); | ||
|
|
||
| const link = document.createElement('a'); | ||
| link.href = URL.createObjectURL(blob); |
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.
nitpick (bug_risk): URL.revokeObjectURL is called on a possibly already removed link.
Store the blob URL in a variable and revoke it after the download to avoid potential issues if the link's href is not a valid blob URL or has already been revoked.
| if filename.startswith('matrix-grid'): | ||
| matrix_filepath = os.path.join(MATRIX_GRIDS_DIR, filename) | ||
| print(f"📁 Checking matrix_grids: {matrix_filepath}") | ||
| print(f"📁 Matrix grids directory exists: {os.path.exists(MATRIX_GRIDS_DIR)}") | ||
| if os.path.exists(MATRIX_GRIDS_DIR): | ||
| print(f"📁 Matrix grids directory contents: {os.listdir(MATRIX_GRIDS_DIR)}") | ||
|
|
||
| if os.path.exists(matrix_filepath): | ||
| print(f"✅ Found matrix grid: {matrix_filepath}") | ||
| print(f"📏 File size: {os.path.getsize(matrix_filepath)} bytes") | ||
| return send_file(matrix_filepath, mimetype='image/png') |
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 (security): Matrix grid images are now served from a dedicated directory.
Please ensure the directory path is configurable and validate filenames to prevent directory traversal vulnerabilities.
| if filename.startswith('matrix-grid'): | |
| matrix_filepath = os.path.join(MATRIX_GRIDS_DIR, filename) | |
| print(f"📁 Checking matrix_grids: {matrix_filepath}") | |
| print(f"📁 Matrix grids directory exists: {os.path.exists(MATRIX_GRIDS_DIR)}") | |
| if os.path.exists(MATRIX_GRIDS_DIR): | |
| print(f"📁 Matrix grids directory contents: {os.listdir(MATRIX_GRIDS_DIR)}") | |
| if os.path.exists(matrix_filepath): | |
| print(f"✅ Found matrix grid: {matrix_filepath}") | |
| print(f"📏 File size: {os.path.getsize(matrix_filepath)} bytes") | |
| return send_file(matrix_filepath, mimetype='image/png') | |
| import os | |
| # Make matrix grids directory configurable | |
| MATRIX_GRIDS_DIR = os.environ.get("MATRIX_GRIDS_DIR", MATRIX_GRIDS_DIR) | |
| # Validate filename to prevent directory traversal | |
| def is_safe_filename(fname): | |
| return ( | |
| ".." not in fname | |
| and "/" not in fname | |
| and "\\" not in fname | |
| and fname == os.path.basename(fname) | |
| ) | |
| if filename.startswith('matrix-grid'): | |
| if not is_safe_filename(filename): | |
| print(f"❌ Unsafe filename detected: {filename}") | |
| return jsonify({"error": "Invalid filename"}), 400 | |
| matrix_filepath = os.path.join(MATRIX_GRIDS_DIR, filename) | |
| print(f"📁 Checking matrix_grids: {matrix_filepath}") | |
| print(f"📁 Matrix grids directory exists: {os.path.exists(MATRIX_GRIDS_DIR)}") | |
| if os.path.exists(MATRIX_GRIDS_DIR): | |
| print(f"📁 Matrix grids directory contents: {os.listdir(MATRIX_GRIDS_DIR)}") | |
| if os.path.exists(matrix_filepath): | |
| print(f"✅ Found matrix grid: {matrix_filepath}") | |
| print(f"📏 File size: {os.path.getsize(matrix_filepath)} bytes") | |
| return send_file(matrix_filepath, mimetype='image/png') |
| if os.path.exists(matrix_filepath): | ||
| print(f"✅ Found matrix grid: {matrix_filepath}") | ||
| print(f"📏 File size: {os.path.getsize(matrix_filepath)} bytes") | ||
| return send_file(matrix_filepath, mimetype='image/png') |
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): Fallback to matrix_grids directory for non-matrix-grid filenames.
This fallback could cause issues if filenames in matrix_grids overlap with others. Please consider limiting the fallback or clarifying its intended purpose.
| response_data["artifacts"][0]["base64"]) | ||
| returned_image = bytesio_to_image_tensor(BytesIO(image_data)) | ||
| if response_api.finish_reason != "SUCCESS": | ||
| raise Exception(f"Stable Image Ultra generation failed: {response_api.finish_reason}.") |
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.
issue (code-quality): Raise a specific error instead of the general Exception or BaseException (raise-specific-error)
Explanation
If a piece of code raises a specific exception type rather than the generic [`BaseException`](https://docs.python.org/3/library/exceptions.html#BaseException) or [`Exception`](https://docs.python.org/3/library/exceptions.html#Exception), the calling code can:- get more information about what type of error it is
- define specific exception handling for it
This way, callers of the code can handle the error appropriately.
How can you solve this?
- Use one of the built-in exceptions of the standard library.
- Define your own error class that subclasses
Exception.
So instead of having code raising Exception or BaseException like
if incorrect_input(value):
raise Exception("The input is incorrect")you can have code raising a specific error like
if incorrect_input(value):
raise ValueError("The input is incorrect")or
class IncorrectInputError(Exception):
pass
if incorrect_input(value):
raise IncorrectInputError("The input is incorrect")| # Try to find name parameter in the data URL | ||
| name_match = re.search(r';name=(.*?);', data_url) | ||
| if name_match: | ||
| return name_match.group(1) |
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 (code-quality): We've found these issues:
- Use named expression to simplify assignment and conditional (
use-named-expression) - Replace m.group(x) with m[x] for re.Match objects (
use-getitem-for-re-match-groups)
| # Try to find name parameter in the data URL | |
| name_match = re.search(r';name=(.*?);', data_url) | |
| if name_match: | |
| return name_match.group(1) | |
| if name_match := re.search(r';name=(.*?);', data_url): | |
| return name_match[1] |
| os.makedirs(SERVED_IMAGES_DIR, exist_ok=True) | ||
| os.makedirs(MATRIX_GRIDS_DIR, exist_ok=True) | ||
|
|
||
| def wait_for_image(prompt_id: str, save_node_id: str = "9", max_wait_time: int = 300) -> List[Dict[str, Any]]: |
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.
issue (code-quality): Low code quality found in wait_for_image - 5% (low-code-quality)
Explanation
The quality score for this function is below the quality threshold of 25%.This score is a combination of the method length, cognitive complexity and working memory.
How can you solve this?
It might be worth refactoring this function to make it shorter and more readable.
- Reduce the function length by extracting pieces of functionality out into
their own functions. This is the most important thing you can do - ideally a
function should be less than 10 lines. - Reduce nesting, perhaps by introducing guard clauses to return early.
- Ensure that variables are tightly scoped, so that code using related concepts
sits together within the function rather than being scattered.
|
|
||
| try: | ||
| # First check in served_images directory (for generated images) | ||
| print(f"🔍 Attempting to serve image: {filename}") |
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.
issue (code-quality): We've found these issues:
- Extract code out into function (
extract-method) - Extract duplicate code into function [×2] (
extract-duplicate-method)
| Save Matrix grid image to server storage (permanent directory) | ||
| """ | ||
| try: | ||
| print(f"🔄 Matrix grid save request received") |
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.
issue (code-quality): Replace f-string with no interpolated values with string [×2] (remove-redundant-fstring)
| from shared_utils import SAMPLER_NAME_MAP | ||
|
|
||
|
|
||
| def transform_to_txt2img_workflow(data): |
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.
issue (code-quality): We've found these issues:
- Replace f-string with no interpolated values with string [×5] (
remove-redundant-fstring) - Low code quality found in transform_to_txt2img_workflow - 20% (
low-code-quality)
Explanation
The quality score for this function is below the quality threshold of 25%.
This score is a combination of the method length, cognitive complexity and working memory.
How can you solve this?
It might be worth refactoring this function to make it shorter and more readable.
- Reduce the function length by extracting pieces of functionality out into
their own functions. This is the most important thing you can do - ideally a
function should be less than 10 lines. - Reduce nesting, perhaps by introducing guard clauses to return early.
- Ensure that variables are tightly scoped, so that code using related concepts
sits together within the function rather than being scattered.
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.
Actionable comments posted: 26
🔭 Outside diff range comments (8)
ComfyUI/comfy_api_nodes/apis/request_logger.py (1)
66-81: Redact secrets before logging (Authorization, cookies, API keys, tokens, passwords)As written, sensitive data can be persisted in logs (see the example Authorization header). This is a significant security/PII risk. Redact known secret-bearing keys in headers/params/body and in response headers before formatting.
Apply within this block to sanitize and also log entries even when empty (use
is not None):- if request_headers: - log_content.append(f"Headers:\n{_format_data_for_logging(request_headers)}") - if request_params: - log_content.append(f"Params:\n{_format_data_for_logging(request_params)}") - if request_data: - log_content.append(f"Data/Body:\n{_format_data_for_logging(request_data)}") + if request_headers is not None: + log_content.append(f"Headers:\n{_format_data_for_logging(_redact_for_logging(request_headers))}") + if request_params is not None: + log_content.append(f"Params:\n{_format_data_for_logging(_redact_for_logging(request_params))}") + if request_data is not None: + log_content.append(f"Data/Body:\n{_format_data_for_logging(_redact_for_logging(request_data))}") @@ - if response_headers: - log_content.append(f"Headers:\n{_format_data_for_logging(response_headers)}") - if response_content: - log_content.append(f"Content:\n{_format_data_for_logging(response_content)}") + if response_headers is not None: + log_content.append(f"Headers:\n{_format_data_for_logging(_redact_for_logging(response_headers))}") + if response_content is not None: + log_content.append(f"Content:\n{_format_data_for_logging(_redact_for_logging(response_content))}")Add this helper (outside the range) to perform redaction:
# Add near the top-level (module scope) from collections.abc import Mapping, Sequence SENSITIVE_KEYS = { "authorization", "proxy-authorization", "x-api-key", "api-key", "apikey", "cookie", "set-cookie", "x-auth-token", "x-access-token", "password", "pass", "passwd", "secret", "client-secret", "token", "access_token", "refresh_token" } def _redact_for_logging(obj): """ Redact well-known sensitive keys in mappings and nested structures. """ if isinstance(obj, Mapping): redacted = {} for k, v in obj.items(): key_norm = str(k).lower().replace("-", "_") # Heuristic: redact known keys or those ending with token/secret/key if ( key_norm in SENSITIVE_KEYS or key_norm.endswith(("token", "secret", "key")) ): redacted[k] = "***REDACTED***" else: redacted[k] = _redact_for_logging(v) return redacted if isinstance(obj, (list, tuple)): return [ _redact_for_logging(v) for v in obj ] return objdream_layer_backend/extras.py (1)
90-96: Add HTTP timeouts to ComfyUI requestsNetwork calls without timeouts can hang the request thread.
Apply timeouts:
- response = requests.post( + response = requests.post( f"{COMFY_API_URL}/prompt", - json={"prompt": workflow} + json={"prompt": workflow}, + timeout=30 ) ... - history_response = requests.get(f"{COMFY_API_URL}/history/{prompt_id}") + history_response = requests.get(f"{COMFY_API_URL}/history/{prompt_id}", timeout=10)Also applies to: 107-110
dream_layer_backend/controlnet.py (1)
26-31: Use absolute ComfyUI input path (avoid brittle relative path)
os.path.join("DreamLayer", "ComfyUI", "input")depends on CWD and can break. Align with the approach used inextras.py(derive from this file’s location), or centralize in a shared util.Example refactor:
- input_dir = os.path.join("DreamLayer", "ComfyUI", "input") + # Derive ComfyUI input dir relative to backend root + backend_root = os.path.dirname(os.path.abspath(__file__)) + project_root = os.path.dirname(backend_root) + comfy_ui_dir = os.path.join(project_root, "ComfyUI") + input_dir = os.path.join(comfy_ui_dir, "input")Consider extracting this into a shared helper to keep directory logic consistent across modules.
Also applies to: 88-93
ComfyUI/comfy_api_nodes/nodes_gemini.py (1)
426-435: Avoid mutable default argumentsUse None and initialize inside to prevent shared default list across calls.
- def prepare_files( - self, file: str, GEMINI_INPUT_FILES: list[GeminiPart] = [] - ) -> tuple[list[GeminiPart]]: + def prepare_files( + self, file: str, GEMINI_INPUT_FILES: Optional[list[GeminiPart]] = None + ) -> tuple[list[GeminiPart]]: @@ - file_path = folder_paths.get_annotated_filepath(file) - input_file_content = self.create_file_part(file_path) - files = [input_file_content] + GEMINI_INPUT_FILES + file_path = folder_paths.get_annotated_filepath(file) + input_file_content = self.create_file_part(file_path) + files = [input_file_content] + (GEMINI_INPUT_FILES or []) return (files,)dream_layer_backend/txt2img_server.py (1)
177-251: Consider adding file size validationThe
/api/save-matrix-gridendpoint accepts base64 image data without size validation, which could lead to memory issues with very large images.Add size validation before processing:
image_data = data['imageData'] if image_data.startswith('data:'): image_data = image_data.split(',')[1] print(f"📊 Received base64 data length: {len(image_data)}") + + # Validate size (e.g., 50MB limit) + max_size_bytes = 50 * 1024 * 1024 # 50MB + estimated_size = len(image_data) * 0.75 # Base64 is ~33% larger + if estimated_size > max_size_bytes: + return jsonify({ + "status": "error", + "message": f"Image too large. Maximum size is {max_size_bytes / 1024 / 1024}MB" + }), 413dream_layer_frontend/src/components/tabs/txt2img/ImagePreview.tsx (1)
140-179: Fix “Send To Img2Img” for Matrix GridsThe
/api/send-to-img2imgendpoint currently only checks the main output directory (Comfy’s default). Matrix grids are written toMATRIX_GRIDS_DIR, so any attempt to “Send To Img2Img” on a grid will always return 404. You should address this in both UI and backend:• Frontend (dream_layer_frontend/src/components/tabs/txt2img/ImagePreview.tsx):
– InhandleSendTo, wrap or disable the “Send To Img2Img” action whenisMatrixGrid(currentImage)is true.
• Backend (dream_layer_backend/dream_layer.py):
– Extendsend_to_img2imgto fall back toMATRIX_GRIDS_DIR(and/orSERVED_IMAGES_DIR) before returning 404. For example:@@ def send_to_img2img(): - image_path = os.path.join(output_dir, filename) - if not os.path.exists(image_path): - return jsonify({"status": "error", "message": "File not found"}), 404 + image_path = os.path.join(output_dir, filename) + if not os.path.exists(image_path): + # Fallback: check matrix grids + from shared_utils import MATRIX_GRIDS_DIR + grid_path = os.path.join(MATRIX_GRIDS_DIR, filename) + if os.path.exists(grid_path): + image_path = grid_path + else: + return jsonify({"status": "error", "message": "File not found"}), 404Let me know if you’d like a PR-ready diff covering both the UI guard and backend patch.
ComfyUI/comfy_api_nodes/nodes_stability.py (2)
127-166: Only attachfileswhen an image is providedPassing
{"image": None}can produce malformed multipart payloads. Omitfileswhen no image is present.- files = { - "image": image_binary - } + files = {"image": image_binary} if image_binary is not None else None @@ - operation = SynchronousOperation( + operation = SynchronousOperation( endpoint=ApiEndpoint( path="/proxy/stability/v2beta/stable-image/generate/ultra", method=HttpMethod.POST, request_model=StabilityStableUltraRequest, response_model=StabilityStableUltraResponse, ), request=StabilityStableUltraRequest( prompt=prompt, negative_prompt=negative_prompt, aspect_ratio=aspect_ratio, seed=seed, strength=image_denoise, style_preset=style_preset, ), files=files, content_type="multipart/form-data", auth_kwargs=kwargs, )Repeat analogous change in
StabilityStableImageSD_3_5Node,StabilityUpscaleConservativeNode,StabilityUpscaleCreativeNode, andStabilityUpscaleFastNode.
260-305: SD 3.5: Use enum value for aspect ratio default and conditionalfilesSame two issues as above: default type and including files only if present.
🧹 Nitpick comments (54)
ComfyUI/comfy_api_nodes/apis/request_logger.py (3)
39-50: Prefer Mapping over dict in type hints for headers/paramsUse Optional[Mapping[str, Any]] instead of Optional[dict] to better express intent, improve static analysis, and accept any mapping type without forcing concrete dicts.
Apply within this function signature:
- request_headers: Optional[dict] = None, - request_params: Optional[dict] = None, + request_headers: Optional[Mapping[str, Any]] = None, + request_params: Optional[Mapping[str, Any]] = None, @@ - response_headers: Optional[dict] = None, + response_headers: Optional[Mapping[str, Any]] = None,Note: This also requires
from typing import Mappingat the top next to the existing typing imports.
25-37: Bound log payload size to prevent massive log filesLarge JSON/text bodies can explode log size. Truncate formatted strings to a safe maximum.
Apply within this function:
def _format_data_for_logging(data): """Helper to format data (dict, str, bytes) for logging.""" - if isinstance(data, bytes): - try: - return data.decode('utf-8') # Try to decode as text - except UnicodeDecodeError: - return f"[Binary data of length {len(data)} bytes]" - elif isinstance(data, (dict, list)): - try: - return json.dumps(data, indent=2, ensure_ascii=False) - except TypeError: - return str(data) # Fallback for non-serializable objects - return str(data) + try: + max_len = MAX_LOG_CHARS # module-level config if available + except NameError: + max_len = 65536 # sane default + if isinstance(data, bytes): + try: + s = data.decode('utf-8') # Try to decode as text + except UnicodeDecodeError: + return f"[Binary data of length {len(data)} bytes]" + elif isinstance(data, (dict, list)): + try: + s = json.dumps(data, indent=2, ensure_ascii=False) + except TypeError: + s = str(data) # Fallback for non-serializable objects + else: + s = str(data) + if len(s) > max_len: + return s[:max_len] + f"\n...[truncated {len(s)-max_len} chars]" + return sAdd this module-level knob (outside the range) if you want to make it configurable:
# Add near the logger init MAX_LOG_CHARS = int(os.getenv("COMFY_API_LOG_MAX_CHARS", "65536"))
56-57: Sanitize operation_id for filenamesReplacing only '/' and ':' still allows other problematic characters. Sanitize to a safe charset.
Apply here:
- filename = f"{timestamp}_{operation_id.replace('/', '_').replace(':', '_')}.log" + safe_operation_id = re.sub(r'[^A-Za-z0-9_.-]+', '_', operation_id) + filename = f"{timestamp}_{safe_operation_id}.log"Add this import (outside the range):
import reREADME.md (2)
105-107: Fix markdownlint warnings: list style and bare URLs in Access sectionSwitch to asterisks and wrap bare URLs to satisfy MD004/MD034.
- - **Frontend:** http://localhost:8080 - - **ComfyUI:** http://localhost:8188 + * **Frontend:** <http://localhost:8080> + * **ComfyUI:** <http://localhost:8188>
154-161: Docs claim support for Runway/Recraft but not reflected in API mappingTable lists “Runway Gen-4, Recraft V3,” but current API_KEY_TO_MODELS (in docs and code) only include BFL/Flux, OpenAI (DALL·E), and Ideogram. Align claims with actual support, or add corresponding keys/models to the mapping.
dream_layer_frontend/src/components/AliasKeyInputs.tsx (1)
139-139: Fix grammar and improve clarity of the noteCurrent: “Please Click of Refresh Models…” has grammatical issues.
Apply:
- <div className="space-y-6">* Please Click of Refresh Models After Adding the Keys to View the Models</div> + <div className="space-y-6 text-xs text-muted-foreground">Note: After adding API keys, click “Refresh Models” to load available models.</div>dream_layer_backend/dream_layer_backend_utils/workflow_loader.py (1)
75-79: Minor: improve readability and robustness of analyze_workflowCurrent generator returns the first matching batch_size; consider deriving the max or keeping first but make it clearer and resilient if prompt is not a dict.
For clarity:
- is_api = bool(workflow.get('extra_data')) - batch_size = next((node['inputs'].get('batch_size', 1) for node in workflow.get('prompt', {}).values() if 'batch_size' in node.get('inputs', {})), 1) - return {'batch_size': batch_size, 'is_api': is_api} + is_api = bool(workflow.get('extra_data')) + prompt_nodes = getattr(workflow.get('prompt', {}), 'values', lambda: [])() + batch_sizes = [ + node.get('inputs', {}).get('batch_size') + for node in prompt_nodes + if isinstance(node, dict) and 'inputs' in node and 'batch_size' in node.get('inputs', {}) + ] + batch_size = max(batch_sizes) if batch_sizes else 1 + return {'batch_size': batch_size, 'is_api': is_api}docs/usage.md (3)
170-170: Remove trailing whitespace in code exampleMinor formatting: drop the trailing space after the URL to avoid lint noise.
Apply:
- response = requests.post('https://api.openai.com/v1/images/generations', + response = requests.post('https://api.openai.com/v1/images/generations',
216-218: Trim trailing whitespace in ControlNet upload exampleSmall cleanup to keep examples tidy.
Apply:
- response = requests.post('http://localhost:5000/api/upload-controlnet-image', - files=files) + response = requests.post('http://localhost:5000/api/upload-controlnet-image', + files=files)
396-396: Ensure newline at EOF and consistent italicsAdd a newline and remove any trailing spaces at the end of the file.
Apply:
-*For more advanced usage, see the [API Reference](api_reference.md) and [Architecture Guide](architecture.md).* +*For more advanced usage, see the [API Reference](api_reference.md) and [Architecture Guide](architecture.md).* +dream_layer_frontend/src/features/Extras/ExtrasPage.tsx (2)
264-264: Confirm removal of upload guidance is intentionalIf users need to add missing upscalers, consider linking to docs or a tooltip near the Select. Otherwise, this simplification reads fine.
288-288: Description text simplification OKLooks good. If we later add per-model docs, consider a small “Learn more” link.
start_dream_layer.bat (1)
126-150: Fix inconsistent step counters (1/4…4/4, then 5/5)Echo labels show 1/4–4/4 for backend steps, then 5/5 for frontend. Use 1/5–4/5 to match 5 total steps.
-echo %BLUE%[STEP 1/4]%NC% Starting Dream Layer backend... +echo %BLUE%[STEP 1/5]%NC% Starting Dream Layer backend... -echo %BLUE%[STEP 2/4]%NC% Starting txt2img_server.py... +echo %BLUE%[STEP 2/5]%NC% Starting txt2img_server.py... -echo %BLUE%[STEP 3/4]%NC% Starting img2img_server.py... +echo %BLUE%[STEP 3/5]%NC% Starting img2img_server.py... -echo %BLUE%[STEP 4/4]%NC% Starting extras.py... +echo %BLUE%[STEP 4/5]%NC% Starting extras.py... -echo %BLUE%[STEP 5/5]%NC% Starting frontend development server... +echo %BLUE%[STEP 5/5]%NC% Starting frontend development server...dream_layer_backend/img2img_server.py (1)
186-186: Conditionally includeworkflowin API responsesI searched the frontend for any usage of the
"workflow"key and didn’t find any references, so removing it by default shouldn’t break clients. To avoid leaking internal details and reduce payload size, let’s only include it when debugging is explicitly enabled.Please confirm there are no other consumers relying on the full workflow before rolling out this change.
Target: dream_layer_backend/img2img_server.py (around line 186)
Suggested diff:
--- a/dream_layer_backend/img2img_server.py +++ b/dream_layer_backend/img2img_server.py @@ -183,7 +183,13 @@ def send_to_comfyui(...): - response = jsonify({ - "status": "success", - "message": "Workflow sent to ComfyUI successfully", - "comfy_response": comfy_response, - "workflow": workflow - }) + include_workflow = os.getenv("DREAMLAYER_DEBUG_RESPONSES", "").lower() in ("1", "true", "yes") + payload = { + "status": "success", + "message": "Workflow sent to ComfyUI successfully", + "comfy_response": comfy_response, + } + if include_workflow: + payload["workflow"] = workflow + response = jsonify(payload)start_dream_layer.sh (1)
173-183: Safer, extension-agnostic cleanup that preserves all matrix gridsGood move preserving matrix grids. Consider deleting all non-grid files regardless of extension and optionally log deletions.
- if [ -d "dream_layer_backend/served_images" ]; then - # Remove only non-matrix files from served_images - find dream_layer_backend/served_images -name "*.png" ! -name "matrix-grid-*" -delete 2>/dev/null || true - print_success "Cleaned up temporary images (matrix grids preserved)" - fi + if [ -d "dream_layer_backend/served_images" ]; then + # Remove all non-matrix files (any extension), keep .gitkeep + find dream_layer_backend/served_images -type f \ + ! -name "matrix-grid-*" \ + ! -name ".gitkeep" \ + -print -delete 2>/dev/null || true + print_success "Cleaned up temporary files in served_images (matrix grids preserved)" + fidream_layer_frontend/src/services/modelService.ts (1)
207-207: Optional: add jitter to reconnection delay to avoid sync stormsFixed 2s backoff can cause thundering herd on mass disconnects. Consider exponential backoff with jitter.
-const RECONNECT_DELAY = 2000; +const BASE_RECONNECT_DELAY = 1000; +const MAX_RECONNECT_DELAY = 15000;And compute delay on reconnect:
const attemptDelay = Math.min( BASE_RECONNECT_DELAY * Math.pow(2, wsReconnectAttempts), MAX_RECONNECT_DELAY ); // Add +/- 20% jitter const jitter = attemptDelay * (0.2 * (Math.random() - 0.5)); const delay = Math.max(500, Math.floor(attemptDelay + jitter)); setTimeout(() => { /* reconnect */ }, delay);dream_layer_backend/dream_layer_backend_utils/api_key_injector.py (4)
70-71: Use direct dict iteration instead of .keys() (SIM118)Simplify loop and satisfy linter.
- for env_key in ENV_KEY_TO_EXTRA_DATA_MAPPING.keys(): + for env_key in ENV_KEY_TO_EXTRA_DATA_MAPPING:
112-119: Rename unused loop variable to underscore (B007)node_id isn’t used; rename to _node_id for clarity.
- for node_id, node_data in workflow_prompt.items(): + for _node_id, node_data in workflow_prompt.items():
141-141: Remove f-prefix from string without placeholders (F541)- print(f"[DEBUG] Injected api_key_comfy_org into workflow") + print("[DEBUG] Injected api_key_comfy_org into workflow")
109-111: Optional: scan from the working copy for consistencyNot a bug, but use workflow_with_keys for introspection to keep reads/writes consistent.
- workflow_prompt = workflow.get('prompt', {}) + workflow_prompt = workflow_with_keys.get('prompt', {})dream_layer_frontend/src/stores/useMatrixStore.ts (1)
16-24: Persisted rehydration: normalize ‘running’ jobs and resume from next pendingOn reload, any ‘running’ jobs should become ‘pending’ and currentJobIndex should point to the next incomplete job. Use persist’s onRehydrateStorage.
Example (adjust for your codebase):
export const useMatrixStore = create<MatrixState>()( persist( (set, get) => ({ // ...state & actions... }), { name: 'matrix-generation-storage', version: 1, onRehydrateStorage: () => (state, error) => { if (error) return; // Normalize after hydration const s = get(); const jobs = s.jobs?.map(j => j.status === 'running' ? { ...j, status: 'pending' } : j) ?? []; const firstPending = jobs.findIndex(j => j.status !== 'completed'); set({ jobs, currentJobIndex: firstPending === -1 ? jobs.length : firstPending, isPaused: true, // resume only when user explicitly starts }); }, partialize: (state) => ({ jobs: state.jobs, currentJobIndex: state.currentJobIndex, isPaused: state.isPaused, }), } ) );I can adapt this into a PR-ready patch if helpful.
Also applies to: 45-47
ComfyUI/comfy_api_nodes/nodes_gemini.py (1)
315-349: Optional: Add a minimal error boundary for API call to improve node UXWith try/except removed, exceptions propagate unhandled and may degrade ComfyUI UX. Consider wrapping the API call to send a concise error via PromptServer and re-raise.
- response = SynchronousOperation( - endpoint=get_gemini_endpoint(model), - request=GeminiGenerateContentRequest( - contents=[ - GeminiContent( - role="user", - parts=parts, - ) - ] - ), - auth_kwargs=kwargs, - ).execute() + try: + response = SynchronousOperation( + endpoint=get_gemini_endpoint(model), + request=GeminiGenerateContentRequest( + contents=[GeminiContent(role="user", parts=parts)] + ), + auth_kwargs=kwargs, + ).execute() + except Exception as e: + if unique_id: + PromptServer.instance.send_progress_text(f"Gemini call failed: {e}", node_id=unique_id) + raisedream_layer_frontend/src/stores/useTxt2ImgGalleryStore.ts (1)
19-23: Optional: cap gallery size to prevent unbounded storage growthPersisting all images can bloat local storage. Consider capping to N most recent images.
- addImages: (newImages) => set((state) => ({ - images: [...newImages, ...state.images], - isLoading: false - })), + addImages: (newImages) => set((state) => { + const images = [...newImages, ...state.images].slice(0, 200); + return { images, isLoading: false }; + }),dream_layer_frontend/src/components/MatrixSettings.tsx (2)
31-32: Strengthen typing for usedParameters to MatrixParameterName[]Improves type safety and autocompletion.
- usedParameters: string[]; + usedParameters: MatrixParameterName[];- const getUsedParameters = (currentAxis: 'xAxis' | 'yAxis' | 'zAxis'): string[] => { + const getUsedParameters = (currentAxis: 'xAxis' | 'yAxis' | 'zAxis'): MatrixParameterName[] => { const used: any[] = []; // existing logic unchanged return used as MatrixParameterName[]; };Also applies to: 143-155
221-224: Copy tweak for clarity“Keep -1 for seeds” may be unclear to users. Consider: “Keep seed = -1 fixed per row”.
dream_layer_backend/txt2img_workflow.py (2)
146-146: Remove unnecessary f-string prefixesThese print statements don't contain any placeholders and don't need the f-string prefix.
- print(f"✅ Workflow loaded successfully") + print("✅ Workflow loaded successfully") - print(f"✅ API keys injected") + print("✅ API keys injected") - print(f"✅ Core settings applied") + print("✅ Core settings applied")Also applies to: 149-149, 165-165
168-191: Remove unnecessary f-string prefixes in logging statementsMultiple logging statements use f-strings without placeholders.
- print(f"🎨 Applying LoRA parameters...") + print("🎨 Applying LoRA parameters...") - print(f"🎮 Applying ControlNet parameters...") + print("🎮 Applying ControlNet parameters...") - print(f"👤 Applying Face Restoration parameters...") + print("👤 Applying Face Restoration parameters...") - print(f"🧩 Applying Tiling parameters...") + print("🧩 Applying Tiling parameters...") - print(f"✨ Applying Hires.fix parameters...") + print("✨ Applying Hires.fix parameters...") - print(f"✨ Applying Refiner parameters...") + print("✨ Applying Refiner parameters...") - print(f"✅ Workflow transformation complete") + print("✅ Workflow transformation complete")dream_layer_frontend/src/utils/matrixGridGenerator.ts (2)
3-11: Add timeout for image loadingThe
loadImagefunction doesn't have a timeout mechanism, which could hang indefinitely if an image fails to load.Consider adding a timeout:
const loadImage = (url: string): Promise<HTMLImageElement> => { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error(`Image load timeout for ${url}`)); + }, 30000); // 30 second timeout + const img = new Image(); img.crossOrigin = 'anonymous'; - img.onload = () => resolve(img); - img.onerror = (err) => reject(new Error(`Failed to load image at ${url}: ${err}`)); + img.onload = () => { + clearTimeout(timeout); + resolve(img); + }; + img.onerror = (err) => { + clearTimeout(timeout); + reject(new Error(`Failed to load image at ${url}: ${err}`)); + }; img.src = url; }); };
117-124: Improve error handling for empty job resultsThe error message for jobs with no results could be more informative.
const imagePromises = completedJobs.map(async (job) => { if (!job.result || job.result.length === 0) { - throw new Error(`Job has no result images: ${JSON.stringify(job)}`); + throw new Error(`Job ${job.id} has no result images. Status: ${job.status}, Error: ${job.error || 'None'}`); }dream_layer_backend/txt2img_server.py (2)
14-17: Remove unused importsSeveral imports are not used in this file.
-import platform -import subprocess import traceback -from shared_utils import SERVED_IMAGES_DIR, serve_image, MATRIX_GRIDS_DIR +from shared_utils import serve_image, MATRIX_GRIDS_DIR
183-183: Remove unnecessary f-string prefixesThese print statements don't contain any placeholders.
- print(f"🔄 Matrix grid save request received") + print("🔄 Matrix grid save request received") - print(f"💾 Matrix grid saved to permanent storage") + print("💾 Matrix grid saved to permanent storage")Also applies to: 220-220
docs/modules/utils.md (1)
421-436: Prevent FileHandler failures: create logs directory before configuring loggingIf the logs directory doesn’t exist,
logging.FileHandler('logs/dream_layer.log')will raise an error. Create it proactively.+import os + def setup_logging(log_level: str = "INFO"): """ Setup logging configuration ... """ - logging.basicConfig( + os.makedirs('logs', exist_ok=True) + logging.basicConfig( level=getattr(logging, log_level.upper()), format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler('logs/dream_layer.log'), logging.StreamHandler() ] )dream_layer_frontend/src/components/tabs/txt2img/ImagePreview.tsx (5)
77-100: ZIP download: avoid opening a blank tab and use consistent filenameNo need to set
target="_blank"for programmatic downloads. Also align naming for consistency.- link.target = '_blank'; - link.download = 'generated-images.zip'; + link.download = 'generated-images.zip';
224-226: “Inpaint” destination unimplemented; hide the button until supportedButton triggers a no-op path. Better UX to hide/disable it until the endpoint exists.
- <Button + {/* Hide Inpaint until endpoint exists */} + {false && <Button variant="outline" size="sm" className="text-xs h-8" onClick={() => handleSendTo('inpaint')} > Inpaint - </Button> + </Button>}
449-453: Provide a fallback for broken thumbnails instead of hiding the imgHiding the image leaves an empty clickable area. Render a placeholder instead to keep the grid visually consistent.
- onError={(e) => { - const img = e.target as HTMLImageElement; - img.style.display = 'none'; - }} + onError={(e) => { + const img = e.target as HTMLImageElement; + img.replaceWith(Object.assign(document.createElement('div'), { + className: 'w-full h-full bg-muted flex items-center justify-center text-[10px] text-muted-foreground', + innerText: 'Thumb failed', + })); + }}
253-322: Remove dead code:formatSettingsDisplayappears unused and contains stale stringsThis function isn’t referenced in the component and includes hardcoded “Version/Networks/Time taken” text. Safe to delete to reduce noise.
324-383: Matrix container UX: consider progressive loading and zoomMatrix grids are large. Consider adding:
- a loading skeleton until onLoad,
- a click-to-zoom modal (or pinch-to-zoom),
- keyboard scroll hints.
dream_layer_backend/dream_layer.py (3)
17-21: Remove unused importread_api_keys_from_envIt’s imported at module scope but only used via a local import in
get_available_models().-from dream_layer_backend_utils import read_api_keys_from_env
163-167: Rename unused loop variable_api_key_valueAvoids linter warnings and clarifies intent.
- for api_key_name, api_key_value in api_keys.items(): + for api_key_name, _api_key_value in api_keys.items():
587-655:/api/save-single-image: overall good; minor lint fixes and portability
- Remove
f-strings without placeholders (lint F541).- Consider normalizing returned
urlto the same image-serving port used elsewhere (5001 vs 5002) for consistency, if applicable.- print(f"🔄 Single image save request received on port 5002") + print("🔄 Single image save request received on port 5002") @@ - print(f"💾 Single image saved to permanent storage") + print("💾 Single image saved to permanent storage")ComfyUI/comfy_api_nodes/nodes_stability.py (4)
69-76: Deduplicate and tighten tooltip textThe tooltip repeats the same sentence twice. Trim to a single, concise sentence to improve UX.
- "tooltip": "What you wish to see in the output image. A strong, descriptive prompt that clearly defines" + - "What you wish to see in the output image. A strong, descriptive prompt that clearly defines" + - "elements, colors, and subjects will lead to better results. " + + "tooltip": "A strong, descriptive prompt that clearly defines elements, colors, and subjects will lead to better results. " +
377-407: Upscale Conservative: minor polish
- Round creativity to two decimals (good).
- Apply conditional
filespattern to avoidNonepayload.
483-515: Upscale Creative: conditionalfilesand consistent roundingMirror the conditional
filesand apply rounding as you did in the conservative node.
566-586: Upscale Fast: conditionalfiles
files={"image": image_binary}should only be included when an image exists (it always does here), but following the same pattern improves consistency across nodes.dream_layer_frontend/src/features/Txt2Img/Txt2ImgPage.tsx (10)
69-95: Make parameter parsing more robust and respect theenabledflag
- The current range parsing is brittle for negatives (e.g., "-1-3") and ignores
parameter.enabled.- Consider handling negative ranges and descending ranges, and short-circuit when a parameter is disabled.
Proposed diff to improve parsing:
-const parseParameterValues = (parameter: MatrixParameter): any[] => { - if (parameter.name === 'Nothing' || !parameter.values?.trim()) { +const parseParameterValues = (parameter: MatrixParameter): any[] => { + if (!parameter?.enabled || parameter.name === 'Nothing' || !parameter.values?.trim()) { console.log(`Parameter ${parameter.name} is Nothing or has no values`); return []; } const str = parameter.values.trim(); console.log(`Parsing parameter ${parameter.name} with values: "${str}", type: ${parameter.type}`); - if (parameter.type === 'range' && str.includes('-')) { - const [start, end] = str.split('-').map(s => Number(s.trim())); - if (!isNaN(start) && !isNaN(end) && start <= end) { - const result = Array.from({ length: end - start + 1 }, (_, i) => start + i); + if (parameter.type === 'range') { + const match = str.match(/^\s*(-?\d+)\s*-\s*(-?\d+)\s*$/); + if (match) { + const start = Number(match[1]); + const end = Number(match[2]); + if (!isNaN(start) && !isNaN(end)) { + const step = start <= end ? 1 : -1; + const len = Math.abs(end - start) + 1; + const result = Array.from({ length: len }, (_, i) => start + i * step); console.log(`Range result for ${parameter.name}:`, result); return result; } } } const result = str.split(',').map(v => { const trimmed = v.trim(); const num = Number(trimmed); return isNaN(num) ? trimmed : num; }).filter(v => v !== ''); console.log(`List result for ${parameter.name}:`, result); return result; }
97-118: Centralize parameter-to-setting mapping and clarify seed behavior
- The switch relies on string literals for parameter names. This is brittle. A centralized mapping keyed by MatrixParameterName reduces typos and keeps UI/logic aligned.
- The log "Keep -1 for seeds is enabled" is confusing. Consider clearer semantics: if keepSeedsForRows is true, always use -1/random seed; otherwise, set explicit seed.
If desired, I can provide a small helper like:
const applyParam = (s, name, v, opts) => { /* map by enum and types */ }
121-168: Consider honoring disabled axes here too and ensure settings are deeply isolated
- You already guard on axis name/values, but if an axis is disabled (
enabled === false) while name/values are set, jobs still get created. Using the improvedparseParameterValues(which respectsenabled) will address this.- Shallow-copying
coreSettingsis fine since you only mutate known scalar fields, but if nested fields are added later, prefer a safer clone for long-term maintainability.
170-272: Avoid base64 for single-image persistence; use FormData and env-configured URLs
- Converting blobs to base64 is memory-heavy and slower. Prefer FormData upload with binary content.
- Backend URLs are hard-coded (http://localhost:5001, http://localhost:5002). Use env vars (e.g., import.meta.env.VITE_API_URL) to support deployments.
Example adjustments:
- Switch to FormData for the image save:
-const imageResponse = await fetch(img.url); -const imageBlob = await imageResponse.blob(); -const reader = new FileReader(); -const base64Data = await new Promise<string>((resolve) => { - reader.onloadend = () => resolve(reader.result as string); - reader.readAsDataURL(imageBlob); -}); -const saveResponse = await fetch('http://localhost:5002/api/save-single-image', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ imageData: base64Data, originalFilename: img.url.split('/').pop() }) -}); +const imageResponse = await fetch(img.url); +const imageBlob = await imageResponse.blob(); +const form = new FormData(); +form.append('file', imageBlob, img.url.split('/').pop() || 'image.png'); +const saveResponse = await fetch(`${import.meta.env.VITE_MEDIA_API_URL}/api/save-single-image`, { + method: 'POST', + body: form +});
- Parameterize API base URLs:
-const response = await fetch('http://localhost:5001/api/txt2img', { +const response = await fetch(`${import.meta.env.VITE_API_URL}/api/txt2img`, {Similarly for interrupt and save endpoints.
536-540: Copy prompts from state, not the DOMQuerying textareas by placeholder is fragile. Use
coreSettingsdirectly.-const handleCopyPrompts = () => { - const promptTextarea = document.querySelector('textarea[placeholder="Enter your prompt here"]') as HTMLTextAreaElement; - const negativePromptTextarea = document.querySelector('textarea[placeholder="Enter negative prompt here"]') as HTMLTextAreaElement; - if (promptTextarea && negativePromptTextarea) navigator.clipboard.writeText(`Prompt: ${promptTextarea.value}\nNegative Prompt: ${negativePromptTextarea.value}`); -}; +const handleCopyPrompts = () => { + navigator.clipboard.writeText( + `Prompt: ${coreSettings.prompt || ''}\nNegative Prompt: ${coreSettings.negative_prompt || ''}` + ); +};
301-317: Pause flow: consider aborting the in-flight requestYou interrupt the backend and reset the current job to 'pending', then set paused. The browser fetch continues until the backend returns. To make pause snappier and avoid wasted client work, wire an AbortController per running job and abort it here.
Happy to sketch a small
useRef<AbortController | null>addition and passsignaltofetch(), resetting the controller per job.
185-189: Parameterize API endpoints with env varsHard-coded URLs hamper deployment and dev configs. Use Vite env vars.
-const response = await fetch('http://localhost:5001/api/txt2img', { +const response = await fetch(`${import.meta.env.VITE_API_URL}/api/txt2img`, {and
-const saveResponse = await fetch('http://localhost:5002/api/save-single-image', { +const saveResponse = await fetch(`${import.meta.env.VITE_MEDIA_API_URL}/api/save-single-image`, {Also applies to: 224-231
578-602: Progress indicator: consider basing it on statuses for clarityCurrently it shows index+1 when generating. Using the count of completed jobs (plus 1 if running) is clearer and resilient to skips/retries.
I can adjust Progress to compute:
- completed = jobs.filter(j => j.status === 'completed').length
- running = jobs[currentJobIndex]?.status === 'running' ? 1 : 0
- display:
${completed + running}/${jobs.length}
424-459: Fire-and-forget sub-grid generation may interleave with main resetSub-grids are launched via
forEach(async ...)and not awaited; then the main gridfinallyresets store and clears loading. This is likely fine UX-wise, but be aware sub-grids may populate after the loader disappears.If you prefer deterministic ordering:
- await sub-grids with
for (const z of zValues) { await ... }orPromise.all.- Only reset after both main and sub-grids finish.
Also applies to: 461-507
67-168: Add lightweight tests for parameter parsing and job expansionGiven the matrix runner is core to this PR, tests for:
- parseParameterValues (lists, ranges, negatives, disabled)
- generateMatrixJobs (combinatorics, seed handling)
- grid metadata axes ordering
will prevent regressions.
I can add vitest tests for these functions if desired.
Also applies to: 407-510
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (54)
ComfyUI/comfy_api_nodes/apis/request_logger.py(2 hunks)ComfyUI/comfy_api_nodes/nodes_gemini.py(2 hunks)ComfyUI/comfy_api_nodes/nodes_stability.py(14 hunks)ComfyUI/user/default/comfy.settings.json(1 hunks)GEMINI_INTEGRATION_DEMO.md(0 hunks)README.md(6 hunks)docs/api_reference.md(4 hunks)docs/installation.md(1 hunks)docs/modules/utils.md(4 hunks)docs/usage.md(3 hunks)dream_layer_backend/controlnet.py(1 hunks)dream_layer_backend/dream_layer.py(15 hunks)dream_layer_backend/dream_layer_backend_utils/api_key_injector.py(3 hunks)dream_layer_backend/dream_layer_backend_utils/workflow_loader.py(3 hunks)dream_layer_backend/extras.py(1 hunks)dream_layer_backend/img2img_server.py(2 hunks)dream_layer_backend/img2img_workflow.py(8 hunks)dream_layer_backend/img2txt_server.py(0 hunks)dream_layer_backend/report_bundle.py(0 hunks)dream_layer_backend/run_registry.py(0 hunks)dream_layer_backend/shared_utils.py(6 hunks)dream_layer_backend/tests/test_report_bundle.py(0 hunks)dream_layer_backend/tests/test_run_registry.py(0 hunks)dream_layer_backend/txt2img_server.py(3 hunks)dream_layer_backend/txt2img_workflow.py(5 hunks)dream_layer_backend/workflows/img2img/gemini_multimodal_workflow.json(0 hunks)dream_layer_backend/workflows/img2img/stability_core_generation_workflow.json(0 hunks)dream_layer_backend/workflows/txt2img/gemini_core_generation_workflow.json(0 hunks)dream_layer_backend/workflows/txt2img/stability_core_generation_workflow.json(0 hunks)dream_layer_frontend/src/components/AliasKeyInputs.tsx(2 hunks)dream_layer_frontend/src/components/MatrixSettings.tsx(1 hunks)dream_layer_frontend/src/components/Navigation/TabsNav.tsx(1 hunks)dream_layer_frontend/src/components/tabs/txt2img/ImagePreview.tsx(10 hunks)dream_layer_frontend/src/features/Extras/ExtrasPage.tsx(3 hunks)dream_layer_frontend/src/features/Img2Txt/Img2TxtPage.tsx(0 hunks)dream_layer_frontend/src/features/Img2Txt/index.ts(0 hunks)dream_layer_frontend/src/features/ReportBundle/ReportBundlePage.tsx(0 hunks)dream_layer_frontend/src/features/ReportBundle/index.ts(0 hunks)dream_layer_frontend/src/features/RunRegistry/RunRegistryPage.tsx(0 hunks)dream_layer_frontend/src/features/RunRegistry/index.ts(0 hunks)dream_layer_frontend/src/features/Txt2Img/Txt2ImgPage.tsx(2 hunks)dream_layer_frontend/src/pages/Index.tsx(0 hunks)dream_layer_frontend/src/services/modelService.ts(4 hunks)dream_layer_frontend/src/stores/useMatrixStore.ts(1 hunks)dream_layer_frontend/src/stores/useReportBundleStore.ts(0 hunks)dream_layer_frontend/src/stores/useRunRegistryStore.ts(0 hunks)dream_layer_frontend/src/stores/useTxt2ImgGalleryStore.ts(2 hunks)dream_layer_frontend/src/types/generationSettings.ts(2 hunks)dream_layer_frontend/src/types/reportBundle.ts(0 hunks)dream_layer_frontend/src/types/runRegistry.ts(0 hunks)dream_layer_frontend/src/utils/matrixGridGenerator.ts(1 hunks)start_dream_layer.bat(1 hunks)start_dream_layer.sh(1 hunks)test_gemini_integration.py(0 hunks)
💤 Files with no reviewable changes (22)
- dream_layer_frontend/src/features/Img2Txt/index.ts
- dream_layer_frontend/src/stores/useRunRegistryStore.ts
- dream_layer_frontend/src/features/RunRegistry/index.ts
- dream_layer_frontend/src/features/ReportBundle/index.ts
- dream_layer_backend/workflows/img2img/stability_core_generation_workflow.json
- dream_layer_backend/workflows/txt2img/gemini_core_generation_workflow.json
- dream_layer_backend/workflows/txt2img/stability_core_generation_workflow.json
- dream_layer_backend/tests/test_run_registry.py
- dream_layer_frontend/src/pages/Index.tsx
- dream_layer_backend/tests/test_report_bundle.py
- dream_layer_frontend/src/types/runRegistry.ts
- dream_layer_frontend/src/types/reportBundle.ts
- dream_layer_backend/workflows/img2img/gemini_multimodal_workflow.json
- dream_layer_frontend/src/stores/useReportBundleStore.ts
- test_gemini_integration.py
- dream_layer_backend/img2txt_server.py
- dream_layer_frontend/src/features/Img2Txt/Img2TxtPage.tsx
- dream_layer_backend/run_registry.py
- dream_layer_backend/report_bundle.py
- dream_layer_frontend/src/features/ReportBundle/ReportBundlePage.tsx
- dream_layer_frontend/src/features/RunRegistry/RunRegistryPage.tsx
- GEMINI_INTEGRATION_DEMO.md
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-07-16T18:40:41.273Z
Learnt from: divyaprakash0426
PR: DreamLayer-AI/DreamLayer#40
File: docker/Dockerfile.backend.dev:4-6
Timestamp: 2025-07-16T18:40:41.273Z
Learning: The DreamLayer project follows an iterative approach to Docker development, where basic Docker setup is established first, and missing dependencies (like PyYAML) are addressed in subsequent iterations when related services (like ComfyUI) are added to the Docker files.
Applied to files:
docs/installation.mdREADME.mdstart_dream_layer.batstart_dream_layer.shdocs/modules/utils.md
📚 Learning: 2025-07-16T19:15:10.526Z
Learnt from: divyaprakash0426
PR: DreamLayer-AI/DreamLayer#40
File: docker/Dockerfile.backend.dev:7-8
Timestamp: 2025-07-16T19:15:10.526Z
Learning: In Docker development environments with bind-mounted source code, Flask applications can achieve hot-reload without changing the CMD directive if the Flask app is started with debug mode enabled (app.run(debug=True) or FLASK_DEBUG=1 environment variable). Flask's built-in Werkzeug reloader can watch files inside bind-mounted volumes and restart the server process within the same container when changes are detected, eliminating the need for container rebuilds on source code changes.
Applied to files:
docs/installation.md
📚 Learning: 2025-07-14T22:55:51.063Z
Learnt from: rockerBOO
PR: DreamLayer-AI/DreamLayer#28
File: dream_layer_frontend/src/components/WorkflowCustomNode.tsx:88-106
Timestamp: 2025-07-14T22:55:51.063Z
Learning: In the DreamLayer frontend codebase, the team prefers to rely on TypeScript's type system for data validation rather than adding defensive programming checks, when the types are well-defined and data flow is controlled.
Applied to files:
docs/installation.mdREADME.md
📚 Learning: 2025-08-06T18:59:31.412Z
Learnt from: rachanavarsha
PR: DreamLayer-AI/DreamLayer#80
File: report_bundler/bundler.py:7-7
Timestamp: 2025-08-06T18:59:31.412Z
Learning: In report_bundler/bundler.py, the REQUIRED_COLUMNS set intentionally contains only essential fields (image_path, sampler, steps, cfg, preset, seed) needed for bundling logic. Additional columns like width, height, grid_label, and notes are treated as optional metadata.
Applied to files:
ComfyUI/comfy_api_nodes/nodes_stability.py
🧬 Code Graph Analysis (6)
dream_layer_backend/img2img_server.py (2)
dream_layer_backend/dream_layer.py (1)
serve_image(561-571)dream_layer_backend/shared_utils.py (1)
serve_image(215-282)
dream_layer_frontend/src/components/MatrixSettings.tsx (1)
dream_layer_frontend/src/types/generationSettings.ts (5)
MatrixParameter(166-171)MatrixParameterName(164-164)MATRIX_PARAMETERS(153-162)MatrixSettings(173-181)defaultMatrixSettings(201-209)
dream_layer_frontend/src/stores/useMatrixStore.ts (1)
dream_layer_frontend/src/types/generationSettings.ts (1)
MatrixJob(183-192)
dream_layer_frontend/src/features/Txt2Img/Txt2ImgPage.tsx (5)
dream_layer_frontend/src/types/generationSettings.ts (6)
defaultMatrixSettings(201-209)MatrixParameter(166-171)Txt2ImgCoreSettings(140-142)MatrixJob(183-192)ImageResult(125-137)MatrixSettings(173-181)dream_layer_frontend/src/stores/useMatrixStore.ts (1)
useMatrixStore(16-49)dream_layer_frontend/src/stores/useTxt2ImgGalleryStore.ts (1)
useTxt2ImgGalleryStore(14-36)dream_layer_frontend/src/utils/matrixGridGenerator.ts (1)
generateMatrixGrid(93-332)dream_layer_frontend/src/lib/utils.ts (1)
cn(4-6)
dream_layer_frontend/src/utils/matrixGridGenerator.ts (1)
dream_layer_frontend/src/types/generationSettings.ts (2)
MatrixJob(183-192)MatrixSettings(173-181)
ComfyUI/comfy_api_nodes/nodes_stability.py (1)
ComfyUI/comfy_api_nodes/nodes_gemini.py (1)
api_call(304-351)
🪛 markdownlint-cli2 (0.17.2)
README.md
105-105: Unordered list style
Expected: asterisk; Actual: dash
(MD004, ul-style)
105-105: Bare URL used
(MD034, no-bare-urls)
106-106: Unordered list style
Expected: asterisk; Actual: dash
(MD004, ul-style)
106-106: Bare URL used
(MD034, no-bare-urls)
🪛 LanguageTool
README.md
[style] ~182-~182: Try using a synonym here to strengthen your writing.
Context: ...g roles. * Closed Beta Invites: Give feedback that shapes 1.0 * **Discord ...
(GIVE_PROVIDE)
[locale-violation] ~185-~185: This word is British English. Did you mean “first floor”?
Context: ...r button right now** and join us at the ground floor
(GROUND_FIRST_FLOOR)
🪛 Ruff (0.12.2)
dream_layer_backend/dream_layer_backend_utils/api_key_injector.py
70-70: Use key in dict instead of key in dict.keys()
Remove .keys()
(SIM118)
112-112: Loop control variable node_id not used within loop body
Rename unused node_id to _node_id
(B007)
141-141: f-string without any placeholders
Remove extraneous f prefix
(F541)
dream_layer_backend/txt2img_workflow.py
5-5: Redefinition of unused json from line 1
Remove definition: json
(F811)
146-146: f-string without any placeholders
Remove extraneous f prefix
(F541)
149-149: f-string without any placeholders
Remove extraneous f prefix
(F541)
165-165: f-string without any placeholders
Remove extraneous f prefix
(F541)
168-168: f-string without any placeholders
Remove extraneous f prefix
(F541)
172-172: f-string without any placeholders
Remove extraneous f prefix
(F541)
176-176: f-string without any placeholders
Remove extraneous f prefix
(F541)
180-180: f-string without any placeholders
Remove extraneous f prefix
(F541)
184-184: f-string without any placeholders
Remove extraneous f prefix
(F541)
188-188: f-string without any placeholders
Remove extraneous f prefix
(F541)
191-191: f-string without any placeholders
Remove extraneous f prefix
(F541)
dream_layer_backend/img2img_workflow.py
77-77: Local variable denoising_strength is assigned to but never used
Remove assignment to unused variable denoising_strength
(F841)
85-85: Local variable clip_skip is assigned to but never used
Remove assignment to unused variable clip_skip
(F841)
86-86: Local variable tiling is assigned to but never used
Remove assignment to unused variable tiling
(F841)
87-87: Local variable hires_fix is assigned to but never used
Remove assignment to unused variable hires_fix
(F841)
88-88: Local variable karras_sigmas is assigned to but never used
Remove assignment to unused variable karras_sigmas
(F841)
dream_layer_backend/txt2img_server.py
14-14: platform imported but unused
Remove unused import: platform
(F401)
15-15: subprocess imported but unused
Remove unused import: subprocess
(F401)
17-17: shared_utils.SERVED_IMAGES_DIR imported but unused
Remove unused import: shared_utils.SERVED_IMAGES_DIR
(F401)
183-183: f-string without any placeholders
Remove extraneous f prefix
(F541)
220-220: f-string without any placeholders
Remove extraneous f prefix
(F541)
dream_layer_backend/dream_layer.py
17-17: dream_layer_backend_utils.read_api_keys_from_env imported but unused
Remove unused import: dream_layer_backend_utils.read_api_keys_from_env
(F401)
163-163: Loop control variable api_key_value not used within loop body
Rename unused api_key_value to _api_key_value
(B007)
593-593: f-string without any placeholders
Remove extraneous f prefix
(F541)
627-627: f-string without any placeholders
Remove extraneous f prefix
(F541)
dream_layer_backend/shared_utils.py
7-7: sys imported but unused
Remove unused import: sys
(F401)
9-9: flask.send_file imported but unused
Remove unused import
(F401)
9-9: flask.jsonify imported but unused
Remove unused import
(F401)
12-12: dream_layer_backend_utils.workflow_loader.analyze_workflow imported but unused
Remove unused import: dream_layer_backend_utils.workflow_loader.analyze_workflow
(F401)
257-257: Undefined name get_directories
(F821)
🪛 ast-grep (0.38.6)
dream_layer_backend/dream_layer.py
[warning] 279-279: Detected Flask app with debug=True. Do not deploy to production with this flag enabled as it will leak sensitive information. Instead, consider using Flask configuration variables or setting 'debug' using system environment variables.
Context: app.run(host='0.0.0.0', port=5002, debug=True, use_reloader=False)
Note: [CWE-489] Active Debug Code. [REFERENCES]
- https://labs.detectify.com/2015/10/02/how-patreon-got-hacked-publicly-exposed-werkzeug-debugger/
(debug-enabled-python)
🔇 Additional comments (23)
ComfyUI/comfy_api_nodes/apis/request_logger.py (1)
6-6: Compat change to typing imports looks goodSwitching to Optional/Any aligns with Python 3.8/3.9 compatibility and avoids PEP 604 unions. No functional impact.
README.md (4)
18-20: Product Vision links addition looks goodAdding the “AI Art” link and separator improves discoverability. No issues.
32-35: Consistent list style for target usersSwitch to asterisks is consistent with the rest of the doc and common markdownlint rules.
147-148: Helpful note on auto-dependency setupGood addition; sets expectations for users. No code impact.
119-125: .env location confirmed – the backend’s API key injector loads from the project root’s.env(i.e.dream_layer/.env), matching the README instructions. No changes required here.ComfyUI/user/default/comfy.settings.json (1)
2-2: Confirm removal of Comfy release/image-fit settings is safeIf any code or UI logic referenced
Comfy.Queue.ImageFitor release info, verify no regressions. Also confirm ComfyUI won’t immediately re-add these keys in a conflicting way on startup.dream_layer_backend/extras.py (1)
316-320: No port collision detected: each Flask service uses a unique portAll backend servers bind to distinct ports, so no two Flask apps will conflict:
- dream_layer_backend/txt2img_server.py → port 5001
- dream_layer_backend/dream_layer.py → port 5002
- dream_layer_backend/extras.py → port 5003
- dream_layer_backend/img2img_server.py → port 5004
No changes needed here.
docs/api_reference.md (4)
24-31: Response format clarity improvedAdding
statusand detailing fields is good. No issues.
85-88: Request format indentation/readability improvedLooks good.
158-167: Upload response schema tightenedGood structure; aligns with typical responses elsewhere in the doc.
183-192: Docs sayGET /api/images/<filename>; extras.py returns/images/<filename>There’s a mismatch between documentation and the URL emitted by
extras.py. Either update extras to use/api/images(and ensure it serves from the same directory) or add a/imagesroute there. See the extras.py comment for concrete fixes.Likely an incorrect or invalid review comment.
dream_layer_backend/controlnet.py (1)
2-4: Top-level imports: good cleanupConsolidating imports at module scope improves readability and avoids repeated imports.
dream_layer_frontend/src/components/Navigation/TabsNav.tsx (1)
2-8: Imports cleanup looks correctOnly the required Lucide icons are imported and used; good for bundle size and clarity.
dream_layer_backend/dream_layer_backend_utils/workflow_loader.py (1)
27-28: Model name containment check for Ideogram is appropriateSwitching to substring containment (“ideogram” in model_name) makes selection more robust with varied naming. LGTM.
dream_layer_frontend/src/features/Extras/ExtrasPage.tsx (2)
252-252: Copy improvement LGTM“Select an upscaling model” is clearer guidance.
276-276: Copy improvement LGTMConsistent placeholder text for both selects is good UX.
dream_layer_backend/img2img_server.py (1)
10-10: Top-level import of serve_image is fineBringing
serve_imageto module scope is OK and avoids repeated imports. No circular import concerns givenserve_imageimports Flask lazily inside the function.dream_layer_frontend/src/services/modelService.ts (1)
239-239: Early return guard LGTMSkipping Blob messages is fine if those frames carry binary payloads we don’t process here.
dream_layer_frontend/src/stores/useTxt2ImgGalleryStore.ts (1)
14-36: LGTM — persistence is correctly scoped to images onlyThe persist + partialize setup keeps transient isLoading out of storage. Image prepending is fine.
dream_layer_frontend/src/types/generationSettings.ts (1)
132-209: Well-structured matrix generation typesThe new matrix generation types and interfaces are well-designed with appropriate defaults and type safety. The separation of concerns between
MatrixParameter,MatrixSettings, andMatrixJobprovides good modularity.dream_layer_backend/shared_utils.py (1)
80-155: wait_for_image: re-import ofget_directoriesis OK, but consider guarding circular importsGiven
dream_layerimportsshared_utils, this local import is the right approach. No change needed—just noting the rationale.dream_layer_backend/dream_layer.py (2)
385-446:/api/show-in-folder: solid multi-directory search and OS handlingGood prioritization of
MATRIX_GRIDS_DIR, thenSERVED_IMAGES_DIR, then output dir. Clear diagnostics and consistent storage labeling.
656-662: Graceful exit path looks goodExits with non-zero code when ComfyUI fails to start. No changes requested.
| { | ||
| "default": StabilityAspectRatio.ratio_1_1, | ||
| "tooltip": "Aspect ratio of generated image.", | ||
| }, | ||
| ), |
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.
Default should use enum value, not enum instance
INPUT_TYPES options are strings ([x.value for x in StabilityAspectRatio]), so the default should be the corresponding .value.
- "aspect_ratio": ([x.value for x in StabilityAspectRatio],
- {
- "default": StabilityAspectRatio.ratio_1_1,
+ "aspect_ratio": ([x.value for x in StabilityAspectRatio],
+ {
+ "default": StabilityAspectRatio.ratio_1_1.value,
"tooltip": "Aspect ratio of generated image.",
},
),Apply the same fix in other nodes that set aspect_ratio defaults (e.g., SD 3.5 node).
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| { | |
| "default": StabilityAspectRatio.ratio_1_1, | |
| "tooltip": "Aspect ratio of generated image.", | |
| }, | |
| ), | |
| "aspect_ratio": ([x.value for x in StabilityAspectRatio], | |
| { | |
| "default": StabilityAspectRatio.ratio_1_1.value, | |
| "tooltip": "Aspect ratio of generated image.", | |
| }, | |
| ), |
🤖 Prompt for AI Agents
In ComfyUI/comfy_api_nodes/nodes_stability.py around lines 79 to 83, the default
value for the aspect ratio input is set to the enum instance
StabilityAspectRatio.ratio_1_1 instead of its string value. Change the default
to StabilityAspectRatio.ratio_1_1.value to match the expected string type. Also,
review other nodes like the SD 3.5 node that set aspect_ratio defaults and
update them similarly to use the enum's .value property.
| --- | ||
|
|
||
| _Need help? Check out the [Quick Start Guide](quick_start.md) for a faster setup._ | ||
| *Need help? Check out the [Quick Start Guide](quick_start.md) for a faster setup.* |
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.
💡 Verification agent
🧩 Analysis chain
Remove trailing whitespace and ensure newline at EOF
Avoid trailing spaces and add a newline at EOF to keep markdown/linters happy.
Apply:
-*Need help? Check out the [Quick Start Guide](quick_start.md) for a faster setup.*
+*Need help? Check out the [Quick Start Guide](quick_start.md) for a faster setup).*
+Quickly verify the target exists:
🏁 Script executed:
#!/bin/bash
fd -a quick_start.mdLength of output: 61
Remove trailing whitespace and add final newline
There’s a stray space at the end of line 338 in docs/installation.md—remove it and ensure the file ends with a newline for Markdown/linter compliance.
• docs/installation.md: line 338
Apply:
-*Need help? Check out the [Quick Start Guide](quick_start.md) for a faster setup.*
+*Need help? Check out the [Quick Start Guide](quick_start.md) for a faster setup.*
+📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| *Need help? Check out the [Quick Start Guide](quick_start.md) for a faster setup.* | |
| *Need help? Check out the [Quick Start Guide](quick_start.md) for a faster setup.* |
🤖 Prompt for AI Agents
In docs/installation.md at line 338, remove the trailing whitespace at the end
of the line and ensure the file ends with a newline character to comply with
Markdown and linter standards.
| display_key = api_key[:8] + "..." + api_key[-4:] if len(api_key) > 12 else api_key | ||
| print(f"[DEBUG] Found {env_key}: {display_key}") | ||
| else: | ||
| print(f"[DEBUG] No {env_key} found in environment") | ||
|
|
||
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.
🛠️ Refactor suggestion
Do not log API keys; also avoid printing full key for short values
Even truncated logging of secrets is risky. Current logic prints the full key if length ≤ 12, which is a security leak.
Replace key printing with a non-sensitive message and use logger at DEBUG level.
- # Safely truncate for display without assuming length
- display_key = api_key[:8] + "..." + api_key[-4:] if len(api_key) > 12 else api_key
- print(f"[DEBUG] Found {env_key}: {display_key}")
+ # Avoid logging secrets. Only log presence.
+ print(f"[DEBUG] {env_key} loaded from environment")Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In dream_layer_backend/dream_layer_backend_utils/api_key_injector.py around
lines 75 to 79, avoid printing API keys directly, even truncated or full for
short keys, as it risks leaking secrets. Replace the print statements with
logger.debug calls that do not include the actual key value; instead, log a
generic message indicating whether the key was found or not, without exposing
any part of the key.
| # Create a copy to avoid modifying the original | ||
| workflow_with_keys = workflow.copy() | ||
|
|
||
| # Ensure extra_data exists | ||
| if "extra_data" not in workflow_with_keys: | ||
| workflow_with_keys["extra_data"] = {} | ||
| print("[DEBUG] Created new extra_data section") | ||
| else: | ||
| print("[DEBUG] Using existing extra_data section") | ||
|
|
||
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.
🛠️ Refactor suggestion
Avoid mutating the original workflow: shallow copy + shared extra_data can leak changes
workflow.copy() is shallow. If extra_data exists, adding keys mutates the original dict. Use a deep copy or at least copy extra_data.
- workflow_with_keys = workflow.copy()
+ workflow_with_keys = copy.deepcopy(workflow)Add at the top of the file:
import copyAlternatively, if you want to avoid full deepcopy, ensure extra_data is detached:
if "extra_data" not in workflow_with_keys:
workflow_with_keys["extra_data"] = {}
print("[DEBUG] Created new extra_data section")
else:
- print("[DEBUG] Using existing extra_data section")
+ # Detach to avoid mutating original dict
+ workflow_with_keys["extra_data"] = dict(workflow_with_keys["extra_data"])
+ print("[DEBUG] Using existing extra_data section")Also applies to: 139-145
🤖 Prompt for AI Agents
In dream_layer_backend/dream_layer_backend_utils/api_key_injector.py around
lines 97 to 106, the code uses a shallow copy of the workflow dictionary, which
causes mutations to the nested extra_data dictionary to affect the original
workflow. To fix this, import the copy module at the top of the file and replace
the shallow copy with a deep copy of the workflow object, or explicitly copy the
extra_data dictionary to detach it from the original. Apply the same fix to
lines 139 to 145 where similar copying occurs.
| current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | ||
| workflow_path = os.path.join( | ||
| current_dir, 'workflows', generation_flow, filename) | ||
|
|
||
| workflow_path = os.path.join(current_dir, 'workflows', generation_flow, filename) | ||
|
|
||
| if not os.path.exists(workflow_path): | ||
| raise FileNotFoundError(f"Workflow file not found: {workflow_path}") | ||
|
|
||
| return workflow_path |
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.
Validate generation_flow to prevent path traversal and bad inputs
generation_flow comes from the request and is directly inserted into the path, allowing traversal via values like "../". Validate against an allowlist before joining paths.
Apply:
def _determine_workflow_path(workflow_request: Dict[str, Any]) -> str:
"""Determine the workflow file path based on request parameters."""
generation_flow = workflow_request.get('generation_flow')
model_name = workflow_request.get('model_name', '').lower() # Convert to lowercase for case-insensitive comparison
controlnet = workflow_request.get('controlnet', False)
lora = workflow_request.get('lora', False)
-
+
+ # Validate generation_flow strictly to avoid path traversal and invalid directories
+ valid_flows = {"txt2img", "img2img"}
+ if generation_flow not in valid_flows:
+ raise ValueError(f"Invalid generation_flow: {generation_flow}. Expected one of {sorted(valid_flows)}.")
# Determine workflow filename based on parameters
@@
- current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
- workflow_path = os.path.join(current_dir, 'workflows', generation_flow, filename)
+ current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+ workflows_dir = os.path.join(current_dir, 'workflows')
+ workflow_path = os.path.join(workflows_dir, generation_flow, filename)
if not os.path.exists(workflow_path):
raise FileNotFoundError(f"Workflow file not found: {workflow_path}")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| workflow_path = os.path.join( | |
| current_dir, 'workflows', generation_flow, filename) | |
| workflow_path = os.path.join(current_dir, 'workflows', generation_flow, filename) | |
| if not os.path.exists(workflow_path): | |
| raise FileNotFoundError(f"Workflow file not found: {workflow_path}") | |
| return workflow_path | |
| def _determine_workflow_path(workflow_request: Dict[str, Any]) -> str: | |
| """Determine the workflow file path based on request parameters.""" | |
| generation_flow = workflow_request.get('generation_flow') | |
| model_name = workflow_request.get('model_name', '').lower() # Convert to lowercase for case-insensitive comparison | |
| controlnet = workflow_request.get('controlnet', False) | |
| lora = workflow_request.get('lora', False) | |
| # Validate generation_flow strictly to avoid path traversal and invalid directories | |
| valid_flows = {"txt2img", "img2img"} | |
| if generation_flow not in valid_flows: | |
| raise ValueError( | |
| f"Invalid generation_flow: {generation_flow}. " | |
| f"Expected one of {sorted(valid_flows)}." | |
| ) | |
| # Determine workflow filename based on parameters | |
| current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) | |
| workflows_dir = os.path.join(current_dir, 'workflows') | |
| workflow_path = os.path.join(workflows_dir, generation_flow, filename) | |
| if not os.path.exists(workflow_path): | |
| raise FileNotFoundError(f"Workflow file not found: {workflow_path}") | |
| return workflow_path |
🤖 Prompt for AI Agents
In dream_layer_backend/dream_layer_backend_utils/workflow_loader.py around lines
39 to 45, the generation_flow variable is used directly in the file path
construction, which risks path traversal attacks. To fix this, validate
generation_flow against a predefined allowlist of acceptable values before using
it in os.path.join. If generation_flow is not in the allowlist, raise an
appropriate exception to prevent unsafe path usage.
| xAxis: matrixSettings.xAxis?.name !== 'Nothing' ? { | ||
| name: matrixSettings.xAxis?.name, | ||
| values: [...new Set(completedJobs.map(job => job.xValue).filter(v => v !== null))].sort() | ||
| } : null, | ||
| yAxis: matrixSettings.yAxis?.name !== 'Nothing' ? { | ||
| name: matrixSettings.yAxis?.name, | ||
| values: [...new Set(completedJobs.map(job => job.yValue).filter(v => v !== null))].sort() | ||
| } : null, | ||
| zAxis: matrixSettings.zAxis?.name !== 'Nothing' ? { | ||
| name: matrixSettings.zAxis?.name, | ||
| values: [...new Set(completedJobs.map(job => job.zValue).filter(v => v !== null))].sort() | ||
| } : null, | ||
| }, |
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.
🛠️ Refactor suggestion
Sort axis values numerically for numbers and naturally for strings
Default .sort() will sort numbers lexicographically ("10" before "2") and mixes types poorly. Use a comparator that handles both.
- values: [...new Set(completedJobs.map(job => job.xValue).filter(v => v !== null))].sort()
+ values: Array.from(new Set(completedJobs.map(job => job.xValue).filter(v => v !== null)))
+ .sort((a, b) => (typeof a === 'number' && typeof b === 'number')
+ ? a - b
+ : String(a).localeCompare(String(b), undefined, { numeric: true }))Apply the same change to yAxis and zAxis blocks.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| xAxis: matrixSettings.xAxis?.name !== 'Nothing' ? { | |
| name: matrixSettings.xAxis?.name, | |
| values: [...new Set(completedJobs.map(job => job.xValue).filter(v => v !== null))].sort() | |
| } : null, | |
| yAxis: matrixSettings.yAxis?.name !== 'Nothing' ? { | |
| name: matrixSettings.yAxis?.name, | |
| values: [...new Set(completedJobs.map(job => job.yValue).filter(v => v !== null))].sort() | |
| } : null, | |
| zAxis: matrixSettings.zAxis?.name !== 'Nothing' ? { | |
| name: matrixSettings.zAxis?.name, | |
| values: [...new Set(completedJobs.map(job => job.zValue).filter(v => v !== null))].sort() | |
| } : null, | |
| }, | |
| xAxis: matrixSettings.xAxis?.name !== 'Nothing' ? { | |
| name: matrixSettings.xAxis?.name, | |
| values: Array.from(new Set(completedJobs.map(job => job.xValue).filter(v => v !== null))) | |
| .sort((a, b) => (typeof a === 'number' && typeof b === 'number') | |
| ? a - b | |
| : String(a).localeCompare(String(b), undefined, { numeric: true })) | |
| } : null, | |
| yAxis: matrixSettings.yAxis?.name !== 'Nothing' ? { | |
| name: matrixSettings.yAxis?.name, | |
| values: Array.from(new Set(completedJobs.map(job => job.yValue).filter(v => v !== null))) | |
| .sort((a, b) => (typeof a === 'number' && typeof b === 'number') | |
| ? a - b | |
| : String(a).localeCompare(String(b), undefined, { numeric: true })) | |
| } : null, | |
| zAxis: matrixSettings.zAxis?.name !== 'Nothing' ? { | |
| name: matrixSettings.zAxis?.name, | |
| values: Array.from(new Set(completedJobs.map(job => job.zValue).filter(v => v !== null))) | |
| .sort((a, b) => (typeof a === 'number' && typeof b === 'number') | |
| ? a - b | |
| : String(a).localeCompare(String(b), undefined, { numeric: true })) | |
| } : null, |
🤖 Prompt for AI Agents
In dream_layer_frontend/src/features/Txt2Img/Txt2ImgPage.tsx around lines 469 to
481, the axis values are sorted using the default .sort() which sorts numbers
lexicographically and does not handle mixed types well. Update the .sort() calls
for xAxis, yAxis, and zAxis values to use a comparator function that sorts
numbers numerically and strings naturally. Implement a comparator that checks
the type of values and sorts numbers by numeric order and strings by
localeCompare for natural sorting.
| const service = this; | ||
|
|
||
| ws.onmessage = (event) => { | ||
| if (event.data instanceof Blob) { | ||
| return; | ||
| } | ||
| try { | ||
| const message: WebSocketMessage = JSON.parse(event.data); | ||
|
|
||
| // Listen for our custom "models-refresh" events | ||
| if (message.type === 'models-refresh') { | ||
| notifyModelRefreshListeners(message.data); | ||
| } | ||
| const data = JSON.parse(event.data); | ||
| service.handleWebSocketMessage(data); | ||
| } catch (error) { | ||
| console.error('Error parsing WebSocket message:', error); | ||
| console.error('Error parsing WebSocket message:', error, 'Data:', event.data); | ||
| } |
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.
💡 Verification agent
🧩 Analysis chain
Critical: service.handleWebSocketMessage is undefined; this is not bound in module scope
const service = this; in a module returns undefined under ESM/strict mode. Calling service.handleWebSocketMessage(data) will throw and break model refresh updates.
Apply this fix:
- Remove
const service = this; - Add a real handler function and call it directly.
- const service = this;
-
ws.onmessage = (event) => {
if (event.data instanceof Blob) {
return;
}
try {
- const data = JSON.parse(event.data);
- service.handleWebSocketMessage(data);
+ const data = JSON.parse(event.data);
+ handleWebSocketMessage(data);
} catch (error) {
console.error('Error parsing WebSocket message:', error, 'Data:', event.data);
}
};Add this function (outside connectWebSocket), which notifies listeners on relevant events:
function handleWebSocketMessage(data: any) {
// Example ComfyUI event shape; adjust if different
if (data?.type === 'model_refresh' && data?.data) {
notifyModelRefreshListeners(data.data as ModelRefreshEvent);
}
}Run to confirm no other definition exists and to spot other call sites:
🏁 Script executed:
#!/bin/bash
rg -n "handleWebSocketMessage" -A2 -B2Length of output: 578
🏁 Script executed:
#!/bin/bash
# Show class definition and connectWebSocket method context
rg -n "class " -A3 -B1 dream_layer_frontend/src/services/modelService.ts
rg -n "connectWebSocket" -A5 -B5 dream_layer_frontend/src/services/modelService.tsLength of output: 820
🏁 Script executed:
#!/bin/bash
# Search for existing refresh notification utilities
rg -n "notifyModelRefreshListeners" -A2 -B2 dream_layer_frontend/src/services/modelService.ts
rg -n "ModelRefresh" -A2 -B2 dream_layer_frontend/src/services/modelService.ts
rg -n "listeners" -A2 -B2 dream_layer_frontend/src/services/modelService.ts
# Show the WebSocket onmessage block in full context
sed -n '240,300p' dream_layer_frontend/src/services/modelService.tsLength of output: 4394
Critical: Fix undefined WebSocket message handler
The current code does
const service = this;
…
ws.onmessage = (event) => {
…
service.handleWebSocketMessage(data);
}but in module scope this is undefined, so service.handleWebSocketMessage will throw. You need to:
- Remove the invalid
thisbinding. - Define a standalone
handleWebSocketMessagethat dispatches"model_refresh"events via the existingnotifyModelRefreshListeners. - Call it directly in
ws.onmessage.
Affected file: dream_layer_frontend/src/services/modelService.ts
Suggested diff:
--- a/dream_layer_frontend/src/services/modelService.ts
+++ b/dream_layer_frontend/src/services/modelService.ts
@@ 230,6 +230,12 @@ const notifyModelRefreshListeners = (event?: ModelRefreshEvent) => {
});
};
+/**
+ * Handle incoming WebSocket messages and notify listeners.
+ */
+const handleWebSocketMessage = (message: WebSocketMessage): void => {
+ if (message.type === 'model_refresh' && message.data) {
+ notifyModelRefreshListeners(message.data);
+ }
+};
const connectWebSocket = (): Promise<WebSocket> => {
return new Promise((resolve, reject) => {
try {
@@ -256,8 +262,7 @@ const connectWebSocket = (): Promise<WebSocket> => {
console.log(`🔌 Connecting to ComfyUI WebSocket: ${wsUrl}`);
- const service = this;
-
ws.onmessage = (event) => {
if (event.data instanceof Blob) {
return;
}
try {
- const data = JSON.parse(event.data);
- service.handleWebSocketMessage(data);
+ handleWebSocketMessage(JSON.parse(event.data) as WebSocketMessage);
} catch (error) {
console.error('Error parsing WebSocket message:', error, 'Data:', event.data);
}🤖 Prompt for AI Agents
In dream_layer_frontend/src/services/modelService.ts lines 264 to 275, the use
of 'this' to assign 'service' is invalid because 'this' is undefined in module
scope, causing 'service.handleWebSocketMessage' to throw an error. To fix this,
remove the 'const service = this;' line, create a standalone function
'handleWebSocketMessage' that processes the parsed data and dispatches
"model_refresh" events using 'notifyModelRefreshListeners', and then call this
standalone function directly inside the 'ws.onmessage' handler instead of using
'service.handleWebSocketMessage'.
| updateJobStatus: (index: number, status: 'completed' | 'failed' | 'running', data?: any) => void; | ||
| reset: () => void; |
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.
🛠️ Refactor suggestion
Narrow data typing and avoid spreading arbitrary fields into MatrixJob
Spreading arbitrary data onto the job risks overwriting fields unintentionally. Restrict data to { result?, error? } and type it accordingly.
- updateJobStatus: (index: number, status: 'completed' | 'failed' | 'running', data?: any) => void;
+ updateJobStatus: (
+ index: number,
+ status: 'completed' | 'failed' | 'running',
+ data?: Partial<Pick<MatrixJob, 'result' | 'error'>>
+ ) => void;- updateJobStatus: (index, status, data) => {
+ updateJobStatus: (index, status, data) => {
set(state => {
const newJobs = [...state.jobs];
const currentJob = newJobs[index];
if (currentJob) {
- newJobs[index] = { ...currentJob, status, ...data };
+ newJobs[index] = {
+ ...currentJob,
+ status,
+ ...(data?.result !== undefined ? { result: data.result } : {}),
+ ...(data?.error !== undefined ? { error: data.error } : {}),
+ };
}
return { jobs: newJobs };
});
},Also applies to: 31-39
🤖 Prompt for AI Agents
In dream_layer_frontend/src/stores/useMatrixStore.ts around lines 12-13 and
31-39, the updateJobStatus function currently allows spreading arbitrary data
fields onto the MatrixJob object, which risks overwriting important fields
unintentionally. To fix this, narrow the type of the data parameter to an object
with optional properties { result?: any; error?: any } and update the code to
only merge these specific fields into the job, avoiding spreading arbitrary
properties.
| set(state => ({ currentJobIndex: state.currentJobIndex + 1 })); | ||
| }, |
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.
🛠️ Refactor suggestion
Clamp currentJobIndex to avoid overshooting the jobs array
Prevent index from incrementing past jobs.length.
- set(state => ({ currentJobIndex: state.currentJobIndex + 1 }));
+ set(state => ({
+ currentJobIndex: Math.min(state.currentJobIndex + 1, state.jobs.length)
+ }));Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In dream_layer_frontend/src/stores/useMatrixStore.ts around lines 26 to 27, the
currentJobIndex is incremented without checking bounds, which can cause it to
exceed the length of the jobs array. Modify the setter to clamp currentJobIndex
so it does not go beyond jobs.length - 1 by adding a condition or using Math.min
to limit the incremented value.
| const base64Data = reader.result as string; | ||
| console.log('📊 Base64 length:', base64Data.length); | ||
|
|
||
| const response = await fetch('http://localhost:5001/api/save-matrix-grid', { |
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.
🛠️ Refactor suggestion
Make API endpoint configurable
The API endpoint URL is hardcoded to http://localhost:5001. This should be configurable for different environments.
Consider using an environment variable or configuration:
- const response = await fetch('http://localhost:5001/api/save-matrix-grid', {
+ const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:5001';
+ const response = await fetch(`${apiUrl}/api/save-matrix-grid`, {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const response = await fetch('http://localhost:5001/api/save-matrix-grid', { | |
| const apiUrl = process.env.REACT_APP_API_URL || 'http://localhost:5001'; | |
| const response = await fetch(`${apiUrl}/api/save-matrix-grid`, { |
🤖 Prompt for AI Agents
In dream_layer_frontend/src/utils/matrixGridGenerator.ts at line 31, the API
endpoint URL is hardcoded to 'http://localhost:5001/api/save-matrix-grid'. To
fix this, replace the hardcoded URL with a configurable value by using an
environment variable or a configuration setting that can be set differently for
each environment. Update the fetch call to use this variable instead of the
fixed string.
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.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
start_dream_layer.bat (1)
126-156: Remove deprecated services and normalize step numbering in both start scriptsThe PR scope deprecates the Run Registry, Report Bundle, and Img2Txt servers, yet both launch scripts still invoke them—and the “STEP x/y” counters are out of sync. Please:
• In start_dream_layer.bat
– Remove the blocks launching:
• Run Registry Server (lines 147–148)
• Report Bundle Server (lines 151–152)
• Img2Txt Server (lines 153–155)
– Renumber the remaining steps from the current mix of 1/4…4/4 then 5/8…7/8 to a consistent 1/5…4/5 (with the frontend as step 5/5).• In start_dream_layer.sh
– Remove thestart_python_servercalls for run_registry.py, report_bundle.py, and img2txt_server.py (lines 211–218).
– Adjust any echoed step counters to match the updated total.Example diff for start_dream_layer.bat (excerpt):
-echo %BLUE%[STEP 5/8]%NC% Starting run registry server... -start "Run Registry Server" … python run_registry.py … -echo %BLUE%[STEP 6/8]%NC% Starting report bundle server... -start "Report Bundle Server" … python report_bundle.py … -echo %BLUE%[STEP 7/8]%NC% Starting img2txt_server.py... -start "Img2Txt Server" … python img2txt_server.py … +REM Removed deprecated servers +echo %BLUE%[STEP 5/5]%NC% Starting frontend…And similarly remove lines 211–218 in start_dream_layer.sh. This keeps both scripts aligned with the PR’s intent.
dream_layer_backend/dream_layer_backend_utils/api_key_injector.py (1)
120-131: Fix function prologue: stray code breaks syntax and overrides parameterThis function reassigns all_api_keys unconditionally and includes stray text lines, causing a SyntaxError and ignoring the optional argument. Clean up and respect the parameter.
-def inject_api_keys_into_workflow(workflow: Dict[str, Any], all_api_keys: Dict[str, str] = None) -> Dict[str, Any]: +from typing import Optional +import copy + +def inject_api_keys_into_workflow(workflow: Dict[str, Any], all_api_keys: Optional[Dict[str, str]] = None) -> Dict[str, Any]: """ Inject API keys from environment variables into workflow extra_data based on nodes present. - - Args: - workflow: The workflow dictionary to inject keys into - - - Returns: - Workflow with appropriate API keys added to extra_data - - """ - # Read all available API keys from environment - all_api_keys = read_api_keys_from_env() - - all_api_keys: Optional dictionary of API keys. If None, reads from environment. + Args: + workflow: The workflow dictionary to inject keys into + all_api_keys: Optional dictionary of API keys. If None, reads from environment. + Returns: + Workflow with appropriate API keys added to extra_data + """ + if all_api_keys is None: + all_api_keys = read_api_keys_from_env()dream_layer_backend/img2img_workflow.py (2)
36-42: Relative template paths will break after os.chdir to ComfyUI; use absolute paths or central loaderstart_comfy_server() changes CWD to the ComfyUI dir. Opening "workflows/img2img/..." relative to CWD will fail. Either use workflow_loader.load_workflow or compute absolute paths anchored to this module. Also, several img2img Stability/Luma workflow files were removed in this PR per summary; referencing them may 404.
Two options:
- Option A (recommended): Use the centralized loader for consistency:
-from dream_layer_backend_utils.workflow_loader import load_workflow +from dream_layer_backend_utils.workflow_loader import load_workflow @@ - workflow_template_path = get_img2img_workflow_template(model_name, use_controlnet, use_lora) - - # Load the workflow from the template file - with open(workflow_template_path, 'r') as f: - workflow = json.load(f) + # Delegate selection & path resolution to shared loader + workflow_request = { + 'generation_flow': 'img2img', + 'model_name': model_name, + 'controlnet': use_controlnet, + 'lora': use_lora, + } + workflow = load_workflow(workflow_request)
- Option B: If you must keep local paths, return absolute paths in get_img2img_workflow_template (see comment below).
236-265: Missing stability workflow file – critical fix requiredThe verification script shows that
dream_layer_backend/workflows/img2img/stability_core_generation_workflow.jsonis referenced inget_img2img_workflow_templatebut no longer exists in the repo. This will cause runtime failures whenever a “stability” model is used.Please address this by one of the following:
- Restore or add back
stability_core_generation_workflow.jsonatdream_layer_backend/workflows/img2img/.- Rename the reference to point to the correct, existing workflow file if it was renamed.
- Remove or adjust the
"stability"conditional branch if Stability AI models now share another workflow.Locations needing attention:
- dream_layer_backend/img2img_workflow.py – the
elif "stability" in model_name_lower:branch (line ~244)- workflows/img2img directory – confirm presence or intended name of the stability JSON
After making the fix, re-run the existence check to ensure all referenced files are present and paths are correct.
♻️ Duplicate comments (6)
dream_layer_backend/dream_layer_backend_utils/workflow_loader.py (1)
17-21: Validate generation_flow and harden path join to prevent traversalgeneration_flow is user-controlled and used directly to build a filesystem path. Validate against an allowlist and ensure the final path resolves under the workflows directory to avoid path traversal and None-related errors. This aligns with a prior review; tagging as duplicate.
Apply:
def _determine_workflow_path(workflow_request: Dict[str, Any]) -> str: """Determine the workflow file path based on request parameters.""" - generation_flow = workflow_request.get('generation_flow') + generation_flow = workflow_request.get('generation_flow') + valid_flows = {"txt2img", "img2img"} + if generation_flow not in valid_flows: + raise ValueError(f"Invalid generation_flow: {generation_flow}. Expected one of {sorted(valid_flows)}.") @@ - current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - workflow_path = os.path.join(current_dir, 'workflows', generation_flow, filename) + current_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + workflows_dir = os.path.join(current_dir, 'workflows') + # Build and normalize, then ensure the path stays within workflows_dir + workflow_path = os.path.normpath(os.path.join(workflows_dir, generation_flow, filename)) + if os.path.commonpath([workflows_dir, workflow_path]) != os.path.abspath(workflows_dir): + raise ValueError("Unsafe workflow path resolution detected")Also applies to: 43-47
dream_layer_backend/dream_layer.py (3)
466-483: Add fallback search paths for images (served and matrix grids)Endpoints should mirror the “Show in Folder” lookup to avoid false 404s for persisted/served images. This was suggested previously; tagging as duplicate.
output_dir, _ = get_directories() - image_path = os.path.join(output_dir, filename) + candidates = [ + os.path.join(output_dir, filename), + os.path.join(SERVED_IMAGES_DIR, filename), + os.path.join(MATRIX_GRIDS_DIR, filename), + ] + image_path = next((p for p in candidates if os.path.exists(p)), None) - if not os.path.exists(image_path): + if not image_path: return jsonify({"status": "error", "message": "File not found"}), 404Apply the same pattern to /api/send-to-extras below.
484-503: Mirror fallback logic in /api/send-to-extrasEnsure consistent UX by checking SERVED_IMAGES_DIR and MATRIX_GRIDS_DIR, not just the output directory.
output_dir, _ = get_directories() - image_path = os.path.join(output_dir, filename) + candidates = [ + os.path.join(output_dir, filename), + os.path.join(SERVED_IMAGES_DIR, filename), + os.path.join(MATRIX_GRIDS_DIR, filename), + ] + image_path = next((p for p in candidates if os.path.exists(p)), None) - if not os.path.exists(image_path): + if not image_path: return jsonify({"status": "error", "message": "File not found"}), 404
295-295: Never run Flask with debug=True in production; gate with env varRunning with debug=True exposes a remote interactive console and sensitive information. Use an environment variable to control debug. This was flagged earlier; tagging as duplicate.
def start_flask_server(): """Start the Flask API server""" print("\nStarting Flask API server on http://localhost:5002") - app.run(host='0.0.0.0', port=5002, debug=True, use_reloader=False) + debug_mode = os.environ.get('FLASK_DEBUG', 'false').lower() == 'true' + app.run(host='0.0.0.0', port=5002, debug=debug_mode, use_reloader=False)dream_layer_backend/dream_layer_backend_utils/api_key_injector.py (2)
133-141: Avoid shallow copy; detach nested structures to prevent side effectsworkflow.copy() is shallow; mutations to prompt/extra_data bleed into the original. Use deepcopy. This was noted before; tagging as duplicate.
- workflow_with_keys = workflow.copy() + workflow_with_keys = copy.deepcopy(workflow)
95-106: Do not log secrets (even partially truncated)Printing API keys (even truncated) risks leakage in logs. Log only presence. This mirrors an earlier review; tagging as duplicate.
- if api_key: - api_keys[env_key] = api_key - # Safely truncate for display without assuming length - display_key = api_key[:8] + "..." + api_key[-4:] if len(api_key) > 12 else api_key - print(f"[DEBUG] Found {env_key}: {display_key}") + if api_key: + api_keys[env_key] = api_key + print(f"[DEBUG] {env_key} present in environment")Also avoid logging values elsewhere in this module (e.g., final extra_data below).
🧹 Nitpick comments (16)
start_dream_layer.bat (3)
168-170: Keep the frontend step label consistent with backend steps.With the change above, “STEP 5/5” for the frontend is correct. If you decide to keep additional backend services (not recommended), then re-label all steps to a single, consistent total (e.g., 1/8..8/8). Option B (if keeping all eight steps):
-echo %BLUE%[STEP 1/4]%NC% Starting Dream Layer backend... +echo %BLUE%[STEP 1/8]%NC% Starting Dream Layer backend... -echo %BLUE%[STEP 2/4]%NC% Starting txt2img_server.py... +echo %BLUE%[STEP 2/8]%NC% Starting txt2img_server.py... -echo %BLUE%[STEP 3/4]%NC% Starting img2img_server.py... +echo %BLUE%[STEP 3/8]%NC% Starting img2img_server.py... -echo %BLUE%[STEP 4/4]%NC% Starting extras.py... +echo %BLUE%[STEP 4/8]%NC% Starting extras.py... -echo %BLUE%[STEP 5/8]%NC% Starting run registry server... +echo %BLUE%[STEP 5/8]%NC% Starting run registry server... -echo %BLUE%[STEP 6/8]%NC% Starting report bundle server... +echo %BLUE%[STEP 6/8]%NC% Starting report bundle server... -echo %BLUE%[STEP 7/8]%NC% Starting img2txt_server.py... +echo %BLUE%[STEP 7/8]%NC% Starting img2txt_server.py... -echo %BLUE%[STEP 5/5]%NC% Starting frontend development server... +echo %BLUE%[STEP 8/8]%NC% Starting frontend development server...If you go with Option B, confirm that all three services are still present and supported by the codebase (they appeared removed in the PR scope).
219-227: Stop killing every cmd.exe on the machine; target only our windows.Force-killing all cmd.exe can terminate unrelated shells. Prefer killing by window title for the processes we started.
Apply:
taskkill /f /im python.exe >nul 2>&1 taskkill /f /im node.exe >nul 2>&1 -taskkill /f /im cmd.exe >nul 2>&1 +rem Kill only our cmd windows by title to avoid nuking unrelated shells +for %%T in ("Dream Layer Backend" "Txt2Img Server" "Img2Img Server" "Extras Server" "LocalTunnel" "Dream Layer Frontend") do ( + taskkill /f /fi "WINDOWTITLE eq %%~T" >nul 2>&1 +)
129-133: Replace fixed sleeps with a readiness check to reduce flakiness.A static 5s delay is brittle on slower machines. Poll the backend until it responds or a timeout elapses.
-echo %YELLOW%[INFO]%NC% Waiting for backend to initialize... -timeout /t 5 /nobreak >nul +echo %YELLOW%[INFO]%NC% Waiting for backend to become ready... +for /l %%i in (1,1,30) do ( + powershell -NoProfile -Command "try{ iwr -UseBasicParsing -TimeoutSec 2 http://localhost:5002/ -ErrorAction Stop | Out-Null; exit 0 } catch { exit 1 }" + if !errorlevel! EQU 0 goto :backend_ready + timeout /t 1 /nobreak >nul +) +echo %YELLOW%[WARNING]%NC% Backend may still be initializing; continuing... +:backend_readydream_layer_backend/dream_layer_backend_utils/workflow_loader.py (2)
51-55: Open JSON with explicit encodingUse encoding='utf-8' to avoid locale-dependent decoding and subtle bugs on non-UTF-8 systems.
-def _load_workflow_json(workflow_path: str) -> Dict[str, Any]: +def _load_workflow_json(workflow_path: str) -> Dict[str, Any]: """Load and parse workflow JSON file.""" - with open(workflow_path, 'r') as file: + with open(workflow_path, 'r', encoding='utf-8') as file: return json.load(file)
80-83: Be defensive when scanning prompt nodesIf workflows differ in structure, ensure node is a dict before accessing inputs; otherwise a KeyError/TypeError could occur. Current code guards for 'inputs' presence, but a quick isinstance on node improves robustness.
- batch_size = next((node['inputs'].get('batch_size', 1) for node in workflow.get('prompt', {}).values() if 'batch_size' in node.get('inputs', {})), 1) + batch_size = next( + ( + node['inputs'].get('batch_size', 1) + for node in workflow.get('prompt', {}).values() + if isinstance(node, dict) and 'batch_size' in node.get('inputs', {}) + ), + 1 + )dream_layer_backend/dream_layer.py (2)
17-18: Remove unused importread_api_keys_from_env is imported at module scope but not used (you re-import it inside get_available_models). Drop this to satisfy Ruff F401.
-from dream_layer_backend_utils import read_api_keys_from_env
178-182: Rename unused loop variable for clarityapi_key_value is unused; rename to _ to satisfy Ruff B007 and clarify intent.
- for api_key_name, api_key_value in api_keys.items(): + for api_key_name, _ in api_keys.items(): if api_key_name in API_KEY_TO_MODELS: formatted_models.extend(API_KEY_TO_MODELS[api_key_name]) print(f"Added {len(API_KEY_TO_MODELS[api_key_name])} models for {api_key_name}")dream_layer_backend/dream_layer_backend_utils/api_key_injector.py (2)
146-154: Guard node access with isinstance and handle unknown shapesSome workflows may carry non-dict values; guard accesses to class_type to avoid exceptions.
- for node_id, node_data in workflow_prompt.items(): - if isinstance(node_data, dict): - class_type = node_data.get('class_type') - if class_type in NODE_TO_API_KEY_MAPPING: - required_env_key = NODE_TO_API_KEY_MAPPING[class_type] - needed_env_keys.add(required_env_key) - print(f"[DEBUG] Found {class_type} node - needs {required_env_key}") + for node_id, node_data in workflow_prompt.items(): + if not isinstance(node_data, Dict): + continue + class_type = node_data.get('class_type') + if class_type in NODE_TO_API_KEY_MAPPING: + required_env_key = NODE_TO_API_KEY_MAPPING[class_type] + needed_env_keys.add(required_env_key) + print(f"[DEBUG] Found {class_type} node - needs {required_env_key}")
181-199: Consistency: minimize duplicated scans and respect extra_data secrecyYou scan the workflow twice (Stability and Luma). Consider a single pass collecting flags to reduce complexity. Also, injecting keys into extra_data is fine but avoid printing them.
No diff included to keep the change small; let me know if you want a consolidated single-pass refactor.
dream_layer_backend/img2img_workflow.py (2)
77-88: Remove or use unused variables to reduce noisedenoising_strength, clip_skip, tiling, hires_fix, and karras_sigmas are assigned but unused (Ruff F841). Remove or wire them into parameter injection if intended.
- denoising_strength = max(0.0, min(1.0, float(data.get('denoising_strength', 0.75)))) @@ - clip_skip = data.get('clip_skip', 1) - tiling = data.get('tiling', False) - hires_fix = data.get('hires_fix', False) - karras_sigmas = data.get('karras_sigmas', False)
176-181: Duplicate API-key injection is redundantYou inject keys twice (once without and once with explicit all_api_keys). Keep one path to reduce log noise and surprises.
- workflow = inject_api_keys_into_workflow(workflow) - - all_api_keys = read_api_keys_from_env() - workflow = inject_api_keys_into_workflow(workflow, all_api_keys) + all_api_keys = read_api_keys_from_env() + workflow = inject_api_keys_into_workflow(workflow, all_api_keys)dream_layer_backend/txt2img_workflow.py (5)
1-6: Deduplicate imports and remove unused modulesjson is imported twice; os appears unused. Clean up to satisfy Ruff F811/F401.
-import json -import random -import os -import traceback -import json +import json +import random +import traceback
7-11: Consolidate duplicate importsinject_api_keys_into_workflow and override_workflow are imported twice.
-from dream_layer_backend_utils.api_key_injector import inject_api_keys_into_workflow -from dream_layer_backend_utils.update_custom_workflow import override_workflow -from dream_layer_backend_utils.api_key_injector import inject_api_keys_into_workflow, read_api_keys_from_env -from dream_layer_backend_utils.update_custom_workflow import override_workflow +from dream_layer_backend_utils.api_key_injector import inject_api_keys_into_workflow, read_api_keys_from_env +from dream_layer_backend_utils.update_custom_workflow import override_workflow
57-71: Remove duplicate closed_source_models and streamline model_name handlingclosed_source_models is defined twice; keep one. Also, you set model_name twice.
- model_name = data.get('model_name', 'juggernautXL_v8Rundiffusion.safetensors') - - # Check if it's a closed-source model (DALL-E, FLUX, Ideogram, Stability AI, Luma, etc.) - closed_source_models = ['dall-e-3', 'dall-e-2', 'flux-pro', 'flux-dev', 'ideogram-v3', 'stability-sdxl', 'stability-sd-turbo', 'photon-1', 'photon-flash-1'] + model_name = data.get('model_name', 'juggernautXL_v8Rundiffusion.safetensors') + closed_source_models = [ + 'dall-e-3', 'dall-e-2', 'flux-pro', 'flux-dev', + 'ideogram-v3', 'stability-sdxl', 'stability-sd-turbo', + 'photon-1', 'photon-flash-1' + ]Remove the earlier closed_source_models assignment at Line 59.
157-166: Avoid redundant double injection of API keysYou inject without explicit keys, then immediately inject again with read_api_keys_from_env. One pass is enough.
- workflow = inject_api_keys_into_workflow(workflow) - - # Inject API keys if needed (for DALL-E, FLUX, etc.) - all_api_keys = read_api_keys_from_env() - workflow = inject_api_keys_into_workflow(workflow, all_api_keys) + all_api_keys = read_api_keys_from_env() + workflow = inject_api_keys_into_workflow(workflow, all_api_keys)
158-207: Remove f-strings with no placeholders (Ruff F541)Several print calls use f"" with no variable interpolation. Remove the f prefix.
Example fixes (apply similarly throughout this block):
- print(f"✅ Workflow loaded successfully") + print("✅ Workflow loaded successfully") @@ - print(f"🎨 Applying LoRA parameters...") + print("🎨 Applying LoRA parameters...") @@ - print(f"✅ Core settings applied") + print("✅ Core settings applied")
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (9)
dream_layer_backend/dream_layer.py(16 hunks)dream_layer_backend/dream_layer_backend_utils/api_key_injector.py(5 hunks)dream_layer_backend/dream_layer_backend_utils/workflow_loader.py(3 hunks)dream_layer_backend/img2img_server.py(2 hunks)dream_layer_backend/img2img_workflow.py(8 hunks)dream_layer_backend/txt2img_workflow.py(6 hunks)dream_layer_frontend/src/components/AliasKeyInputs.tsx(2 hunks)start_dream_layer.bat(2 hunks)start_dream_layer.sh(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- dream_layer_frontend/src/components/AliasKeyInputs.tsx
- start_dream_layer.sh
- dream_layer_backend/img2img_server.py
🧰 Additional context used
🪛 Ruff (0.12.2)
dream_layer_backend/img2img_workflow.py
77-77: Local variable denoising_strength is assigned to but never used
Remove assignment to unused variable denoising_strength
(F841)
85-85: Local variable clip_skip is assigned to but never used
Remove assignment to unused variable clip_skip
(F841)
86-86: Local variable tiling is assigned to but never used
Remove assignment to unused variable tiling
(F841)
87-87: Local variable hires_fix is assigned to but never used
Remove assignment to unused variable hires_fix
(F841)
88-88: Local variable karras_sigmas is assigned to but never used
Remove assignment to unused variable karras_sigmas
(F841)
dream_layer_backend/dream_layer.py
17-17: dream_layer_backend_utils.read_api_keys_from_env imported but unused
Remove unused import: dream_layer_backend_utils.read_api_keys_from_env
(F401)
178-178: Loop control variable api_key_value not used within loop body
Rename unused api_key_value to _api_key_value
(B007)
608-608: f-string without any placeholders
Remove extraneous f prefix
(F541)
642-642: f-string without any placeholders
Remove extraneous f prefix
(F541)
dream_layer_backend/txt2img_workflow.py
5-5: Redefinition of unused json from line 1
Remove definition: json
(F811)
158-158: f-string without any placeholders
Remove extraneous f prefix
(F541)
165-165: f-string without any placeholders
Remove extraneous f prefix
(F541)
181-181: f-string without any placeholders
Remove extraneous f prefix
(F541)
184-184: f-string without any placeholders
Remove extraneous f prefix
(F541)
188-188: f-string without any placeholders
Remove extraneous f prefix
(F541)
192-192: f-string without any placeholders
Remove extraneous f prefix
(F541)
196-196: f-string without any placeholders
Remove extraneous f prefix
(F541)
200-200: f-string without any placeholders
Remove extraneous f prefix
(F541)
204-204: f-string without any placeholders
Remove extraneous f prefix
(F541)
207-207: f-string without any placeholders
Remove extraneous f prefix
(F541)
dream_layer_backend/dream_layer_backend_utils/api_key_injector.py
223-223: SyntaxError: Expected ',', found newline
🪛 ast-grep (0.38.6)
dream_layer_backend/dream_layer.py
[warning] 294-294: Detected Flask app with debug=True. Do not deploy to production with this flag enabled as it will leak sensitive information. Instead, consider using Flask configuration variables or setting 'debug' using system environment variables.
Context: app.run(host='0.0.0.0', port=5002, debug=True, use_reloader=False)
Note: [CWE-489] Active Debug Code. [REFERENCES]
- https://labs.detectify.com/2015/10/02/how-patreon-got-hacked-publicly-exposed-werkzeug-debugger/
(debug-enabled-python)
| print(f"[DEBUG] Final extra_data: {workflow_with_keys['extra_data']}") | ||
|
|
||
| return workflow_with_keys | ||
| return workflow_with_keys |
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.
Avoid printing secrets in final extra_data
Printing the entire extra_data may leak API keys. Mask values or print only keys.
- print(f"[DEBUG] Final extra_data: {workflow_with_keys['extra_data']}")
+ print(f"[DEBUG] Final extra_data keys: {list(workflow_with_keys.get('extra_data', {}).keys())}")📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| print(f"[DEBUG] Final extra_data: {workflow_with_keys['extra_data']}") | |
| return workflow_with_keys | |
| return workflow_with_keys | |
| # Avoid printing values (which may include secrets); only show which keys are present | |
| print(f"[DEBUG] Final extra_data keys: {list(workflow_with_keys.get('extra_data', {}).keys())}") | |
| return workflow_with_keys |
🧰 Tools
🪛 Ruff (0.12.2)
223-223: SyntaxError: Expected ',', found newline
🤖 Prompt for AI Agents
In dream_layer_backend/dream_layer_backend_utils/api_key_injector.py around
lines 221 to 223, the code prints the entire workflow_with_keys['extra_data']
which may contain API keys and sensitive values; instead remove or replace the
raw print with either (1) logging only the keys (e.g.,
list(workflow_with_keys['extra_data'].keys())) or (2) mask all values before
logging (e.g., replace each value with a fixed mask like '****' or only show
first/last N chars), and ensure any logger used is configured appropriately; do
not output full secret values to stdout.
| @app.route('/api/show-in-folder', methods=['POST']) | ||
| def show_in_folder(): | ||
| """Show image file in system file manager (cross-platform)""" | ||
| try: | ||
| filename = request.json.get('filename') | ||
| if not filename: | ||
| return jsonify({"status": "error", "message": "No filename provided"}), 400 | ||
|
|
||
| output_dir, _ = get_directories() | ||
| print(f"DEBUG: output_dir='{output_dir}', filename='{filename}'") | ||
| image_path = os.path.join(output_dir, filename) | ||
|
|
||
| if not os.path.exists(image_path): | ||
|
|
||
| image_path = None | ||
|
|
||
| if filename.startswith('matrix-grid'): | ||
| matrix_filepath = os.path.join(MATRIX_GRIDS_DIR, filename) | ||
| if os.path.exists(matrix_filepath): | ||
| image_path = matrix_filepath | ||
| print(f"🔍 Found matrix grid in permanent storage: {image_path}") | ||
|
|
||
| if not image_path: | ||
| served_filepath = os.path.join(SERVED_IMAGES_DIR, filename) | ||
| if os.path.exists(served_filepath): | ||
| image_path = served_filepath | ||
| print(f"🔍 Found in served_images: {image_path}") | ||
|
|
||
| if not image_path: | ||
| output_dir, _ = get_directories() | ||
| output_filepath = os.path.join(output_dir, filename) | ||
| if os.path.exists(output_filepath): | ||
| image_path = output_filepath | ||
| print(f"🔍 Found in output_dir: {image_path}") | ||
|
|
||
| if not image_path and not filename.startswith('matrix-grid'): | ||
| matrix_filepath = os.path.join(MATRIX_GRIDS_DIR, filename) | ||
| if os.path.exists(matrix_filepath): | ||
| image_path = matrix_filepath | ||
| print(f"🔍 Found in matrix_grids (fallback): {image_path}") | ||
|
|
||
| if not image_path: | ||
| print(f"❌ File not found: {filename}") | ||
| print(f" Checked matrix_grids: {os.path.join(MATRIX_GRIDS_DIR, filename)}") | ||
| print(f" Checked served_images: {os.path.join(SERVED_IMAGES_DIR, filename)}") | ||
| output_dir, _ = get_directories() | ||
| output_filepath = os.path.join(output_dir, filename) | ||
| print(f" Checked output_dir: {output_filepath}") | ||
| return jsonify({"status": "error", "message": "File not found"}), 404 | ||
|
|
||
| # Detect operating system and use appropriate command | ||
|
|
||
| system = platform.system() | ||
|
|
||
| if system == "Darwin": # macOS | ||
| subprocess.run(['open', '-R', image_path]) | ||
| return jsonify({"status": "success", "message": f"Opened {filename} in Finder"}) | ||
| elif system == "Windows": # Windows | ||
| subprocess.run(['explorer', '/select,', image_path]) | ||
| return jsonify({"status": "success", "message": f"Opened {filename} in File Explorer"}) | ||
| elif system == "Linux": # Linux | ||
| # Open the directory containing the file (can't highlight specific file reliably) | ||
| subprocess.run(['xdg-open', output_dir]) | ||
| return jsonify({"status": "success", "message": f"Opened directory containing {filename}"}) | ||
| subprocess.run(["open", "-R", image_path]) | ||
| elif system == "Windows": | ||
| subprocess.run(["explorer", "/select,", image_path]) | ||
| elif system == "Linux": | ||
| subprocess.run(["xdg-open", os.path.dirname(image_path)]) | ||
| else: | ||
| return jsonify({"status": "error", "message": f"Unsupported operating system: {system}"}), 400 | ||
|
|
||
|
|
||
| return jsonify({ | ||
| "status": "success", | ||
| "message": f"Opened {filename} in file manager", | ||
| "path": image_path, | ||
| "storage": "permanent" if image_path.startswith(MATRIX_GRIDS_DIR) else "temporary" | ||
| }) | ||
|
|
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.
🛠️ Refactor suggestion
Constrain filename input and robustly detect storage type
- Validate filename to be a basename; reject path separators to prevent odd resolutions into subdirs.
- storage detection via startswith can be brittle; use commonpath.
filename = request.json.get('filename')
if not filename:
return jsonify({"status": "error", "message": "No filename provided"}), 400
-
- image_path = None
+ # Sanitize filename to a basename only
+ if os.path.basename(filename) != filename or any(sep in filename for sep in ('..', '/', '\\')):
+ return jsonify({"status": "error", "message": "Invalid filename"}), 400
+
+ image_path = None
@@
- return jsonify({
- "status": "success",
- "message": f"Opened {filename} in file manager",
- "path": image_path,
- "storage": "permanent" if image_path.startswith(MATRIX_GRIDS_DIR) else "temporary"
- })
+ storage = "temporary"
+ try:
+ if os.path.commonpath([os.path.abspath(MATRIX_GRIDS_DIR), os.path.abspath(image_path)]) == os.path.abspath(MATRIX_GRIDS_DIR):
+ storage = "permanent"
+ except Exception:
+ pass
+ return jsonify({
+ "status": "success",
+ "message": f"Opened {filename} in file manager",
+ "path": image_path,
+ "storage": storage
+ })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @app.route('/api/show-in-folder', methods=['POST']) | |
| def show_in_folder(): | |
| """Show image file in system file manager (cross-platform)""" | |
| try: | |
| filename = request.json.get('filename') | |
| if not filename: | |
| return jsonify({"status": "error", "message": "No filename provided"}), 400 | |
| output_dir, _ = get_directories() | |
| print(f"DEBUG: output_dir='{output_dir}', filename='{filename}'") | |
| image_path = os.path.join(output_dir, filename) | |
| if not os.path.exists(image_path): | |
| image_path = None | |
| if filename.startswith('matrix-grid'): | |
| matrix_filepath = os.path.join(MATRIX_GRIDS_DIR, filename) | |
| if os.path.exists(matrix_filepath): | |
| image_path = matrix_filepath | |
| print(f"🔍 Found matrix grid in permanent storage: {image_path}") | |
| if not image_path: | |
| served_filepath = os.path.join(SERVED_IMAGES_DIR, filename) | |
| if os.path.exists(served_filepath): | |
| image_path = served_filepath | |
| print(f"🔍 Found in served_images: {image_path}") | |
| if not image_path: | |
| output_dir, _ = get_directories() | |
| output_filepath = os.path.join(output_dir, filename) | |
| if os.path.exists(output_filepath): | |
| image_path = output_filepath | |
| print(f"🔍 Found in output_dir: {image_path}") | |
| if not image_path and not filename.startswith('matrix-grid'): | |
| matrix_filepath = os.path.join(MATRIX_GRIDS_DIR, filename) | |
| if os.path.exists(matrix_filepath): | |
| image_path = matrix_filepath | |
| print(f"🔍 Found in matrix_grids (fallback): {image_path}") | |
| if not image_path: | |
| print(f"❌ File not found: {filename}") | |
| print(f" Checked matrix_grids: {os.path.join(MATRIX_GRIDS_DIR, filename)}") | |
| print(f" Checked served_images: {os.path.join(SERVED_IMAGES_DIR, filename)}") | |
| output_dir, _ = get_directories() | |
| output_filepath = os.path.join(output_dir, filename) | |
| print(f" Checked output_dir: {output_filepath}") | |
| return jsonify({"status": "error", "message": "File not found"}), 404 | |
| # Detect operating system and use appropriate command | |
| system = platform.system() | |
| if system == "Darwin": # macOS | |
| subprocess.run(['open', '-R', image_path]) | |
| return jsonify({"status": "success", "message": f"Opened {filename} in Finder"}) | |
| elif system == "Windows": # Windows | |
| subprocess.run(['explorer', '/select,', image_path]) | |
| return jsonify({"status": "success", "message": f"Opened {filename} in File Explorer"}) | |
| elif system == "Linux": # Linux | |
| # Open the directory containing the file (can't highlight specific file reliably) | |
| subprocess.run(['xdg-open', output_dir]) | |
| return jsonify({"status": "success", "message": f"Opened directory containing {filename}"}) | |
| subprocess.run(["open", "-R", image_path]) | |
| elif system == "Windows": | |
| subprocess.run(["explorer", "/select,", image_path]) | |
| elif system == "Linux": | |
| subprocess.run(["xdg-open", os.path.dirname(image_path)]) | |
| else: | |
| return jsonify({"status": "error", "message": f"Unsupported operating system: {system}"}), 400 | |
| return jsonify({ | |
| "status": "success", | |
| "message": f"Opened {filename} in file manager", | |
| "path": image_path, | |
| "storage": "permanent" if image_path.startswith(MATRIX_GRIDS_DIR) else "temporary" | |
| }) | |
| @app.route('/api/show-in-folder', methods=['POST']) | |
| def show_in_folder(): | |
| """Show image file in system file manager (cross-platform)""" | |
| try: | |
| filename = request.json.get('filename') | |
| if not filename: | |
| return jsonify({"status": "error", "message": "No filename provided"}), 400 | |
| # Sanitize filename to a basename only | |
| if os.path.basename(filename) != filename or any(sep in filename for sep in ('..', '/', '\\')): | |
| return jsonify({"status": "error", "message": "Invalid filename"}), 400 | |
| image_path = None | |
| if filename.startswith('matrix-grid'): | |
| matrix_filepath = os.path.join(MATRIX_GRIDS_DIR, filename) | |
| if os.path.exists(matrix_filepath): | |
| image_path = matrix_filepath | |
| print(f"🔍 Found matrix grid in permanent storage: {image_path}") | |
| if not image_path: | |
| served_filepath = os.path.join(SERVED_IMAGES_DIR, filename) | |
| if os.path.exists(served_filepath): | |
| image_path = served_filepath | |
| print(f"🔍 Found in served_images: {image_path}") | |
| if not image_path: | |
| output_dir, _ = get_directories() | |
| output_filepath = os.path.join(output_dir, filename) | |
| if os.path.exists(output_filepath): | |
| image_path = output_filepath | |
| print(f"🔍 Found in output_dir: {image_path}") | |
| if not image_path and not filename.startswith('matrix-grid'): | |
| matrix_filepath = os.path.join(MATRIX_GRIDS_DIR, filename) | |
| if os.path.exists(matrix_filepath): | |
| image_path = matrix_filepath | |
| print(f"🔍 Found in matrix_grids (fallback): {image_path}") | |
| if not image_path: | |
| print(f"❌ File not found: {filename}") | |
| print(f" Checked matrix_grids: {os.path.join(MATRIX_GRIDS_DIR, filename)}") | |
| print(f" Checked served_images: {os.path.join(SERVED_IMAGES_DIR, filename)}") | |
| output_dir, _ = get_directories() | |
| output_filepath = os.path.join(output_dir, filename) | |
| print(f" Checked output_dir: {output_filepath}") | |
| return jsonify({"status": "error", "message": "File not found"}), 404 | |
| system = platform.system() | |
| if system == "Darwin": # macOS | |
| subprocess.run(["open", "-R", image_path]) | |
| elif system == "Windows": | |
| subprocess.run(["explorer", "/select,", image_path]) | |
| elif system == "Linux": | |
| subprocess.run(["xdg-open", os.path.dirname(image_path)]) | |
| else: | |
| return jsonify({"status": "error", "message": f"Unsupported operating system: {system}"}), 400 | |
| - return jsonify({ | |
| - "status": "success", | |
| - "message": f"Opened {filename} in file manager", | |
| - "path": image_path, | |
| - "storage": "permanent" if image_path.startswith(MATRIX_GRIDS_DIR) else "temporary" | |
| # Determine storage type more robustly | |
| storage = "temporary" | |
| try: | |
| root = os.path.abspath(MATRIX_GRIDS_DIR) | |
| if os.path.commonpath([root, os.path.abspath(image_path)]) == root: | |
| storage = "permanent" | |
| except Exception: | |
| pass | |
| return jsonify({ | |
| "status": "success", | |
| "message": f"Opened {filename} in file manager", | |
| "path": image_path, | |
| "storage": storage | |
| }) |
🤖 Prompt for AI Agents
In dream_layer_backend/dream_layer.py around lines 400-461, validate and
normalize the filename and use robust storage detection: first reject any
filename containing path separators by comparing filename to
os.path.basename(filename) and return 400 if they differ; then use only the
basename for all os.path.join lookups (no startswith-based logic). When
determining the storage type, normalize to absolute paths and use
os.path.commonpath (e.g. os.path.commonpath([os.path.abspath(image_path),
os.path.abspath(MATRIX_GRIDS_DIR)]) == os.path.abspath(MATRIX_GRIDS_DIR)) to
decide if the file is in MATRIX_GRIDS_DIR (otherwise temporary), replacing any
brittle startswith checks. Ensure all paths used for subprocess calls are
absolute and safe.
| @app.route('/api/save-single-image', methods=['POST']) | ||
| def save_single_image(): | ||
| """ | ||
| Save single generated image to server storage for persistence | ||
| """ | ||
| try: | ||
| print(f"🔄 Single image save request received on port 5002") | ||
| data = request.json | ||
|
|
||
| if not data or 'imageData' not in data: | ||
| print("❌ No image data in request") | ||
| return jsonify({ | ||
| "status": "error", | ||
| "message": "No image data provided" | ||
| }), 400 | ||
|
|
||
| image_data = data['imageData'] | ||
| if image_data.startswith('data:'): | ||
| image_data = image_data.split(',')[1] | ||
|
|
||
| print(f"📊 Received base64 data length: {len(image_data)}") | ||
|
|
||
| # Decode base64 image | ||
| image_bytes = base64.b64decode(image_data) | ||
| print(f"📊 Decoded image bytes: {len(image_bytes)}") | ||
|
|
||
| image = Image.open(io.BytesIO(image_bytes)) | ||
| print(f"📊 Image dimensions: {image.size}") | ||
|
|
||
| timestamp = int(time.time() * 1000) | ||
| original_filename = data.get('originalFilename', 'image.png') | ||
| filename = f"single_{timestamp}_{original_filename}" | ||
| print(f"📁 Generated filename: {filename}") | ||
|
|
||
| # Save to the same directory as Matrix grids for consistency | ||
| os.makedirs(MATRIX_GRIDS_DIR, exist_ok=True) | ||
| filepath = os.path.join(MATRIX_GRIDS_DIR, filename) | ||
| print(f"📁 Full save path: {filepath}") | ||
|
|
||
| image.save(filepath, format='PNG') | ||
| print(f"💾 Single image saved to permanent storage") | ||
|
|
||
| if os.path.exists(filepath): | ||
| file_size = os.path.getsize(filepath) | ||
| print(f"✅ Successfully saved single image: {filename}") | ||
| print(f"📏 File size: {file_size} bytes") | ||
|
|
||
| return jsonify({ | ||
| "status": "success", | ||
| "filename": filename, | ||
| "url": f"http://localhost:5002/api/images/{filename}", | ||
| "filesize": file_size, | ||
| "storage": "permanent" | ||
| }) | ||
| else: | ||
| print(f"❌ File was not created: {filepath}") | ||
| return jsonify({ | ||
| "status": "error", | ||
| "message": "Failed to save single image to permanent storage" | ||
| }), 500 | ||
|
|
||
| except Exception as e: | ||
| print(f"❌ Error saving single image: {str(e)}") | ||
| traceback.print_exc() | ||
| return jsonify({ | ||
| "status": "error", | ||
| "message": str(e) | ||
| }), 500 |
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.
🛠️ Refactor suggestion
Sanitize originalFilename, drop f-strings without placeholders, and verify image before saving
- Sanitize originalFilename via werkzeug.utils.secure_filename to avoid path traversal or OS-specific issues.
- Avoid f-strings without placeholders (Ruff F541).
- Verify the image and bound dimensions to mitigate zip-bomb-like payloads.
+from werkzeug.utils import secure_filename
@@
- print(f"🔄 Single image save request received on port 5002")
+ print("🔄 Single image save request received on port 5002")
@@
- print(f"📊 Received base64 data length: {len(image_data)}")
+ print(f"📊 Received base64 data length: {len(image_data)}")
@@
- image = Image.open(io.BytesIO(image_bytes))
- print(f"📊 Image dimensions: {image.size}")
+ # Verify and bound the image
+ with Image.open(io.BytesIO(image_bytes)) as im:
+ im.verify() # Validate file structure
+ with Image.open(io.BytesIO(image_bytes)) as image:
+ max_px = 20000 # guardrail
+ if image.width > max_px or image.height > max_px:
+ return jsonify({"status": "error", "message": "Image too large"}), 400
+ print(f"📊 Image dimensions: {image.size}")
@@
- original_filename = data.get('originalFilename', 'image.png')
- filename = f"single_{timestamp}_{original_filename}"
+ original_filename = data.get('originalFilename', 'image.png')
+ safe_original = secure_filename(os.path.basename(original_filename)) or "image.png"
+ filename = f"single_{timestamp}_{safe_original}"
print(f"📁 Generated filename: {filename}")
@@
- print(f"💾 Single image saved to permanent storage")
+ print("💾 Single image saved to permanent storage")
@@
- print(f"❌ File was not created: {filepath}")
+ print(f"❌ File was not created: {filepath}")Note: you already read Image earlier; with the verify pattern above, use the second with-block's image object for saving.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @app.route('/api/save-single-image', methods=['POST']) | |
| def save_single_image(): | |
| """ | |
| Save single generated image to server storage for persistence | |
| """ | |
| try: | |
| print(f"🔄 Single image save request received on port 5002") | |
| data = request.json | |
| if not data or 'imageData' not in data: | |
| print("❌ No image data in request") | |
| return jsonify({ | |
| "status": "error", | |
| "message": "No image data provided" | |
| }), 400 | |
| image_data = data['imageData'] | |
| if image_data.startswith('data:'): | |
| image_data = image_data.split(',')[1] | |
| print(f"📊 Received base64 data length: {len(image_data)}") | |
| # Decode base64 image | |
| image_bytes = base64.b64decode(image_data) | |
| print(f"📊 Decoded image bytes: {len(image_bytes)}") | |
| image = Image.open(io.BytesIO(image_bytes)) | |
| print(f"📊 Image dimensions: {image.size}") | |
| timestamp = int(time.time() * 1000) | |
| original_filename = data.get('originalFilename', 'image.png') | |
| filename = f"single_{timestamp}_{original_filename}" | |
| print(f"📁 Generated filename: {filename}") | |
| # Save to the same directory as Matrix grids for consistency | |
| os.makedirs(MATRIX_GRIDS_DIR, exist_ok=True) | |
| filepath = os.path.join(MATRIX_GRIDS_DIR, filename) | |
| print(f"📁 Full save path: {filepath}") | |
| image.save(filepath, format='PNG') | |
| print(f"💾 Single image saved to permanent storage") | |
| if os.path.exists(filepath): | |
| file_size = os.path.getsize(filepath) | |
| print(f"✅ Successfully saved single image: {filename}") | |
| print(f"📏 File size: {file_size} bytes") | |
| return jsonify({ | |
| "status": "success", | |
| "filename": filename, | |
| "url": f"http://localhost:5002/api/images/{filename}", | |
| "filesize": file_size, | |
| "storage": "permanent" | |
| }) | |
| else: | |
| print(f"❌ File was not created: {filepath}") | |
| return jsonify({ | |
| "status": "error", | |
| "message": "Failed to save single image to permanent storage" | |
| }), 500 | |
| except Exception as e: | |
| print(f"❌ Error saving single image: {str(e)}") | |
| traceback.print_exc() | |
| return jsonify({ | |
| "status": "error", | |
| "message": str(e) | |
| }), 500 | |
| From top of file (add alongside other imports): | |
| from werkzeug.utils import secure_filename | |
| In save_single_image(), update as follows: | |
| @app.route('/api/save-single-image', methods=['POST']) | |
| def save_single_image(): | |
| """ | |
| Save single generated image to server storage for persistence | |
| """ | |
| try: | |
| print("🔄 Single image save request received on port 5002") | |
| data = request.json | |
| if not data or 'imageData' not in data: | |
| print("❌ No image data in request") | |
| return jsonify({ | |
| "status": "error", | |
| "message": "No image data provided" | |
| }), 400 | |
| image_data = data['imageData'] | |
| if image_data.startswith('data:'): | |
| image_data = image_data.split(',')[1] | |
| print(f"📊 Received base64 data length: {len(image_data)}") | |
| # Decode base64 image | |
| image_bytes = base64.b64decode(image_data) | |
| - image = Image.open(io.BytesIO(image_bytes)) | |
| # Verify and bound the image to prevent zip-bombs or corrupt files | |
| with Image.open(io.BytesIO(image_bytes)) as im: | |
| im.verify() # Validate file structure | |
| with Image.open(io.BytesIO(image_bytes)) as image: | |
| max_px = 20000 # maximum width/height in pixels | |
| if image.width > max_px or image.height > max_px: | |
| return jsonify({"status": "error", "message": "Image too large"}), 400 | |
| print(f"📊 Image dimensions: {image.size}") | |
| timestamp = int(time.time() * 1000) | |
| - original_filename = data.get('originalFilename', 'image.png') | |
| original_filename = data.get('originalFilename', 'image.png') | |
| safe_original = secure_filename(os.path.basename(original_filename)) or "image.png" | |
| filename = f"single_{timestamp}_{safe_original}" | |
| print(f"📁 Generated filename: {filename}") | |
| # Save to the same directory as Matrix grids for consistency | |
| os.makedirs(MATRIX_GRIDS_DIR, exist_ok=True) | |
| filepath = os.path.join(MATRIX_GRIDS_DIR, filename) | |
| print(f"📁 Full save path: {filepath}") | |
| image.save(filepath, format='PNG') | |
| print("💾 Single image saved to permanent storage") | |
| if os.path.exists(filepath): | |
| file_size = os.path.getsize(filepath) | |
| print(f"✅ Successfully saved single image: {filename}") | |
| print(f"📏 File size: {file_size} bytes") | |
| return jsonify({ | |
| "status": "success", | |
| "filename": filename, | |
| "url": f"http://localhost:5002/api/images/{filename}", | |
| "filesize": file_size, | |
| "storage": "permanent" | |
| }) | |
| else: | |
| print(f"❌ File was not created: {filepath}") | |
| return jsonify({ | |
| "status": "error", | |
| "message": "Failed to save single image to permanent storage" | |
| }), 500 | |
| except Exception as e: | |
| print(f"❌ Error saving single image: {str(e)}") | |
| traceback.print_exc() | |
| return jsonify({ | |
| "status": "error", | |
| "message": str(e) | |
| }), 500 |
🧰 Tools
🪛 Ruff (0.12.2)
608-608: f-string without any placeholders
Remove extraneous f prefix
(F541)
642-642: f-string without any placeholders
Remove extraneous f prefix
(F541)
Description
Implements Task #2: Matrix runner.
Changes Made
Frontend
Backend
Evidence Required ✅
UI Screenshot
Generated Image
Logs
Tests (Optional)
Checklist
Summary by Sourcery
Add a complete matrix runner feature to Txt2Img including a new UI for axis-based parameter expansion, a robust persisted job runner with pause/resume, client-side grid rendering and server persistence, and improve image preview/download and “show in folder” reliability. Ensure generated images and grids survive refresh and backend restarts.
New Features:
/api/save-matrix-gridendpoint/api/save-single-imageendpoint and maintain gallery after backend restartsEnhancements:
Documentation:
Summary by CodeRabbit
New Features
Enhancements
Removals
Chores