Skip to content

Conversation

@gihyuness
Copy link

@gihyuness gihyuness commented Aug 10, 2025

Description

Implements Task #2: Matrix runner.

  • Form accepts lists or ranges for seeds, steps, samplers, CFG, etc.
  • Deterministic expansion into a job list with total count shown.
  • Pause/Resume without duplicates; survives page refresh and browser reloads.
  • Final Grid image is built on the client via Canvas and saved server‑side (persistent URL).
  • Images are also persisted on the server and reload after backend restarts.
  • Added progress display and robust job runner that resumes the exact next job.
  • Large, unclipped axis labels; X‑only grids have no unnecessary left padding.
  • Reliable PNG download and “Show in Folder” for both grids and single images.

Changes Made

  • Frontend

    • Add dream_layer_frontend/src/components/MatrixSettings.tsx (X/Y/Z axes, List/Range inputs, Draw legend, Include sub images/grids, Keep seeds for rows).
    • Add dream_layer_frontend/src/stores/useMatrixStore.ts (persisted jobs/currentJobIndex/isPaused; actions to advance, pause, resume, reset).
    • Add dream_layer_frontend/src/utils/matrixGridGenerator.ts (X/Y/Z grid, big labels, smart margins, server save).
    • Update dream_layer_frontend/src/features/Txt2Img/Txt2ImgPage.tsx (job expansion, runner loop, deterministic order, pause/resume without skip/dup, progress text, single-image persistence).
    • Update dream_layer_frontend/src/components/tabs/txt2img/ImagePreview.tsx (grid preview with horizontal scroll, remove extra labels, reliable PNG download, working “Show in Folder”, detailed matrix settings display).
    • Update dream_layer_frontend/src/stores/useTxt2ImgGalleryStore.ts (persist images across refresh).
    • Update dream_layer_frontend/src/types/generationSettings.ts (Matrix types; ImageResult extended with isMatrixGrid, matrixAxes, matrixInfo, totalJobs).
    • Update dream_layer_frontend/src/services/modelService.ts (fix this context; ignore Blob frames; safer JSON parse).
  • Backend

    • dream_layer_backend/dream_layer.py: add POST /api/save-matrix-grid and POST /api/save-single-image; centralize image serving; enhance “show in folder”.
    • dream_layer_backend/shared_utils.py: robust image copy/serve, late import to break circular deps, consistent dirs.
    • dream_layer_backend/txt2img_server.py: remove duplicate endpoints; rely on shared serving.

Evidence Required ✅

UI Screenshot

UI Screenshot
UI Screenshot
UI Screenshot
UI Screenshot
UI Screenshot

Generated Image

Generated Image
Generated Image

Logs

# Server boot (dream_layer.log)
Setting output directory to: /Users/joey/PycharmProjects/dream/DreamLayer/Dream_Layer_Resources/output
Device: cpu
ComfyUI server is ready!
Starting Flask API server on http://localhost:5002

# Models + API keys loaded
127.0.0.1 - - [..] "GET /api/models HTTP/1.1" 200 -
127.0.0.1 - - [..] "GET /api/models HTTP/1.1" 200 -
[DEBUG] Found BFL_API_KEY: [..]
[DEBUG] Found OPENAI_API_KEY: [..]
[DEBUG] Total API keys loaded: 2
Added 2 models for BFL_API_KEY
Added 2 models for OPENAI_API_KEY

# Requests during usage
127.0.0.1 - - [..] "GET /api/fetch-prompt?type=positive HTTP/1.1" 200 -
127.0.0.1 - - [..] "GET /api/fetch-prompt?type=positive HTTP/1.1" 200 -

# Generation cycles
got prompt
Prompt executed in 17.42 seconds
got prompt
Prompt executed in 16.57 seconds
...

# File utility
127.0.0.1 - - [..] "OPTIONS /api/show-in-folder HTTP/1.1" 200 -
127.0.0.1 - - [..] "POST /api/show-in-folder HTTP/1.1" 200 -

Tests (Optional)

# Test results

Checklist

  • UI screenshot provided
  • Generated image provided
  • Logs provided
  • Tests added (optional)
  • Code follows project style
  • Self-review completed

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:

  • Add Matrix generation UI with X/Y/Z axes, list/range inputs, legend, and customization options
  • Implement deterministic matrix job runner with pause/resume, persisting state across page refresh and reload
  • Generate and save final grid images on client using Canvas and persist them server-side via new /api/save-matrix-grid endpoint
  • Persist single images to server via new /api/save-single-image endpoint and maintain gallery after backend restarts

