Commit Graph
295 Commits
Author SHA1 Message Date
Jason Hoku cbc82026bc fix: route all config_builder prints through safe_print to avoid Windows OSError 2026-04-22 17:33:35 -07:00
Jason Hoku 276e03d6a3 bump ver 2026-04-13 10:31:08 -07:00
Jason HokuandClaude Opus 4.6 ffbd8ae1e4 fix: manifest merge uses correct field names for favorites preservation
merge_manifest_user_changes() was checking for wrong field names:
- "favorite" → should be "favorited" (how dashboard stores it)
- "notes" → should be "note" (singular)
- "rejected" was correct

This caused the merge to NEVER preserve favorites when the generation
pipeline saved the manifest. The race condition:
1. User favorites image → dashboard saves via /save_changes (100ms delay)
2. Pipeline saves manifest via save_manifest() before dashboard's save
3. Merge should preserve favorites from disk, but wrong key names meant
   it found nothing to preserve
4. Next pipeline save overwrites the manifest without the favorites

Now uses .get("favorited"), .get("rejected"), .get("note") matching
the actual field names in the manifest JSON.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 16:19:00 -07:00
Jason HokuandClaude Opus 4.6 1fc660ad78 fix: Revise & Generate sends complete config with model, seed, prompts
The GENERATE NEW button was only sending sampler/scheduler/steps/cfg/
denoise/lora — missing model, seed, prompts, and all advanced settings.
The generated image would use whatever the sampler node had as defaults
instead of the actual parameters from the source image.

Fixed triggerGen() to build a complete config entry:
- Model and seed from the (now editable) modal fields
- Positive and negative prompts from the modal textareas
- seed_behavior set to "fixed" so the exact seed is used
- All advanced settings carried from the source item: vae, clip_type,
  model_type, text_encoders, attention_mode, clip_skip,
  model_sampling_override + shift values, advanced_sampling,
  flux_guidance, gguf_options

Also wraps output in {"configs": [...]} format (was sending bare array).

Template changes:
- Model input: removed disabled attribute + opacity
- Seed input: removed disabled attribute + opacity
Both are now editable so users can change model or seed before re-gen.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 15:18:37 -07:00
Jason HokuandClaude Opus 4.6 1e574d6927 fix: insert upscaled items at start of manifest for correct sort order
Dashboard fast-path sort assumes newest items are at the start of the
items array. append() put upscaled images at the end (oldest position),
making them appear as the oldest items when sorting by newest.

