From 048ee3ca1b59512a3b0a90123a8f14f9bdf26cce Mon Sep 17 00:00:00 2001 From: JasonHoku Date: Mon, 16 Feb 2026 14:56:26 -0800 Subject: [PATCH] Update Readme & Project Structure --- .claude/settings.local.json | 3 +- ProjectStructure.md | 327 +++++++++++++++++++++++++++++++++--- README.md | 155 ++++++++++++++++- 3 files changed, 452 insertions(+), 33 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index e1108ba..6adf3f3 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -6,7 +6,8 @@ "WebFetch(domain:api.comfy.org)", "WebFetch(domain:github.com)", "WebFetch(domain:raw.githubusercontent.com)", - "WebFetch(domain:api.github.com)" + "WebFetch(domain:api.github.com)", + "Bash(grep:*)" ] } } diff --git a/ProjectStructure.md b/ProjectStructure.md index d873073..a737207 100644 --- a/ProjectStructure.md +++ b/ProjectStructure.md @@ -14,14 +14,25 @@ ComfyUI-Ultimate-Sampler-Grid/ ├── 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 +├── 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 │ ├── web/ # [ComfyUI Integration Layer] │ ├── dashboard.js # Registers Dashboard Node & Message Handling │ ├── config_builder.js # Registers Builder Node & Custom UI Widget -│ └── smart_json_text.js # Registers JSON Node & Syntax Highlighting +│ ├── 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 │ └── resources/ # [The Dashboard SPA Core] ├── template.html # HTML Skeleton for the generated report @@ -36,6 +47,22 @@ ComfyUI-Ultimate-Sampler-Grid/ ``` +**Output Directory Structure (Generated at Runtime):** +```text +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 +``` --- @@ -49,21 +76,72 @@ ComfyUI-Ultimate-Sampler-Grid/ --- +### **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 for images `{directory_path, session_name}` | +| GET | `/config_tester/view_external` | Serve whitelisted external images `?path=...` | +| POST | `/config_tester/whitelist_directory` | Re-whitelist dir after restart `{directory_path}` | + +--- + ### **1. The Backend: Core Logic & Orchestration** *Located in the root directory.* * **`sampler_node.py`** (The Interface) -* **Role:** The primary ComfyUI node. Validates inputs and passes control to the orchestrator. -* **Key Logic:** Disables caching features if "Optional" inputs are connected (since external changes can't be tracked). +* **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) +* **`generation_orchestrator.py`** (The Conductor — largest file, ~987 lines) * **Role:** Manages the generation loop. +* **Key Functions:** `run_generation_loop()`, `check_if_job_completed()`, `setup_session_directories()`, `load_model_by_type()`, `get_model_cache_key()`, `calculate_clip_hash()` * **Key Logic:** -* **Smart Skip:** Checks `manifest.json` to skip existing images. -* **Lookahead:** Triggers background loading for the *next* job while the current one runs. -* **Interrupts:** Catches cancel signals to save the manifest before stopping. +* **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". @@ -73,9 +151,17 @@ ComfyUI-Ultimate-Sampler-Grid/ * **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. + + * **`html_generator.py`** (The Builder) -* **Role:** Compiles the SPA. -* **Key Logic:** Reads all files from `/resources/`, injects them into `template.html`, and embeds the `manifest.json` data as a JavaScript variable (`fullManifest`), allowing the dashboard to load instantly without an initial API call. +* **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` @@ -86,20 +172,134 @@ ComfyUI-Ultimate-Sampler-Grid/ *Located in the root directory.* * **`model_cache.py`** (The 3-Tier Cache) -* **Role:** Thread-safe resource manager. -* **Tiers:** 1. Raw Files (RAM), 2. Incremental States (Partial LoRA stacks), 3. Final Results (Patched Models). +* **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. +* **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. -* **Key Logic:** Scans the grid for all prompts using the current model and encodes them in one massive GPU batch. +* **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`. + + +--- + +### **Key Data Shapes** + +**Config Array State** (`node.state.config_arrays[n]` in JS, parsed from `lora_config` widget in Python): +```json +{ + "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"], + "seed_behavior": "fixed", + "text_encoders": [], + "clip_type": "stable_diffusion", + "gguf_options": {} +} +``` + +**Manifest Item** (`manifest.json` `items[n]`): +```json +{ + "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 +``` --- @@ -108,15 +308,32 @@ ComfyUI-Ultimate-Sampler-Grid/ *Located in `/web/`.* * **`dashboard.js`** -* **Role:** Registers the Dashboard node. Listens for server events to update the iframe. +* **Role:** Registers the Dashboard node (`UltimateGridDashboard`). Listens for `ultimate_grid.update` and `ultimate_grid.progress` 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. +* **Widgets:** "RELOAD / SHOW SESSION" button → `forceLoadSession()`, "DELETE SESSION" button → POST `/config_tester/delete_session` -* **`config_builder.js`** -* **Role:** Registers the Builder node. Provides the complex UI for creating JSON configs. +* **`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. Adds syntax highlighting and validation. +* **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()`, `getStyles()` @@ -149,7 +366,7 @@ ComfyUI-Ultimate-Sampler-Grid/ * **`logic_events.js`** (The Handler) * **Role:** Interaction & API Bridge. * **Key Logic:** -* **Keyboard Shortcuts:** Maps `Arrow Keys` (Pan), `Space` (Scroll), `+/-` (Zoom), `F` (Fit). +* **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. @@ -168,15 +385,49 @@ ComfyUI-Ultimate-Sampler-Grid/ * **`logic_ui.js`** (The Renderer) * **Role:** DOM Manipulation. -* **Key Logic:** Generates the HTML for individual cards and the "Revise" modal. +* **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. -* **Key Logic:** Debouncers, formatters, and helper functions. +* **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()` | +| `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()` | + +**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()` | +| `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** @@ -188,4 +439,32 @@ ComfyUI-Ultimate-Sampler-Grid/ 5. **Compilation:** `html_generator.py` reads the `manifest.json` and all `.js` files in `/resources/`, baking them into a single `dashboard_html` string. 6. **Display:** The **Dashboard Node** (JS) renders this HTML in an iframe. 7. **Boot:** `logic_init.js` wakes up, loads the data into `logic_state.js`, and `logic_virtual.js` starts rendering cards. -8. **Interaction:** You click "Favorite" in the UI -> `logic_events.js` sends an API request to `__init__.py` -> Python updates `manifest.json` on disk. \ No newline at end of file +8. **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** + +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. \ No newline at end of file diff --git a/README.md b/README.md index 79fd074..d7ba2ca 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,10 @@ Stop guessing which Sampler, Scheduler, Prompt, Denoise, Model, Lora or CFG valu - [Strength Locking](#strength-locking) - [Folder Expansion (Models & LoRAs)](#folder-expansion-models--loras) - [LoRA Stacking](#lora-stacking) + - [VAEs Section](#vaes-section) + - [Attention Modes](#attention-modes) + - [Model-Specific Prompts](#model-specific-prompts) + - [Recursive Cartesian Prompt Builder](#recursive-cartesian-prompt-builder) - [Dashboard Interface](#-dashboard-interface) - [Header Bar](#header-bar) - [Toolbar](#toolbar) @@ -117,6 +121,12 @@ Stop guessing which Sampler, Scheduler, Prompt, Denoise, Model, Lora or CFG valu * **VAE Batching:** Includes a `vae_batch_size` input to batch decode images, significantly speeding up large grid runs. * **Live Dashboard Updates:** Configure `flush_batch_every` to update the dashboard incrementally (e.g., every 4 images) instead of waiting for the entire batch to complete. * **Remote VAE Support:** Offload VAE decoding to remote servers (HuggingFace endpoints or local) for 20-30% faster generation and lower VRAM usage. +* **VAE Selection & Iteration:** Test multiple VAEs per config — add individual VAEs from a searchable dropdown or expand entire VAE folders. Each VAE becomes a dimension in the Cartesian grid. +* **Attention Mode Testing:** Grid-test different attention implementations (`default`, `sageattn`, `xformers`, etc.) as a combinatorial dimension. Find the optimal attention backend for your hardware. +* **CLIP Encoding Combinators:** Advanced prompt syntax with `AND`, `CAT`, `AVG(weight)`, and `BREAK` keywords for multi-segment conditioning. `AND` creates separate conditioning entries, `CAT` concatenates token contexts, `AVG(0.5)` blends prompts, and `BREAK` forces new 77-token chunks. +* **Model-Specific Prompts:** Per-config prompt prefix and suffix fields for model-appropriate quality tags (e.g., prepend `"masterpiece, best quality"` for anime models, append `"4k, photorealistic"` for realistic models). +* **Recursive Cartesian Prompt Builder:** Visual chip-based prompt editor with `{option1|option2|option3}` syntax that expands into all combinations. Nest multiple groups for exponential prompt variation. +* **Model & LoRA On/Off Switches:** Toggle individual models and LoRAs on/off without removing them from the config. Bypassed entries are omitted from both the preview JSON and the generated config output. ### 🎨 Interactive Dashboard (The "IDE") * **Infinite Canvas with Pan/Zoom:** Google Maps-style navigation with mouse drag, mousewheel zoom, and keyboard shortcuts. @@ -130,6 +140,10 @@ Stop guessing which Sampler, Scheduler, Prompt, Denoise, Model, Lora or CFG valu * **Auto-Load Sessions:** Dashboard automatically loads when generation starts — no manual session name entry needed. * **Session Management:** Save and load previous testing sessions directly from the UI. * **Keyboard Navigation:** `Space` to scroll rows, `Arrow Keys` to pan, `+/-` to zoom, `0` to reset, `F` to auto-fit. +* **Real-Time ETA Progress Bar:** Shows estimated time remaining during generation with a progress bar in the dashboard header. +* **Unified Settings Panel:** Gear icon (⚙️) opens a consolidated settings panel with Session management, Grid Display controls, Export Favorites, and Scan External Directory — replacing the old separate SESSION button. +* **Scan External Directory:** Load image sessions from any folder (including other ComfyUI instances) into the dashboard for viewing and filtering. Reads PNG metadata for full config display. +* **Export Favorites:** Copy all favorited images to a dedicated export folder with optional CivitAI metadata packing for sharing. ### ⚡ The "Revise & Generate" Workflow * **One-Click Revision:** Click "REVISE" on any image to open a detail view. @@ -421,6 +435,9 @@ Config Arrays are the building blocks of your testing grid. Each array defines a * **Schedulers:** Comma-separated list of schedulers (e.g., `"normal, karras, exponential"`). * **Steps:** Comma-separated list of step counts (e.g., `"20, 30, 40"`). * **CFG:** Comma-separated list of CFG scale values (e.g., `"6.0, 7.0, 8.0"`). +* **Samplers Dropdown:** Searchable dropdown auto-populated with all available samplers from the connected sampler node. Select multiple to add them as a comma-separated list. +* **Schedulers Dropdown:** Searchable dropdown auto-populated with all available schedulers. Works the same as the samplers dropdown. +* **Attention Mode:** Dropdown selector for attention implementations (`default`, `sageattn`, `xformers`, etc.). Select multiple to test as a grid dimension. #### Iteration Count Display @@ -439,6 +456,7 @@ Updates in real-time as you add/remove items. * **📋 Duplicate Config** — Creates an exact copy of this config array (useful for variations). * **🗑️ Delete Config** — Removes this entire config array. * **▼ Collapse/Expand** — Toggle visibility of Models and LoRAs sections. +* **➕ Add VAE** — Adds a new VAE slot to the VAEs section for testing multiple VAE decoders. ### Models Section @@ -450,6 +468,8 @@ Each model card includes: - **Folder (Separate):** Expand folder — each model becomes a separate test. Multiplies iteration count by number of models in folder. - **Folder (Combined):** *Reserved for future use.* * **🗑️ Remove Model:** Deletes this model from the config (at least one model is always required). +* **🔘 On/Off (Bypass) Switch:** Toggle a model on or off without removing it. Bypassed models are grayed out and excluded from the generated config output. +* **🔍 CivitAI Model Lookup:** Opens a metadata panel showing model info fetched from CivitAI (name, base model, tags, creator, description). Includes a direct link to the model page. ### LoRAs Section @@ -466,6 +486,7 @@ Each LoRA card includes: * **🔍 Lookup Trigger Words:** Opens the Trigger Word Lookup Modal (fetches from CivitAI API). * **🚫 Bypass Trigger Fetch:** Toggle to prevent automatic trigger word lookup for this LoRA. * **🗑️ Remove LoRA:** Deletes this LoRA from the config (at least one is always required). +* **🔘 On/Off (Bypass) Switch:** Toggle a LoRA on or off without removing it. Bypassed LoRAs are grayed out and excluded from both the preview JSON and the Python node's `config_json` output. ### LoRA Trigger Words @@ -561,6 +582,47 @@ Combine multiple LoRAs into a single generation by adding multiple LoRA slots to **Mixed Expansion + Manual:** You can combine folder expansion with manual stacking — for example, a combined quality folder plus a specific character LoRA stacked together. +### VAEs Section + +Each VAE card includes: + +* **VAE Name (Searchable):** Dropdown with fuzzy search through all available VAE models. Supports folder structure display. +* **Type Selection:** + - **Single File:** Test just this specific VAE. + - **Folder (Separate):** Expand folder — each VAE becomes a separate iteration, multiplying the total grid size. +* **🗑️ Remove VAE:** Deletes this VAE from the config. + +VAEs are treated as a Cartesian dimension just like samplers or models. If you add 3 VAEs, your total iteration count is multiplied by 3. + +### Attention Modes + +Select which attention backend implementations to test. Available options depend on your system (e.g., `default`, `sageattn`, `xformers`). Like other parameters, you can select multiple modes and they become a grid dimension. + +### Model-Specific Prompts + +Per-config prompt prefix and suffix fields that are prepended/appended to all prompts generated by that config array: + +* **Prompt Prefix:** Text prepended to the positive prompt (e.g., `"masterpiece, best quality, "` for anime models). +* **Prompt Suffix:** Text appended to the positive prompt (e.g., `", 4k, photorealistic"` for realistic models). + +These fields are especially useful when testing multiple models that require different quality tags or style descriptors. + +### Recursive Cartesian Prompt Builder + +A visual chip-based prompt editor that supports variation groups using `{option1|option2|option3}` syntax: + +``` +a {photo|painting|sketch} of a {cat|dog} in a {forest|city} +``` + +This expands into all combinations (3 × 2 × 2 = 12 prompts): +- "a photo of a cat in a forest" +- "a photo of a cat in a city" +- "a painting of a dog in a forest" +- ... etc. + +The editor provides a visual chip interface for building and editing these variation groups. Each group is displayed as a clickable chip that can be expanded to add/remove options. Multiple groups can be nested for exponential prompt variation. + --- ## 🖥️ Dashboard Interface @@ -572,12 +634,14 @@ Combine multiple LoRAs into a single generation by adding multiple LoRA slots to * **Zoom Controls:** `⊙` (reset), `−` (zoom out), `+` (zoom in). ### Toolbar -* **Session Controls:** - - Dashboard auto-loads when connected to sampler and generation starts. - - **LOAD** to view previous results by session name. - - **SAVE** to persist current state to disk. - - **DELETE** to remove session and all images. - +* **⚙️ Settings Panel (Gear Icon):** Opens a unified settings panel with four sections: + - **Session:** Name input + SAVE/LOAD buttons. Dashboard auto-loads when connected to sampler and generation starts. + - **Grid Display:** Go to Image #, Column Count, Reset Zoom controls. + - **Export Favorites:** Export starred images to a dedicated folder with optional CivitAI metadata packing. + - **Scan External Directory:** Load image sessions from any folder path into the dashboard. + +* **ETA Progress Bar:** During generation, a real-time progress bar appears in the header showing estimated time remaining and completion percentage. + * **Filter Groups:** Click colored buttons to toggle visibility: - **Model** (Purple): Filter by checkpoint - **Sampler** (Cyan): Filter by sampler type @@ -585,9 +649,9 @@ Combine multiple LoRAs into a single generation by adding multiple LoRA slots to - **Denoise** (Red): Filter by denoise value - **LoRA** (Orange): Filter by LoRA configs - **Shift+Click:** Isolate single filter (deselect all others) - + * **Sort Button:** Cycles between **Sort: Oldest** (default), **Sort: Newest**, and **Sort: Fastest** (by generation time). Preference is saved to localStorage. - + * **Fullscreen Button (⛶):** Expand dashboard to fill entire screen. ### Navigation & Controls @@ -972,6 +1036,10 @@ The Config Builder generates JSON in the exact format required by the Ultimate S "cfg": [6.0, 7.0, 8.0], "lora": "style.safetensors:0.8:0.8 + detail.safetensors:0.5:0.5", "model": "base_xl.safetensors", + "vae": ["vae_v1.safetensors", "vae_v2.safetensors"], + "attention_mode": ["default", "sageattn"], + "prompt_prefix": "masterpiece, best quality, ", + "prompt_suffix": ", 4k, detailed", "lora_omit_triggers": ["masterpiece", "best quality"], "lora_triggerwords_append_settings": { "style.safetensors": "positive_start", @@ -980,6 +1048,9 @@ The Config Builder generates JSON in the exact format required by the Ultimate S "lora_bypass_states": { "technique_lora.safetensors": true }, + "model_bypass_states": { + "old_model.safetensors": true + }, "lora_strength_lock": { "style.safetensors": true } @@ -992,6 +1063,10 @@ The Config Builder generates JSON in the exact format required by the Ultimate S - **Array:** `["euler", "dpmpp_2m"]` or `[6.0, 7.0, 8.0]` - **LoRA string:** `"name:model_str:clip_str"` — stack with ` + ` - **Folder:** `"path/"` (separate) or `"path/*"` (combined) +- **VAE:** Single VAE name or array of VAE names (each becomes a grid dimension) +- **Attention mode:** Single mode string or array of modes to test +- **Prompt prefix/suffix:** Strings prepended/appended to all prompts in this config +- **Bypass states:** Object mapping model/LoRA names to `true` (bypassed) or `false` (active) --- @@ -1049,6 +1124,51 @@ Fetch complete metadata for a specific LoRA from CivitAI. ### `/configbuilder/refresh_models` (POST) Signal to clear frontend caches (called by ComfyUI when refreshing node definitions). +### `/configbuilder/lookup_model_metadata` (POST) +Fetch metadata for a checkpoint model from CivitAI (same as LoRA metadata but for models). + +**Request:** +```json +{ + "model_name": "realistic_vision_v5.safetensors" +} +``` + +**Response:** +```json +{ + "metadata": { + "name": "Realistic Vision V5.1", + "model_name": "Realistic Vision", + "base_model": "SD 1.5", + "description": "...", + "tags": ["realistic", "photorealistic"], + "url": "https://civitai.com/models/12345", + "hash": "abc123...", + "creator": "SG161222" + } +} +``` + +### `/configbuilder/scan_directory` (POST) +Scan an external directory for image sessions and load them into the dashboard. + +**Request:** +```json +{ + "path": "D:/other_comfyui/output/benchmarks/my_session" +} +``` + +**Response:** +```json +{ + "success": true, + "images_found": 42, + "manifest": { ... } +} +``` + --- ## File Locations @@ -1097,6 +1217,7 @@ ComfyUI/output/benchmarks/model-data/ | `+/-` | Zoom in/out | | `0` | Reset zoom to 1:1 | | `F` | Auto-fit first row to viewport width | +| `Escape` | Close active modal/popup (priority: Revision > Filters > Settings > Fullscreen) | ### Config Builder | Shortcut | Action | @@ -1110,6 +1231,24 @@ ComfyUI/output/benchmarks/model-data/ ## 📝 Changelog +### Update 2/16/26 — Config Builder & Dashboard Overhaul +* 🎛️ **VAE Selection & Iteration:** Test multiple VAEs per config with searchable dropdown and folder expansion. VAEs are a full Cartesian dimension. +* 🧠 **Attention Mode Testing:** Grid-test different attention implementations (`default`, `sageattn`, `xformers`) as a combinatorial dimension. +* 🔗 **CLIP Encoding Combinators:** `AND`, `CAT`, `AVG(weight)`, and `BREAK` keywords for multi-segment conditioning and prompt blending. +* 📝 **Model-Specific Prompts:** Per-config prompt prefix and suffix fields for model-appropriate quality tags. +* 🔄 **Recursive Cartesian Prompt Builder:** Visual chip-based editor with `{option1|option2}` syntax expanding into all combinations. +* 🔘 **Model & LoRA On/Off Switches:** Toggle entries on/off without removing them. Bypassed entries excluded from preview and output. +* 🔍 **CivitAI Model Metadata Lookup:** Fetch model info from CivitAI directly in the Config Builder (same as existing LoRA lookup). +* 📋 **Sampler/Scheduler Dropdowns:** Searchable dropdowns auto-populated from the connected sampler node, replacing free-text CSV entry. +* ⚙️ **Unified Settings Panel:** SESSION button merged into a gear icon settings panel with Session, Grid Display, Export Favorites, and Scan Directory sections. +* 📊 **Real-Time ETA Progress Bar:** Shows estimated time remaining during generation in the dashboard header. +* 📂 **Scan External Directory:** Load image sessions from any folder into the dashboard for viewing and filtering. +* 📦 **Export Favorites:** Copy starred images to a dedicated folder with optional CivitAI metadata packing. +* 🛑 **Cancel During Text Encoding:** Properly cancels the entire run when interrupted during the text encoding step. +* 🔄 **Connected Node Change Detection:** Detects when upstream nodes have changed for smarter job resuming. +* ⌨️ **Close Modal Hotkeys:** Escape key closes modals in priority order (Revision > Filters > Settings > Fullscreen). +* 🧹 **Dashboard Topbar Consolidation:** Streamlined header with condensed controls. + ### Update 2/5/26 — Code Refactoring & Performance Improvements * 🏗️ **Major Code Refactoring:** Reorganized codebase into 6 modular files for better maintainability (`trigger_words.py`, `batch_encoding.py`, `manifest_utils.py`, `model_loader.py`, `image_generation.py`, `generation_orchestrator.py`). * 🎯 **CLIP Skip Support:** Control CLIP layer usage with `clip_skip` parameter. Essential for anime models (-2) vs realistic models (0). Supports arrays for testing multiple values.