Enhancements:

  • Improve Txt2ImgPage workflow with progress display, job controls (Generate/Pause/Resume/Interrupt), and deterministic expansion of matrix jobs
  • Enhance ImagePreview component with reliable PNG download fallback, error handling, and refined “Show in Folder” across multiple storage locations
  • Persist image gallery state using a persisted store to survive browser reloads

Documentation:

  • Update documentation for matrix generation settings, new API endpoints, and refine installation and usage guides

Summary by CodeRabbit

  • New Features

    • Matrix-based batch image generation with MatrixSettings UI, matrix job management, matrix grid generator, and persistent gallery/store support.
    • New server endpoint to save matrix grid images permanently.
  • Enhancements

    • Improved image preview UX (error states, matrix-grid rendering) and more robust image serving and logging.
    • API-key injection and broader model provisioning support.
  • Removals

    • Run Registry, Report Bundle, Img2Txt features and related workflows/tests/pages removed.
  • Chores

    • Cleanup: formatting, imports, startup scripts, and config tidy-ups.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Aug 10, 2025

Reviewer's Guide

Implements 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 saving

sequenceDiagram
    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'
Loading

Class diagram for Matrix job and settings types

classDiagram
    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
Loading

File-Level Changes

Change Details Files
Introduce MatrixSettings UI and parameter types for defining matrix axes
  • Add MatrixSettings.tsx component with axis selectors, input type toggles and legend/sub-image options
  • Define new MatrixParameter, MatrixSettings and MatrixJob types and defaults in generationSettings.ts
dream_layer_frontend/src/components/MatrixSettings.tsx
dream_layer_frontend/src/types/generationSettings.ts
Implement matrix job store and runner logic in Txt2ImgPage
  • Create useMatrixStore.ts with persisted jobs list, current index, pause/resume, advance and reset actions
  • Enhance Txt2ImgPage.tsx: parseParameterValues, generateMatrixJobs, job runner useEffect, progress display and pause/resume controls
dream_layer_frontend/src/stores/useMatrixStore.ts
dream_layer_frontend/src/features/Txt2Img/Txt2ImgPage.tsx
Build and persist final grid image via Canvas and server
  • Add matrixGridGenerator.ts to load individual images, draw them into a labeled grid and POST to /api/save-matrix-grid
  • Hook grid generation into Txt2ImgPage effect once all jobs complete
dream_layer_frontend/src/utils/matrixGridGenerator.ts
dream_layer_frontend/src/features/Txt2Img/Txt2ImgPage.tsx
Extend backend for durable single image and grid persistence
  • Introduce POST /api/save-single-image and /api/save-matrix-grid endpoints in dream_layer.py and txt2img_server.py
  • Update shared_utils and show-in-folder logic to recognize a new matrix_grids directory and serve grid images
dream_layer_backend/dream_layer.py
dream_layer_backend/txt2img_server.py
dream_layer_backend/shared_utils.py
Persist image gallery and matrix state across reloads
  • Wrap useTxt2ImgGalleryStore with persist to keep images after refresh
  • Wrap useMatrixStore with persist to retain unfinished jobs and pause state
dream_layer_frontend/src/stores/useTxt2ImgGalleryStore.ts
dream_layer_frontend/src/stores/useMatrixStore.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 10, 2025

Note

Other AI code review bot(s) detected

CodeRabbit 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.

Walkthrough

Removed 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