Changed both SeedVR2 and regular upscale paths from append() to
insert(0, ...) so upscaled images appear at the top (newest position).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 14:51:50 -07:00
Jason HokuandClaude Opus 4.6 c5265d0e83 fix: initialize total_upscale_duration in upscale_runner SeedVR2 path
UnboundLocalError when SeedVR2 upscale completed — total_upscale_duration
was referenced but never initialized. Added initialization to 0 at the
start of each per-image loop.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 14:23:31 -07:00
Jason HokuandClaude Opus 4.6 c9234ea78e fix: mock V3 API execution context for SeedVR2 node calls
SeedVR2 nodes use ComfyUI's V3 API which calls get_executing_context()
internally to get the current node_id. When calling execute() directly
from our code (outside ComfyUI's execution pipeline), the context is
None, causing AttributeError.

Fix: wrap all SeedVR2 execute() calls inside a CurrentNodeContext
context manager with a dummy prompt_id and node_id. This provides the
required execution context that V3 API nodes expect.

Also fixed indentation — all three execute() calls (DiT, VAE, Upscaler)
are now properly inside the with block.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 14:06:46 -07:00
Jason HokuandClaude Opus 4.6 96726094bd fix: SeedVR2 node discovery via NODE_CLASS_MAPPINGS, not module import
The previous approach tried 'from custom_nodes import ComfyUI_SeedVR2...'
which fails because ComfyUI doesn't expose custom nodes as importable
packages. Now scans nodes.NODE_CLASS_MAPPINGS directly for the three
SeedVR2 node classes (LoadDiTModel, LoadVAEModel, VideoUpscaler).
This works for both V2 and V3 API nodes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:33:03 -07:00
Jason HokuandClaude Opus 4.6 20a4677971 fix: add SeedVR2 mode to dashboard upscale_runner.py
The dashboard upscale (⬆ button) goes through upscale_runner.py, not
generation_orchestrator.py. SeedVR2 mode was only wired into the
inline orchestrator path — dashboard upscales silently produced no
output because mode="seedvr2" resulted in empty combos list.

Now upscale_runner.py handles mode=="seedvr2":
- Opens source image from disk as PIL (no latent decode needed)
- Calls seedvr2_upscale() from image_generation.py
- Saves result as _upscaled.webp in images directory
- Creates manifest entry with proper gen_index, file URL, metadata
- Supports chained steps (SeedVR2 → next step gets PIL result)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 13:26:01 -07:00
Jason HokuandClaude Opus 4.6 9e7911aa23 feat: SeedVR2 upscale mode in Builder UI and Dashboard
Adds SeedVR2 as a new upscale mode alongside HiRes Fix, Model Upscale,
and Model+HiRes. Requires ComfyUI-SeedVR2_VideoUpscaler to be installed
as a dependency — the plugin calls its nodes programmatically.

Data Model:
- New seedvr2 options object on each upscale step with all SeedVR2
  parameters: dit_model, resolution, max_resolution, seed,
  color_correction, batch_size, noise scales, blocks_to_swap,
  attention_mode, offload_device, cache_model, VAE tiling options
- _defaultSeedVR2Options() / createDefaultSeedVR2Options() helpers
- Migration: ensureStepFields adds seedvr2 defaults to existing configs

Builder UI (conf-builder-config-management.js):
- "SeedVR2 Upscale" added to mode dropdown
- When selected, shows full SeedVR2 settings: DiT model selection
  (10 variants: 3B/7B x fp16/fp8/GGUF/sharp), resolution, seed,
  color correction, noise scales, batch size, VRAM optimization
  (blocks_to_swap, attention_mode, offload), VAE tiling with
  conditional encode/decode tile size and overlap fields

Dashboard (logic_upscale_modal.js):
- Same SeedVR2 mode and fields in renderPipelineEditor via _renderStep
- All fields use existing _makeSelect/_makeNumber/_makeCheckbox helpers

Python Execution (image_generation.py):
- New seedvr2_upscale(pil_image, seedvr2_config) function
- Imports SeedVR2 nodes from NODE_CLASS_MAPPINGS at runtime
- Calls SeedVR2LoadDiTModel → SeedVR2LoadVAEModel → SeedVR2VideoUpscaler
- Converts PIL→tensor→SeedVR2→tensor→PIL
- Clear error if SeedVR2 not installed

Pipeline Integration (generation_orchestrator.py):
- Inline upscale path: mode=="seedvr2" branch decodes current latent
  to PIL, calls seedvr2_upscale, saves result, updates manifest
- Supports chained steps: non-final SeedVR2 steps re-encode to latent
  for the next step in the pipeline

Preset compatible: seedvr2 config serializes naturally as JSON, saved
and loaded with upscale presets without any special handling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 12:29:00 -07:00
Jason HokuandClaude Opus 4.6 da7b789098 fix: copy model_type from manifest, remove dead g reference
Fixed ReferenceError: g is not defined — leftover reference from
deleted groups code. Now copies d.model_type directly from each
manifest item into the flat config, as it should.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 01:01:32 -07:00
Jason HokuandClaude Opus 4.6 7b597b3098 fix: determine model_type from file extension, not manifest data
The manifest's model_type field was unreliable — in-memory activeData
could have stale values from a previous load, or the Builder UI bug
(per-array not per-model model_type) could poison it.

Now the export ignores d.model_type entirely and determines type from
the actual file extension:
- .gguf → model_type = "gguf"
- anything else → no model_type field (defaults to "checkpoint")

Also removed dead grouping code (was from the old Config Builder state
format, unused since switching to flat config export) and fixed empty
text_encoders array being included (empty [] is truthy in JS).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 00:58:39 -07:00
Jason HokuandClaude Opus 4.6 8daa31c4f7 fix: export favorites validates model_type from manifest items
Manifest items could have incorrect model_type (e.g. "diffusion_model"
on regular .safetensors checkpoints) due to a Builder UI bug where a
single config array's model_type applied to all models in that array.

The export now validates model_type against the actual file extension:
- .gguf files → model_type = "gguf"
- .safetensors files marked "diffusion_model" → dropped (defaults to
  checkpoint in expand_configs, which is correct)
- Genuine diffusion_model files (non-.safetensors) → preserved

This prevents the "GGUF and diffusion models do not include a bundled
VAE" error when re-running exported configs of regular checkpoints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 00:39:40 -07:00
Jason HokuandClaude Opus 4.6 f97219f891 fix: export favorites handles GGUF/diffusion models without VAE
GGUF and diffusion_model types don't bundle a VAE, so setting
vae="Default" in the export causes the sampler to throw ValueError.

Fix: when model_type is not checkpoint and no explicit VAE was stored
in the manifest item, set vae="None" instead of "Default". Also shows
a warning in the export alert when GGUF/diffusion models are detected,
reminding the user to connect a VAE to the sampler's optional_vae input.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 00:26:53 -07:00
Jason HokuandClaude Opus 4.6 c2f9955917 fix: export favorites produces sampler-compatible flat config format
The export was producing a Config Builder state object (config_arrays
with arrays of samplers/schedulers/models), but the sampler node's
configs_json input expects flat config entries with single values per
field (sampler, scheduler, model, etc.).

Now produces {"configs": [...]} with one flat config per favorite:
- Each favorite becomes its own config entry with exact field values
  read directly from the manifest item
- Deduplicates identical configs (same model+lora+sampler+steps+seed)
- Wraps in {"configs": [...]} which the sampler node's parser expects
- Alert message updated: "Paste into Sampler Grid node's configs_json"

All optional fields (text_encoders, model_sampling, advanced_sampling,
flux_guidance, clip_skip, gguf_options) only included when present.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 00:03:56 -07:00
Jason HokuandClaude Opus 4.6 820b2a82c7 fix: export favorites config captures ALL settings from manifest items
The previous export only captured model/lora/sampler/scheduler/steps/cfg
and defaulted everything else. Now reads every field from manifest items:

- VAE (collected unique values per group, not hardcoded "None")
- Text encoders (array or string format)
- Clip type (from item, not defaulted to stable_diffusion)
- Attention mode (xformers, pytorch, etc.)
- Resolutions (width x height from each favorite as {width, height})
- Model type (checkpoint/gguf/diffusion_model)
- GGUF options
- Model sampling override + shift values
- Advanced sampling settings (guider, scheduler)
- Flux guidance value
- Model prompt prefix/suffix
- Uses config_positive/config_negative (raw prompts without triggers)

Each group takes advanced settings from its first item (these are
typically identical across favorites from the same model+lora combo).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 23:49:20 -07:00
Jason HokuandClaude Opus 4.6 75fcc46a91 feat: Export Favorites As Config JSON in Dashboard cog menu
Adds a button in the Dashboard's Export Favorites section that generates
a complete Config Builder JSON from all favorited items, ready to paste
into the Builder UI's lora_config widget.

How it works:
- Collects all favorited items from activeData
- Groups by unique (model, lora, positive, negative) combination
- Each group becomes a config array with all unique samplers,
  schedulers, steps, cfg values from that group's favorites
- LoRA stacks ("lora1 + lora2") are preserved with combine=true
- Per-config prompts are set via use_custom_prompts + positive_prompt_groups
- Session name gets "_rerun" suffix

Output is copied to clipboard. User can then paste into the Config
Builder, adjust resolution/steps/other settings, and regenerate just
the favorited configurations with different parameters.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 23:41:12 -07:00
Jason HokuandClaude Opus 4.6 9de031ad94 fix: safe_print in generate_config to survive Windows stdout corruption
Windows users occasionally see 'OSError [Errno 22] Invalid argument'
from colorama's write_plain_text when stdout gets into a bad state
(often after a long-running or interrupted session). This crashed the
Config Builder node at its very first print statement, breaking the
entire workflow.

Adds a module-level safe_print() helper that:
1. Tries normal print() first
2. Falls back to raw sys.__stdout__.write on OSError/ValueError
3. Silently drops the message if even that fails

generate_config() now aliases print = safe_print at the top so every
print call in the function is automatically resilient without renaming
each one.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 19:00:31 -07:00
Jason HokuandClaude Opus 4.6 8b6158d0df fix: scanner manifest order + remove video click fullscreen
Two small fixes:

1. Directory scanner sort inversion:
   The Dashboard's fast-path for oldest/newest sort assumes activeData
   is stored newest-first (generated sessions prepend new items). The
   scanner was writing oldest-first, so both sort modes appeared inverted.
   Fix: sort files by mtime DESCENDING (newest first) and assign
   gen_index via (total-1-i) so oldest=0, newest=N-1 is preserved.

2. Video click-to-fullscreen removed:
   Clicking a video in the Dashboard no longer triggers browser
   fullscreen — the interaction was disruptive. Videos still autoplay
   when visible and double-click still toggles favorite.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 09:53:27 -07:00
Jason HokuandClaude Opus 4.6 3375857215 fix: directory scanner sorts by mtime and assigns sequential gen_index
Oldest/Newest sort in the Dashboard uses (gen_index ?? id) as the sort
key. Scanned items had no gen_index and their id was a scan-time
timestamp + random number, which didn't reflect file chronology at all.
Result: Oldest and Newest sort produced effectively random order.

Fix:
- os.scandir captures mtime alongside each file path
- Files sorted by (mtime, path) ascending — oldest first
- Each item gets a sequential gen_index (0 = oldest, N-1 = newest)
- Applied to both new-scan path (_process_single_image) and
  cached-manifest path (existing sessions get re-indexed on next scan)

Newest files sort to top in Newest mode, bottom in Oldest mode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 09:49:19 -07:00
Jason HokuandClaude Opus 4.6 e8af36671a feat: pure-Python MP4/MOV dimension probe for directory scanner
Adds _probe_mp4_dimensions() — a minimal ISO BMFF container parser that
extracts real width/height from MP4/MOV/M4V files without any new
dependencies. Reads only box headers (a few hundred bytes) by navigating
moov → trak → tkhd and parsing the 16.16 fixed-point dimensions.

Handles:
- 32-bit and 64-bit box sizes (size=1 largesize)
- Version 0 and version 1 tkhd boxes (time field sizing differs)
- Multiple tracks — audio tracks have 0x0 dimensions, loop until a
  video track with non-zero dimensions is found
- Container boxes (moov, trak, mdia, minf, stbl, edts) recursively
- Graceful fallback to 1280x720 default if parsing fails (WebM, corrupt)

Also fixes cached-manifest path: if a video entry exists in a previous
scan's manifest but has missing or stale default (1280x720) dimensions,
re-probe the actual file to get real values. This fixes existing sessions
where videos were scanned before this probe was added.

Resolves the "all square cards" issue where the grid's average aspect
ratio calculation was poisoned by uniform 1280x720 defaults across all
videos in the dataset.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 09:30:50 -07:00
Jason HokuandClaude Opus 4.6 3e1f541422 fix: video autoplay + poster frame loading
Two issues were causing grey/unloaded video cards:

1. preload="none" blocked browsers from fetching the first frame for
   poster display. Changed to preload="metadata" so the first frame
   shows even before play starts.

2. IntersectionObserver had edge cases with CSS transforms on the
   virtual scroll canvas — observer wasn't firing for transformed
   cards. Replaced with direct control via virtual scroll lifecycle:
   - Videos get src set + .load() called when card is added to DOM
   - Playback starts after fragment is appended to grid (play() needs
     element to be in the DOM)
   - Videos are paused + reset via _stopVideoPlayback when cards are
     pooled or removed

This uses the virtual scroll range directly (which already handles
pan/zoom/scroll correctly) instead of relying on IntersectionObserver
which has known issues with CSS transforms.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 09:07:51 -07:00
Jason HokuandClaude Opus 4.6 b3fab600c7 feat: media type filter (image vs video) in Dashboard filter panel
Adds a new "Type" filter row to the filter panel that lets users toggle
between showing images and videos. Works alongside all existing filters
(sampler, scheduler, model, upscale, etc.).

- logic_state.js: new filters.mediaType Set
- logic_pipeline.js: filter check in both incrementalFilter and
  executePipeline; included in getFilterKey() hash for cache
  invalidation
- logic_ui.js initFilters(): populate mediaType values from
  d.media_type (defaults to "image" for backwards compat)
- template.html: new .filter-row with #filter-mediaType container

Items without media_type still appear under "image" automatically.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:44:22 -07:00
Jason HokuandClaude Opus 4.6 dbd9833fee feat: autoplay visible videos in Dashboard via IntersectionObserver
Replaces hover-to-play with viewport-visibility autoplay for a more
Instagram-like browsing experience. Videos auto-play when their card
enters the viewport (25% threshold) and pause+reset when scrolled off.

- New videoAutoplayObserver in logic_virtual.js with threshold: 0.25
- Each rendered video is registered with the observer
- Pool/removal flow unobserves and pauses the video to prevent leaks
- createCard() in logic_ui.js drops the hover-play listeners; keeps
  click-to-fullscreen and loadedmetadata aspect ratio handlers
- Observer also lazy-loads src on first intersection as a safety net

Uses native IntersectionObserver so it respects zoom, pan, and any
viewport transform automatically — no manual bounds math needed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:44:21 -07:00
Jason HokuandClaude Opus 4.6 b641beb94f fix: external directory scanner includes videos in symlink step
_create_image_links() had a hardcoded IMAGE_EXTENSIONS set that only
linked .png/.jpg/.jpeg/.webp/.bmp files into the external_images/ symlink
directory. Videos discovered by the scanner were added to the manifest
but never linked, causing ComfyUI's /view endpoint to return 404 for
every video.

Now imports MEDIA_EXTENSIONS from directory_scanner.py so images and
videos share the same allowlist — single source of truth.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 00:37:17 -07:00
Jason HokuandClaude Opus 4.6 0feb11c6bd feat: video support in Dashboard and external directory scanner
Adds video file support throughout the Dashboard with zero new Python
dependencies — browsers handle all playback, metadata, and thumbnails
natively via HTML5 <video> elements.

directory_scanner.py:
- Add VIDEO_EXTENSIONS (.mp4, .webm, .mov, .m4v) to scan list
- _process_single_image() detects videos by extension and creates a
  video manifest item with media_type="video"
- Videos get default 1280x720 dimensions; browser updates on loadedmetadata
- Image items now tagged with media_type="image" for symmetry

Dashboard (logic_ui.js createCard):
- Renders <video preload="none" muted loop playsinline> for video items
- Hides REVISE and upscale buttons for videos (don't apply)
- Shows "▶ VIDEO" badge in top-left corner
- Hover-play: videos play on mouseenter, pause+reset on mouseleave
- Click: requests native browser fullscreen with controls
- loadedmetadata handler updates card aspect ratio from videoWidth/videoHeight

Dashboard (logic_virtual.js):
- Lazy loader extended to swap data-src → src for <video> elements

CSS (report.css):
- .video-badge styling (purple pill, top-left)
- .card video positioning (absolute, cover-fit)

Works with any codec the browser supports (H.264, VP9, AV1, H.265).
Existing features unchanged: favorites, rejects, sorting, filtering,
DOM card pool, merge sessions, quick favorite hotkeys all work on videos.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 14:23:02 -07:00
Jason Hoku 9d2827427f bump ver 2026-04-10 13:12:05 -07:00
Jason HokuandClaude Opus 4.6 e8c41a9b56 docs: add strategic roadmap items from R&D competitive analysis
Added 4 new roadmap items based on competitive landscape research:
- #25 Image Quality Metrics (Laplacian/entropy/Sobel — unique differentiator)
- #26 Analytics Panel (aggregate stats, charts, export)
- #27 Static Dashboard (eliminate HTML regeneration)
- #28 Onboarding Wizard & Tutorial

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-07 23:45:39 -07:00
Jason HokuandClaude Opus 4.6 beacb02101 fix: fullscreen toggle loads correct session instead of default
When toggling fullscreen, the iframe reload was reading the session name
from the node's widget (default "my_session") instead of the session
the user had actually selected in the Dashboard picker.

Now the Dashboard sends its current session name in the fullscreen
postMessage, and the handler prefers that over the widget default.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 10:32:45 -07:00
Jason HokuandClaude Opus 4.6 1d86313fcc fix: distribution wait loop timeout prevents infinite block
The master's wait-for-workers loop could block forever if a worker
crashed without cleanup, leaving a job in "Claimed" state. The
_release_timed_out_jobs() was only called during claim_job, never
during the wait loop.

Fixes:
- Call release_timed_out_jobs() every 5s during the wait loop to
  reclaim jobs from dead workers (no heartbeat)
- Add configurable wait_timeout (default 600s/10min) from distribution
  config — after timeout, force-reclaim all remaining claimed jobs and
  proceed to finalization instead of looping forever
- Add public release_timed_out_jobs() method to DistributionManager
- Remaining jobs after timeout are marked completed and abandoned with
  a warning, unblocking the ComfyUI queue

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 09:59:57 -07:00
Jason HokuandClaude Opus 4.6 93016b5888 feat: partial re-render for Builder UI config sections
Add rerenderConfigSection(node, arrayIdx) that rebuilds only one config
array section instead of the entire UI. debouncedRenderUI now accepts
optional arrayIdx parameter — when provided, uses partial re-render.

This avoids the full DOM rebuild (7k+ nodes) when making changes within
a single config section (add/remove model/lora, toggle settings). Falls
back to full renderUI if the target section isn't found in the DOM.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:27:26 -07:00
Jason HokuandClaude Opus 4.6 d46ec6a9b0 feat: upscaling support in distributed mode
Workers now generate base images only, and the master runs all upscale
pipelines after distributed generation completes. Uses the existing
run_deferred_upscales() function. The warning message now says upscales
will run on master after collection instead of "not supported."

Builds deferred queue from all generated items in existing_data, runs
after remote VAE cleanup but before final manifest save.

Also adds model count endpoint for fast change detection.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:27:25 -07:00
Jason HokuandClaude Opus 4.6 017f7b894d feat: merge sessions in Dashboard — load multiple sessions side-by-side
Add mergeSession() function that loads another session's manifest and
appends its items to the current view. Items are tagged with _session
field and deduplicated by ID. Each session row in the picker gets a
"+Merge" button. The merge is view-only (not saved) with a notification
toast showing how many images were added.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:27:25 -07:00
Jason HokuandClaude Opus 4.6 956d75ba5d perf: DOM card pool for virtual scroller — recycle instead of destroy
When cards scroll out of view, they're now pooled (hidden, up to 100)
instead of removed from the DOM. When new cards need to be created,
pooled cards are recycled by swapping in new content via createCard().
This cuts DOM allocation churn by ~90% during rapid scrolling with
large datasets (10k+ images).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:27:24 -07:00
Jason HokuandClaude Opus 4.6 4d362b8c6f feat: config schema validation warns on JS/Python field sync drift
Adds a validation check in generate_config() that compares each output
config's keys against the expected set (sampler, scheduler, steps, cfg,
model, lora, etc.). Logs a warning with the missing keys if any are
absent, catching dual-codepath sync drift before it causes runtime bugs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 22:27:24 -07:00
Jason HokuandClaude Opus 4.6 7b437f0e2f feat: instant model loading with localStorage cache + background polling
Performance improvements for Builder UI model/LoRA loading:

localStorage persistent cache:
- Model lists cached to localStorage on first fetch
- On page reload, cached data renders instantly (no network wait)
- Cache expires after 24 hours, cleared on explicit Refresh Models
- Saves all categories: checkpoints, diffusion, GGUF, text encoders,
  VAEs, upscale models, samplers, schedulers, clip types

Background count polling:
- New /configbuilder/model_counts endpoint returns file counts per
  category (fast — uses folder_paths internal stat-based cache)
- Frontend polls every 30s, compares counts to last known values
- On change: auto-refreshes model lists and re-renders UI
- No manual Refresh Models needed when adding new models

Deduplicated /object_info:
- Shared _getObjectInfo() cache ensures the expensive /object_info
  endpoint is called at most once per session (was called 2-3x)
- getAvailableLoras() and getAvailableModels() share the cached response

Polling lifecycle:
- Starts after initial render, stops when node is removed
- Only active while ConfigBuilder node exists on canvas

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:13:18 -07:00
Jason HokuandClaude Opus 4.6 7bc61d47be feat: section presets for Models and LoRAs, CLIP weight array UI
Section Presets:
- Add generic /configbuilder/section_presets endpoint (GET/POST with
  ?section=models|loras|prompts query param)
- Add preset dropdown (Load/Save/Delete) to Models and LoRAs section
  headers in Builder UI
- Models presets save: models list, text_encoders, clip_type
- LoRAs presets save: loras list, weight_arrays, strength_lock

CLIP Weight Array UI:
- Add "+ Compare CLIP" button below CLIP strength slider when strength
  lock is disabled, mirroring the existing model strength array button
- Stores CLIP arrays in lora_weight_arrays[name_clip]

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:06:22 -07:00
Jason HokuandClaude Opus 4.6 7d5fbb213d perf: virtualize searchable select dropdown, fix event listener leak
- Limit dropdown to first 50 options (was creating ALL options as DOM
  nodes — 500+ for LoRAs, 100+ for models — on every focus/keystroke)
- Show "... N more — type to filter" message when truncated
- Use event delegation (single onmouseover/onmouseout/onclick on parent)
  instead of per-option event handlers
- Replace document.addEventListener("click") with input.onblur to close
  dropdown (was leaking global click listeners that accumulated on every
  re-render — 50+ listeners after a few refreshes)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:06:10 -07:00
Jason HokuandClaude Opus 4.6 5ee1834587 feat: LoRA weight array Cartesian expansion in generation pipeline
Add _expand_lora_weight_arrays() that expands bracket notation in LoRA
strength fields into Cartesian product of configs:
  lora1:[0.5, 1.0]:1.0 + lora2:0.8:[0.7, 1.0] → 4 configs

Distinguishes weight arrays (brackets in strength after :) from folder
random syntax (brackets in name before :). Runs before folder expansion
so both features work together correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 16:05:58 -07:00
Jason HokuandClaude Opus 4.6 0691c26d77 feat: add Flux2 support — CLIP type, model sampling, and override
- Add "flux2" to clip_types and dual_clip_types lists so CLIPType.FLUX2
  is correctly passed to ComfyUI's text encoder loader
- Add "flux2" model_sampling_override option in Builder UI dropdown
- Implement ModelSamplingFlux2 handler using fixed shift (default 2.02,
  matching ComfyUI's Flux2 supported_models.py)
- Handle "flux2" in config_builder_node.py config generation with
  default shift value

Users with Flux2 models (Klein, etc.) should set:
- CLIP Type: flux2
- Model Sampling Override: Flux2
- For best results: enable Advanced Sampling with Flux2 Scheduler

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:50:46 -07:00
Jason HokuandClaude Opus 4.6 5fc90097dc fix: persist JSON/Visual toggle and add scrollable prompt sections
- Prompt editor JSON/Visual mode now persists in node.uiState.promptRawMode
  across Refresh Models and other re-renders (was a local variable, always
  reset to Visual)
- Added promptRawMode:{} to uiState initialization
- createPromptGroupEditor accepts optional {rawModeKey, uiState} to persist
- Positive prompt groups sections now have max-height: 1000px with overflow
  scroll, preventing long prompt lists from pushing everything down

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:50:34 -07:00
Jason HokuandClaude Opus 4.6 0c82d2621c feat: quick favorite visible cards with number keys 1-9
Press 1-9 to toggle favorite on the Nth visible card in reading order
(left-to-right, top-to-bottom). Uses getBoundingClientRect() for
accurate card detection regardless of zoom, pan, or dynamic card sizes.
Visual feedback: green/red glow + number badge overlay.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:50:23 -07:00
Jason HokuandClaude Opus 4.6 7d7324d880 fix: worker submission race condition after master finishes
The delayed manager cleanup used a flat 30s timer, which could expire
before a slow worker finished submitting its last result. Now polls
every 10s (up to 5 minutes) checking if any registered workers still
have recent heartbeats before cleaning up the manager.

Also added clarifying comment on submit_result route — it intentionally
does NOT check is_active, only manager existence, to accept late
submissions from workers that were mid-generation when master finished.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 20:50:12 -07:00
Jason HokuandClaude Opus 4.6 833158fab6 fix: worker model sync parses LoRA combo strings correctly
The LoRA field in job configs uses a combo string format:
"name:model_str:clip_str + name2:model_str:clip_str"
Parts can also be folder references like "FolderName/:0.90:0.90"
or random selection syntax "Folder/[3,0.5]".

_extract_required_models() was passing the entire combo string as a
single filename, causing WinError 123 (invalid path) because colons
are illegal in Windows filenames.

Now splits on " + ", extracts the filename before ":", and handles:
- Individual LoRA files: synced from master
- Folder references (trailing /): expanded locally via folder_paths
- Random syntax ([count,strength]): expanded locally via folder_paths

Same fix applied to distribution_manager._get_upcoming_models() which
builds the pre-fetch list for workers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 14:03:55 -07:00
Jason HokuandClaude Opus 4.6 50359eac40 fix: upscale runner manifest entries, sorting, and VRAM cleanup
- Add "file" URL to manifest entries (dashboard needs /view?filename=...
  URL, not just the filename field)
- Exclude "file" and "gen_index" from source item copy to prevent
  stale values leaking through
- Assign gen_index = len(manifest_data["items"]) so upscaled images
  sort as newest instead of near their source image
- Send all new items per image via update_data event, not just the last
- Set PromptServer.last_prompt_id before sampling (KSampler progress
  callbacks require it, fixes AttributeError)
- Clear VRAM and RAM (soft_empty_cache, gc.collect, cuda.empty_cache)
  on completion, cancellation, and error via finally block

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 12:34:30 -07:00
Jason HokuandClaude Opus 4.6 c971e2934d feat: full pipeline editor in dashboard upscale modal
Replace the simplified single-step config editor with renderPipelineEditor(),
a full pipeline/step UI matching the Builder UI feature set:
- Multiple pipelines with add/remove, active toggle, name, collapsible
- Multiple steps per pipeline with all settings (mode, resize, ratio,
  denoise, hires steps, tiled sampling, tiled VAE, repeat)
- Multi-model selector with searchable dropdown and chips
- HiRes prompt adjustment section
- Iteration count display per step

Also fixes:
- Preset double-entry bug: save now deduplicates by name in both
  Builder UI and dashboard modal
- Builder UI hires_force_uniform_tiles checkbox: || true -> !== false
  (was always displaying as checked regardless of stored value)
- Model list fetch: GET instead of POST (matching the endpoint)
- Modal size: 600px wide, 90vh tall for better fit
- Cancel button: sends cancel request to server during active upscale
  instead of just closing the modal

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 12:34:13 -07:00
Jason HokuandClaude Opus 4.6 be233a9369 docs: update AI-CONTEXT, ProjectStructure, and Roadmap for this session
- AI-CONTEXT: added network_utils.py, upscale_runner.py, updated
  session settings format (pipelines, deferred upscale, start_at_job)
- ProjectStructure: added new files (network_utils, upscale_runner,
  logic_upscale_modal), preset files, updated line counts
- Pipeline editor refactor plan saved for next session

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:17:59 -07:00
Jason HokuandClaude Opus 4.6 b342127f83 fix: extract filename from manifest file URL in upscale runner
Manifest items use 'file' with a /view?filename=... URL, not a
'filename' field. Now parses the URL to extract the actual filename
when 'filename' is not directly available.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:12:50 -07:00
Jason HokuandClaude Opus 4.6 bc8e2d05cf fix: initialize preset array before async fetch, add save logging
presetSelect._presets was undefined if save was clicked before the
async fetch completed. Now initialized to [] immediately. Added
console logging to debug preset save/load flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:06:05 -07:00
Jason HokuandClaude Opus 4.6 d630f7c8a4 feat: rewrite upscale modal with inline config editor
Replace preset-only UI with inline upscale settings: mode selector
(hires/model/both), upscale model dropdown, ratio, denoise, hires
steps. Mode changes dynamically show/hide relevant fields. Presets
load/save below the config editor. No longer requires going to the
Builder UI first.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 23:59:59 -07:00