Files
JasonHoku-ComfyUI-Ultimate-…/ProjectStructure.md
T
JasonHokuandClaude Opus 4.6 989da199a3 Update README.md and ProjectStructure.md for v3.0
- Add distributed processing features (multi-worker, master text
  encoding, model sync, per-worker logging)
- Add comprehensive AI development notes with architecture diagrams,
  file dependency graphs, gotchas, and testing instructions
- Update file structure to include all 21 Python modules, distribution
  files, and conf-builder-distribution.js
- Add distribution API routes reference
- Add distribution module descriptions and communication patterns
- Update data flow to include distribution pipeline
- Add 8 new critical development gotchas (13-20)
- Add new changelog entry for March 2026
- Bump version to 3.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 01:08:34 -08:00

45 KiB

ComfyUI Registry Security Requirements

This node is published to the ComfyUI Registry. Every version is scanned by an automated security scanner. Versions that fail the scan receive NodeVersionStatusFlagged status and are hidden from ComfyUI Manager. All contributors must follow these rules:

  1. No import requests — Use urllib.request (stdlib) instead. The scanner flags outbound HTTP libraries as potential data exfiltration vectors.
  2. No subprocess, os.system, eval(), exec() — These are blocked as arbitrary code execution risks.
  3. No runtime pip install — Package installation must go through requirements.txt only, never called at runtime.
  4. No code obfuscation — No encoded strings, multi-statement tricks, or undefined variable patterns.
  5. No custom file-serving endpoints — Use ComfyUI's built-in WEB_DIRECTORY for JS and the built-in /view endpoint for images. Never use web.FileResponse or create custom routes that serve files from disk. For external images, create symlinks into the output directory and use /view?filename=X&type=output&subfolder=Y.
  6. Path containment on all filesystem operations — Every endpoint that touches the filesystem must validate that resolved paths stay within the expected base directory (e.g., output/benchmarks/). Use os.path.realpath() + startswith() checks.
  7. Sanitize all user-supplied path components — Session names, filenames, etc. must be sanitized with re.sub(r'[^\w\-]', '', name) or os.path.basename() before use.

Reference: https://blog.comfy.org/p/comfyui-2025-jan-security-update


Updated Project File Structure

ComfyUI-Ultimate-Sampler-Grid/
├── __init__.py                  # Server Routes & Node Mappings
├── sampler_node.py              # Main "Sampler Grid" Node
├── dashboard_node.py            # "Dashboard Viewer" Node
├── config_builder_node.py       # "Config Builder" Node
├── json_text_node.py            # "Smart JSON" Node
│
├── generation_orchestrator.py   # The "Conductor" (Loop Logic, ~1700 lines)
├── image_generation.py          # KSampler & VAE Wrappers
├── model_loader.py              # Checkpoint/LoRA/VAE/GGUF Loading Logic
├── model_cache.py               # 3-Tier Model Caching System
├── remote_vae.py                # Remote VAE Offloading
│
├── batch_encoding.py            # Batch CLIP Encoding with Combinator Support
├── conditioning_cache.py        # Disk-based Conditioning Cache
├── lora_utils.py                # LoRA Searching & Validation
├── trigger_words.py             # CivitAI Trigger Word Fetching & Prompt Assembly
├── config_utils.py              # Config Parsing, Expansion & Cartesian Products
├── directory_scanner.py         # External Directory Session Scanner
├── metadata_packer.py           # PNG/WebP Metadata Embedding for Exports
├── manifest_utils.py            # Manifest Read/Write/Merge Helpers
├── html_generator.py            # Reads /resources/ to build the Dashboard HTML
│
├── distribution_manager.py      # Distributed Job Queue Coordinator (Master)
├── distribution_worker.py       # Worker Thread for Remote Processing
├── distribution_routes.py       # Distribution REST API Endpoints
│
├── web/                         # [ComfyUI Integration Layer]
│   ├── dashboard.js             # Registers Dashboard Node & Message Handling
│   ├── smart_json_text.js       # Registers JSON Node & Syntax Highlighting
│   └── conf_builder/            # [Config Builder UI Modules]
│       ├── conf-builder-main.js           # Main entry, widget registration
│       ├── conf-builder-ui-components.js  # UI rendering (dropdowns, sliders, cards)
│       ├── conf-builder-config-management.js # Save/Load/Duplicate config logic
│       ├── conf-builder-utilities.js      # State-to-JSON conversion, iteration counting
│       └── conf-builder-distribution.js   # Distribution settings UI (workers, toggles)
│
└── resources/                   # [The Dashboard SPA Core]
    ├── template.html            # HTML Skeleton for the generated report
    ├── report.css               # Styling (Infinite Canvas, Cards, Modals)
    ├── logic_init.js            # Bootstrapper (Load JSON, Init State)
    ├── logic_state.js           # State Store (Redux-style: filters, favorites)
    ├── logic_pipeline.js        # Data Processing (Filter -> Sort -> Layout)
    ├── logic_virtual.js         # Virtual DOM / Infinite Scroller Engine
    ├── logic_ui.js              # UI Renderer (DOM creation, Modal handling)
    ├── logic_events.js          # Interaction Handler (Hotkeys, API calls)
    └── logic_utils.js           # Helpers (Debounce, Formatters)

Output Directory Structure (Generated at Runtime):

ComfyUI/output/
├── ultimate-configs/                  # Saved config presets (NOT under benchmarks/)
│   └── {name}.json
└── benchmarks/
    ├── loras_tags.json                # LoRA trigger word cache (shared)
    ├── model_hashes.json              # SHA256 hash cache for CivitAI lookups
    ├── model-data/                    # CivitAI metadata cache per model
    │   └── {model_name}/metadata.json
    └── {session_name}/
        ├── manifest.json              # Generation metadata + user annotations
        ├── images/
        │   └── img_{n}.webp
        └── favorites/                 # Created by export_favorites endpoint

Project Architecture Overview

ComfyUI-Ultimate-Sampler-Grid is a hybrid application.

  1. The Backend (Python): Runs inside ComfyUI. It orchestrates the grid generation, manages resources (caching/loading), and serves the API.
  2. The Frontend Bridge (JS): Runs in the ComfyUI browser tab. It registers the custom nodes and widgets.
  3. The Dashboard SPA (HTML/JS/CSS): A standalone, virtualized "IDE" for viewing results. It is generated by the backend but runs entirely in the client's browser, communicating back to the server only when saving changes.

Node Class Mappings

Internal Name Python Class Purpose
UltimateSamplerGrid SamplerGridTester Main generation node — receives configs, runs Cartesian grid
UltimateGridDashboard SamplerConfigDashboardViewer Dashboard viewer — renders iframe with results
UltimateConfigBuilder UltimateConfigBuilder Visual config builder — outputs configs_json string
SmartJSONText SmartJSONTextNode JSON text widget with syntax highlighting

API Routes Reference

All routes registered in __init__.py via PromptServer.instance.routes.

Config Builder APIs (/configbuilder/):

Method Route Purpose
GET /configbuilder/list_configs List saved config preset filenames
POST /configbuilder/save_config Save config preset {name, data}
POST /configbuilder/load_config Load config preset {name}
POST /configbuilder/lookup_triggers Bulk LoRA trigger word lookup {loras: [...]}
POST /configbuilder/lookup_lora_metadata Full CivitAI metadata for LoRA {lora_name}
POST /configbuilder/lookup_model_metadata Full CivitAI metadata for model {model_name, model_type}
GET /configbuilder/model_lists All model/sampler/scheduler/VAE lists
POST /configbuilder/refresh_models Signal frontend cache clear

Dashboard/Tester APIs (/config_tester/):

Method Route Purpose
POST /config_tester/delete_session Delete session folder {session_name}
POST /config_tester/save_changes Delta save — only changed items {session_name, changed_items}
POST /config_tester/save_manifest Full merge save {session_name, manifest}
POST /config_tester/get_session_html Generate dashboard HTML on-demand {session_name, node_id}
POST /config_tester/export_favorites Export favorited images {session_name, pack_metadata, organize_by_prompt, organize_by_lora}
POST /config_tester/scan_directory Scan external dir, create symlinks, use built-in /view {directory_path, session_name}

Distribution APIs (/distribution/, registered in distribution_routes.py):

Method Route Purpose
GET /distribution/claim_job Worker claims next pending job (200 + job JSON, 204 = no jobs, 503 = inactive)
POST /distribution/submit_result Worker uploads completed image (multipart: {metadata, image})
GET /distribution/status Get distribution status, worker stats, job counts
POST /distribution/register_worker Worker registration with master
GET /distribution/download_model Download model file for model sync {category, filename}
POST /distribution/heartbeat Worker keepalive signal

1. The Backend: Core Logic & Orchestration