Cohort / File(s) Change Summary
Backend: Removed services & tests
dream_layer_backend/run_registry.py, dream_layer_backend/report_bundle.py, dream_layer_backend/img2txt_server.py, dream_layer_backend/tests/test_run_registry.py, dream_layer_backend/tests/test_report_bundle.py
Deleted run registry, report bundle, and img2txt backend services and their tests.
Backend: Workflow removals
dream_layer_backend/workflows/.../gemini_multimodal_workflow.json, .../stability_core_generation_workflow.json, workflows/txt2img/gemini_core_generation_workflow.json, workflows/txt2img/stability_core_generation_workflow.json
Deleted Gemini and Stability workflow JSON files.
Backend: Stability/Gemini node changes & API-key injector
ComfyUI/comfy_api_nodes/nodes_gemini.py, ComfyUI/comfy_api_nodes/nodes_stability.py, dream_layer_backend/dream_layer_backend_utils/api_key_injector.py, dream_layer_backend/dream_layer_backend_utils/workflow_loader.py
Removed internal try/except blocks in Gemini node methods; replaced direct Stability HTTP calls with SynchronousOperation proxy, renamed hidden inputs (stability_api_keyauth_token/comfy_api_key), and extended injector to read and inject STABILITY_API_KEY/LUMA_API_KEY at runtime; workflow selection mapping expanded.
Backend: Image serving, matrix support & endpoints
dream_layer_backend/shared_utils.py, dream_layer_backend/dream_layer.py, dream_layer_backend/txt2img_server.py, dream_layer_backend/img2img_server.py, dream_layer_backend/controlnet.py, dream_layer_backend/img2img_workflow.py, dream_layer_backend/txt2img_workflow.py, dream_layer_backend/extras.py
Added MATRIX_GRIDS_DIR and single/matrix-grid save endpoints; improved serve_image to check matrix grids and added detailed logging/tracebacks; removed run registration POSTs and centralized imports; adjusted SERVER_URL port.
Frontend: Removed features (UI, stores, types)
dream_layer_frontend/src/features/RunRegistry/*, dream_layer_frontend/src/features/ReportBundle/*, dream_layer_frontend/src/features/Img2Txt/*, dream_layer_frontend/src/stores/useRunRegistryStore.ts, dream_layer_frontend/src/stores/useReportBundleStore.ts, dream_layer_frontend/src/types/reportBundle.ts, dream_layer_frontend/src/types/runRegistry.ts
Removed Run Registry, Report Bundle, and Img2Txt pages, stores, types, and related exports/imports.
Frontend: Matrix features & gallery persistence
dream_layer_frontend/src/types/generationSettings.ts, dream_layer_frontend/src/components/MatrixSettings.tsx, dream_layer_frontend/src/stores/useMatrixStore.ts, dream_layer_frontend/src/utils/matrixGridGenerator.ts, dream_layer_frontend/src/features/Txt2Img/Txt2ImgPage.tsx, dream_layer_frontend/src/stores/useTxt2ImgGalleryStore.ts, dream_layer_frontend/src/components/tabs/txt2img/ImagePreview.tsx
Added matrix-related types, MatrixSettings component, useMatrixStore (persisted), matrix grid generator utility, extended Txt2Img to run matrix jobs and compose grids, enabled gallery persistence, and improved image preview error handling/rendering and download flows.
Frontend: Navigation & UI adjustments
dream_layer_frontend/src/components/Navigation/TabsNav.tsx, dream_layer_frontend/src/pages/Index.tsx, dream_layer_frontend/src/components/AliasKeyInputs.tsx, dream_layer_frontend/src/features/Extras/ExtrasPage.tsx
Removed tabs/imports for deleted pages, adjusted tab ordering, fixed ApiKeys form state init, and set default values/placeholders for upscaler selects.
Docs & config
GEMINI_INTEGRATION_DEMO.md, README.md, docs/api_reference.md, docs/installation.md, docs/modules/utils.md, docs/usage.md, ComfyUI/user/default/comfy.settings.json
Deleted Gemini demo; removed STABILITY_API_KEY references; expanded utils docs with many helper definitions; applied formatting/whitespace edits; removed some settings from comfy.settings.json.
Build & startup scripts
start_dream_layer.bat, start_dream_layer.sh
Adjusted startup sequence and cleanup to preserve/handle matrix_grids, removed cleanup for some removed services, and updated displayed service info.
Misc: small refactors & types
ComfyUI/comfy_api_nodes/apis/request_logger.py, dream_layer_backend/shared_utils.py, dream_layer_backend/controlnet.py, dream_layer_frontend/src/services/modelService.ts
Standardized type annotations (Optional/Any), moved repeated imports to module scope, cleaned comments, and refined WebSocket message handling.

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
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Possibly related PRs

  • Merge Stability ai integration into main #74 — Modifies Stability integration in ComfyUI/comfy_api_nodes/nodes_stability.py; likely to overlap with this PR’s Stability refactor and hidden input renames.
  • feat: offline model manager #20 — Touches shared model-upload and model-refresh utilities (dream_layer_backend/shared_utils.py); may conflict with the API-key injector and upload changes here.
  • Feature/controlnet integration imgtoimg #3 — Edits ControlNet/image workflow handling (dream_layer_backend/controlnet.py and img2img workflows); potentially overlapping with controlnet and workflow selection updates.

Poem

A rabbit hopped through fields of code,
Nudged old features down a tidy road.
Workflows trimmed and grids set free,
Seeds arranged in matrix-tree.
The gallery hums — new images glow,
A nimble repo ready to grow. 🐇✨

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 Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a 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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +97 to +106
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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): 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.

Comment on lines 50 to +59
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);
Copy link
Contributor

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.

Comment on lines +225 to +235
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')
Copy link
Contributor

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.

Suggested change
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')
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (bug_risk): 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}.")
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): Raise a specific error instead of the general Exception or BaseException (raise-specific-error)

ExplanationIf 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?

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")

Comment on lines +226 to 230
# 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)
Copy link
Contributor

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:

Suggested change
# 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]]:
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): Low code quality found in wait_for_image - 5% (low-code-quality)


ExplanationThe 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}")
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): We've found these issues:

Save Matrix grid image to server storage (permanent directory)
"""
try:
print(f"🔄 Matrix grid save request received")
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): 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):
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (code-quality): We've found these issues:


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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 obj
dream_layer_backend/extras.py (1)

90-96: Add HTTP timeouts to ComfyUI requests

Network 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 in extras.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 arguments

Use 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 validation

The /api/save-matrix-grid endpoint 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"
+            }), 413
dream_layer_frontend/src/components/tabs/txt2img/ImagePreview.tsx (1)

140-179: Fix “Send To Img2Img” for Matrix Grids

The /api/send-to-img2img endpoint currently only checks the main output directory (Comfy’s default). Matrix grids are written to MATRIX_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):
– In handleSendTo, wrap or disable the “Send To Img2Img” action when isMatrixGrid(currentImage) is true.
• Backend (dream_layer_backend/dream_layer.py):
– Extend send_to_img2img to fall back to MATRIX_GRIDS_DIR (and/or SERVED_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"}), 404

Let 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 attach files when an image is provided

Passing {"image": None} can produce malformed multipart payloads. Omit files when 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, and StabilityUpscaleFastNode.


260-305: SD 3.5: Use enum value for aspect ratio default and conditional files

Same 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/params

Use 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 Mapping at the top next to the existing typing imports.


25-37: Bound log payload size to prevent massive log files

Large 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 s

Add 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 filenames

Replacing 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 re
README.md (2)

105-107: Fix markdownlint warnings: list style and bare URLs in Access section

Switch 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 mapping

Table 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 note

Current: “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_workflow

Current 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 example

Minor 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 example

Small 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 italics

Add 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 intentional

If 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 OK

Looks 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 include workflow in API responses

I 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 grids

Good 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)"
+    fi
dream_layer_frontend/src/services/modelService.ts (1)

207-207: Optional: add jitter to reconnection delay to avoid sync storms

Fixed 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 consistency

Not 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 pending

On 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 UX

With 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)
+            raise
dream_layer_frontend/src/stores/useTxt2ImgGalleryStore.ts (1)

19-23: Optional: cap gallery size to prevent unbounded storage growth

Persisting 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 prefixes

These 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 statements

Multiple 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 loading

The loadImage function 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 results

The 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 imports

Several 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 prefixes

These 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 logging

If 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 filename

No 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 supported

Button 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 img

Hiding 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: formatSettingsDisplay appears unused and contains stale strings

This 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 zoom

Matrix 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 import read_api_keys_from_env

It’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_value

Avoids 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 url to 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 text

The 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 files pattern to avoid None payload.

483-515: Upscale Creative: conditional files and consistent rounding

Mirror the conditional files and apply rounding as you did in the conservative node.


566-586: Upscale Fast: conditional files

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 the enabled flag

  • 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 improved parseParameterValues (which respects enabled) will address this.
  • Shallow-copying coreSettings is 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 DOM

Querying textareas by placeholder is fragile. Use coreSettings directly.

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

You 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 pass signal to fetch(), resetting the controller per job.


185-189: Parameterize API endpoints with env vars

Hard-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 clarity

Currently 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 reset

Sub-grids are launched via forEach(async ...) and not awaited; then the main grid finally resets 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 ... } or Promise.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 expansion

Given 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

📥 Commits

Reviewing files that changed from the base of the PR and between 1911cd3 and cee53e7.

📒 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.md
  • README.md
  • start_dream_layer.bat
  • start_dream_layer.sh
  • docs/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.md
  • README.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 ☺️ --- ## Get Involved Today 1. **St...

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

Switching 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 good

Adding the “AI Art” link and separator improves discoverability. No issues.


32-35: Consistent list style for target users

Switch to asterisks is consistent with the rest of the doc and common markdownlint rules.


147-148: Helpful note on auto-dependency setup

Good 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 safe

If any code or UI logic referenced Comfy.Queue.ImageFit or 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 port

All 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 improved

Adding status and detailing fields is good. No issues.


85-88: Request format indentation/readability improved

Looks good.


158-167: Upload response schema tightened

Good structure; aligns with typical responses elsewhere in the doc.


183-192: Docs say GET /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 /images route 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 cleanup

Consolidating imports at module scope improves readability and avoids repeated imports.

dream_layer_frontend/src/components/Navigation/TabsNav.tsx (1)

2-8: Imports cleanup looks correct

Only 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 appropriate

Switching 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 LGTM

Consistent placeholder text for both selects is good UX.

dream_layer_backend/img2img_server.py (1)

10-10: Top-level import of serve_image is fine

Bringing serve_image to module scope is OK and avoids repeated imports. No circular import concerns given serve_image imports Flask lazily inside the function.

dream_layer_frontend/src/services/modelService.ts (1)

239-239: Early return guard LGTM

Skipping 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 only

The 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 types

The new matrix generation types and interfaces are well-designed with appropriate defaults and type safety. The separation of concerns between MatrixParameter, MatrixSettings, and MatrixJob provides good modularity.

dream_layer_backend/shared_utils.py (1)

80-155: wait_for_image: re-import of get_directories is OK, but consider guarding circular imports

Given dream_layer imports shared_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 handling

Good prioritization of MATRIX_GRIDS_DIR, then SERVED_IMAGES_DIR, then output dir. Clear diagnostics and consistent storage labeling.


656-662: Graceful exit path looks good

Exits with non-zero code when ComfyUI fails to start. No changes requested.

Comment on lines +79 to 83
{
"default": StabilityAspectRatio.ratio_1_1,
"tooltip": "Aspect ratio of generated image.",
},
),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
{
"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.*
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

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.md

Length 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.

Suggested change
*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.

Comment on lines +75 to +79
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")

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

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.

Comment on lines 97 to +106
# 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")

Copy link
Contributor

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 copy

Alternatively, 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.

Comment on lines 39 to 45
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
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines +469 to +481
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,
},
Copy link
Contributor

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.

Suggested change
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.

Comment on lines +264 to 275
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

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

Length 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.ts

Length 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.ts

Length 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 this binding.
  • Define a standalone handleWebSocketMessage that dispatches "model_refresh" events via the existing notifyModelRefreshListeners.
  • 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'.

Comment on lines +12 to +13
updateJobStatus: (index: number, status: 'completed' | 'failed' | 'running', data?: any) => void;
reset: () => void;
Copy link
Contributor

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.

Comment on lines +26 to +27
set(state => ({ currentJobIndex: state.currentJobIndex + 1 }));
},
Copy link
Contributor

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', {
Copy link
Contributor

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.

Suggested change
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.

Copy link
Contributor

@coderabbitai coderabbitai bot left a 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 scripts

The 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 the start_python_server calls 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 parameter

This 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 loader

start_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 required

The verification script shows that dream_layer_backend/workflows/img2img/stability_core_generation_workflow.json is referenced in get_img2img_workflow_template but 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.json at dream_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 traversal

generation_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"}), 404

Apply the same pattern to /api/send-to-extras below.


484-503: Mirror fallback logic in /api/send-to-extras

Ensure 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 var

Running 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 effects

workflow.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_ready
dream_layer_backend/dream_layer_backend_utils/workflow_loader.py (2)

51-55: Open JSON with explicit encoding

Use 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 nodes

If 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 import

read_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 clarity

api_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 shapes

Some 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 secrecy

You 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 noise

denoising_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 redundant

You 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 modules

json 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 imports

inject_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 handling

closed_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 keys

You 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.

📥 Commits

Reviewing files that changed from the base of the PR and between 4b895eb and 6575a21.

📒 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)

Comment on lines 221 to +223
print(f"[DEBUG] Final extra_data: {workflow_with_keys['extra_data']}")

return workflow_with_keys
return workflow_with_keys
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

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.

Suggested change
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.

Comment on lines 400 to +461
@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"
})

Copy link
Contributor

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.

Suggested change
@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.

Comment on lines +602 to +669
@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
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

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.

Suggested change
@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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant