- 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>
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:
- No
import requests— Useurllib.request(stdlib) instead. The scanner flags outbound HTTP libraries as potential data exfiltration vectors. - No
subprocess,os.system,eval(),exec()— These are blocked as arbitrary code execution risks. - No runtime
pip install— Package installation must go throughrequirements.txtonly, never called at runtime. - No code obfuscation — No encoded strings, multi-statement tricks, or undefined variable patterns.
- No custom file-serving endpoints — Use ComfyUI's built-in
WEB_DIRECTORYfor JS and the built-in/viewendpoint for images. Never useweb.FileResponseor 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. - 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/). Useos.path.realpath()+startswith()checks. - Sanitize all user-supplied path components — Session names, filenames, etc. must be
sanitized with
re.sub(r'[^\w\-]', '', name)oros.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.
- The Backend (Python): Runs inside ComfyUI. It orchestrates the grid generation, manages resources (caching/loading), and serves the API.
- The Frontend Bridge (JS): Runs in the ComfyUI browser tab. It registers the custom nodes and widgets.
- 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 togeneration_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()returnsNaNwhen 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
InterruptProcessingExceptionduring text encoding AND image generation. Flushes pending VAE batch and saves manifest before stopping. -
ETA Tracking: Sends
ultimate_grid.progressevents to dashboard viaPromptServer.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_vaefor 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_vaeto save images and update the manifest in chunks (preventing VRAM overflow). -
config_builder_node.py(The Builder UI Backend) -
Role:
UltimateConfigBuildernode. All state stored in singlelora_configwidget 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_configwidget — 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.pymust 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 intotemplate.html, embedsmanifest.jsonasfullManifestJS 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
ModelCacheclass. -
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 viaconcurrent.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 vialora_triggerwords_append_settings. Also appliesmodel_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()callsmerge_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
RLockfor 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)
- Thread-safe with
-
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_jobevery 2 seconds - Caches model/CLIP/VAE between jobs (reuses when same model)
- Checks for
encoded_positive/encoded_negativein job data — uses master conditionings if available, falls back to local CLIP encoding otherwise - Model sync: downloads models from master via
/distribution/download_modelif enabled - Exits after 30 consecutive empty polls or 503 response (master finished)
- Polls
-
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 forultimate_grid.update,ultimate_grid.progress, andultimate_grid.distribution_statusserver events. -
Key Logic: Forwards events to matching iframe via
postMessage(). Auto-loads session when update received for unloaded session. Handles fullscreen toggle via.dashboard-fullscreenCSS class. Auto-loads on node creation with 500ms delay viaforceLoadSession()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*ListenerInstalledguard to prevent duplicate registration:ultimate_grid.update— Live data updates to iframeultimate_grid.progress— ETA/progress bar updatesultimate_grid.distribution_status— Distribution worker statuswindow "message"— Fullscreen toggle from iframe
-
config_builder.js(entry point, loadsconf_builder/modules) -
Role: Registers the Builder node (
UltimateConfigBuilder). Entry point for the modular Config Builder UI. -
Key Logic:
onNodeCreatedMUST be synchronous (async init runs in fire-and-forget IIFE). UsesDate.now()cache-busting on moduleimport()calls. Hooksapp.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
fullManifestexists (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:
-
StateObject: Holdsitems(all images),filteredItems(visible),selection(active card), andfavorites. -
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
localStorageso 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
fetchto hit endpoints like/config_tester/save_manifest(when you star/reject an image) and/config_tester/delete_session. -
Live Updates: Listens for the
executedevent 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
- User Config: You set parameters in the Config Builder (visual UI) or Sampler Node (JSON text).
- Config Output:
config_builder_node.pywraps configs in{"configs": [...], "_distribution": {...}}. Thesampler_node.pyunwraps the"configs"key and extracts distribution settings. - Expansion:
generation_orchestrator.pyexpands configs into a full list of jobs (Cartesian product). - 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. - Generation: The backend loops through its share of jobs, using ModelCache and ConditioningCache for speed.
- Storage: Images are saved to disk; metadata is appended to
manifest.json. - Compilation:
html_generator.pyreads themanifest.jsonand all.jsfiles in/resources/, baking them into a singledashboard_htmlstring. - Display: The Dashboard Node (JS) renders this HTML in an iframe. Auto-loads on node creation.
- Boot:
logic_init.jscallsinit()which callsshowSessionLandingIfEmpty(). If no data, the session landing page is shown. If data exists,logic_virtual.jsrenders the grid. - Interaction: You click "Favorite" in the UI ->
logic_events.jssends an API request to__init__.py-> Python updatesmanifest.jsonon disk.
Critical Development Gotchas
⚠️ GOLDEN RULE: DO NOT REMOVE ANY CODE. DO NOT REMOVE ANY COMMENTS. ONLY CHANGE WHAT IS NECESSARY.
-
Dual Data Path — Frontend
convertStateToConfigs()(inconf-builder-utilities.js) and Pythongenerate_config()(inconfig_builder_node.py) are independent implementations that both transformnode.stateinto config JSON. Both must stay in sync when adding new config fields or the UI preview will mismatch the actual output. -
Widget Bridge — The
lora_configwidget (STRING type) holds the entire node state as serialized JSON. Pythongenerate_config()ignores all other widget params and reads onlylora_config. Thesamplers,schedulers,steps,cfgwidgets inINPUT_TYPESare vestigial (kept for ComfyUI schema compliance only). -
onNodeCreated Must Be Synchronous — In
conf-builder-main.js,onNodeCreatedMUST remain synchronous. Async initialization runs inside a fire-and-forget(async () => { ... })()IIFE. MakingonNodeCreateditself async breaks widget registration. -
Resources Are Inlined —
html_generator.pyreads 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). -
Manifest Merge on Save —
manifest_utils.pysave_manifest()callsmerge_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. -
Optional Inputs Disable Caches —
sampler_node.pydisables conditioning cache whenoptional_model/optional_clip/optional_positive/optional_negativeare connected.IS_CHANGED()returnsNaN(forces re-execution).check_if_job_completed()skips model/lora/prompt matching. -
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. -
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. -
Cache Busting —
conf-builder-main.jsusesDate.now()timestamp onimport()calls for module loading. Also interceptswindow.fetchto detect/object_infocalls and trigger cache refresh. Static JS served via ComfyUI's/extensions/path. -
Config Presets Path — Saved to
output/ultimate-configs/(defined in__init__.py), NOToutput/benchmarks/configs/. Session data goes underoutput/benchmarks/{session_name}/. -
Field Name Inconsistency:
favoritevsfavorited—manifest_utils.pymerge_manifest_user_changes()uses the key"favorite". But__init__.pyroutes likesave_manifestandexport_favoritescheck"favorited". When touching favorite/unfavorite logic, check which key name the specific code path uses. -
Prompt Expansion is Recursive —
parse_prompt_input_nested()inconfig_utils.pysupports 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. -
Config Builder Output is ALWAYS Wrapped —
config_builder_node.pyalways outputs{"configs": [...]}(with optional"_distribution"key). Thesampler_node.pyMUST unwrap the"configs"key before passing toexpand_configs(). Both the_distributionpresent AND absent cases must be handled (see lines 269-286 ofsampler_node.py). -
Dashboard Auto-Load on Creation —
web/dashboard.jscallsforceLoadSession()with 500ms delay when a dashboard node is created. Theget_session_htmlendpoint in__init__.pyreturns a full HTML template with empty manifest for non-existent sessions (NOT a 404). This ensures the landing page shows immediately. -
logic_pipeline.jsLegacy Init Guard — Lines 489-505 have aDOMContentLoadedhandler that callsloadSession()ONLY whenfullManifest.items.length > 0. Without this guard,loadSession()triggers an alert popup for non-existent sessions. The proper initialization path islogic_init.js→init()→showSessionLandingIfEmpty(). -
Distribution Default Values Must Stay in Sync —
claim_timeoutdefault (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 foruse_master_encodingdefault (false). -
Conditioning Serialization Uses np.copy() —
_serializable_to_conditioning()ingeneration_orchestrator.pymust callnp.frombuffer(...).copy()beforetorch.from_numpy()becausenp.frombuffer()returns read-only arrays that are incompatible with GPU tensor operations. -
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. -
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 haveundefinedfor the new field. -
ComfyUI Registry Security — No
import requests, nosubprocess/os.system/eval()/exec(), no runtimepip install, no custom file-serving endpoints, no code obfuscation. Useurllib.requestfor HTTP. Use ComfyUI's/viewendpoint 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