Located in the root directory.

  • sampler_node.py (The Interface)

  • Role: The primary ComfyUI node (SamplerGridTester). Validates inputs and passes control to the orchestrator.

  • Key Functions: run_tests() (entry point → delegates to generation_orchestrator.run_generation_loop()), find_existing_match(), get_latent_channels()

  • Key Logic: Disables caching features if "Optional" inputs are connected (since external changes can't be tracked). IS_CHANGED() returns NaN when optionals connected (forces re-execution), deterministic hash otherwise.

  • Required Inputs: ckpt_name, positive_text, negative_text, seed, denoise, configs_json, resolutions_json, session_name, overwrite_existing, flush_batch_every, add_random_seeds_to_gens, lora_triggerwords_mode, remote_vae_endpoint, save_conditioning_cache_to_file, enable_model_cache

  • Optional Inputs: optional_model (MODEL), optional_clip (CLIP), optional_vae (VAE), optional_positive (CONDITIONING), optional_negative (CONDITIONING), optional_latent (LATENT)

  • generation_orchestrator.py (The Conductor — largest file, ~1700 lines)

  • Role: Manages the generation loop, distributed processing, and master text encoding.

  • Key Functions: run_generation_loop(), check_if_job_completed(), setup_session_directories(), load_model_by_type(), get_model_cache_key(), calculate_clip_hash(), _conditioning_to_serializable(), _serializable_to_conditioning(), _preencode_all_conditionings(), _run_distributed_generation(), _send_distribution_status()

  • Key Logic:

  • Smart Skip: check_if_job_completed() matches against manifest items by seed/resolution/sampler/scheduler/steps/cfg/denoise. Skips model/lora/prompt matching when optional inputs connected.

  • Lookahead: Triggers model_cache.preload_lora_model() / preload_base_model() for the next job while the current one runs.

  • Interrupts: Catches InterruptProcessingException during text encoding AND image generation. Flushes pending VAE batch and saves manifest before stopping.

  • ETA Tracking: Sends ultimate_grid.progress events to dashboard via PromptServer.instance.send_sync().

  • Node Change Detection: Detects when connected upstream nodes have changed for smarter job resuming.

  • Pre-encoding: Single model → batch-encode all unique prompts upfront. Multi-model → re-encode per model switch.

  • VAE Switching: Flushes pending batch before switching VAE. Tracks default_model_vae for reverting to "Default".

  • Distribution: _run_distributed_generation() manages the distributed pipeline — populates job queue, optionally pre-encodes conditionings, notifies workers, and processes master's share of jobs.

  • Master Text Encoding: _preencode_all_conditionings() groups configs by (model, LoRA), loads each combination, encodes all unique prompts, and serializes via _conditioning_to_serializable(). Handles multi-entry conditionings (AND combinator).

  • Per-Worker Logging: Final summary prints each worker's completed/failed job counts from manager.get_status()["workers"].

  • image_generation.py (The Worker)

  • Role: Wraps KSampler and VAE Decode.

  • Key Logic: Implements flush_batch_with_vae to save images and update the manifest in chunks (preventing VRAM overflow).

  • config_builder_node.py (The Builder UI Backend)

  • Role: UltimateConfigBuilder node. All state stored in single lora_config widget as JSON string.

  • Key Functions: generate_config(), process_lora_array(), get_available_sessions(), expand_lora_folders(), lookup_lora_triggers()

  • Key Logic: Python reads ONLY lora_config widget — all other INPUT_TYPES widgets (samplers, schedulers, steps, cfg) are vestigial and ignored. Also defines API routes for trigger/metadata lookup and model lists.

  • Distribution Output: When distribution is enabled, wraps configs in {"configs": [...], "_distribution": {...}} with keys: enabled, worker_urls, claim_timeout (default 600), use_master_encoding, sync_models_to_workers. When disabled, still wraps in {"configs": [...]} — sampler_node.py must unwrap in both cases.

  • html_generator.py (The Builder)

  • Role: Compiles the SPA into a single inlined HTML string.

  • Key Functions: get_html_template(title, manifest_data, node_id)

  • Key Logic: Reads all files from /resources/ in dependency order, injects them into template.html, embeds manifest.json as fullManifest JS variable. Replaces placeholders: __CSS_CONTENT__, __JS_CONTENT__, __TITLE__, __NODE_ID__. No separate HTTP requests — everything inlined.

  • JS Load Order: logic_state.js → logic_utils.js → logic_ui.js → logic_virtual.js → logic_pipeline.js → logic_events.js → logic_init.js


2. The Backend: Resource Management

Located in the root directory.

  • model_cache.py (The 3-Tier Cache)

  • Role: Thread-safe resource manager. Single ModelCache class.

  • Tiers: 1. LoRA File Cache (raw state_dicts in RAM), 2. Incremental States (partial LoRA stacks via check_incremental_cache()), 3. Final Patched Models (get_lora_model() / put_lora_model()).

  • Key Methods: get_base_model(), put_base_model(), preload_lora_model(), preload_base_model(), register_schedule(), set_current_step(), clear(), print_stats()

  • Async Preloading: preload_* methods run loaders in background threads via concurrent.futures, results retrieved later.

  • conditioning_cache.py (The Prompt Cache)

  • Role: Caches CLIP text encodings to disk.

  • Key Logic: Hashes (Prompt + CLIP State + LoRA Config) to ensure validity. Auto-disabled when optional inputs connected.

  • batch_encoding.py (The Accelerator)

  • Role: Bulk prompt encoder with combinator support.

  • Key Functions: encode_prompt_with_combinators(clip, text, clip_skip), batch_encode_prompts(clip, positives, negatives, cond_cache, clip_skip)

  • Combinators: AND (multi-cond with optional weights like :1.5), CAT (tensor concatenation), AVG(weight) (weighted average, default 0.5), BREAK (native tokenizer segment break at 77-token chunks).

  • model_loader.py (The Loader)

  • Role: Checkpoint, LoRA, VAE, GGUF, and diffusion model loading.

  • Key Functions: load_checkpoint(), load_loras(), load_vae_by_name(), load_diffusion_model_and_clip(), load_loras_for_preencoding(), cleanup_model_references(), print_incompatible_loras_summary()

  • trigger_words.py (Trigger Word Engine)

  • Role: CivitAI trigger word fetching and prompt assembly.

  • Key Functions: collect_unique_prompts_with_triggers(expanded_configs, mode), build_prompt_with_triggers(config, mode), get_filtered_lora_triggers(lora_string, omit_list)

  • Modes: "None", "Append To End", "Append To Start", "Read From Config". Per-LoRA placement via lora_triggerwords_append_settings. Also applies model_prompt_prefix / model_prompt_suffix.

  • config_utils.py (Expansion Engine)

  • Role: Config parsing, Cartesian product expansion, folder expansion.

  • Key Functions: expand_configs(), parse_prompt_input_nested(), prepare_input_jobs(), parse_lora_definition(), get_files_from_folder(), sanitize_session_name()

  • Key Logic: parse_prompt_input_nested() supports arbitrarily deep nested arrays for Cartesian prompting. "folder/" = expand individually, "folder/*" = stack all together.

  • manifest_utils.py (Manifest Persistence)

  • Key Functions: load_existing_manifest(), save_manifest(), merge_manifest_user_changes()

  • Key Logic: save_manifest() calls merge_manifest_user_changes() first — reloads from disk to preserve user favorites/rejected/notes that may have changed concurrently.

  • directory_scanner.py (External Image Scanner)

  • Key Functions: scan_directory_for_images(), parse_a1111_parameters()

  • Key Logic: Parses A1111-style PNG metadata into dashboard-compatible manifest items. Preserves user tags on re-scan by merging with existing manifest.

  • metadata_packer.py (Export Metadata)

  • Key Functions: pack_metadata_into_image(), extract_metadata_from_image(), calculate_file_hash()

  • Key Logic: Packs ComfyUI-compatible metadata into PNG for CivitAI uploads. Maintains SHA256 hash cache at benchmarks/model_hashes.json.


2b. The Backend: Distribution System

Located in the root directory. Enables multi-machine parallel processing.

  • distribution_manager.py (The Job Coordinator)

  • Role: Thread-safe distributed job queue running on the master. Manages job lifecycle: PENDING → CLAIMED → COMPLETED/FAILED.

  • Classes: JobState (constants), DistributedJob (individual job), DistributionManager (coordinator)

  • Key Methods: populate_jobs(), claim_job(worker_id), complete_job(job_id), get_stats(), set_encoded_conditionings(), get_encoded_conditionings_for_job()

  • Key Logic:

    • Thread-safe with RLock for concurrent worker access
    • Job timeout handling (default 600s) — re-queues timed-out jobs as PENDING
    • MAX_FAIL_COUNT = 3 — jobs that fail 3 times are abandoned
    • _job_to_dict() attaches pre-encoded conditionings to job claim response if available
    • Tracks per-worker stats (completed, failed, last_seen)
  • distribution_worker.py (The Remote Worker)

  • Role: Background thread running on remote ComfyUI instances. Polls master for jobs, processes locally, uploads results.

  • Class: WorkerThread(threading.Thread) — daemon thread

  • Key Methods: run() (main loop), _process_job(), _download_model_from_master(), _prefetch_models(), stop()

  • Key Logic:

    • Polls /distribution/claim_job every 2 seconds
    • Caches model/CLIP/VAE between jobs (reuses when same model)
    • Checks for encoded_positive/encoded_negative in job data — uses master conditionings if available, falls back to local CLIP encoding otherwise
    • Model sync: downloads models from master via /distribution/download_model if enabled
    • Exits after 30 consecutive empty polls or 503 response (master finished)
  • distribution_routes.py (The API Layer)

  • Role: Registers /distribution/* API endpoints on the PromptServer.

  • Global State: _distribution_manager (active manager, master mode), _worker_thread (active worker, worker mode) — both thread-safe via getters/setters

  • Key Endpoints: claim_job (GET), submit_result (POST multipart), status (GET), register_worker (POST), download_model (GET), heartbeat (POST)


Key Data Shapes

Config Array State (node.state.config_arrays[n] in JS, parsed from lora_config widget in Python):

{
  "name": "Config 1",
  "samplers": ["euler", "dpmpp_2m"],
  "schedulers": ["normal", "karras"],
  "steps": "20, 30",
  "cfg": "7.0",
  "models": [{"path": "model.safetensors", "type": "checkpoint"}],
  "vaes": ["None"],
  "loras": ["lora.safetensors:1.00:1.00"],
  "lora_bypass_states": {"lora_name": true},
  "model_bypass_states": {"model_path": true},
  "lora_omit_triggers": [],
  "lora_triggerwords_append_settings": {},
  "lora_strength_lock": {},
  "combine": false,
  "positive_prompt_groups": [],
  "negative_prompt": "",
  "use_custom_prompts": false,
  "model_prompt_prefix": "",
  "model_prompt_suffix": "",
  "attention_modes": ["default"],
  "resolutions": [],
  "seed_behavior": "fixed",
  "text_encoders": [],
  "clip_type": "stable_diffusion",
  "gguf_options": {},
  "model_sampling_override": "None",
  "model_sampling_shift": 3.0,
  "use_advanced_sampling": false,
  "advanced_guider": "Basic",
  "use_flux_guidance": false,
  "flux_guidance_value": 3.5
}

Distribution Config (embedded in config_builder_node.py output as _distribution key):

{
  "enabled": true,
  "worker_urls": ["http://192.168.1.100:8188", "http://192.168.1.101:8188"],
  "claim_timeout": 600,
  "use_master_encoding": false,
  "sync_models_to_workers": false
}

Top-level state fields (in conf-builder-main.js default state, outside config_arrays):

{
  "session_name": "my_session",
  "config_name": "my_config",
  "auto_save": false,
  "include_none": true,
  "label_mode": "full",
  "global_positive_groups": [],
  "global_negative": "",
  "distribution_enabled": false,
  "worker_urls": [],
  "claim_timeout": 600,
  "use_master_encoding": false,
  "sync_models_to_workers": false
}

Manifest Item (manifest.json items[n]):

{
  "id": "uuid-string",
  "seed": 12345, "cfg": 7.0, "steps": 20,
  "sampler": "euler", "scheduler": "normal",
  "model": "model.safetensors", "lora": "lora:1.0:1.0",
  "vae": "vae.safetensors", "attention_mode": "default",
  "positive": "prompt text", "negative": "negative text",
  "denoise": 1.0, "width": 1024, "height": 1024,
  "batch_idx": 0, "generation_time": 45.2,
  "file": "/view?filename=img_0.webp&type=output&subfolder=benchmarks/session/images",
  "favorite": false, "rejected": false, "notes": ""
}

Communication Patterns

Widget Bridge (Config Builder → Python):

JS node.state → JSON.stringify() → lora_config widget.value → Python json.loads(lora_config)

All other widget params in INPUT_TYPES (samplers, schedulers, steps, cfg) are ignored by Python.

Live Dashboard Updates (Server → Dashboard iframe):

generation_orchestrator → PromptServer.send_sync("ultimate_grid.update")
  → dashboard.js api.addEventListener → postMessage({type: 'update_data'})
  → iframe logic_events.js message handler

Progress Updates (Server → Dashboard iframe):

generation_orchestrator → PromptServer.send_sync("ultimate_grid.progress")
  → dashboard.js → postMessage({type: 'progress_update'})
  → iframe #eta-bar update

Dashboard → Parent Frame:

iframe postMessage({type: 'toggle_fullscreen', node_id})
  → dashboard.js listener → toggle .dashboard-fullscreen class

Distribution Status Updates (Server → Dashboard iframe):

generation_orchestrator → PromptServer.send_sync("ultimate_grid.distribution_status")
  → dashboard.js api.addEventListener → postMessage({type: 'distribution_status'})
  → iframe displays worker activity

Distribution Job Flow (Master ↔ Worker):

Master:
  1. generation_orchestrator → DistributionManager.populate_jobs()
  2. [Optional] _preencode_all_conditionings() → manager.set_encoded_conditionings()
  3. POST /distribution/register_worker on each worker
  4. Workers poll GET /distribution/claim_job
  5. manager._job_to_dict() attaches encoded_positive/encoded_negative if available
  6. Worker processes job → POST /distribution/submit_result (multipart: metadata + image)
  7. Master saves image + updates manifest

3. The Frontend: ComfyUI Integration

Located in /web/.

  • dashboard.js

  • Role: Registers the Dashboard node (UltimateGridDashboard). Listens for ultimate_grid.update, ultimate_grid.progress, and ultimate_grid.distribution_status server events.

  • Key Logic: Forwards events to matching iframe via postMessage(). Auto-loads session when update received for unloaded session. Handles fullscreen toggle via .dashboard-fullscreen CSS class. Auto-loads on node creation with 500ms delay via forceLoadSession() so the dashboard shows content immediately (landing page or session grid) instead of a blank black box.

  • Widgets: "RELOAD / SHOW SESSION" button → forceLoadSession(), "DELETE SESSION" button → POST /config_tester/delete_session

  • Event Listeners (Singletons): Each uses window.__ultimateGrid*ListenerInstalled guard to prevent duplicate registration:

    • ultimate_grid.update — Live data updates to iframe
    • ultimate_grid.progress — ETA/progress bar updates
    • ultimate_grid.distribution_status — Distribution worker status
    • window "message" — Fullscreen toggle from iframe
  • config_builder.js (entry point, loads conf_builder/ modules)

  • Role: Registers the Builder node (UltimateConfigBuilder). Entry point for the modular Config Builder UI.

  • Key Logic: onNodeCreated MUST be synchronous (async init runs in fire-and-forget IIFE). Uses Date.now() cache-busting on module import() calls. Hooks app.refreshComboInNodes() to detect model/LoRA changes.

  • Node Methods: node.saveState(), node.renderUI(), node.loadSession(sessionName), node.saveConfigToBackend(), node.loadConfigFromBackend(filename), node.triggerAutoSave() (2s debounce)

  • smart_json_text.js

  • Role: Registers the JSON node (SmartJSONText). Adds syntax highlighting and validation.

  • conf_builder/conf-builder-utilities.js (The Data Layer)

  • Key Exports: convertStateToConfigs(), getIterationCount(), parseLoraString(), buildLoraString(), refreshAllConfigBuilders(), clearAllCaches(), normalizePath(), getAvailableLoras(), getModelLists()

  • Global Caches: availableLoras, availableModels, availableVAEs, availableSamplers, availableSchedulers, availableSessions, availableConfigs

  • conf_builder/conf-builder-config-management.js (The UI Builder)

  • Key Exports: renderUI(), updatePreview(), renderConfigSection(), renderSessionSection(), createConfigArrayElement(), createModelElement()

  • conf_builder/conf-builder-ui-components.js (The Widget Library)

  • Key Exports: createSearchableSelect(), createSlider(), createInputGroup(), createTopBar(), createSidebar(), createSectionHeader(), getStyles()

  • conf_builder/conf-builder-distribution.js (Distribution Settings UI)

  • Role: Renders the distribution settings section in the Config Builder.

  • Key Exports: renderDistributionSection(), renderDistributionSettingsSection()

  • Features: Worker URL management (add/remove), connection testing via /distribution/test_worker, status indicators (green/yellow/red dots), claim timeout slider (30-900s, default 600), toggles for "Use Master Text Encoding" and "Sync Models to Workers".


4. The Frontend: The Dashboard SPA

Located in /resources/. This is the "App" users interact with.

  • logic_init.js (The Bootstrapper)

  • Role: Main Entry Point.

  • Key Logic:

  • initDashboard(): The first function called.

  • Data Injection: detects if fullManifest exists (injected by Python). If not, it attempts to fetch it (View Mode).

  • Lifecycle: Calls initLogicState() -> applyPipeline() -> initVirtualScroller() -> initGlobalEvents().

  • logic_state.js (The Brain)

  • Role: Central State Store (Redux-style).

  • Key Logic:

  • State Object: Holds items (all images), filteredItems (visible), selection (active card), and favorites.

  • Triple JSON Lists: Manages the logic for the "Accepted", "Favorites", and "Rejected" JSON bars at the bottom of the UI.

  • Persistence: Saves user preferences (Sort Order, Column Count) to localStorage so they persist across reloads.

  • logic_events.js (The Handler)

  • Role: Interaction & API Bridge.

  • Key Logic:

  • Keyboard Shortcuts: Maps Arrow Keys (Pan), Space (Scroll), +/- (Zoom), F (Fit), Escape (close modals with priority chain).

  • API Calls: Uses fetch to hit endpoints like /config_tester/save_manifest (when you star/reject an image) and /config_tester/delete_session.

  • Live Updates: Listens for the executed event from ComfyUI to trigger a soft-refresh of the image list.

  • logic_virtual.js (The Engine)

  • Role: Virtual Scroller.

  • Key Logic: Calculates viewport geometry to render only the ~20 visible cards out of thousands, enabling high performance.

  • logic_pipeline.js (The Processor)

  • Role: Filtering & Sorting.

  • Key Logic: Transforms the raw item list based on active filters (Model, Sampler, etc.) before passing it to the virtual scroller.

  • logic_ui.js (The Renderer)

  • Role: DOM Manipulation.

  • Key Logic: Generates the HTML for individual cards, the "Revise" modal, and the unified Settings panel (gear icon). Manages modal open/close with overlay and body overflow lock.

  • logic_utils.js (The Helpers)

  • Role: Utilities and server communication.

  • Key Functions: loadSession(), saveState(), exportFavorites(), scanDirectory(), triggerGen(), toggleFullscreen(), rejectItem()


Key Functions Quick Reference

Python (by file):

File Key Functions
sampler_node.py run_tests(), find_existing_match(), get_latent_channels(), IS_CHANGED()
config_builder_node.py generate_config(), process_lora_array(), get_available_sessions(), expand_lora_folders()
generation_orchestrator.py run_generation_loop(), check_if_job_completed(), setup_session_directories(), load_model_by_type(), _preencode_all_conditionings(), _conditioning_to_serializable(), _serializable_to_conditioning(), _run_distributed_generation()
batch_encoding.py encode_prompt_with_combinators(), batch_encode_prompts()
config_utils.py expand_configs(), parse_prompt_input_nested(), prepare_input_jobs(), parse_lora_definition()
model_loader.py load_checkpoint(), load_loras(), load_vae_by_name(), load_diffusion_model_and_clip()
model_cache.py ModelCache.get_base_model(), .put_base_model(), .preload_lora_model(), .register_schedule()
trigger_words.py collect_unique_prompts_with_triggers(), build_prompt_with_triggers(), get_filtered_lora_triggers()
manifest_utils.py load_existing_manifest(), save_manifest(), merge_manifest_user_changes()
directory_scanner.py scan_directory_for_images(), parse_a1111_parameters()
metadata_packer.py pack_metadata_into_image(), extract_metadata_from_image(), calculate_file_hash()
distribution_manager.py DistributionManager.populate_jobs(), .claim_job(), .complete_job(), .get_stats(), .set_encoded_conditionings(), .get_encoded_conditionings_for_job()
distribution_worker.py WorkerThread.run(), ._process_job(), ._download_model_from_master(), ._prefetch_models(), .stop()
distribution_routes.py get_distribution_manager(), set_distribution_manager(), endpoint handlers for /distribution/*

JavaScript (by file):

File Key Functions
conf-builder-utilities.js convertStateToConfigs(), getIterationCount(), parseLoraString(), buildLoraString(), refreshAllConfigBuilders()
conf-builder-config-management.js renderUI(), updatePreview(), createConfigArrayElement(), createModelElement()
conf-builder-ui-components.js createSearchableSelect(), createSlider(), getStyles()
conf-builder-main.js ensureModulesLoaded(), node.saveState(), node.renderUI(), node.loadSession(), node.triggerAutoSave()
conf-builder-distribution.js renderDistributionSection(), renderDistributionSettingsSection()
logic_ui.js toggleCogMenu(), toggleFiltersPopup(), initFilters()
logic_utils.js loadSession(), exportFavorites(), scanDirectory(), triggerGen(), toggleFullscreen()
logic_virtual.js renderDOM(), updateVisibleItems(), calculateVisibleRange(), autoFitZoom(), goToImage()
logic_pipeline.js updateDataPipeline(), executePipeline(), processNewData(), incrementalFilter()
logic_events.js Message handler for progress_update and update_data types

Summary of Data Flow

  1. User Config: You set parameters in the Config Builder (visual UI) or Sampler Node (JSON text).
  2. Config Output: config_builder_node.py wraps configs in {"configs": [...], "_distribution": {...}}. The sampler_node.py unwraps the "configs" key and extracts distribution settings.
  3. Expansion: generation_orchestrator.py expands configs into a full list of jobs (Cartesian product).
  4. Distribution (optional): If enabled, _run_distributed_generation() populates the job queue, optionally pre-encodes conditionings, and notifies workers. Workers poll for jobs, process locally, and upload results.
  5. Generation: The backend loops through its share of jobs, using ModelCache and ConditioningCache for speed.
  6. Storage: Images are saved to disk; metadata is appended to manifest.json.
  7. Compilation: html_generator.py reads the manifest.json and all .js files in /resources/, baking them into a single dashboard_html string.
  8. Display: The Dashboard Node (JS) renders this HTML in an iframe. Auto-loads on node creation.
  9. Boot: logic_init.js calls init() which calls showSessionLandingIfEmpty(). If no data, the session landing page is shown. If data exists, logic_virtual.js renders the grid.
  10. Interaction: You click "Favorite" in the UI -> logic_events.js sends an API request to __init__.py -> Python updates manifest.json on disk.

Critical Development Gotchas

⚠️ GOLDEN RULE: DO NOT REMOVE ANY CODE. DO NOT REMOVE ANY COMMENTS. ONLY CHANGE WHAT IS NECESSARY.

  1. Dual Data Path — Frontend convertStateToConfigs() (in conf-builder-utilities.js) and Python generate_config() (in config_builder_node.py) are independent implementations that both transform node.state into config JSON. Both must stay in sync when adding new config fields or the UI preview will mismatch the actual output.

  2. Widget Bridge — The lora_config widget (STRING type) holds the entire node state as serialized JSON. Python generate_config() ignores all other widget params and reads only lora_config. The samplers, schedulers, steps, cfg widgets in INPUT_TYPES are vestigial (kept for ComfyUI schema compliance only).

  3. onNodeCreated Must Be Synchronous — In conf-builder-main.js, onNodeCreated MUST remain synchronous. Async initialization runs inside a fire-and-forget (async () => { ... })() IIFE. Making onNodeCreated itself async breaks widget registration.

  4. Resources Are Inlined — html_generator.py reads all /resources/ files and bakes them into one HTML string. Zero external HTTP requests. Changes to dashboard JS/CSS require re-generating the HTML (re-run the sampler node or call /config_tester/get_session_html).

  5. Manifest Merge on Save — manifest_utils.py save_manifest() calls merge_manifest_user_changes() which reloads from disk before saving. This preserves user favorites/rejected/notes that may have been changed concurrently while generation was running.

  6. Optional Inputs Disable Caches — sampler_node.py disables conditioning cache when optional_model/optional_clip/optional_positive/optional_negative are connected. IS_CHANGED() returns NaN (forces re-execution). check_if_job_completed() skips model/lora/prompt matching.

  7. Folder Expansion Syntax — "folder/" = expand to individual entries (one combination each). "folder/*" = stack ALL files in folder together as a single combined entry. Weight array syntax "lora:[0.5, 0.8]:1.0" creates grid search over strengths. No trailing slash = single file match.

  8. LoRA String Format — "name:model_str:clip_str", stacked with " + " separator. parseLoraString() in JS, parse_lora_definition() in Python. Default strengths: 1.0 for both if colon-separated parts are missing.

  9. Cache Busting — conf-builder-main.js uses Date.now() timestamp on import() calls for module loading. Also intercepts window.fetch to detect /object_info calls and trigger cache refresh. Static JS served via ComfyUI's /extensions/ path.

  10. Config Presets Path — Saved to output/ultimate-configs/ (defined in __init__.py), NOT output/benchmarks/configs/. Session data goes under output/benchmarks/{session_name}/.

  11. Field Name Inconsistency: favorite vs favorited — manifest_utils.py merge_manifest_user_changes() uses the key "favorite". But __init__.py routes like save_manifest and export_favorites check "favorited". When touching favorite/unfavorite logic, check which key name the specific code path uses.

  12. Prompt Expansion is Recursive — parse_prompt_input_nested() in config_utils.py supports arbitrarily deep nested arrays. Flat list = OR (options). List containing sub-lists = AND (Cartesian product). This is used by both the node text inputs and per-config prompt groups from the builder UI.

  13. Config Builder Output is ALWAYS Wrapped — config_builder_node.py always outputs {"configs": [...]} (with optional "_distribution" key). The sampler_node.py MUST unwrap the "configs" key before passing to expand_configs(). Both the _distribution present AND absent cases must be handled (see lines 269-286 of sampler_node.py).

  14. Dashboard Auto-Load on Creation — web/dashboard.js calls forceLoadSession() with 500ms delay when a dashboard node is created. The get_session_html endpoint in __init__.py returns a full HTML template with empty manifest for non-existent sessions (NOT a 404). This ensures the landing page shows immediately.

  15. logic_pipeline.js Legacy Init Guard — Lines 489-505 have a DOMContentLoaded handler that calls loadSession() ONLY when fullManifest.items.length > 0. Without this guard, loadSession() triggers an alert popup for non-existent sessions. The proper initialization path is logic_init.js → init() → showSessionLandingIfEmpty().

  16. Distribution Default Values Must Stay in Sync — claim_timeout default (600) appears in: distribution_manager.py (constructor), config_builder_node.py (fallback), conf-builder-distribution.js (slider default), conf-builder-main.js (default state). All four must match. Same for use_master_encoding default (false).

  17. Conditioning Serialization Uses np.copy() — _serializable_to_conditioning() in generation_orchestrator.py must call np.frombuffer(...).copy() before torch.from_numpy() because np.frombuffer() returns read-only arrays that are incompatible with GPU tensor operations.

  18. Multi-Entry Conditioning (AND combinator) — The AND combinator creates multiple [tensor, {pooled_output}] entries in a conditioning list. Serialization in _conditioning_to_serializable() must iterate ALL entries, not just [0]. Deserialization in _serializable_to_conditioning() must reconstruct the full list.

  19. State Migration for New Fields — When adding new fields to the Config Builder default state in conf-builder-main.js (around line 89), also add a migration check (around line 363) to backfill the default for existing saved workflows. Without migration, old workflows will have undefined for the new field.

  20. ComfyUI Registry Security — No import requests, no subprocess/os.system/eval()/exec(), no runtime pip install, no custom file-serving endpoints, no code obfuscation. Use urllib.request for HTTP. Use ComfyUI's /view endpoint for serving files. Sanitize all user-supplied paths. See top of this file for full rules.


Notes for AI Development

This section provides critical context for AI assistants working on this codebase.

Architecture Mental Model:

┌─────────────────────────────────────────────────────────────┐
│ ComfyUI Browser Tab                                         │
│  ┌──────────────────┐  ┌──────────────────────────────────┐│
│  │ Config Builder    │  │ Dashboard Node                   ││
│  │ (conf-builder-*) │  │ (dashboard.js)                   ││
│  │ Direct DOM widget │  │  ┌──────────────────────────────┐││
│  │                   │  │  │ iframe (srcdoc)              │││
│  │                   │  │  │ Dashboard SPA                │││
│  │                   │  │  │ (resources/logic_*.js)       │││
│  │                   │  │  │ postMessage() ↕              │││
│  │                   │  │  └──────────────────────────────┘││
│  └──────────────────┘  └──────────────────────────────────┘│
│           ↕ widget value              ↕ server events       │
├─────────────────────────────────────────────────────────────┤
│ ComfyUI Server (Python)                                     │
│  ┌──────────────┐  ┌────────────────┐  ┌────────────────┐  │
│  │config_builder│  │sampler_node.py │  │__init__.py     │  │
│  │_node.py      │→ │                │  │(API endpoints) │  │
│  └──────────────┘  │  ↓             │  └────────────────┘  │
│                    │generation_     │                       │
│                    │orchestrator.py │                       │
│                    │  ↓             │                       │
│  ┌─────────┐     │model_loader +  │  ┌─────────────────┐ │
│  │ModelCache│←────│image_generation│  │DistributionMgr  │ │
│  └─────────┘     │  ↓             │  │(master jobs)    │ │
│                    │html_generator │  └─────────────────┘ │
│                    └────────────────┘         ↕ HTTP       │
│                                        ┌─────────────────┐ │
│                                        │WorkerThread     │ │
│                                        │(remote workers) │ │
│                                        └─────────────────┘ │
└─────────────────────────────────────────────────────────────┘

File Dependency Graph (Python):

sampler_node.py
  → generation_orchestrator.py (main entry)
    → config_utils.py (expand_configs, prepare_input_jobs)
    → model_loader.py (load_checkpoint, load_loras)
    → image_generation.py (generate_image, flush_batch)
    → batch_encoding.py (encode_prompt_with_combinators)
    → trigger_words.py (build_prompt_with_triggers)
    → manifest_utils.py (save_manifest, load_existing_manifest)
    → model_cache.py (ModelCache)
    → conditioning_cache.py (ConditioningCache)
    → remote_vae.py (RemoteVAEDecodeWorker)
    → html_generator.py (get_html_template)
    → distribution_manager.py (DistributionManager)
    → distribution_worker.py (WorkerThread)

config_builder_node.py
  → lora_utils.py (trigger word lookup, CivitAI API)

__init__.py
  → all node classes (sampler, dashboard, config_builder, json_text)
  → distribution_routes.py (distribution API endpoints)
  → html_generator.py (on-demand HTML generation)
  → metadata_packer.py (export favorites)
  → directory_scanner.py (scan external directories)
  → manifest_utils.py (save/load manifests)

Server Event Types:

Event Name Sender Data Fields Purpose
ultimate_grid.update generation_orchestrator.py session_name, node, manifest, meta, new_items New images generated
ultimate_grid.progress generation_orchestrator.py session_name, progress_pct, eta_str, current_job, total_jobs, avg_duration, total_generated, total_elapsed, finish_time ETA/progress bar
ultimate_grid.distribution_status generation_orchestrator.py session_name, workers, stats Distribution worker status

Testing Changes:

  • Python backend files: Restart ComfyUI
  • web/ JS files: Refresh browser (Ctrl+F5)
  • resources/ JS/CSS/HTML files: Dashboard HTML must be regenerated — re-run the sampler node or call /config_tester/get_session_html
  • Config Builder state fields: Also update migration checks in conf-builder-main.js