Extends the shared web/js/multi_loader.js to a third kind ("video") for
AI2GoMultiVideoLoader / AI2GoMultiVideoLoaderAdvanced: NODES map entries,
video/* MIME acceptance, drop-zone wording, a video-row preview (mm:ss +
WxH probed via a hidden <video>), and a serializeValue guard on force_rate
mirroring the existing max_size guard so a '' value can't fail validation.
README and the design spec's amendments section are extended to match.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Multi Loader nodes previously sized their output sockets to the loaded
file count, so deleting one file shrank the sockets and destroyed any
wire on a later socket. Add an output_slots combo (auto/1-8, front-end
only — Python already returns a fully padded output list) so a fixed
socket count can be pinned and wires survive file-list edits; the
status line now flags file/socket-count mismatches.
Also drop the separate + Add button: the drop zone itself is now
clickable and opens the same file picker, so there is one field for
both drag-and-drop and click-to-browse.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Batch already means one-item-per-queued-run in this pack (AI2GoPromptBatch);
these loaders emit everything in a single run, so Batch was misleading.
Renamed the four loader nodes/files/classes to Multi (AI2GoMultiImageLoader,
AI2GoMultiImageLoaderAdvanced, AI2GoMultiAudioLoader, AI2GoMultiAudioLoaderAdvanced),
and reworded DOWNSCALE_MODES from fit/crop/stretch to the plain-word
"keep aspect ratio"/"crop to square"/"stretch to square" (behavior unchanged).
Nothing published yet, so no back-compat aliases were added.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Six findings from the whole-branch review, fixed together:
1. addFiles() re-checks the row count before each push inside the upload loop, closing a
race where two overlapping drops could push a node past MAX_FILES.
2. removeAt() now warns whenever a wired socket is disturbed, including when the removed
row was the last one (previously required a promoted row too).
3. onConfigure no longer overwrites a files_json that failed to parse with "[]"; it shows
the parse error in the status line and leaves the original widget value in place.
4. parseFiles' mirror comment documents both of its intentional deviations from Python's
parse_files (empty input OK, entries beyond MAX_FILES ignored rather than rejected).
5. The image/audio MIME filter (and file-picker accept attr) now allows empty-MIME files
and, for audio, video/* containers like .ogg that the backend decodes fine.
6. Missing-file and decode errors in the image/audio loaders now include the file's
1-based position alongside its name.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Save Metadata (Civitai) graph trace only recognized ComfyUI's stock
LoraLoader/LoraLoaderModelOnly, so LoRAs loaded via rgthree's multi-LoRA
nodes were silently missing from the written metadata and the preview.
- tracer.py: harvest LoRAs from "Power Lora Loader (rgthree)" (lora_N dicts)
and "Lora Loader Stack (rgthree)" (flat lora_0N/strength_0N pairs), several
per node, in load order. Skip disabled, empty ("None") and zero-strength
slots to match what actually loads. Stock loaders still work.
- save_civitai_metadata.js: mirror the same detection in the live-graph
Test-detection preview (kept in sync with the Python tracer).
- Preview warning now distinguishes positive/negative (may be resolved at run
time by the AI2Go Prompt Batch node) from genuinely-unresolved fields.
- Tests for both rgthree loaders (multiple loras, ordering, skipped slots).
- README: document the supported LoRA loaders.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Real-world testing feedback (PR #9):
- Detect models loaded via UNETLoader ("Load Diffusion Model", unet_name in
the diffusion_models folder) + GGUF unet loaders, not just checkpoints;
carry the folder so AutoV2 hashing looks in the right place. Fixes
"model (?)" on Flux/SD3/Krea workflows. Mirrored in the JS Test button.
- Expand %date:FORMAT% filename tokens (frontend feature that doesn't reach a
custom node's widget) before get_save_image_path, so e.g.
"%date:yyyy-MM-dd%/Krea-Turbo" works. New comfy-free filenames module.
- Test-detection preview panel now reserves its natural height and grows the
node to fit (measured, like the Prompt Batch editor) instead of overflowing.
Advanced node sockets kept as-is per user preference. +10 unit tests (39 passed).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- megapixel/auto now preserve the aspect ratio when clamped to the profile's per-side cap (16:9 at max -> 2048x1152, not 2048x2048); aspect is prioritized over hitting the exact target size.
- Replaced the Apply button with a read-only readout (resulting WxH + MP); edits and new connections auto-push the dims to a connected Prompt Builder canvas.
- Added a profile-aware warning shown when the request is clamped to the profile's maximum (e.g. Ideogram 4 max 2048x2048).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds AI2GoResolutionSelector: a profile-aware (Ideogram 4 = x16, 256-2048) resolution node with raw/auto/megapixel modes, INT width/height outputs for any workflow, and an Apply button that pushes dims into a connected node's width/height widgets (refreshing the Ideogram 4 Prompt Builder canvas via the same write pattern the Style Wizard uses). Dims also flow on graph execution.
Reverts the prompt builder's width/height to KJ-original (removes the embedded resolution_mode/aspect_ratio/megapixels widgets) so its node/JSON stays copy-paste compatible with the upstream KJNodes Ideogram 4 node.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Old workflows/images saved before the resolution selector (or by a build with a different widget order) could land non-numeric junk in resolution_mode/aspect_ratio/megapixels via ComfyUI's positional widgets_values restore (shown as NaN). onConfigure now restores them from a name-keyed blob when present and coerces any remaining junk to sane defaults.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The button now clears the wizard, applies the cleared state to the builder,
and closes the modal in one click.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bug: after "Clear all" + "Apply", the builder's photo (or art_style) field
stayed filled. applyToBuilder computed kind=null when both were empty and
skipped the style block entirely, leaving the builder's previous value.
Fix: mirror the wizard exactly — when both are empty, set the builder's
"style" combo to "none" (its DynamicCombo drops the photo/art_style
sub-widget, actually clearing it), matching how the builder's own
applyCaption handles a style-less import.
Also add a "Clear & apply" button (clears the wizard and pushes the cleared
state to the builder in one click).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bump the Pick-tab status text 11px -> 13px and color the not-connected
warning red (bold) instead of orange, so it reads clearly.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An extra ")" in the parsePresets() error-message concatenation closed the
string early and left a dangling token, so web/js/ideogram4_style_wizard.js
failed to parse as an ES module. The whole extension never registered:
the node showed its raw wizard_data widget with no "Open Style Wizard" button.
`node --check` on the .js false-passed; validated now as strict ESM (.mjs)
and via a mocked import()+onNodeCreated harness (button is added again).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The context menu gains a "Move to root" item that unparents the right-clicked node (or the whole
selection if that row is part of it) — a discoverable alternative to dragging onto the header.
Disabled when nothing in the target is nested. The node's subtree comes with it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The lock condition was "(effectively off) && (not own-disabled)", so a child that was ALREADY
individually disabled stayed clickable after the group was disabled — and clicking it merely flipped
it into the locked state. Lock now keys purely on whether a parent group is off (ancestorOff), so
once a group is disabled ALL its children are consistently 🚫 + non-toggleable until the group is
re-enabled.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The per-row eye reflected only the box's OWN state, so children under a disabled group still showed
👁 even though they were hidden everywhere. Now the eye shows EFFECTIVE visibility: a child of a
disabled group reads as 🚫, faded and non-toggleable (the group is the master switch — flip it to
show them again). The whole disabled row dims for a clear hidden look.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The enable toggle was a tiny native checkbox at the left of each Overview row — easy to miss on the
dark dock. Replaced it with a clear button: 👁 when enabled, 🚫 when disabled (click to toggle),
styled like the other row buttons.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Each region and group row in the Overview gets an enable checkbox. Disabling a box (or a group —
which cascades to all its members) hides it from the canvas AND excludes it from the exported prompt,
the bbox output, and the preview render. Still listed in the Overview (dimmed) so it can be re-enabled.
- JS: isDisabled(b) walks the parent chain (own or any ancestor disabled); applied in the canvas draw,
hit-testing, and buildCaptionObject (so token estimate reflects it too). Row checkbox toggles
b.disabled and commits; effectively-disabled rows render dimmed. State persists + round-trips via
the _ai2go sidecar / workflow.
- Python: _eff_disabled(box, by_id) mirrors the cascade; skips disabled in execute (elements + bboxes)
and in _render_preview.
JS + Python parse clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy and templates now emit the clean caption PLUS an "_ai2go" sidecar holding the full editor box
list (groups, parenting, ids, names, collapse/lock). Paste/import/template-load restores from it when
present, so AI2Go→AI2Go round-trips losslessly. KJNodes (and the model) ignore the unknown key, so:
- AI2Go → KJ: pastes as a normal flat scene.
- KJ → AI2Go: no sidecar → imports flat (as before).
- AI2Go → AI2Go: hierarchy + groups fully preserved.
The sidecar is added only when there's hierarchy/groups to keep (plain scenes stay clean). The Python
`prompt` output and the token estimate use the clean caption — the sidecar is clipboard/template-only.
JS parses clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Duplicate: derive the shift delta from the copy's actual bounding box (not the group frame, since a
member can protrude after individual editing) and apply one uniform delta with no per-clone clamp, so
the copy's internal layout is preserved exactly. Falls back to a negative shift when the group is
pinned to an edge (no more copy-on-top-of-original).
- Mirror: skip whole LOCKED sub-branches as a unit (collect members without crossing a locked box) so a
nested sub-group frame and its children always mirror together; clamp the whole reflected interval
(keeps box size, stays on-canvas) instead of one edge; validate the axis.
JS parses clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Three buttons in a selected group's panel:
- Duplicate: clones the group and its whole subtree (fresh ids, parents remapped within the copy,
shifted slightly so the copy is visible and stays on-canvas; copy is editable).
- Mirror ⇆ / Mirror ⇅: flip the group's members left↔right / top↕bottom about the group's frame
(frame unchanged). Locked and unplaced members are left as-is.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Previously a group frame could be resized but it did nothing to the children. Now resizing a
group's frame (corner/edge handle) scales its placed children proportionally about the same anchor
edge — reusing the existing group-resize transform. Skips locked sub-branches and unplaced (nobbox)
children. Resizing a normal region is unchanged (children don't scale). Show frames with 👁️ first
to grab a group's handles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Toolbar button icon is now 👁️ (lit/active = group frames visible).
- Group frames are HIDDEN on the canvas by default (showGroups defaults off; only `true` shows them).
Click 👁️ to reveal them. Groups remain in the Overview tree at all times and are never exported.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>