Compare commits

..
Author SHA1 Message Date
Vito Sansevero 98e9dc4661 chore(release): 1.3.0 with the Model Resolution Picker
- PR #108 changed pyproject.toml without bumping the version, so the
  registry publish on main failed with "The node version already exists".
- Bump to 1.3.0 (new node, route and override file) and add the release
  notes to CHANGELOG.md and the README.
2026-10-27 15:54:32 -07:00
Vito 60f197274c Merge pull request #108 from ComfyAssets/feat/model-resolution-picker
feat(model_resolution_picker): add the Model Resolution Picker node
2026-10-27 15:44:16 -07:00
Vito Sansevero 0cd25a1073 test(model_resolution_picker): cover an override step off the latent grid being ignored whole
- A step of 3 in a user override must drop the whole file with the panel warning and leave the shipped SDXL step at 64.
2026-10-27 15:01:55 -07:00
Vito Sansevero ef5c9bb85a fix(model_resolution_picker): require family steps to be multiples of 8
- An override family could declare a step such as 3; sizes rounded to it
  are not divisible by 8, so create_empty_latent_batch raised ValueError
  at execution instead of producing a latent (Codex review on PR #108).
- validate_table now rejects a step that is not a multiple of 8 with a
  message naming the latent grid; the shipped steps (16, 32, 64) already
  comply, and the schema docs state the rule.
2026-10-27 15:01:19 -07:00
Vito Sansevero f04f2584a8 fix(model_resolution_picker): refit on the next frame after a resize notification
- The ResizeObserver callback measured the panel and resized the node
  synchronously, which forces a layout while the stylesheet may still be
  applying; WebKit then reports "ResizeObserver loop completed with
  undelivered notifications" as a page error, and the browser fixture
  fails any test with a page error (CI WebKit job on PR #108).
- The callback now only schedules a single-flight requestAnimationFrame
  that measures and refits, so no observed box changes during delivery.
- Reproduced in the pinned Playwright WebKit image: the picker tests pass
  48/48 (16 tests × 3) with this change.
2026-10-27 14:54:11 -07:00
Vito Sansevero dfe11928b9 fix(model_resolution_picker): fit the node height to the panel content
- New nodes started at a fixed 340×600, so one-entry size lists left a
  large empty area between the list and the Swap button.
- The panel now reports its measured content height as both the minimum
  and maximum DOM-widget height, and the extension sets the node height to
  the host's computeSize() after every render, keeping the width.
- Hidden or detached panels are not measured, the first measurement is
  retried for a few frames, and a ResizeObserver on the panel children
  refits when the stylesheet arrives after the first render (the coverage
  run exposed that race; a held-stylesheet regression test pins it).
- Workflows load at their saved width and are re-fitted on load; the spec
  and docs describe the new sizing rule.
2026-10-27 14:07:47 -07:00
Vito Sansevero 959c6b9e9f style(model_resolution_picker): format the override tests 2026-10-27 12:50:43 -07:00
Vito Sansevero 35f4463742 docs(model_resolution_picker): correct when override edits apply and say shipped families
- edits show up on a page reload or Refresh node definitions, not on the
  next node mount, because the frontend caches the payload per page
- the 14 count refers to shipped families; an override can add or remove
2026-10-27 12:50:24 -07:00
Vito Sansevero b9ca7976aa fix(model_resolution_picker): map four-value legacy workflows on load
- workflows saved before the resolution widget load shifted by one slot
- onConfigure now maps exactly four widgets_values onto model, width,
  height and batch_size and resets resolution to custom
- the host test stub applies widgets_values and calls onConfigure like LiteGraph
2026-10-27 12:50:16 -07:00
Vito Sansevero 729652facf fix(model_resolution_picker): include file size in the override cache key
- a replacement with the same mtime but different size was served stale
2026-10-27 12:49:17 -07:00
Vito Sansevero 47e95f95c3 fix(model_resolution_picker): bound sizes to the width/height widget limits
- table validation accepts sides 64..8192, matching the widget min/max
- parse_size_label (Python and JS) caps digits and rejects out-of-range
  sizes so create() falls back to width/height instead of overflowing
2026-10-27 12:49:10 -07:00
Vito Sansevero 623915fd7d fix(model_resolution_picker): feed override families into the model combo
- family_labels() and the combo default now read the merged table, so an
  appended family is selectable and a removed one is not offered
- an alias used as the override default is normalised to its label
- the frontend rebuilds the model options from the payload and keeps a
  saved family that is no longer listed so old workflows still show it
2026-10-27 12:48:42 -07:00
Vito Sansevero 0d3ffcb460 docs(model_resolution_picker): add the override example and loading state
- replace the {ex} placeholder with a real override example validated against validate_table
- list the loading state among the cases that show custom
- say the override default may be a label or alias
2026-10-27 12:42:43 -07:00
Vito Sansevero 03ef5b17d6 docs(model_resolution_picker): document the JSON table, override file and resolution dropdown
- describe the families.json schema and the user override file with a full example
- document the warnings behaviour and resolution dropdown precedence
- add the dropdown sync rule to web/ui/README.md
2026-10-27 12:40:25 -07:00
Vito Sansevero d55bc72c34 fix(model_resolution_picker): reset a stale resolution label and use binary megapixels
- Set resolution to custom whenever the panel has no selection (wired,
  failed, non-finite or loading) so a stale label cannot override width/height.
- Compute label megapixels with 1 MP = 1,048,576 px, matching the panel
  header, with half-up rounding so Python and JS labels agree on ties.
2026-10-27 12:31:09 -07:00
Vito Sansevero eaf03874b3 feat(model_resolution_picker): add a resolution dropdown synced with the chip grid
- Add a native resolution COMBO (shipped sizes only) so a size can be picked
  and saved in the workflow without the panel; custom or unparseable labels
  fall back to width/height on both sides.
- Mirror the dropdown with the chips: options follow the active chip, the
  value tracks the current size, and picking a label writes width/height.
- Draw a decorative ratio shape on each ratio chip and show the first
  families warning in the status line.
2026-10-27 12:22:16 -07:00
Vito Sansevero 211d69e04e fix(model_resolution_picker): ignore an override that nests too deeply to parse
- catch RecursionError alongside ValueError when loading, merging and validating
- add tests for deep nesting, remove vs families, and a default naming a removed family
2026-10-27 12:03:04 -07:00
Vito Sansevero cd6d0d9650 feat(model_resolution_picker): merge a user override file into the family table
- add override.py: override_path, merge_override, mtime-cached current_table
- resolve_family and families_payload consult the merged table and expose warnings
- an invalid override is ignored whole with a logged warning
2026-10-27 12:01:20 -07:00
Vito Sansevero 469d3740b8 fix(model_resolution_picker): stop huge integers from overflowing the table validator
- treat ints as finite instead of calling math.isfinite on them
- bound step (4096), megapixels (64) and side length (65536) with normal problem messages
- cover step, megapixels, default and size values of 10**400
2026-10-27 11:58:11 -07:00
Vito Sansevero cb3027717e fix(model_resolution_picker): keep the table validator from raising on malformed data
- check default, labels and aliases are strings before set membership
- reject non-finite step and megapixels via a shared _is_step helper
- cover unhashable and Infinity/NaN inputs in the garbage test
2026-10-27 11:56:54 -07:00
Vito Sansevero db64038b25 refactor(model_resolution_picker): move the family table to families.json with a validator
- ship the table as families.json with per-family notes from the old source comments
- add table.py: load_table, pure validate_table and build_families
- families.py loads and validates at import, raising RuntimeError on problems
- include *.json as package data
2026-10-27 11:54:19 -07:00
Vito Sansevero 61b7d83593 docs(model_resolution_picker): add ratio shapes on chips to the addendum 2026-10-27 11:52:57 -07:00
Vito Sansevero 3421981dcd docs(model_resolution_picker): add addendum for JSON table, user override and resolution dropdown 2026-10-27 11:51:31 -07:00
Vito Sansevero 38f820c8f5 fix(model_resolution_picker): accept renamed labels, dedupe chip sizes, apply early model change
- VALIDATE_INPUTS lets unknown family labels reach create()
- no size is listed under two chips; SD 1.5 3:4 gets true 3:4 sizes
- model change before the families payload loads is applied on load
- custom ratio: clear error on chip click, cap sides at 8192, accept X
- real-host assertions for model change and wired-in state
2026-10-27 11:02:45 -07:00
Vito Sansevero baae2a0fac docs(model_resolution_picker): name the manual GPU check cases
- name Flux.2 / Klein / MOSS at 1248x832 and HunyuanImage 2.1 at 2560x1536
- require saved PNG dimensions to match the node outputs, recorded in the PR
2026-10-27 10:39:59 -07:00
Vito Sansevero f9f9f9a134 docs(model_resolution_picker): document the node, families and keyframe use
- add README tools row, section and status row
- add ui-migration row and web/ui README section
- add per-node doc with family table and manual GPU check
2026-10-27 10:38:09 -07:00
Vito Sansevero c8a1823569 test(model_resolution_picker): cover both renderers and workflow round-trip on the pinned host
- extract the real-host beforeEach into tests/comfyui/host-setup.mjs and proxy /kikotools/model_resolution/ to the fixture
- add the picker spec: renderer switching, shared swap, themes, workflow reload, scope abort
- add light and dark screenshots for the docs
2026-10-27 10:36:01 -07:00
Vito Sansevero 655049eec8 feat(model_resolution_picker): model change, custom ratio, wired-in and failure handling
- render the static chip list in every state; payload only drives enabled/active
- re-derive size on model change via sizeOnModelChange
- validate custom ratio, keep last valid list, ignore repeated commits
- add swap, model change, custom ratio and route failure browser tests
- add coverage floors for the three picker files
2026-10-27 10:31:15 -07:00
Vito Sansevero c62f363831 fix(model_resolution_picker): keep focus, pressed hover and wired-in state current
- restore keyboard focus to the equivalent button after each render
- keep the accent background when hovering a pressed chip or size
- refresh the panel when input links change
- treat a size listed in the clicked chip as a no-op
2026-10-27 10:23:32 -07:00
Vito Sansevero 97a4c7b78b feat(model_resolution_picker): add the chip grid panel and extension
- add resolution picker view with chip grid, size list and custom ratio input
- add extension that projects native widgets and writes sizes via callbacks
- add browser tests for rendering, theming, idempotent mount and disposal
2026-10-27 10:20:02 -07:00
Vito Sansevero bc4a3f44f9 feat(ui): add resolution picker derivation module
- add pure ratio parsing, chip derivation and size generation helpers
- round sizes to the family step with a 64 pixel floor
2026-10-27 10:16:02 -07:00
Vito Sansevero 408fc5e2f8 feat(ui): let swapDimensions work on nodes without a preset widget
- Treat a missing preset widget like preset value 'custom' in swapDimensions
- Keep every preset access behind the custom check
- Add a test that swaps width and height directly on a preset-less node
2026-10-27 10:14:24 -07:00
Vito Sansevero 83ed5ae42d feat(model_resolution_picker): add node, families route and registration
- add ModelResolutionPickerNode rounding to the family step
- serve GET /kikotools/model_resolution/families
- register the node in NODE_CLASS_MAPPINGS
2026-10-27 10:13:07 -07:00
Vito Sansevero f916636c6a feat(model_resolution_picker): add the model family resolution table
- add CHIPS, MIRROR, Family and the FAMILIES literal table
- add family_labels, resolve_family and families_payload helpers
- cover step, ratio, mirror and sort invariants with tests
2026-10-27 10:09:50 -07:00
Vito Sansevero 2f497cec5a feat(model_resolution_picker): add step rounding and size generation
- add round_to_step, generate_size and ratio_label pure helpers
- cover nearest-step rounding, minimum clamp, size math and aliases
2026-10-27 10:06:50 -07:00
Vito Sansevero a7061a29e6 docs(model_resolution_picker): add implementation plan 2026-10-27 10:01:51 -07:00
Vito Sansevero 98e18267f5 docs(model_resolution_picker): add approved design for the Model Resolution Picker node
- New node: model dropdown drives a Pixaroma-style ratio chip grid and size
  list, outputs width, height and an empty latent, with the toolkit Swap W×H
- Family table lives in Python and is served to the frontend by a local route
- Panel state is derived from the native model/width/height widgets only
- Video families are listed as keyframe sizes for first/last-frame generation
- Records the builder-check calls and the SDXL default
2026-10-27 09:54:37 -07:00
Vito 1c31ea118f Merge pull request #107 from ComfyAssets/fix/empty-latent-batch-spatial-ratio
fix: mark EmptyLatentBatch latents with their 8x spatial ratio
2026-10-27 04:48:08 -07:00
Vito Sansevero 297f2b1863 fix: mark EmptyLatentBatch latents with their 8x spatial ratio
ComfyUI resizes an empty latent to the model's latent format only when the
latent carries downscale_ratio_spacial, as core EmptyLatentImage sets it.
Without it, models with 16x VAEs (Flux.2, Klein, MOSS-Image, Qwen-Image 2.1)
sampled at twice the requested width and height.
2026-10-26 20:07:03 -07:00
Vito eb2db82a80 Merge pull request #106 from ComfyAssets/test/pinned-comfyui-ci
test(ci): automate real ComfyUI checks and prepare 1.2.0
2026-10-16 18:32:11 -07:00
Vito Sansevero 1820fb19ea test(ci): enforce reviewed CPU dependency versions 2026-10-16 17:29:08 -07:00
Vito Sansevero 7edf08f1e2 docs: repair legacy README references 2026-10-16 17:26:23 -07:00
Vito Sansevero 1721778cb5 test(ci): automate pinned ComfyUI checks and prepare 1.2.0 2026-10-16 17:23:11 -07:00
Vito 6bac864c4d Merge pull request #104 from ComfyAssets/fix/follow-execution-lifecycle
fix(follow_execution): honor live settings and stabilize subscriptions
2026-10-16 17:05:02 -07:00
Vito Sansevero 8dc81364a0 test(follow_execution): verify native menus in both renderers 2026-10-16 16:58:34 -07:00
Vito Sansevero 957ca3c142 fix(follow_execution): honor live settings and stabilize subscriptions 2026-10-16 16:50:58 -07:00
Vito ad73422472 Merge pull request #101 from ComfyAssets/chore/deprecate-gemini
chore(gemini): soft-deprecate legacy prompt node
2026-10-16 16:42:35 -07:00
Vito 1a7e0dfcd7 Merge branch 'main' into chore/deprecate-gemini 2026-10-16 16:36:20 -07:00
Vito 0b85600d38 Merge pull request #102 from ComfyAssets/feat/shared-custom-colors
feat(custom_colors): adopt shared ComfyAssets picker
2026-10-16 16:34:34 -07:00
Vito Sansevero ca65cc7961 test(loader): await initial gallery before keyboard controls 2026-10-16 16:25:33 -07:00
Vito Sansevero 6adba639bb feat(custom_colors): adopt shared ComfyAssets picker 2026-10-16 16:17:28 -07:00
Vito Sansevero 1e86b73168 chore(gemini): soft-deprecate legacy prompt node 2026-10-16 15:44:31 -07:00
Vito 0fa595a9b7 Merge pull request #98 from ComfyAssets/feat/shared-embedding-autocomplete
feat(autocomplete): unify UI and restore tag management
2026-10-16 15:38:45 -07:00
Vito Sansevero f8ce4cb982 fix(autocomplete): address review findings and WebKit readiness 2026-10-16 15:31:46 -07:00
Vito Sansevero 42b6009f1b feat(autocomplete): restore tag imports and advanced settings 2026-10-16 15:11:56 -07:00
Vito Sansevero 963ac0a4af docs(autocomplete): document versioned Danbooru tag pack and terms 2026-10-16 14:45:29 -07:00
Vito Sansevero b4bd8b98b1 feat(ui): unify Embedding Autocomplete controls and suggestions 2026-10-16 13:49:11 -07:00
Vito ed6675721d Merge pull request #95 from ComfyAssets/feat/shared-display-views
feat(ui): unify Display Text and Display Any output views
2026-10-16 13:25:23 -07:00
Vito Sansevero b0c0d53676 feat(ui): unify Display Text and Display Any output views 2026-10-16 13:20:28 -07:00
Vito b9103ad9c4 Merge pull request #93 from ComfyAssets/feat/shared-node-dimensions-dialog
feat(ui): migrate Node Dimensions to shared dialog controls
2026-10-16 13:09:13 -07:00
Vito Sansevero b5694ecfe5 feat(ui): migrate Node Dimensions to shared dialog controls 2026-10-16 13:00:36 -07:00
Vito 1255c4e9cc Merge pull request #91 from ComfyAssets/feat/shared-dimension-swap
feat(ui): unify dimension swap controls for both nodes
2026-10-16 12:45:22 -07:00
Vito Sansevero 26557a99b6 feat(ui): unify dimension swap controls for both nodes 2026-10-16 12:35:23 -07:00
Vito 848674d474 Merge pull request #89 from ComfyAssets/feat/shared-workflow-timer
feat(ui): migrate Workflow Timer to shared controls
2026-10-16 12:23:27 -07:00
Vito Sansevero 0c47dc6085 feat(ui): migrate Workflow Timer to shared controls 2026-10-16 12:18:02 -07:00
Vito ee7d1e9011 Merge pull request #87 from ComfyAssets/feat/shared-seed-history
feat(ui): migrate Seed History to shared controls
2026-10-16 12:00:07 -07:00
Vito Sansevero b7a29a1731 feat(ui): migrate Seed History to shared controls 2026-10-16 11:55:57 -07:00
Vito 3cabbffb40 Merge pull request #85 from ComfyAssets/feat/shared-image-viewer
feat(ui): migrate Save Image viewer to shared window controls
2026-10-16 11:37:46 -07:00
Vito Sansevero 47dc177517 feat(ui): migrate saved-image viewer to shared window controls 2026-10-16 11:30:11 -07:00
Vito ddb356fe17 Merge pull request #83 from ComfyAssets/feat/live-image-loader-ui
feat(ui): ship shared ComfyAssets controls in Local Image Loader
2026-10-16 11:08:36 -07:00
Vito Sansevero 9e2b271824 fix(ui): keep gallery thumbnails large and adjustable 2026-10-16 11:03:33 -07:00
Vito Sansevero 2e7fac4b70 docs(ui): capture the loader in both ComfyUI renderers 2026-10-16 10:53:07 -07:00
Vito Sansevero 740eccec27 feat(ui): migrate Local Image Loader to shared ComfyAssets controls 2026-10-16 10:52:28 -07:00
Vito 55bce1004b Merge pull request #79 from ComfyAssets/dependabot/github_actions/actions/setup-node-7
build(deps): bump actions/setup-node from 6 to 7
2026-10-16 09:18:54 -07:00
Vito 63a8596b81 Merge branch 'main' into dependabot/github_actions/actions/setup-node-7 2026-10-16 09:14:46 -07:00
Vito 11494c747c Merge pull request #78 from ComfyAssets/dependabot/github_actions/actions/setup-python-7
build(deps): bump actions/setup-python from 6 to 7
2026-10-16 09:14:32 -07:00
Vito 88a9238ee2 Merge branch 'main' into dependabot/github_actions/actions/setup-python-7 2026-10-16 09:10:46 -07:00
Vito 3879f18d79 Merge pull request #72 from ComfyAssets/feat/checkpoint-folder-iterator
feat(xyz_helpers): Checkpoint Folder Iterator and Model Compare Sheet
2026-10-16 09:10:28 -07:00
Vito Sansevero 64f70b6970 fix(model_compare_sheet): hide staging files from output image discovery 2026-10-16 09:03:56 -07:00
Vito Sansevero ae0fb7f229 fix(model_compare_sheet): replace rerun tiles without retaining stale batches 2026-10-16 08:59:29 -07:00
Vito ddf73a667e Merge branch 'main' into feat/checkpoint-folder-iterator 2026-10-16 08:57:09 -07:00
dependabot[bot] 46999c8d90 build(deps): bump actions/setup-node from 6 to 7
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6 to 7.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-16 15:47:59 +00:00
dependabot[bot] 852864a270 build(deps): bump actions/setup-python from 6 to 7
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-10-16 15:47:47 +00:00
Vito c182c775f4 Merge pull request #77 from ComfyAssets/test/merge-confidence
test: add extension workflows coverage and required merge checks
2026-10-16 08:46:26 -07:00
Vito Sansevero 02108be2d1 test: cover gallery pagination and directory autocomplete deterministically 2026-10-16 08:42:23 -07:00
Vito Sansevero 2a9fbe78c7 test: allow same-origin WebKit blob requests in network guard 2026-10-16 08:36:38 -07:00
Vito Sansevero 5a8a8b62e4 test: add extension workflows coverage and required merge checks 2026-10-16 08:32:11 -07:00
Vito Sansevero 335cc53961 fix(model_compare_sheet): reject run_name segments on both separators
- Split run_name on "/" and "\" and reject empty, "." and ".." parts, so
  a Windows-style "..\foo" can no longer resolve outside model_compare
  and let the index-0 archive step rename an unrelated output folder
- Reject drive-letter names and require the resolved directory to sit
  strictly below output/model_compare after symlinks are followed
- Nested run names such as project/date/sweep still work
2026-10-16 08:11:17 -07:00
Vito Sansevero c5e00c130b feat(xyz_helpers): add Checkpoint Folder Iterator and Model Compare Sheet
- Checkpoint Folder Iterator loads one checkpoint per queued run from a
  folder (relative to models/checkpoints or absolute), so a whole folder
  can be swept with only one model in memory at a time
- Model Compare Sheet saves each run's image as a tile under
  output/model_compare/<run_name>/ and rebuilds a labeled contact sheet
  from disk every run, so the grid survives between independent prompts
- Starting a sweep at index 0 archives stale tiles to a timestamped folder
- Running past the last checkpoint raises a clear error instead of
  wrapping, so an over-queued batch never restarts and archives a
  finished sweep
- Checkpoints load from the scanned absolute path, so folders outside the
  configured roots work as the tooltip promises
- Extract the LoRA Folder Batch folder-scanning, natural-sort and regex
  filter helpers into kikotools/base/model_folders.py so both tools share
  one implementation
2026-10-16 08:08:00 -07:00
Vito 78f1498ea8 Merge pull request #71 from ComfyAssets/release/1.1.2
Release 1.1.2 - ComfyUI scanner fixes
2026-10-15 04:21:22 -07:00
Vito Sansevero c65f3aad03 chore(release): 1.1.2 with .comfyignore for registry archive
- Add .comfyignore so `comfy node publish` ships only the runtime
  package: __init__.py, kikotools/, web/, examples/, README, LICENSE,
  pyproject.toml, requirements.txt
- Exclude tests/, scripts/, .github/, dev tooling configs, contributor
  docs, and the stray example.py reference file (not imported anywhere)
- Registry scanner flagged 1.1.1 on five info-level pattern matches;
  two were in tests/ and the historical Model.eval matches came from
  example.py, none of which should be in the published archive
- Bump version to 1.1.2
2026-10-15 04:16:17 -07:00
Vito 9a55c23282 Merge pull request #70 from ComfyAssets/release/1.1.1
Release 1.1.1
2026-10-13 18:36:23 -07:00
Vito Sansevero e90bde5849 chore: release 1.1.1
Gemini Prompt node migrated from the deprecated google-generativeai SDK to google-genai.
2026-10-13 18:29:24 -07:00
Vito Sansevero 49523e10de chore(gemini_prompt): migrate to google-genai SDK
- google-generativeai is deprecated and emits a FutureWarning at node
  load; google-genai is Google's supported replacement
- Image analysis now uses genai.Client().models.generate_content
  instead of the module-level configure/GenerativeModel API
- Model listing reads supported_actions, the new SDK's name for
  supported_generation_methods
- Install instructions, requirements.txt, and README updated to the
  new package name
2026-10-13 18:27:52 -07:00
Vito 8e6cf4233a Merge pull request #69 from ComfyAssets/release/1.1.0
Release 1.1.0
2026-10-13 08:12:57 -07:00
Vito Sansevero 6301cf90f8 chore: release 1.1.0
Security hardening and fixes (#64), Project File Path node (#65, #67), runtime state untracked (#66), dependency bumps (#68).
2026-10-13 08:01:04 -07:00
Vito f87eb959b3 Merge pull request #68 from ComfyAssets/chore/dependency-bumps
Apply pending Dependabot bumps (#56-#63)
2026-10-13 07:47:06 -07:00
Vito Sansevero 7783ed8a16 build(deps): apply pending Dependabot bumps
Combines #56-#63:
- torch >=2.11.0, pillow >=12.2.0, mypy >=1.20.0, pytest-mock >=3.15.1,
  pre-commit >=4.5.1 in both pyproject.toml and requirements-dev.txt
  (Dependabot only edited pyproject, but CI installs requirements-dev.txt)
- actions/checkout v7, actions/cache v6, softprops/action-gh-release v3
  across all workflows, including the new test/coverage/js jobs
2026-10-13 07:44:16 -07:00
Vito 726fcee761 Merge pull request #66 from ComfyAssets/chore/untrack-runtime-state
Stop tracking runtime state files
2026-10-13 07:42:21 -07:00
Vito 98f3defe50 Merge pull request #67 from ComfyAssets/fix/project-file-path-output-label
Project File Path: label output STRING like djz-nodes
2026-10-13 07:41:55 -07:00
Vito Sansevero 143a6c47f6 fix(project_file_path): label the output STRING like djz-nodes
Drop RETURN_NAMES so ComfyUI shows the output as STRING, matching the original Project File Path Generator. Links connect by slot index, so existing workflows are unaffected.
2026-10-13 07:38:33 -07:00
Vito Sansevero 76a52765c7 chore: stop tracking runtime state files
The image loader's config.json/selections.json (one user's saved folders
and selections) and the Gemini model cache were committed, so every
install shipped them as defaults, and running from a symlinked checkout
dirtied the tree. Untrack them and ignore them, plus gemini_config.json
which would hold an API key. All loaders already handle the files being
absent.
2026-10-13 07:37:27 -07:00
Vito f308b23631 Merge pull request #65 from ComfyAssets/feature/project-file-path
Add Project File Path node
2026-10-13 07:32:25 -07:00
Vito Sansevero d65c11c76c feat(project_file_path): add Project File Path node
Builds a save prefix like MyProject/2026-10-13/images/image for a save
node's filename_prefix, replacing djz-nodes' Project File Path Generator
with a fresh implementation:

- Same inputs, root defaults to empty (prefixes are already relative to
  the output folder, so "output" nested as output/output/...)
- Optional date folder; IS_CHANGED tracks the date instead of forcing a
  re-run on every queue
- Components are split on / and \ with '..' segments dropped, so a path
  can't climb out of the output folder
- Only real file extensions are stripped (render_v1.5 keeps its dot)
- The separator option is applied last instead of being undone by
  normpath; an empty filename raises a clear error
2026-10-13 07:27:49 -07:00
Vito 1879f42f83 Merge pull request #64 from ComfyAssets/fix/review-security-and-coverage
Security hardening, crash fixes, cross-platform CI and 95% coverage
2026-10-13 07:26:02 -07:00
Vito Sansevero c2d85d4ba3 test: compare normalized save subfolder (forward slashes on Windows) 2026-10-13 07:23:11 -07:00
Vito Sansevero 2246484b99 fix: keep console output ASCII so Windows legacy consoles can load the node
A Windows console on a legacy code page (cp1252, or cp437 in cmd)
raises UnicodeEncodeError on emoji. The startup banner printed a 🫶
prefix and display names like "Model Downloader 🌐", which aborts
loading the whole node pack. Same approach as ComfyUI_PromptManager:

- Banner prints ASCII-only display names (emoji stay in the UI names)
- Preset validation messages print "1024x1024" instead of "1024×1024"
- Explicit UTF-8 for text files (Windows defaults to cp1252)
- Regression test imports the package with a strict cp1252/cp437 stdout
2026-10-13 07:20:11 -07:00
Vito Sansevero 2a637322b9 test: fix Windows-only test failures
- Close PIL images and NamedTemporaryFile handles before deleting the
  files; Windows refuses to delete open files (WinError 32)
- Set USERPROFILE alongside HOME: expanduser ignores HOME on Windows
- Compare realpaths in the nested save-prefix test; Windows temp dirs
  use 8.3 short names (RUNNER~1)
2026-10-13 07:18:30 -07:00
Vito Sansevero 684e61638b chore: exclude gitignored referance/ from flake8 2026-10-13 07:13:50 -07:00
Vito Sansevero a0b132ef7b ci: run pytest on Linux, Windows and macOS with an 85% coverage gate
tests.yml only ran inline smoke scripts on Ubuntu, never the pytest
suite, so broken tests went unnoticed on main. Mirror ComfyUI_PromptManager:

- pytest on ubuntu/windows/macos x Python 3.10-3.13 (fail-fast off)
- separate coverage job enforcing fail_under = 85
- syntax check every web/*.js as an ES module
- CPU-only torch on Linux to avoid the ~2.5 GB CUDA wheel
2026-10-13 07:13:08 -07:00
Vito Sansevero 935e94b651 fix(web): restore custom colors on group menus and honor auto-shade
- Groups have no node-menu hook, so the menu-hook refactor dropped their
  custom color entry; add it via getCanvasMenuItems using the group under
  the mouse, as the frontend's own Comfy.GroupOptions does
- The "Auto-shade Title" setting was read but never applied
- Share one hidden color input instead of three
- Don't mark the canvas dirty while merely building the node menu
2026-10-13 07:07:34 -07:00
Vito Sansevero 7513479419 refactor(web): use extension menu hooks instead of patching LiteGraph
Replace monkey-patches of LGraphCanvas.onMenuNodeColors and the
canvas/node menu getters (applied after a 500ms timeout, and scraping
.litecontextmenu DOM for "No color") with the frontend's
getNodeMenuItems/getCanvasMenuItems extension hooks. Custom colors move
into a "Custom Colors" submenu; follow-execution state moves to module
scope so repeated setup() calls no longer stack wrappers.
2026-10-13 06:57:41 -07:00
Vito Sansevero 865dde6cdf feat(web): offer "Add folder & open" when browsing an unsaved folder
The backend now 403s folders outside the saved paths. Show the full path
and add it only on an explicit click; server-side refusals (e.g. a
too-broad folder) are shown in the status line.
2026-10-13 06:56:49 -07:00
Vito Sansevero 30fc6aa7a7 fix(local_image_loader): refuse filesystem roots and home as saved paths
save_paths is reachable without auth, so saving "/" made every media
file on the machine servable. Reject filesystem/drive roots, the home
directory and its ancestors (realpath-resolved, so symlinks count), and
ignore such entries already in config.json.
2026-10-13 06:56:49 -07:00
Vito Sansevero a055543d16 fix(model_downloader): never write into custom_nodes or save code files
ComfyUI registers custom_nodes in folder_names_and_paths, so treating
every registered folder as a download root let a queued prompt drop a
.py file there that runs on the next restart.

- Skip custom_nodes when collecting allowed roots
- save_response only writes model/data extensions (.safetensors, .gguf,
  .ckpt, .pth, .json, ...), so any future code-holding folder is still
  protected
2026-10-13 06:41:13 -07:00
Vito Sansevero 78fd20d89a fix(local_image_loader): load the resolved path that was validated 2026-10-13 06:38:23 -07:00
Vito Sansevero 4b66592715 test: cover gemini, embedding autocomplete, batch prompts, presets and plot nodes 2026-10-13 06:38:20 -07:00
Vito Sansevero dc3fcf65e2 fix: save path escape, global RNG reseed and small node bugs
- KikoSaveImage: reject absolute or '..' filename_prefix directories that
  resolved outside the output folder
- Film grain: use a local torch.Generator instead of reseeding the global
  RNG; strength 0 no longer skips the toe adjustment
- Purge VRAM: IS_CHANGED returns NaN so caching never skips the purge
- Resolution calculator: clamp outputs to >= 8
- Plot sampler params: font size floor of 1 for images under 32px wide
- Gemini prompt: drop Image.fromarray mode= argument (removed in Pillow)
2026-10-13 06:38:20 -07:00
Vito Sansevero 8196e65906 chore: remove dead modules and consolidate test config
- Delete kikotools/core (registry/settings) and batch_prompts/node_simple
  which nothing imports
- pytest.ini used the [tool:pytest] header and was ignored; make it the
  single pytest config (drop the pyproject duplicate), keep
  --strict-markers
- Add pytest-asyncio to dev deps; enforce 85% coverage via
  [tool.coverage.report] fail_under
2026-10-13 06:38:20 -07:00
Vito Sansevero c8de8f26d6 fix(model_downloader): confine downloads to model dirs and harden fetches
- Reject filenames with path components; reduce server-derived names
  (Content-Disposition, CivitAI redirect, URL) to a safe basename
- save_path must resolve inside models_dir or a registered model folder
  (including extra_model_paths.yaml roots); relative paths resolve
  against ComfyUI's base path
- Only http/https for user URLs and redirects; drop the Authorization
  header on cross-host redirects
- Stream to <file>.part and rename on success, removing the partial file
  on any failure, so a dropped connection no longer leaves a truncated
  model that later runs treat as complete
- 60s timeout on every request; shared streaming loop in BaseDownloader
- Custom downloader honours Content-Disposition before the default name
2026-10-13 06:38:20 -07:00
Vito Sansevero 10027fcef9 fix(local_image_loader): confine file routes to saved roots and media types
/view served any readable file (e.g. ~/.ssh/id_rsa) to anyone who could
reach the server; the only guard was a '..' substring check.

- Resolve paths with realpath and require commonpath containment in the
  saved paths or ComfyUI input/output directories
- /view serves image/video/audio only, /thumbnail and load_media images
  only; the extension check applies to the resolved path, so symlinks
  can't smuggle other files
- /images 403s outside the roots; page/per_page validated and clamped
- /list_directories (folder picker) returns directory names only
- load_media refuses disallowed paths from a tampered selections.json
- Route handlers moved to module level so they are unit tested
- Update stale tests to the (image, info) return contract
2026-10-13 06:34:13 -07:00
Vito Sansevero 60ce078aed fix(xyz_helpers): raise on invalid sampler/LoRA input instead of hanging
- LoRA strength strings: step <= 0 looped forever and reversed ranges
  returned [] (ZeroDivisionError later); now raise ValueError, and cap
  generated values at 1000
- Flux sampler params: unparseable steps/guidance/shift/denoise/seed
  tokens raise with the field name instead of being dropped, and an
  empty LoRA folder raises instead of returning (None, [])
- Detect Flux schnell via unet_config guidance_embed (the ModelType.FLOW
  check never matched Flux)
- IS_CHANGED: re-run Flux params when a seed is '?'; LoRA folder batch
  hashes file list + mtimes instead of always re-executing
- LoRA folder scan uses commonpath containment across all lora roots
- Prompt separators must be whole lines; failed encodes raise
2026-10-13 06:33:17 -07:00
Vito Sansevero 37c00b633c fix(web): escape server data in innerHTML and plug listener leaks
- Escape file names, paths and image metadata interpolated into
  innerHTML in the local image loader and save image viewer (XSS via
  crafted file names or a shared workflow's filename_prefix)
- Save image viewer: bind document drag listeners once and remove them
  in disconnectedCallback instead of adding two per run; drop debug
  logging and the window.kikoSaveImageTest helper
- Local image loader: check response.ok and surface 400/403 errors,
  disconnect the ResizeObserver on node removal
- Gemini prompt: restore the user's node color after the run flash
- Workflow timer: chain onRemoved, merge properties in onConfigure
- Seed history: build entries with textContent
2026-10-13 06:32:14 -07:00
Vito c7f24a3262 Merge pull request #55 from ComfyAssets/feature/batch-list-converter
feat: add batch/list conversion nodes for IMAGE and LATENT
2026-02-09 17:46:52 -08:00
Vito Sansevero c69281e795 fix(batch_list_converter): preserve all latent dict keys during split/join
Previously split_latent_batch and join_latent_batch only handled the
"samples" key, silently dropping metadata like noise_mask or batch_index.
This broke inpaint and masked workflows after a round-trip conversion.
2026-02-09 17:11:01 -08:00
Vito Sansevero 3bf8391e88 feat(batch_list_converter): add batch/list conversion nodes for IMAGE and LATENT
Add 4 utility nodes for converting between batched tensors and lists,
eliminating the dependency on Impact Pack for this common operation:
- ImageBatchToImageList (OUTPUT_IS_LIST)
- ImageListToImageBatch (INPUT_IS_LIST)
- LatentBatchToLatentList (OUTPUT_IS_LIST)
- LatentListToLatentBatch (INPUT_IS_LIST)
2026-02-09 16:39:28 -08:00
Vito d13bfe8fb4 Merge pull request #53 from ComfyAssets/feature/seed-range-and-latent-output
fix: 32-bit seed range, latent batch_size output
2026-02-09 16:32:17 -08:00
Vito Sansevero 702d0c889c fix(seed_history): accept legacy kwargs for backward compatibility
Old workflows may still pass 'mode' to output_seed. Using **kwargs
prevents TypeError when ComfyUI invokes with the extra argument.
2026-02-09 16:29:11 -08:00
Vito Sansevero d1a5282ca0 style: fix Black formatting issues
Remove extra blank line in local_image_loader/node.py and wrap long
tuple assertion in test_empty_latent_batch.py.
2026-02-09 16:26:08 -08:00
Vito Sansevero 36212adf73 ci: reduce test matrix to Python 3.11, 3.12, 3.13
Drop 3.8, 3.9, and 3.10 which are no longer needed.
2026-02-09 16:25:30 -08:00
Vito Sansevero 2cb2262293 chore(local_image_loader): update saved paths and selections 2026-02-09 16:22:10 -08:00
Vito Sansevero 8773c8f249 feat(latent): add batch_size as 4th output from EmptyLatentBatchNode
Exposes batch_size as an output so downstream nodes can reference it
directly without needing a separate input.
2026-02-09 16:22:03 -08:00
Vito Sansevero 0a1cbe4990 fix(seeds): reduce seed range from 64-bit to 32-bit across all nodes
JS Math.random() only has 53 bits of integer precision, making the
64-bit range produce non-uniform values. 32-bit (0xFFFFFFFF) is the
standard ComfyUI seed range and works correctly in both Python and JS.

Also removes the redundant mode input from SeedHistoryNode, relying on
ComfyUI's built-in control_after_generate widget instead.
2026-02-09 16:21:56 -08:00
Vito c4a27137e7 Merge pull request #52 from ComfyAssets/feature/height-width-2-vec
Feature/height width 2 vec
2025-12-21 06:49:05 -08:00
Vito Sansevero 20270d9501 test: Add unit tests for KikoWorkflowTimerNode 2025-12-21 06:45:35 -08:00
Vito Sansevero 2467dff704 feat(timer): add real-time workflow execution timer 2025-12-21 06:45:24 -08:00
Vito Sansevero cecd1e01e9 feat(timer): add Sketch-style color picker widget 2025-12-21 06:45:10 -08:00
Vito Sansevero e504f314aa chore: bump version to 1.0.28 in pyproject.toml 2025-12-21 06:45:00 -08:00
Vito Sansevero f12debe729 chore: update image paths in selections.json 2025-12-21 06:44:49 -08:00
Vito Sansevero c64e835d2e chore(local_image_loader): update config paths 2025-12-21 06:44:38 -08:00
Vito Sansevero 36d37db8a9 feat(init): add KikoWorkflowTimerNode to mappings 2025-12-21 06:44:24 -08:00
Vito Sansevero 969fd9e25e docs: Add Workflow Timer section to README.md 2025-12-21 06:44:14 -08:00
Vito Sansevero 6916b47a89 chore: bump version to 1.0.27 in pyproject.toml 2025-12-17 07:15:01 -08:00
Vito 8457db0041 Merge pull request #50 from ComfyAssets/dependabot/github_actions/actions/checkout-6
build(deps): bump actions/checkout from 5 to 6
2025-12-17 07:07:19 -08:00
Vito 8a84927adf Merge pull request #51 from ComfyAssets/dependabot/github_actions/actions/cache-5
build(deps): bump actions/cache from 4 to 5
2025-12-17 07:06:54 -08:00
Vito Sansevero 8c0b89ff70 style(node): Reformat code for readability 2025-12-17 07:06:16 -08:00
Vito Sansevero b527635254 test: update return types in test_assertion 2025-12-17 07:02:08 -08:00
Vito Sansevero 14738b80c5 feat(image_loader): add directory listing API endpoint 2025-12-17 07:01:55 -08:00
Vito Sansevero bff1276d06 style: Add newline at EOF in JSON file 2025-12-17 07:01:46 -08:00
Vito Sansevero a7805ad587 feat: add WidthHeightToVec2Node to mappings 2025-12-17 07:01:34 -08:00
Vito Sansevero f2093d6567 style(docs): Ensure newline at EOF in markdown files 2025-12-17 07:01:22 -08:00
Vito Sansevero 0e26f0ad36 style: Remove trailing spaces in config files 2025-12-17 07:00:53 -08:00
Vito Sansevero 9a05f20790 style: Remove unnecessary whitespace for consistency 2025-12-17 07:00:31 -08:00
Vito Sansevero 6368582e1b feat: Add WidthHeightToVec2Node with tests 2025-12-17 06:59:11 -08:00
dependabot[bot] 97ea5b9a7d build(deps): bump actions/cache from 4 to 5
Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 16:28:14 +00:00
dependabot[bot] f5f956ab4e build(deps): bump actions/checkout from 5 to 6
Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-24 16:55:51 +00:00
Vito Sansevero 498fae1211 feat(image_loader): add search_files function 2025-11-14 13:05:52 -08:00
Vito Sansevero d24912036c chore(local_image_loader): update last_path in config.json 2025-11-14 13:05:52 -08:00
Vito 523b0509f1 Update pyproject.toml 2025-11-12 08:23:11 -08:00
Vito 5f8117aa56 Merge pull request #49 from wzgrx/patch-1
Update requirements.txt
2025-11-12 08:22:31 -08:00
wzgrx 22dc975353 Update requirements.txt 2025-10-25 22:05:14 +08:00
Vito Sansevero 66afb2b204 chore: bump version to 1.0.25 in pyproject.toml 2025-10-18 13:09:46 -07:00
Vito 1dd1dcf895 Merge pull request #48 from ComfyAssets/feature/downloader
Feature/downloader
2025-10-18 13:08:00 -07:00
Vito Sansevero 60f30b068e docs: add Model Downloader to README.md 2025-10-18 12:59:40 -07:00
Vito Sansevero 1439270fe5 refactor: Reorder pendingFetches initialization 2025-10-18 12:59:32 -07:00
Vito Sansevero f8210d8f69 feat(presets): Add new SDXL portrait and landscape presets 2025-10-18 12:46:31 -07:00
Vito Sansevero aceb34b9b0 feat(seed_history): add seed mode handling and validation 2025-10-18 12:46:19 -07:00
Vito Sansevero e9ce1fd2cf feat(model_downloader): handle download interruption 2025-10-18 12:45:46 -07:00
Vito Sansevero 9b888443ac feat(model_downloader): add interrupt handling 2025-10-18 12:45:37 -07:00
Vito Sansevero f4743df3ef feat(model_downloader): add cancel download support 2025-10-18 12:45:27 -07:00
Vito Sansevero 6a68983ef4 feat(model_downloader): Add interrupt check support 2025-10-18 12:45:09 -07:00
Vito Sansevero fb01fa24ae chore: update selections.json with new images 2025-10-18 12:44:43 -07:00
Vito Sansevero 65f68f59a1 chore: update last_path in config.json 2025-10-18 12:44:12 -07:00
Vito c3fab5581b Merge pull request #47 from ComfyAssets/bugs/fix-save-and-sampler
Bugs/fix save and sampler
2025-10-07 07:34:30 -07:00
Vito Sansevero 1ea2b4cc90 fix(ci): update Sampler Combo test to match SAMPLERS list return type 2025-10-07 07:28:19 -07:00
Vito Sansevero f33f39f134 chore: update .gitignore with .serena entry 2025-10-07 07:22:01 -07:00
Vito Sansevero 5b57d4fc35 test: Add tests for image counter functionality 2025-10-07 07:21:09 -07:00
Vito Sansevero a1625dddad refactor(node): simplify sampler return logic 2025-10-07 07:20:58 -07:00
Vito Sansevero a88232f59a refactor(compact_node): simplify sampler return logic 2025-10-07 07:20:47 -07:00
Vito Sansevero 6746b86685 feat(kiko_save_image): add persistent counter for filenames 2025-10-07 07:20:35 -07:00
Vito Sansevero 17af18d397 chore: bump version to 1.0.24 in pyproject.toml 2025-10-05 07:42:43 -07:00
Vito 703989599d Merge pull request #46 from ComfyAssets/alert-autofix-14
Potential fix for code scanning alert no. 14: Use of a broken or weak cryptographic hashing algorithm on sensitive data
2025-10-05 07:42:14 -07:00
Vito Sansevero 8399fad96b fix: prevent URL substring sanitization bypass attacks
Fixed incomplete URL substring sanitization vulnerability (CodeQL alert)
by implementing proper domain validation using urlparse().netloc instead
of substring checking with 'in url'.

Changes:
- civitai.py: Added explicit domain validation before processing URLs
  - Only allow exact matches: 'civitai.com' and 'www.civitai.com'
  - Reject URLs like 'evil.com/civitai.com' or 'civitai.com.evil.com'

- detector.py: Improved URL detection methods
  - _is_civitai_url: Changed from 'in parsed.netloc' to exact match
  - _is_huggingface_url: Added allowlist of valid HF domains
    - Supports: huggingface.co, www.huggingface.co, cdn.huggingface.co,
      cdn-lfs.huggingface.co

Security Impact:
Prevents subdomain attacks and URL smuggling where malicious URLs could
bypass validation by including legitimate domain names as substrings:
- https://evil.com/civitai.com/malicious
- https://civitai.com.evil.com/models/123
- https://subdomain.civitai.com/attack

All security tests pass with 100% malicious URL rejection rate.
2025-10-05 07:32:18 -07:00
Vito Sansevero 0c69abc829 fix: improve regex pattern to detect script tag bypass attempts
Improved the HTML filtering regex to properly detect all variations of
script tags including bypass attempts with whitespace before the closing
bracket (e.g., '<script >' and '</script >').

Changed from word boundary pattern /<script\b/gi to a more comprehensive
pattern /<\s*\/?script[^>]*>/gi that matches:
- Optional whitespace after opening bracket
- Optional forward slash for closing tags
- Any characters until closing bracket (catches attributes and whitespace)

This fixes the CodeQL security alert for bad HTML filtering regexp that
could be bypassed with malformed tags.

Also updated iframe, embed, and object tag patterns for consistency.
2025-10-05 07:29:41 -07:00
Vito Sansevero e00406747f security: exclude api_token from IS_CHANGED hash to fix CodeQL warning
The api_token is sensitive data and shouldn't be included in the SHA256
hash. The hash is only used for ComfyUI cache invalidation, where the
URL change is sufficient to trigger re-execution. Including the token
was unnecessary and triggered a security warning.

This fixes the CodeQL alert: py/weak-sensitive-data-hashing
2025-10-05 07:24:46 -07:00
VitoandCopilot Autofix powered by AI a21e677629 Potential fix for code scanning alert no. 14: Use of a broken or weak cryptographic hashing algorithm on sensitive data
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-10-05 07:18:17 -07:00
Vito 13e425959b Merge pull request #45 from ComfyAssets/fix/nodes-latent-v3-schema
fix: replace LatentBatch import with local implementation for V3 sche…
2025-10-05 06:14:12 -07:00
Vito Sansevero 0a6ee72748 fix: replace LatentBatch import with local implementation for V3 schema compatibility
Refs #43

ComfyUI is converting nodes_latent.py to V3 Schema on October 8th, which will
break direct imports of LatentBatch. This commit replaces the import with a
local implementation copied directly from ComfyUI source code.

Changes:
- Removed: from comfy_extras.nodes_latent import LatentBatch
- Added: Local batch_latents() and reshape_latent_to() functions
- Updated: latentbatch.batch() calls to use batch_latents()
- Added: torch and comfy.utils imports for tensor operations
- Added: Comprehensive unit tests for latent batching functionality

The local implementation is functionally identical to the original and ensures
the node will continue working after the V3 schema migration.

Test Coverage:
- 5 new tests in TestLatentBatchingFunctions class
- All 16 tests passing (11 existing + 5 new)
- Tests cover tensor operations, batch indexing, and reshape logic
2025-10-05 06:08:21 -07:00
Vito b03f0ecf22 Merge pull request #44 from ComfyAssets/feature/download-assets
Feature/download assets
2025-10-05 06:00:35 -07:00
Vito Sansevero ed81bf4cfd ci: add file check for model_downloader and text_input 2025-10-05 05:40:12 -07:00
Vito Sansevero 58a6c05d98 ci: add tests for ModelDownloader and TextInput nodes 2025-10-05 05:40:04 -07:00
Vito Sansevero 51bb7711b8 feat(init): add ModelDownloader and TextInput nodes 2025-10-05 05:39:35 -07:00
Vito Sansevero 8f459c502a feat(model_downloader): add model downloaders tool 2025-10-05 05:39:21 -07:00
Vito Sansevero af1bc6845a feat(text_input): add text input tool for ComfyUI 2025-10-05 05:39:09 -07:00
Vito Sansevero 27431b3a92 test(model_downloader): add unit tests for model downloader 2025-10-05 05:39:00 -07:00
Vito Sansevero 67ef0a44d7 test(text_input): add unit tests for TextInputNode 2025-10-05 05:38:47 -07:00
Vito a501260bbf Merge pull request #42 from ComfyAssets/fix/swap
Fix/swap
2025-09-24 14:52:28 -07:00
Vito Sansevero 3a4651b191 chore: bump version to 1.0.23 in pyproject.toml 2025-09-24 14:32:58 -07:00
Vito Sansevero 2f3d6d62f3 refactor(js): update callback params with app.canvas 2025-09-24 14:32:45 -07:00
Vito fb805c4a3d Merge pull request #41 from ComfyAssets/fix/leaks
Fix/leaks
2025-09-23 07:58:03 -07:00
Vito Sansevero 4e84588a94 style(tests): improve code formatting consistency 2025-09-23 07:39:27 -07:00
Vito Sansevero df7776280e chore: bump version to 1.0.22 in pyproject.toml 2025-09-23 07:24:22 -07:00
Vito Sansevero 8fd92530ee test: Add tests for embedding autocomplete features 2025-09-23 07:23:41 -07:00
Vito Sansevero 081f5f2310 refactor(autocomplete): enhance widget handling logic 2025-09-23 07:23:27 -07:00
Vito Sansevero ad7e6e647f feat(web): add Qwen presets for image dimensions 2025-09-23 07:23:11 -07:00
Vito Sansevero 04218704b3 feat(presets): add Qwen presets and categories 2025-09-23 07:22:53 -07:00
Vito a4db4390ea Merge pull request #40 from ComfyAssets/dependabot/github_actions/actions/setup-python-6
build(deps): bump actions/setup-python from 5 to 6
2025-09-18 17:16:19 -07:00
dependabot[bot] c38753758b build(deps): bump actions/setup-python from 5 to 6
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 5 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-08 16:33:10 +00:00
Vito Sansevero 17b97ed17a chore: bump version to 1.0.21 in pyproject.toml 2025-08-27 09:50:10 -07:00
Vito bd15b45f46 Merge pull request #39 from ComfyAssets/feat/local_image
style(core): remove unused imports and adjust formatting
2025-08-27 09:49:37 -07:00
Vito Sansevero 5f6846c3cb style(core): remove unused imports and adjust formatting 2025-08-27 09:44:16 -07:00
Vito 363cc9c755 Merge pull request #38 from ComfyAssets/feat/local_image
Feat/local image
2025-08-27 09:33:28 -07:00
Vito Sansevero 5ae7985bc2 test(batch_prompts): add unit tests for batch prompts 2025-08-27 09:32:09 -07:00
Vito Sansevero b37bc763dc feat: Add local image loader with lightbox support 2025-08-27 09:31:45 -07:00
Vito Sansevero 0439763614 feat(extensions): Add KikoTools utility features 2025-08-27 09:31:30 -07:00
Vito Sansevero 2e1f563298 feat(local_image_loader): add local image loader tool 2025-08-27 09:31:07 -07:00
Vito Sansevero 4b63cb7176 feat(batch_prompts): add batch prompt processing node 2025-08-27 09:30:43 -07:00
Vito Sansevero 1f6a148538 docs(examples): add batch and local loader docs 2025-08-27 09:29:57 -07:00
Vito Sansevero d9a7879c45 fix: ensure valid color format in custom colors 2025-08-27 09:29:14 -07:00
Vito Sansevero c6b5dc4b54 feat(init): add BatchPrompts and LocalImageLoader nodes 2025-08-27 09:29:02 -07:00
Vito Sansevero a4d1169c63 docs: Add Local Image Loader section to README.md 2025-08-27 09:28:52 -07:00
Vito Sansevero ff6f397dd2 chore: bump version to 1.0.20 in pyproject.toml 2025-08-19 11:54:47 -07:00
Vito 97913deae3 Merge pull request #37 from ComfyAssets/bugfix/empty-latent-batch-swap-button
fix: convert Empty Latent Batch swap button from canvas to DOM widget
2025-08-19 11:53:59 -07:00
Vito Sansevero e6c8dd583f fix: convert Empty Latent Batch swap button from canvas to DOM widget
- Switch from unreliable canvas-based drawing to DOM widget approach
- Eliminate coordinate calculation complexity and mouse position issues
- Use same proven pattern as Seed History node buttons
- Add proper hover/click animations and visual feedback
- Fix callback bug in heightWidget.callback assignment
- Remove all canvas drawing, mouse handling, and coordinate code
- Button now works consistently without coordinate system problems

Resolves swap button not working issue by using reliable DOM elements
instead of manual canvas coordinate calculations.
2025-08-19 11:48:53 -07:00
Vito Sansevero f9db6f8635 chore: bump version to 1.0.19 in pyproject.toml 2025-08-16 17:49:54 -07:00
Vito 1b18873e65 Merge pull request #36 from ComfyAssets/feature/lora-auto-batching
feat: Add auto-batching support for large LoRA collections
2025-08-16 17:49:16 -07:00
Vito Sansevero d2b30f0a78 feat: Add auto-batching support for large LoRA collections
- Add auto-batching functionality to split large LoRA collections into manageable chunks
- Implement batch_size parameter to control number of LoRAs per batch (default: 25)
- Add batch_index parameter to select which batch to process
- Include batch tracking metadata in LORA_PARAMS for visualization
- Display batch info in plot parameters when available
- Update lora_list output to show batch header when auto-batching is enabled
- Add comprehensive tests for batching functionality
- Update documentation with detailed auto-batching usage instructions

This feature prevents UI disconnection issues when processing large numbers of LoRAs
by allowing users to process them in smaller batches sequentially.
2025-08-16 17:27:59 -07:00
Vito Sansevero 917421529b chore(pyproject): bump version to 1.0.18 2025-08-14 19:12:49 -07:00
Vito d29dcb8564 Merge pull request #35 from ComfyAssets/feature/remove-cfg-custom-slider
refactor: Remove custom CFG slider display in Sampler Combo nodes, requested from user
2025-08-14 19:11:05 -07:00
Vito Sansevero b95bf8e64a refactor: Remove custom CFG slider display in Sampler Combo nodes
- Remove "display": "slider" parameter from CFG input in SamplerComboNode
- Remove "display": "slider" parameter from CFG input in SamplerComboCompactNode
- Both nodes now use ComfyUI's standard float input instead of custom slider
- All tests passing (354 unit tests)
2025-08-14 19:06:44 -07:00
Vito Sansevero b8622e21da chore: bump version to 1.0.17 in pyproject.toml 2025-08-13 07:08:43 -07:00
Vito 4c8f20ef88 Merge pull request #34 from ComfyAssets/bugfix/display-any
Bugfix/display any
2025-08-13 07:08:12 -07:00
Vito 26d135e106 Merge pull request #33 from ComfyAssets/dependabot/github_actions/actions/checkout-5
build(deps): bump actions/checkout from 4 to 5
2025-08-13 07:05:21 -07:00
Vito Sansevero db7d0dc86e test: Add line break and spacing adjustments 2025-08-13 07:04:55 -07:00
Vito Sansevero b5e24dbe57 test: Update test categories with emoji prefix 2025-08-13 07:01:21 -07:00
Vito Sansevero eb1e646453 test: Update CATEGORY assertion emoji in test 2025-08-13 07:01:07 -07:00
Vito Sansevero 793579a1dd refactor(web): remove redundant title update code 2025-08-13 07:00:55 -07:00
dependabot[bot] eba899b30d build(deps): bump actions/checkout from 4 to 5
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-11 22:33:21 +00:00
Vito Sansevero 914ba8e003 chore: bump version to 1.0.16 in pyproject.toml 2025-08-10 11:38:38 -07:00
Vito 51b057982e Merge pull request #32 from ComfyAssets/feature/follow-execution-and-custom-colors
feat: add follow execution and custom colors UI features
2025-08-10 11:37:58 -07:00
Vito Sansevero 3a3a6ebab9 feat: add follow execution and custom colors UI features
- Add follow execution feature with settings integration
  - Automatically centers canvas on currently executing node
  - Configurable via ComfyUI settings panel
  - Supports auto-start on workflow execution
  - Adds right-click menu options when enabled

- Add custom colors feature with extended options
  - Based on ComfyUI-Custom-Scripts with enhancements from PR #433
  - Three color picker modes: Full, Title only, Background only
  - Multi-node selection support
  - Configurable via settings to enable/disable individual options
  - Auto-shading option for better contrast

Both features are disabled by default and can be enabled in ComfyUI settings under the 🫶 KikoTools sections.
2025-08-10 11:35:22 -07:00
Vito Sansevero 886917d95c chore: bump version to 1.0.15 in pyproject.toml 2025-08-10 06:58:30 -07:00
Vito Sansevero bf55338b61 fix: update all remaining category assertions to handle emoji prefixes
- Change startswith checks to contains checks for ComfyAssets/
- All nodes use '🫶 ComfyAssets/...' format with emoji prefix
2025-08-10 06:51:43 -07:00
Vito Sansevero c561e59650 fix: update test assertions to match emoji prefixes in node categories 2025-08-10 06:50:30 -07:00
Vito Sansevero 9e67f06a21 fix: update base node test to handle emoji prefix in category 2025-08-10 06:44:51 -07:00
Vito Sansevero 90e4b57dc6 style: add missing newline to any_type.py for black formatting 2025-08-10 06:33:12 -07:00
Vito Sansevero 386e48c2e1 feat: add Kiko Purge VRAM node for intelligent memory management
- Add comprehensive VRAM management tool with 4 purge modes (soft, aggressive, models_only, cache_only)
- Implement smart memory threshold triggering to avoid unnecessary purges
- Add detailed memory reporting showing before/after stats and freed MB
- Support passthrough design for seamless workflow integration
- Include graceful CPU fallback for non-CUDA environments
- Add comprehensive test suite with 14 tests covering all functionality
- Update documentation with detailed usage examples and parameters
- Create reusable AnyType class for wildcard input matching

The node provides essential memory management capabilities for complex workflows,
helping prevent OOM errors and optimize multi-model processing pipelines.
2025-08-10 06:25:48 -07:00
Vito Sansevero 624ba92723 feat: add Kiko Purge VRAM node for intelligent memory management
- Add comprehensive VRAM management tool with 4 purge modes (soft, aggressive, models_only, cache_only)
- Implement smart memory threshold triggering to avoid unnecessary purges
- Add detailed memory reporting showing before/after stats and freed MB
- Support passthrough design for seamless workflow integration
- Include graceful CPU fallback for non-CUDA environments
- Add comprehensive test suite with 14 tests covering all functionality
- Update documentation with detailed usage examples and parameters
- Create reusable AnyType class for wildcard input matching

The node provides essential memory management capabilities for complex workflows,
helping prevent OOM errors and optimize multi-model processing pipelines.
2025-08-10 06:17:42 -07:00
Vito Sansevero 0ae29f576a chore: bump version to 1.0.14 in pyproject.toml 2025-08-08 21:02:43 -07:00
Vito Sansevero ca9fd5153a style(kikotools): Update category names and refactor nodes 2025-08-08 21:01:48 -07:00
Vito c0d239f31f Update README.md
Update into.
2025-08-08 20:34:20 -07:00
Vito Sansevero 6d27520641 docs: Update README with new image tools added 2025-08-08 18:33:55 -07:00
Vito Sansevero 7cde5a5ceb docs: update image URLs in README.md 2025-08-08 18:31:02 -07:00
Vito Sansevero ecdde6bbd7 chore: bump version to 1.0.13 in pyproject.toml 2025-08-08 18:28:29 -07:00
Vito 0557c040f8 Merge pull request #31 from ComfyAssets/feature/embedding-autocomplete
Feature/embedding autocomplete
2025-08-08 18:28:04 -07:00
Vito Sansevero f7d007d8c4 style: Fix line break in test assertion 2025-08-08 18:16:08 -07:00
Vito Sansevero 80045954a5 refactor(node): handle missing folder_paths module 2025-08-08 18:07:38 -07:00
Vito Sansevero 6f176e7b78 test: Refactor folder_paths mocking setup 2025-08-08 18:00:35 -07:00
Vito Sansevero 576efbba41 feat: complete embedding autocomplete implementation with all features
- Remove debug code and console.log statements
- Fix test suite to properly mock folder_paths module
- Update test expectations to match actual implementation
- Add comprehensive README documentation with feature list
- Add placeholder images for documentation screenshots
- Include diagnostic scripts for testing embedding paths
- All tests passing (338 passed, 2 skipped)

Features implemented:
- Autocomplete for embeddings, LoRAs, and custom tags
- Custom word list loading from URL (with security validation)
- Configurable triggers and settings
- Auto-insert comma, replace underscores, Tab/Enter selection
- Smart scrolling in suggestion list
- Secure content validation to prevent XSS attacks

Credits to pythongosssss/ComfyUI-Custom-Scripts for inspiration
2025-08-08 17:45:21 -07:00
Vito Sansevero 593bd4de79 fix: support both 'embedding:' and 'embeddings:' triggers
- Accept both singular and plural forms
- Common user expectation to use plural
- Regex pattern now matches embeddings?:
- Works with 120 loaded embeddings
2025-08-08 15:19:52 -07:00
Vito Sansevero a8fb5ddaa7 fix: use file_name property for embedding names
- Extract embedding names from file_name property
- Filter out null/undefined entries
- Successfully processes 120 embeddings with proper names
- Cleaner extraction logic based on actual API response structure
2025-08-08 15:17:49 -07:00
Vito Sansevero f67e619930 fix: properly extract embedding names from object items
- Add comprehensive object property checking
- Try multiple property names (name, filename, title, id, embedding_name)
- Log item structure to understand format
- Handle both string and object item formats
2025-08-08 15:14:37 -07:00
Vito Sansevero f9e2f5bcc5 fix: improve embedding pagination with api method
- Try api.getEmbeddings(page) for pagination
- Add better logging to see item format
- Gracefully fall back to first page if pagination fails
- Log sample items to understand structure
2025-08-08 15:09:45 -07:00
Vito Sansevero 2cfeab9e76 fix: handle paginated embeddings API response
- Detect and parse paginated response format (items array)
- Fetch all pages to get complete embeddings list (113 total)
- Support both paginated and object formats for compatibility
- Extract actual embedding names from items array
- Debug shows successful trigger detection for 'embedding:'
2025-08-08 15:06:31 -07:00
Vito Sansevero f5ce23ad12 fix: handle ComfyUI's embeddings object response format
- Parse embeddings from object keys instead of expecting array
- Remove file extensions from embedding names
- Add fallback method for LoRAs using /object_info API
- Delay widget attachment to catch dynamically created widgets
- Better detection of textarea widgets regardless of type
2025-08-08 15:03:40 -07:00
Vito Sansevero 05a9520436 debug: add comprehensive logging to diagnose autocomplete issues
- Add console logging to JS for resource fetching and widget attachment
- Add debug mode with window.kikoDebug for inspection
- Log Python API endpoint registration and file discovery
- Track widget creation and event handling
- Show first 5 items when loading resources
2025-08-08 15:00:28 -07:00
Vito Sansevero c1fcc4566b fix: improve embedding autocomplete detection and triggers
- Use ComfyUI's native api.getEmbeddings() for proper embedding detection
- Add dedicated /kikotools/autocomplete/loras endpoint for LoRA files
- Improve trigger detection for "embedding:" and "<lora:" patterns
- Context-aware suggestions based on trigger type
- Better insertion logic that maintains correct syntax
- Sort suggestions by relevance (exact match, starts with, alphabetical)
- Fix character matching patterns to include underscores and hyphens
2025-08-08 14:55:55 -07:00
Vito Sansevero 40b91fc44f feat: enhance embedding autocomplete with interactive test panel
- Add debug/test panel to the node with helpful usage hints
- Display counts of available embeddings and LoRAs
- Show sample items and status information
- Include clear instructions for triggering autocomplete
- Update display name with 🫶 branding
- Make node an OUTPUT_NODE to display information
2025-08-08 13:58:45 -07:00
Vito Sansevero 3e7734bbc3 feat: add KikoEmbeddingAutocomplete with settings registry system
- Implement centralized settings registry for all KikoTools
- Create KikoEmbeddingAutocomplete node with backend API
- Add frontend JavaScript autocomplete widget with ComfyUI integration
- Support for embeddings and LoRAs with smart filtering
- Configurable settings in ComfyUI UI with 🫶 branding
- Include keyboard navigation and real-time suggestions
2025-08-08 13:20:09 -07:00
Vito 17400d88f7 Merge pull request #30 from ComfyAssets/feature/kiko-film-grain
feat: add KikoFilmGrain node for realistic film grain effects
2025-08-07 18:50:48 -07:00
Vito Sansevero 0f79601065 feat: add KikoFilmGrain node for realistic film grain effects
- Implement film grain effect with customizable parameters (scale, strength, saturation, toe, seed)
- Use pure PyTorch operations for better GPU utilization (no OpenCV dependencies)
- Apply ITU-R BT.709 color space conversion for accurate grain distribution
- Implement screen blend mode for better highlight preservation
- Add channel-specific weighting matching real film characteristics (3x blue, 2x red)
- Preserve alpha channel when present
- Add comprehensive test suite (20 tests covering all functionality)
- Include documentation and example workflow
- Register node under ComfyAssets/image category

Improvements over reference implementation:
- More efficient memory management avoiding numpy/OpenCV conversions
- Better grain mixing algorithm with proper color science
- Improved performance through PyTorch-native operations
2025-08-07 18:42:37 -07:00
Vito Sansevero df60457929 chore: bump version to 1.0.12 in pyproject.toml 2025-08-07 08:01:18 -07:00
Vito 5d0e8194a1 Merge pull request #29 from ComfyAssets/fix/display-nodes-scrolling
Fix/display nodes scrolling
2025-08-07 08:00:50 -07:00
Vito Sansevero 218a208bf0 feat(display): add copy buttons as ComfyUI widgets
- Add copy buttons using addCustomWidget for proper integration
- Display Text: separate copy buttons for positive/negative prompts
- Display Any: single copy button for entire value
- Visual feedback shows "✓ Copied\!" for 1.5 seconds
- Buttons work with ComfyUI's widget system

Restores copy functionality while maintaining scrolling fixes
2025-08-07 07:25:21 -07:00
Vito Sansevero a7b612d799 fix(display): handle DOM not ready for appendChild operations
- Add proper null checks before appendChild calls
- Use requestAnimationFrame to ensure DOM elements exist
- Check both inputEl and parentNode before adding buttons
- Prevent "Cannot read properties of null" errors on load

Fixes workflow loading errors with display nodes
2025-08-07 07:05:09 -07:00
Vito Sansevero df3d4c19cb fix(display): use ComfyUI's native STRING widgets for proper scrolling
- Replace custom draw implementations with ComfyWidgets["STRING"]
- Fix cursor display issues (was showing + instead of text cursor)
- Enable native scrolling behavior for both Display Text and Display Any
- Maintain all existing features (copy buttons, split view for prompts)
- Add proper widget cleanup and state management
- Reference: ShowText implementation from ComfyUI-Custom-Scripts

Fixes scrolling and cursor issues in display nodes
2025-08-07 06:45:35 -07:00
Vito Sansevero 938d0d93de chore(pyproject): bump version to 1.0.11 2025-08-07 06:35:34 -07:00
Vito e65bf123b5 Merge pull request #28 from ComfyAssets/feat/add-comfyui-essentials-nodes
Feat/add comfyui essentials nodes
2025-08-07 06:25:36 -07:00
Vito Sansevero b348273d3a fix(tests): update GitHub Actions tests for emoji categories
- Fix RETURN_TYPES assertion for Sampler Combo (SCHEDULERS is a list)
- Update all CATEGORY assertions to support emoji-based categories
- Change from exact match to startswith('ComfyAssets/') for flexibility
- All tests now properly validate the new category system
2025-08-07 06:16:33 -07:00
Vito Sansevero cc1ffe2605 docs: correct emoji categories in README
- Update categories to match actual implementation:
  - Seed History: 🌱 Seeds (not 🎯 Advanced)
  - Sampler Combo: 🌀 Samplers (not ⚙️ Sampling)
  - Display Text/Any: 👁️ Display (not 📋 Text/🔍 Debug)
- Correct total unique categories count to 8
- All 16 nodes now correctly documented with their actual categories
2025-08-07 06:10:21 -07:00
Vito Sansevero ef30413e12 docs: add LoRA testing workflow screenshot and examples
- Add screenshot for xyz_helpers_lora_testing workflow
- Include LoRA testing workflow in Common Workflows section
- Show real-world usage with strength ranges and combinatorial mode
2025-08-07 05:45:10 -07:00
Vito Sansevero 5485aa8c19 feat(xyz-helpers): add ComfyUI_essentials nodes adaptation
BREAKING CHANGE: Node categories now use emoji-based organization

Add 6 new xyz-helper nodes adapted from comfyui-essentials-nodes:
- FluxSamplerParams: FLUX-optimized parameter generator with batch support
- LoRAFolderBatch: Batch process multiple LoRAs from folders
- PlotParameters: Visualize parameter effects with graphs
- SamplerSelectHelper: Intelligent sampler selection with recommendations
- SchedulerSelectHelper: Optimal scheduler selection for samplers
- TextEncodeSamplerParams: Combined text encoding and parameter management

Changes:
- Port and enhance nodes from comfyui-essentials (now in maintenance mode)
- Add comprehensive documentation with attribution to original author (cubiq)
- Create example workflows for xyz-helpers tools
- Update all node categories to use emoji-based organization
- Fix all unit tests to pass with new category system
- Update README with xyz-helpers section and attribution

Attribution: xyz-helpers adapted from github.com/cubiq/ComfyUI_essentials

All tests passing (318 pass, 2 skip)
2025-08-07 05:41:23 -07:00
Vito Sansevero a8ee5930ff chore: bump version to 1.0.10 in pyproject.toml 2025-08-04 10:36:11 -07:00
Vito Sansevero 9fd80793db feat(init): add ImageScaleDownByNode support 2025-08-04 10:35:54 -07:00
Vito Sansevero 972c487dd4 Add image_scale_down_by tool and display_any.js
- Add new image_scale_down_by tool for downscaling images/latents
- Add display_any.js web component for node display
- Include comprehensive unit tests for the new tool
2025-08-04 07:17:55 -07:00
Vito 1a3efd3802 Merge pull request #26 from ComfyAssets/alert-autofix-8
Potential fix for code scanning alert no. 8: Workflow does not contain permissions
2025-08-02 08:48:43 -07:00
VitoandCopilot Autofix powered by AI 34f54e515d Potential fix for code scanning alert no. 8: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-08-02 08:38:37 -07:00
Vito c844abea51 Merge pull request #25 from ComfyAssets/alert-autofix-1
Potential fix for code scanning alert no. 1: Workflow does not contain permissions
2025-08-02 08:22:06 -07:00
VitoandCopilot Autofix powered by AI 404a1efd61 Potential fix for code scanning alert no. 1: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-08-02 08:14:03 -07:00
Vito 4ae514dacf Merge pull request #24 from ComfyAssets/alert-autofix-10
Potential fix for code scanning alert no. 10: Workflow does not contain permissions
2025-08-02 08:12:04 -07:00
VitoandCopilot Autofix powered by AI 5efae8eeb8 Potential fix for code scanning alert no. 10: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2025-08-02 08:00:10 -07:00
Vito 85288c8fd8 Create SECURITY.md 2025-08-02 07:54:21 -07:00
Vito 7a97f7c2bc Create CODE_OF_CONDUCT.md 2025-08-02 07:50:25 -07:00
Vito a4692a286c Merge pull request #22 from ComfyAssets/dependabot/github_actions/softprops/action-gh-release-2
build(deps): bump softprops/action-gh-release from 1 to 2
2025-08-02 07:48:07 -07:00
Vito 72a3fea3cb Merge pull request #23 from ComfyAssets/dependabot/github_actions/actions/cache-4
build(deps): bump actions/cache from 3 to 4
2025-08-02 07:47:44 -07:00
Vito d5d4145a04 Merge pull request #21 from ComfyAssets/dependabot/github_actions/actions/setup-python-5
build(deps): bump actions/setup-python from 4 to 5
2025-08-02 07:46:49 -07:00
dependabot[bot] 0e288dd109 build(deps): bump actions/cache from 3 to 4
Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-02 14:43:46 +00:00
dependabot[bot] c4882f894e build(deps): bump softprops/action-gh-release from 1 to 2
Bumps [softprops/action-gh-release](https://github.com/softprops/action-gh-release) from 1 to 2.
- [Release notes](https://github.com/softprops/action-gh-release/releases)
- [Changelog](https://github.com/softprops/action-gh-release/blob/master/CHANGELOG.md)
- [Commits](https://github.com/softprops/action-gh-release/compare/v1...v2)

---
updated-dependencies:
- dependency-name: softprops/action-gh-release
  dependency-version: '2'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-02 14:43:43 +00:00
dependabot[bot] 6cbe6e5ae6 build(deps): bump actions/setup-python from 4 to 5
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-02 14:43:40 +00:00
Vito Sansevero df20afb83e style(dependabot): fix indentation in config file 2025-08-02 07:42:56 -07:00
Vito 7d63e11e18 Create dependabot.yml 2025-08-02 07:40:43 -07:00
Vito a8364b5c57 Merge pull request #20 from ComfyAssets/feature/add-tools-toc
docs: add tools table of contents to README
2025-08-02 07:35:13 -07:00
Vito Sansevero 332a74225d docs: add tools table of contents to README
- Add comprehensive TOC table under Current Tools section
- Include tool names with emojis as clickable links
- Add brief descriptions for each tool
- Categorize tools by functionality (Image Processing, Debugging, etc.)
- Improve navigation and tool discovery for users
2025-08-02 07:31:06 -07:00
Vito d757b623d6 Merge pull request #19 from ComfyAssets/feature/add-readme-screenshots
docs: add screenshots and complete documentation for all nodes
2025-08-02 07:24:25 -07:00
Vito Sansevero 64e844ec42 style: fix code formatting with black
- Add missing newlines at end of files
- Fix whitespace and indentation issues
- Format long function calls properly
2025-08-02 07:20:40 -07:00
Vito Sansevero 3ed188d63f docs: add screenshots and complete documentation for all nodes
- Add PNG screenshots for 7 nodes in README.md
- Create missing documentation files (display_text.md, kiko_save_image.md)
- Update gemini_prompt.md with new features (model refresh, enhanced SDXL)
- Add missing example workflow JSON files for 5 nodes
- Include Display Any and Image to Multiple Of nodes in README
- Update node count from 8 to 10 in stats section
2025-08-02 07:03:46 -07:00
Vito b9cc9f295d Merge pull request #18 from ComfyAssets/feature/display-text-and-gemini-improvements
feat: add Display Text node with smart formatting and enhance Gemini …
2025-08-01 21:26:54 -07:00
Vito Sansevero 271cd020c1 merge: resolve conflicts with main branch model management improvements 2025-08-01 16:35:39 -07:00
Vito Sansevero e34807855a feat: add Display Text node with smart formatting and enhance Gemini with model refresh
Display Text improvements:
- Add new DisplayText node with intelligent prompt detection and split view
- Implement text wrapping that reflows when node is resized
- Add scrollable content with mouse wheel support and visual indicators
- Include always-visible copy button with visual feedback for easy prompt copying
- Auto-detect SDXL-style prompts and display in side-by-side format
- Strip prompt labels when copying for direct use in workflows

Gemini model refresh functionality:
- Add refresh button to fetch latest available Gemini models dynamically
- Implement model caching system with persistent storage
- Support for Gemini 2.0 and 2.5 models with automatic detection
- Enhanced SDXL prompt template with improved layered structure
- Better error handling and status feedback for model operations

Documentation and version updates:
- Update README with comprehensive Display Text and Gemini feature descriptions
- Add detailed usage examples and workflow patterns
- Bump version to 1.0.9 in pyproject.toml
- Update stats to reflect 8 total nodes and new AI integration features
2025-08-01 14:57:56 -07:00
Vito d32e18f844 Merge pull request #17 from ComfyAssets/feature/gemini-dynamic-models
Feature/gemini dynamic models
2025-08-01 13:36:04 -07:00
Vito Sansevero 228b74ae5e chore: add flake8 complexity exceptions for Gemini module 2025-08-01 13:30:38 -07:00
Vito Sansevero 6197b482df feat: implement dynamic model fetching for Gemini node
- Add dynamic model fetching with 24-hour caching
- Update prompt templates based on 2025 best practices:
  - FLUX: Natural language descriptions
  - SDXL: Simplified with natural language support
  - Danbooru: Strict tagging conventions
  - Video: Optimized for WAN 2.2
- Add cache file to .gitignore
- Handle missing API key gracefully on initial load
2025-08-01 13:30:31 -07:00
Vito Sansevero f62129afda fix: update pre-commit config to use line-length 88
- Update black line-length from 127 to 88 to match pyproject.toml
- Update flake8 max-line-length from 127 to 88 for consistency
- Remove broken pre-commit hook that was referencing non-existent pyenv
2025-08-01 11:02:44 -07:00
Vito Sansevero 8d5065c975 chore: bump version to 1.0.8 in pyproject.toml 2025-08-01 10:58:57 -07:00
Vito 2d27c32bfd Merge pull request #16 from ComfyAssets/feature/display-any
Feature/display any
2025-08-01 10:51:23 -07:00
Vito Sansevero 3ecab5ac08 fix: implement proper AnyType class for wildcard input matching
- Add AnyType class that inherits from str and overrides __ne__ to always return False
- This matches ComfyUI's type checking system for wildcard inputs
- Based on implementation from ComfyUI_essentials
- Add comprehensive tests for AnyType behavior
- Fixes type mismatch errors when connecting any node type
2025-08-01 10:47:20 -07:00
Vito Sansevero 70592114f9 fix: correct wildcard input type syntax for DisplayAny node
- Change from ('*', {}) to ('*') for proper ComfyUI wildcard type
- Update test to match the corrected syntax
- Fixes 'Return type mismatch' error when connecting nodes
2025-08-01 10:47:20 -07:00
Vito Sansevero 407fc4ca7b feat: add DisplayAny node for debugging and inspection
- Universal input acceptance for any data type
- Two display modes: raw value and tensor shape
- Extracts tensor shapes from nested structures
- Comprehensive unit tests with 100% coverage
- Full documentation with usage examples
- OUTPUT_NODE for UI display functionality
2025-08-01 10:47:20 -07:00
Vito cb7d5246f9 Merge pull request #15 from ComfyAssets/chore/housekeeping
Chore/housekeeping
2025-08-01 10:31:39 -07:00
Vito 9829fc001d Merge pull request #14 from ComfyAssets/fix/black-config-main
fix: update black line-length to 88 and reformat codebase
2025-08-01 09:50:10 -07:00
Vito Sansevero e84ec6721c fix: update black line-length to 88 and reformat codebase
- Update pyproject.toml to use black's default line-length of 88
- This matches what the CI workflow expects (black --check without args)
- Reformat all Python files to comply with the new line length
- This will prevent CI failures due to formatting discrepancies
2025-08-01 09:45:43 -07:00
Vito 80fac8e544 Merge pull request #12 from ComfyAssets/feature/gemini-prompt
feat: add Gemini Prompt Engineer node
2025-08-01 09:45:09 -07:00
Vito Sansevero efc079a95b fix: reformat with black default settings (88 char) to match CI 2025-08-01 09:41:21 -07:00
Vito Sansevero bbd239cbd6 fix: apply black formatting with line-length 127 for CI compliance 2025-08-01 09:41:21 -07:00
Vito Sansevero 589fbf3568 fix: remove trailing whitespace in gemini_prompt node.py 2025-08-01 09:41:21 -07:00
Vito Sansevero c595cabaa0 chore: trigger CI 2025-08-01 09:41:21 -07:00
Vito Sansevero ca504d5f74 fix: code formatting for Gemini prompt node
- Fix missing newlines at end of files
- Apply black formatting
- Remaining non-critical warnings for long lines in prompts
2025-08-01 09:41:21 -07:00
Vito Sansevero f559fe220e feat: add Gemini Prompt Engineer node
- Add GeminiPromptNode for AI-powered prompt engineering
- Integrates with Google's Gemini API for prompt generation
- Includes various prompt templates and generation modes
- Add comprehensive tests and documentation
- Register node in ComfyAssets category
2025-08-01 09:41:21 -07:00
Vito Sansevero 90c1aa402d Merge remote-tracking branch 'origin/main' into chore/housekeeping 2025-08-01 09:37:01 -07:00
Vito 932e30ade0 Merge pull request #13 from ComfyAssets/feature/image-to-multiple-of
Feature/image to multiple of
2025-08-01 09:34:25 -07:00
Vito Sansevero f9540bd984 chore: update black line-length to 88 to match CI configuration 2025-08-01 09:30:16 -07:00
Vito Sansevero a8af833c31 chore: trigger CI 2025-08-01 09:10:28 -07:00
Vito Sansevero 005c3bdf65 fix: code formatting for Image to Multiple Of node
- Apply black formatting
- Remove unused torch import from logic.py
- All critical linting issues resolved
2025-08-01 09:01:39 -07:00
Vito 67a59a0d3b Merge pull request #11 from ComfyAssets/chore/housekeeping
chore: project housekeeping and configuration updates
2025-08-01 08:53:03 -07:00
Vito Sansevero bb5653fc0e fix: resolve flake8 linting errors in example.py
- Remove unused variable 'temp' assignment
- Remove unused exception variable assignments
- All flake8 checks now pass
2025-08-01 08:43:22 -07:00
Vito Sansevero ab23992c29 chore: project housekeeping and configuration updates
- Add code quality tools: flake8, mypy, black, pre-commit
- Add .gitattributes for line ending consistency
- Add .secrets.baseline for secret scanning
- Update GitHub workflows for better CI/CD
- Update documentation formatting and examples
- Add CLAUDE.md for AI assistant guidance
- Add scripts directory for automation tools
- Update project configuration in pyproject.toml
- Improve type hints and code formatting across all modules
- Update test configurations and fixtures
2025-08-01 08:35:08 -07:00
Vito Sansevero 9007b10d42 feat: add Image to Multiple Of node
- Add ImageToMultipleOfNode for image dimension adjustment
- Ensures image dimensions are multiples of specified values
- Supports both padding and cropping modes
- Useful for model-specific dimension requirements
- Add tests and documentation
- Register node in ComfyAssets category
2025-08-01 08:28:10 -07:00
Vito b71bfa8d4e Merge pull request #10 from ComfyAssets/fix-samplers
Fix samplers
2025-07-26 14:18:48 -07:00
Vito Sansevero c1128addc7 chore: bump version to 1.0.7 in pyproject.toml 2025-07-26 14:16:06 -07:00
Vito Sansevero a49071f824 fix(resolution_calculator): update scale factor tooltip 2025-07-26 14:15:39 -07:00
Vito Sansevero bbdd27f498 fix(ci): correct return type in tests.yml configuration 2025-07-23 13:56:57 -07:00
Vito Sansevero 22f62bf7b4 test: Update test assertions for sampler combo node 2025-07-23 13:56:45 -07:00
Vito Sansevero 4ff6067dad refactor(compact_node): update sampler return type 2025-07-23 13:29:46 -07:00
Vito Sansevero ad13e66506 refactor(node): update sampler handling logic 2025-07-23 13:29:32 -07:00
Vito Sansevero b16f6f40bd style(logic): fix whitespace issues in logic.py 2025-07-21 08:23:18 -07:00
Vito Sansevero 6dfa66963b feat: Add subfolder support in image URL handling 2025-07-21 08:20:27 -07:00
Vito Sansevero ab016e0903 feat(logic): add subfolder info to enhanced data 2025-07-21 08:20:17 -07:00
Vito Sansevero f4228a850c refactor(logic): improve path handling in image saving 2025-07-21 07:56:13 -07:00
Vito Sansevero 79042b78d2 chore: bump version to 1.0.5 in pyproject.toml 2025-06-28 09:31:40 -07:00
Vito Sansevero 0c4e59c4e9 test: Remove unused imports from test file 2025-06-28 09:14:45 -07:00
Vito Sansevero 4a0a206d61 refactor(node): use helper methods for tensor validation 2025-06-28 09:14:34 -07:00
Vito Sansevero 8e0d4485bd style: Remove unused imports in node.py 2025-06-28 09:14:23 -07:00
Vito Sansevero 92a3b1db4e style: Remove unused import 'Any' 2025-06-28 09:13:03 -07:00
Vito Sansevero ab628b1bf2 style: Remove unused import 'os' 2025-06-28 09:11:49 -07:00
Vito Sansevero 7e712a17d9 docs: Add Kiko Save Image section to README.md 2025-06-28 09:11:42 -07:00
Vito Sansevero 269fb2ba80 ci: add checks for KikoSaveImageNode imports 2025-06-28 09:11:36 -07:00
Vito Sansevero e17fdddcd7 refactor(tests/ui): Remove 'subfolder' support 2025-06-28 08:55:25 -07:00
Vito Sansevero 5d1f01e6cb refactor(node): replace 'subfolder' with 'popup' 2025-06-28 08:54:46 -07:00
Vito Sansevero b3b8826044 refactor(logic): Rename 'subfolder' to 'popup' parameter 2025-06-28 08:54:35 -07:00
Vito Sansevero 9dbb1f749d chore: bump version to 1.0.4 in pyproject.toml 2025-06-27 21:06:30 -07:00
Vito Sansevero 682f2b0a47 feat(ui): Add KikoSaveImage UI enhancements 2025-06-27 21:06:00 -07:00
Vito Sansevero 1233cf693e test(kiko_save_image): add unit tests for save image tool 2025-06-27 21:05:52 -07:00
Vito Sansevero 32d44a282f feat(kiko_save_image): add new image saving tool 2025-06-27 21:05:42 -07:00
Vito Sansevero bb79c7434f feat(init): add KikoSaveImageNode to tools and mappings 2025-06-27 21:05:25 -07:00
Vito Sansevero bd8c0a42bc fix: handle import error for testing environment 2025-06-27 21:05:14 -07:00
Vito Sansevero 5d9e71dc7b chore: bump version to 1.0.3 in pyproject.toml 2025-06-20 08:03:48 -07:00
Vito 321d89dcc4 Merge pull request #9 from ComfyAssets/latent-batch
style: Add blank lines for better readability
2025-06-20 08:02:57 -07:00
Vito Sansevero cd77d06ac9 style: Add blank lines for better readability 2025-06-20 07:50:49 -07:00
Vito d23ff34b27 Merge pull request #8 from ComfyAssets/latent-batch
Latent batch
2025-06-19 12:00:57 -07:00
Vito Sansevero cc725d27f6 chore: bump version to 1.0.2 in pyproject.toml 2025-06-19 11:56:26 -07:00
Vito Sansevero 4c3d3958d6 docs: Add Empty Latent Batch documentation 2025-06-19 11:56:05 -07:00
Vito Sansevero 2c992b5c97 feat(empty-latent-batch): add preset & batch processing 2025-06-19 11:55:51 -07:00
Vito Sansevero 8628bc39bb feat(init): add EmptyLatentBatchNode support 2025-06-19 10:22:33 -07:00
Vito Sansevero 3654867a21 feat(empty_latent_batch): add empty latent batch tool 2025-06-19 10:22:05 -07:00
Vito Sansevero 85af1b38f9 test: Add unit tests for EmptyLatentBatch features 2025-06-19 10:21:45 -07:00
Vito 03189afd85 Merge pull request #7 from ComfyAssets/version
Version
2025-06-16 18:32:40 -07:00
Vito Sansevero 69db6e12b4 feat(makefile): add virtualenv setup and commands 2025-06-16 18:29:16 -07:00
Vito Sansevero fb9c313724 feat(init): Add version parsing from pyproject.toml 2025-06-16 18:29:08 -07:00
Vito Sansevero ffae4e9f21 chore: update version to 1.0.1 in pyproject.toml 2025-06-16 18:28:58 -07:00
Vito 24b257ea6d Merge pull request #6 from ComfyAssets/registry
build(ci): add publish workflow and dependencies
2025-06-16 06:49:45 -07:00
351 changed files with 47835 additions and 1707 deletions
+29
View File
@@ -0,0 +1,29 @@
# Files excluded from the Comfy Registry archive (gitignore syntax).
# Everything here is development-only; the published node needs
# __init__.py, kikotools/, web/, examples/, README, LICENSE, pyproject.toml.
# Test suite and tooling
tests/
scripts/
.github/
Makefile
pytest.ini
mypy.ini
.flake8
.pre-commit-config.yaml
.secrets.baseline
requirements-dev.txt
# Reference / scratch code not imported by the node
example.py
# Agent and contributor docs
CLAUDE.md
CONTRIBUTING.md
CODE_OF_CONDUCT.md
tools/
.ci-comfyui/
coverage/
TESTING.md
AGENTS.md
+37
View File
@@ -0,0 +1,37 @@
[flake8]
max-line-length = 127
max-complexity = 10
exclude =
node_modules,
.git,
__pycache__,
.mypy_cache,
.pytest_cache,
venv,
env,
build,
dist,
*.egg-info,
.tox,
referance
ignore =
# W503: line break before binary operator (conflicts with Black)
W503,
# E203: whitespace before ':' (conflicts with Black)
E203,
# E501: line too long (we use max-line-length)
E501
per-file-ignores =
# Allow unused imports in __init__.py files
__init__.py:F401,F403
# Allow assertions in tests
tests/*:S101
# Allow higher complexity for Gemini prompt module
kikotools/tools/gemini_prompt/logic.py:C901
kikotools/tools/gemini_prompt/models.py:C901
kikotools/tools/gemini_prompt/node.py:C901
# Statistics
count = True
statistics = True
+41
View File
@@ -0,0 +1,41 @@
# Auto detect text files and perform LF normalization
* text=auto
# Python files
*.py text eol=lf
*.pyi text eol=lf
# Configuration files
*.json text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
*.toml text eol=lf
*.ini text eol=lf
*.cfg text eol=lf
# Documentation
*.md text eol=lf
*.rst text eol=lf
*.txt text eol=lf
# Scripts
*.sh text eol=lf
*.bash text eol=lf
# Git files
.gitignore text eol=lf
.gitattributes text eol=lf
# ComfyUI specific
*.workflow text eol=lf
# Binary files
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.webp binary
*.safetensors binary
*.ckpt binary
*.pt binary
*.pth binary
+1 -1
View File
@@ -45,4 +45,4 @@ Paste any error messages or stack traces here
If possible, attach the ComfyUI workflow file (.json) that reproduces the issue.
**Additional context**
Add any other context about the problem here.
Add any other context about the problem here.
+2 -2
View File
@@ -37,7 +37,7 @@ Describe how the tool should process inputs and generate outputs.
**Model Compatibility:**
- [ ] SDXL optimized
- [ ] FLUX optimized
- [ ] FLUX optimized
- [ ] General purpose
- [ ] Specific model requirements: [describe]
@@ -64,4 +64,4 @@ Are there existing ComfyUI nodes that do something similar? How would this be di
- [ ] Yes, I can help with implementation
- [ ] Yes, I can help with testing
- [ ] Yes, I can help with documentation
- [ ] No, but I'd be happy to test it
- [ ] No, but I'd be happy to test it
+16
View File
@@ -0,0 +1,16 @@
{
"required_status_checks": {
"strict": true,
"contexts": [
"py3.10 on ubuntu-latest", "py3.11 on ubuntu-latest", "py3.12 on ubuntu-latest", "py3.13 on ubuntu-latest",
"py3.10 on windows-latest", "py3.11 on windows-latest", "py3.12 on windows-latest", "py3.13 on windows-latest",
"py3.10 on macos-latest", "py3.11 on macos-latest", "py3.12 on macos-latest", "py3.13 on macos-latest",
"coverage gate (85%)", "frontend syntax", "frontend workflows and coverage", "real ComfyUI compatibility", "code-quality"
]
},
"enforce_admins": true,
"required_pull_request_reviews": null,
"restrictions": null,
"allow_force_pushes": false,
"allow_deletions": false
}
+14
View File
@@ -0,0 +1,14 @@
version: 2
updates:
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/tools/testing"
schedule:
interval: "weekly"
+116 -44
View File
@@ -1,4 +1,6 @@
name: Code Quality
permissions:
contents: read
on:
push:
@@ -11,15 +13,15 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Set up Python 3.10
uses: actions/setup-python@v4
uses: actions/setup-python@v7
with:
python-version: '3.10'
- name: Cache pip dependencies
uses: actions/cache@v3
uses: actions/cache@v6
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-quality-${{ hashFiles('**/requirements-dev.txt') }}
@@ -29,6 +31,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements-dev.txt
- name: Format check with Black
@@ -59,7 +62,7 @@ jobs:
import sys
import os
sys.path.insert(0, os.getcwd())
# Test that all imports work correctly
try:
from kikotools import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
@@ -67,70 +70,82 @@ jobs:
except ImportError as e:
print(f'Warning: Package-level imports failed: {e}')
# This is expected since we don't have ComfyUI installed
# Test individual module imports
from kikotools.base import ComfyAssetsBaseNode
from kikotools.tools.resolution_calculator import ResolutionCalculatorNode
from kikotools.tools.resolution_calculator.logic import extract_dimensions
from kikotools.tools.resolution_calculator.node import ResolutionCalculatorNode as NodeClass
# Test Width Height Selector imports
from kikotools.tools.width_height_selector import WidthHeightSelectorNode
from kikotools.tools.width_height_selector.logic import get_preset_dimensions
from kikotools.tools.width_height_selector.presets import PRESET_OPTIONS, PRESET_METADATA
# Test Sampler Combo imports
from kikotools.tools.sampler_combo import SamplerComboNode
from kikotools.tools.sampler_combo.logic import get_sampler_combo, SAMPLERS, SCHEDULERS
# Test Seed History imports
from kikotools.tools.seed_history import SeedHistoryNode
from kikotools.tools.seed_history.logic import generate_random_seed, validate_seed_value
# Test Kiko Save Image imports
from kikotools.tools.kiko_save_image import KikoSaveImageNode
from kikotools.tools.kiko_save_image.logic import process_image_batch, validate_save_inputs
# Test Model Downloader imports
from kikotools.tools.model_downloader import ModelDownloaderNode
from kikotools.tools.model_downloader.detector import URLDetector, DownloaderType
from kikotools.tools.model_downloader.base import BaseDownloader
# Test Text Input imports
from kikotools.tools.text_input import TextInputNode
print('✓ All module imports successful')
"
- name: Check code style consistency
run: |
echo "Checking code style consistency..."
# Check for consistent naming
find kikotools/ -name "*.py" -exec grep -l "class.*Node" {} \; | while read file; do
if ! grep -q "ComfyAssetsBaseNode" "$file" && ! grep -q "class ComfyAssetsBaseNode" "$file"; then
echo "Checking $file for ComfyUI node inheritance..."
fi
done
# Check for proper docstrings
python -c "
import ast
import os
def check_docstrings(filepath):
with open(filepath, 'r') as f:
tree = ast.parse(f.read())
for node in ast.walk(tree):
if isinstance(node, (ast.FunctionDef, ast.ClassDef)):
if not ast.get_docstring(node) and not node.name.startswith('_'):
print(f'Warning: {filepath}:{node.lineno} - {node.name} missing docstring')
for root, dirs, files in os.walk('kikotools'):
for file in files:
if file.endswith('.py') and not file.startswith('__'):
filepath = os.path.join(root, file)
check_docstrings(filepath)
print('✓ Docstring check completed')
"
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Set up Python 3.10
uses: actions/setup-python@v4
uses: actions/setup-python@v7
with:
python-version: '3.10'
@@ -147,7 +162,7 @@ jobs:
- name: Check for hardcoded secrets
run: |
echo "Checking for potential secrets..."
# Check for common secret patterns
if grep -r -i "password\|secret\|key\|token" kikotools/ --include="*.py" | grep -v "# " | grep -v "def " | grep -v "class "; then
echo "Warning: Potential hardcoded secrets found"
@@ -158,16 +173,17 @@ jobs:
architecture:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Set up Python 3.10
uses: actions/setup-python@v4
uses: actions/setup-python@v7
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements-dev.txt
- name: Check architecture compliance
@@ -176,82 +192,138 @@ jobs:
import sys
import os
sys.path.insert(0, os.getcwd())
print('Checking architecture compliance...')
# Test separation of concerns
from kikotools.tools.resolution_calculator import logic, node
# Logic module should not import node-specific things
import inspect
logic_source = inspect.getsource(logic)
if 'ComfyUI' in logic_source and 'INPUT_TYPES' not in logic_source:
print('⚠️ Warning: Logic module contains ComfyUI-specific code')
else:
print('✓ Logic module properly separated')
# Node module should inherit from base
from kikotools.tools.resolution_calculator.node import ResolutionCalculatorNode
from kikotools.base import ComfyAssetsBaseNode
if issubclass(ResolutionCalculatorNode, ComfyAssetsBaseNode):
print('✓ Node properly inherits from base class')
else:
print('❌ Node does not inherit from base class')
sys.exit(1)
# Check that nodes have proper ComfyUI interface
required_attrs = ['INPUT_TYPES', 'RETURN_TYPES', 'RETURN_NAMES', 'FUNCTION', 'CATEGORY']
# Test Resolution Calculator Node
for attr in required_attrs:
if not hasattr(ResolutionCalculatorNode, attr):
print(f'❌ ResolutionCalculatorNode missing required attribute: {attr}')
sys.exit(1)
# Test Width Height Selector Node
from kikotools.tools.width_height_selector.node import WidthHeightSelectorNode
if issubclass(WidthHeightSelectorNode, ComfyAssetsBaseNode):
print('✓ WidthHeightSelectorNode properly inherits from base class')
else:
print('❌ WidthHeightSelectorNode does not inherit from base class')
sys.exit(1)
for attr in required_attrs:
if not hasattr(WidthHeightSelectorNode, attr):
print(f'❌ WidthHeightSelectorNode missing required attribute: {attr}')
sys.exit(1)
# Test Sampler Combo Node
from kikotools.tools.sampler_combo.node import SamplerComboNode
if issubclass(SamplerComboNode, ComfyAssetsBaseNode):
print('✓ SamplerComboNode properly inherits from base class')
else:
print('❌ SamplerComboNode does not inherit from base class')
sys.exit(1)
for attr in required_attrs:
if not hasattr(SamplerComboNode, attr):
print(f'❌ SamplerComboNode missing required attribute: {attr}')
sys.exit(1)
# Test Seed History Node
from kikotools.tools.seed_history.node import SeedHistoryNode
if issubclass(SeedHistoryNode, ComfyAssetsBaseNode):
print('✓ SeedHistoryNode properly inherits from base class')
else:
print('❌ SeedHistoryNode does not inherit from base class')
sys.exit(1)
for attr in required_attrs:
if not hasattr(SeedHistoryNode, attr):
print(f'❌ SeedHistoryNode missing required attribute: {attr}')
sys.exit(1)
# Test Kiko Save Image Node
from kikotools.tools.kiko_save_image.node import KikoSaveImageNode
if issubclass(KikoSaveImageNode, ComfyAssetsBaseNode):
print('✓ KikoSaveImageNode properly inherits from base class')
else:
print('❌ KikoSaveImageNode does not inherit from base class')
sys.exit(1)
# KikoSaveImage is an output node, so it doesn't have RETURN_TYPES/RETURN_NAMES
save_required_attrs = ['INPUT_TYPES', 'FUNCTION', 'CATEGORY']
for attr in save_required_attrs:
if not hasattr(KikoSaveImageNode, attr):
print(f'❌ KikoSaveImageNode missing required attribute: {attr}')
sys.exit(1)
# Check that it's properly marked as an output node
if not hasattr(KikoSaveImageNode, 'OUTPUT_NODE') or not KikoSaveImageNode.OUTPUT_NODE:
print('❌ KikoSaveImageNode missing OUTPUT_NODE = True')
sys.exit(1)
# Test Model Downloader Node
from kikotools.tools.model_downloader.node import ModelDownloaderNode
if issubclass(ModelDownloaderNode, ComfyAssetsBaseNode):
print('✓ ModelDownloaderNode properly inherits from base class')
else:
print('❌ ModelDownloaderNode does not inherit from base class')
sys.exit(1)
# ModelDownloader is an output node, so it doesn't have RETURN_TYPES/RETURN_NAMES
download_required_attrs = ['INPUT_TYPES', 'FUNCTION', 'CATEGORY']
for attr in download_required_attrs:
if not hasattr(ModelDownloaderNode, attr):
print(f'❌ ModelDownloaderNode missing required attribute: {attr}')
sys.exit(1)
# Check that it's properly marked as an output node
if not hasattr(ModelDownloaderNode, 'OUTPUT_NODE') or not ModelDownloaderNode.OUTPUT_NODE:
print('❌ ModelDownloaderNode missing OUTPUT_NODE = True')
sys.exit(1)
# Test Text Input Node
from kikotools.tools.text_input.node import TextInputNode
if issubclass(TextInputNode, ComfyAssetsBaseNode):
print('✓ TextInputNode properly inherits from base class')
else:
print('❌ TextInputNode does not inherit from base class')
sys.exit(1)
for attr in required_attrs:
if not hasattr(TextInputNode, attr):
print(f'❌ TextInputNode missing required attribute: {attr}')
sys.exit(1)
print('✓ All architecture checks passed for all tools')
"
@@ -259,22 +331,22 @@ jobs:
run: |
python -c "
import os
# Count test files vs implementation files
test_files = 0
impl_files = 0
for root, dirs, files in os.walk('tests'):
test_files += len([f for f in files if f.startswith('test_') and f.endswith('.py')])
for root, dirs, files in os.walk('kikotools'):
impl_files += len([f for f in files if f.endswith('.py') and not f.startswith('__')])
print(f'Implementation files: {impl_files}')
print(f'Test files: {test_files}')
if test_files >= impl_files * 0.5: # At least 50% test coverage by file count
print('✓ Adequate test file coverage')
else:
print('⚠️ Warning: Low test file coverage')
"
"
+1 -1
View File
@@ -18,7 +18,7 @@ jobs:
if: ${{ github.repository_owner == 'ComfyAssets' }}
steps:
- name: Check out code
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
submodules: true
- name: Publish Custom Node
+32 -54
View File
@@ -1,5 +1,8 @@
name: Release
permissions:
contents: read
on:
push:
tags:
@@ -7,53 +10,28 @@ on:
jobs:
create-release:
outputs:
version: ${{ steps.get_version.outputs.version }}
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Set up Python 3.10
uses: actions/setup-python@v4
uses: actions/setup-python@v7
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements-dev.txt
- name: Run full test suite
run: |
python -c "
import sys
import os
sys.path.insert(0, os.getcwd())
# Run comprehensive tests before release
from kikotools.base import ComfyAssetsBaseNode
from kikotools.tools.resolution_calculator.logic import extract_dimensions, calculate_scaled_dimensions
from kikotools.tools.resolution_calculator.node import ResolutionCalculatorNode
import torch
print('Running pre-release validation...')
# Test all major functionality
node = ResolutionCalculatorNode()
# Test various scenarios
test_cases = [
(torch.randn(1, 512, 512, 3), 2.0),
(torch.randn(1, 1024, 1024, 3), 1.5),
(torch.randn(1, 1216, 832, 3), 1.53), # User scenario
]
for i, (image, scale) in enumerate(test_cases):
width, height = node.calculate_resolution(scale, image=image)
print(f'✓ Test case {i+1}: {image.shape[2]}×{image.shape[1]} → {width}×{height} (scale: {scale})')
assert width % 8 == 0 and height % 8 == 0
print('🎉 All pre-release tests passed!')
"
- name: Run full test suite and coverage gate
run: python -m pytest tests --cov=kikotools --cov-report=term-missing
- name: Extract version from tag
id: get_version
@@ -64,22 +42,22 @@ jobs:
run: |
cat > release_notes.md << 'EOF'
## ComfyUI-KikoTools ${{ steps.get_version.outputs.version }}
### 🎉 What's New
#### Resolution Calculator Tool
- **Smart Input Handling**: Works with both IMAGE and LATENT tensors
- **Model Optimized**: Specific optimizations for SDXL and FLUX models
- **Model Optimized**: Specific optimizations for SDXL and FLUX models
- **Constraint Enforcement**: Automatically ensures dimensions divisible by 8
- **Flexible Scaling**: Supports scale factors from 1.0x to 8.0x
### 📦 Installation
#### ComfyUI Manager
1. Search for "ComfyUI-KikoTools"
2. Click Install
3. Restart ComfyUI
#### Manual Installation
```bash
cd ComfyUI/custom_nodes/
@@ -87,29 +65,29 @@ jobs:
cd ComfyUI-KikoTools
pip install -r requirements-dev.txt
```
### 🚀 Quick Start
Look for **ComfyAssets** nodes in your ComfyUI node browser!
### 📊 Technical Details
- **Nodes**: 1 (Resolution Calculator)
- **Test Coverage**: 100%
- **Python Coverage Gate**: 85% combined statement/branch coverage
- **Python Support**: 3.8+
- **ComfyUI Compatibility**: Latest
### 🐛 Bug Reports
Found an issue? Please report it [here](https://github.com/ComfyAssets/ComfyUI-KikoTools/issues).
---
**Full Changelog**: https://github.com/ComfyAssets/ComfyUI-KikoTools/compare/v0.0.0...${{ steps.get_version.outputs.version }}
EOF
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v3
with:
tag_name: ${{ steps.get_version.outputs.version }}
name: ComfyUI-KikoTools ${{ steps.get_version.outputs.version }}
@@ -128,18 +106,18 @@ jobs:
runs-on: ubuntu-latest
needs: create-release
if: success()
steps:
- name: Community notification placeholder
run: |
echo "🎉 Release ${{ needs.create-release.outputs.version }} created!"
echo "Consider posting to:"
echo "- ComfyUI Discord"
echo "- Reddit r/ComfyUI"
echo "- Reddit r/ComfyUI"
echo "- ComfyUI-Manager database"
echo ""
echo "Release includes:"
echo "- Resolution Calculator tool"
echo "- Complete documentation"
echo "- Example workflows"
echo "- 100% test coverage"
echo "- Enforced Python coverage and frontend regression checks"
+165 -438
View File
@@ -1,5 +1,8 @@
name: Tests
permissions:
contents: read
on:
push:
branches: [main, develop]
@@ -8,467 +11,191 @@ on:
jobs:
test:
runs-on: ubuntu-latest
name: py${{ matrix.python-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
python-version: [3.8, 3.9, "3.10", "3.11", "3.12"]
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
cache: pip
cache-dependency-path: requirements-dev.txt
- name: Cache pip dependencies
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements-dev.txt') }}
restore-keys: |
${{ runner.os }}-pip-
# The default Linux torch wheel bundles CUDA (~2.5 GB); tests only need CPU.
# Windows and macOS default wheels are already CPU-only.
- name: Install CPU torch (Linux)
if: runner.os == 'Linux'
run: |
python -m pip install --upgrade pip
pip install torch --index-url https://download.pytorch.org/whl/cpu
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
- name: Run core functionality tests
run: |
python -c "
import sys
import os
sys.path.insert(0, os.getcwd())
- name: Run tests
run: python -m pytest tests --junitxml=test-results/pytest.xml
# Test imports
from kikotools.base import ComfyAssetsBaseNode
from kikotools.tools.resolution_calculator.logic import extract_dimensions, calculate_scaled_dimensions
from kikotools.tools.resolution_calculator.node import ResolutionCalculatorNode
import torch
print('✓ All imports successful')
# Test base node
assert ComfyAssetsBaseNode.CATEGORY == 'ComfyAssets'
print('✓ Base node tests passed')
# Test dimension extraction
mock_image = torch.randn(1, 1216, 832, 3)
width, height = extract_dimensions(image=mock_image)
assert width == 832 and height == 1216
print('✓ Dimension extraction tests passed')
# Test scaling
new_width, new_height = calculate_scaled_dimensions(832, 1216, 1.5)
assert new_width % 8 == 0 and new_height % 8 == 0
print('✓ Scaling tests passed')
# Test node interface
node = ResolutionCalculatorNode()
result_width, result_height = node.calculate_resolution(1.5, image=mock_image)
assert isinstance(result_width, int) and isinstance(result_height, int)
assert result_width % 8 == 0 and result_height % 8 == 0
print('✓ Node interface tests passed')
# Test user scenario
width, height = node.calculate_resolution(1.53, image=mock_image)
print(f'✓ User scenario test: 832×1216 → {width}×{height} (1.53x scale)')
print('🎉 All tests passed!')
"
- name: Test Width Height Selector
run: |
python -c "
import sys
import os
sys.path.insert(0, os.getcwd())
# Test Width Height Selector imports
from kikotools.tools.width_height_selector.node import WidthHeightSelectorNode
from kikotools.tools.width_height_selector.presets import PRESET_OPTIONS, PRESET_METADATA
from kikotools.tools.width_height_selector.logic import get_preset_dimensions
print('✓ Width Height Selector imports successful')
# Test preset structure
assert len(PRESET_OPTIONS) > 0
assert 'custom' in PRESET_OPTIONS
assert len(PRESET_METADATA) > 0
print('✓ Preset structure tests passed')
# Test node interface
node = WidthHeightSelectorNode()
input_types = node.INPUT_TYPES()
assert 'required' in input_types
assert 'preset' in input_types['required']
assert 'width' in input_types['required']
assert 'height' in input_types['required']
print('✓ Node interface tests passed')
# Test formatted presets
preset_options = input_types['required']['preset'][0]
assert 'custom' in preset_options
formatted_count = len([opt for opt in preset_options if ' - ' in opt and 'MP' in opt])
assert formatted_count > 0
print(f'✓ Found {formatted_count} formatted presets')
# Test dimension calculation
result = node.get_dimensions('1024×1024', 512, 512)
assert result == (1024, 1024)
print('✓ Dimension calculation tests passed')
# Test formatted preset dimensions
formatted_preset = '1024×1024 - 1:1 (1.1MP) - SDXL'
result = node.get_dimensions(formatted_preset, 512, 512)
assert result == (1024, 1024)
print('✓ Formatted preset tests passed')
# Test preset extraction
extracted = node._extract_preset_name(formatted_preset)
assert extracted == '1024×1024'
print('✓ Preset extraction tests passed')
print('🎉 All Width Height Selector tests passed!')
"
- name: Test Sampler Combo
run: |
python -c "
import sys
import os
sys.path.insert(0, os.getcwd())
# Test Sampler Combo imports
from kikotools.tools.sampler_combo.node import SamplerComboNode
from kikotools.tools.sampler_combo.logic import (
get_sampler_combo, validate_sampler_settings, SAMPLERS, SCHEDULERS
)
print('✓ Sampler Combo imports successful')
# Test node interface
node = SamplerComboNode()
input_types = node.INPUT_TYPES()
assert 'required' in input_types
assert 'sampler_name' in input_types['required']
assert 'scheduler' in input_types['required']
assert 'steps' in input_types['required']
assert 'cfg' in input_types['required']
print('✓ Sampler Combo interface tests passed')
# Test return types
assert node.RETURN_TYPES == (SAMPLERS, SCHEDULERS, 'INT', 'FLOAT')
assert node.RETURN_NAMES == ('sampler_name', 'scheduler', 'steps', 'cfg')
assert node.CATEGORY == 'ComfyAssets'
print('✓ Sampler Combo return types tests passed')
# Test sampler combo functionality
result = node.get_sampler_combo('euler', 'normal', 20, 7.0)
assert result == ('euler', 'normal', 20, 7.0)
print('✓ Sampler combo functionality tests passed')
# Test validation
assert validate_sampler_settings('euler', 'normal', 20, 7.0) == True
print('✓ Sampler validation tests passed')
# Test available samplers and schedulers
samplers = node.get_available_samplers()
schedulers = node.get_available_schedulers()
assert len(samplers) > 0
assert len(schedulers) > 0
assert 'euler' in samplers
assert 'normal' in schedulers
print(f'✓ Found {len(samplers)} samplers and {len(schedulers)} schedulers')
print('🎉 All Sampler Combo tests passed!')
"
- name: Test Seed History
run: |
python -c "
import sys
import os
sys.path.insert(0, os.getcwd())
# Test Seed History imports
from kikotools.tools.seed_history.node import SeedHistoryNode
from kikotools.tools.seed_history.logic import (
generate_random_seed, validate_seed_value, sanitize_seed_value
)
print('✓ Seed History imports successful')
# Test node interface
node = SeedHistoryNode()
input_types = node.INPUT_TYPES()
assert 'required' in input_types
assert 'seed' in input_types['required']
print('✓ Seed History interface tests passed')
# Test return types
assert node.RETURN_TYPES == ('INT',)
assert node.RETURN_NAMES == ('seed',)
assert node.CATEGORY == 'ComfyAssets'
print('✓ Seed History return types tests passed')
# Test seed output functionality
result = node.output_seed(12345)
assert result == (12345,)
print('✓ Seed output functionality tests passed')
# Test seed validation
assert validate_seed_value(12345) == True
assert validate_seed_value(-1) == False
print('✓ Seed validation tests passed')
# Test seed generation
new_seed = generate_random_seed()
assert isinstance(new_seed, int)
assert validate_seed_value(new_seed) == True
print('✓ Seed generation tests passed')
# Test seed sanitization
clean_seed = sanitize_seed_value(12345)
assert clean_seed == 12345
print('✓ Seed sanitization tests passed')
# Test node helper methods
assert node.is_seed_in_range(12345) == True
assert node.is_seed_in_range(-1) == False
assert node.get_default_seed() == 12345
print('✓ Seed helper methods tests passed')
print('🎉 All Seed History tests passed!')
"
- name: Test error handling for all tools
run: |
python -c "
import sys
import os
sys.path.insert(0, os.getcwd())
print('=== Testing Error Handling for All Tools ===')
# Test Resolution Calculator error handling
from kikotools.tools.resolution_calculator.node import ResolutionCalculatorNode
res_node = ResolutionCalculatorNode()
try:
res_node.calculate_resolution(2.0) # No input provided
assert False, 'Should have raised ValueError'
except ValueError:
print('✓ Resolution Calculator error handling test passed')
try:
res_node.calculate_resolution(0.0) # Invalid scale
assert False, 'Should have raised ValueError'
except ValueError:
print('✓ Resolution Calculator scale factor validation test passed')
# Test Width Height Selector error handling
from kikotools.tools.width_height_selector.node import WidthHeightSelectorNode
wh_node = WidthHeightSelectorNode()
# Test invalid preset fallback
result = wh_node.get_dimensions('invalid_preset', 800, 600)
assert result == (800, 600) # Should fallback to custom dimensions
print('✓ Width Height Selector invalid preset handling test passed')
# Test Sampler Combo error handling
from kikotools.tools.sampler_combo.node import SamplerComboNode
sampler_node = SamplerComboNode()
# Test with invalid sampler (should use safe defaults)
result = sampler_node.get_sampler_combo('invalid_sampler', 'normal', 20, 7.0)
assert result == ('euler', 'normal', 20, 7.0) # Safe defaults
print('✓ Sampler Combo invalid input handling test passed')
# Test Seed History error handling
from kikotools.tools.seed_history.node import SeedHistoryNode
seed_node = SeedHistoryNode()
# Test invalid seed value (should use fallback)
result = seed_node.output_seed(-1) # Invalid negative seed
assert result == (12345,) # Fallback seed
print('✓ Seed History invalid seed handling test passed')
print('🎉 All error handling tests passed for all tools!')
"
- name: Test ComfyUI integration readiness for all tools
run: |
python -c "
import sys
import os
sys.path.insert(0, os.getcwd())
print('=== Testing ComfyUI Integration for All Tools ===')
# Test Resolution Calculator
from kikotools.tools.resolution_calculator.node import ResolutionCalculatorNode
res_class = ResolutionCalculatorNode
assert hasattr(res_class, 'INPUT_TYPES')
assert hasattr(res_class, 'RETURN_TYPES')
assert hasattr(res_class, 'RETURN_NAMES')
assert hasattr(res_class, 'FUNCTION')
assert hasattr(res_class, 'CATEGORY')
input_types = res_class.INPUT_TYPES()
assert 'required' in input_types
assert 'optional' in input_types
assert 'scale_factor' in input_types['required']
assert 'image' in input_types['optional']
assert 'latent' in input_types['optional']
assert res_class.RETURN_TYPES == ('INT', 'INT')
assert res_class.RETURN_NAMES == ('width', 'height')
assert res_class.CATEGORY == 'ComfyAssets'
print('✓ Resolution Calculator ComfyUI integration passed')
# Test Width Height Selector
from kikotools.tools.width_height_selector.node import WidthHeightSelectorNode
wh_class = WidthHeightSelectorNode
assert hasattr(wh_class, 'INPUT_TYPES')
assert hasattr(wh_class, 'RETURN_TYPES')
assert hasattr(wh_class, 'RETURN_NAMES')
assert hasattr(wh_class, 'FUNCTION')
assert hasattr(wh_class, 'CATEGORY')
input_types = wh_class.INPUT_TYPES()
assert 'required' in input_types
assert 'preset' in input_types['required']
assert 'width' in input_types['required']
assert 'height' in input_types['required']
assert wh_class.RETURN_TYPES == ('INT', 'INT')
assert wh_class.RETURN_NAMES == ('width', 'height')
assert wh_class.CATEGORY == 'ComfyAssets'
print('✓ Width Height Selector ComfyUI integration passed')
# Test Sampler Combo
from kikotools.tools.sampler_combo.node import SamplerComboNode
sampler_class = SamplerComboNode
assert hasattr(sampler_class, 'INPUT_TYPES')
assert hasattr(sampler_class, 'RETURN_TYPES')
assert hasattr(sampler_class, 'RETURN_NAMES')
assert hasattr(sampler_class, 'FUNCTION')
assert hasattr(sampler_class, 'CATEGORY')
input_types = sampler_class.INPUT_TYPES()
assert 'required' in input_types
assert 'sampler_name' in input_types['required']
assert 'scheduler' in input_types['required']
assert 'steps' in input_types['required']
assert 'cfg' in input_types['required']
assert sampler_class.CATEGORY == 'ComfyAssets'
print('✓ Sampler Combo ComfyUI integration passed')
# Test Seed History
from kikotools.tools.seed_history.node import SeedHistoryNode
seed_class = SeedHistoryNode
assert hasattr(seed_class, 'INPUT_TYPES')
assert hasattr(seed_class, 'RETURN_TYPES')
assert hasattr(seed_class, 'RETURN_NAMES')
assert hasattr(seed_class, 'FUNCTION')
assert hasattr(seed_class, 'CATEGORY')
input_types = seed_class.INPUT_TYPES()
assert 'required' in input_types
assert 'seed' in input_types['required']
assert seed_class.RETURN_TYPES == ('INT',)
assert seed_class.RETURN_NAMES == ('seed',)
assert seed_class.CATEGORY == 'ComfyAssets'
print('✓ Seed History ComfyUI integration passed')
print('🎉 All tools ComfyUI integration readiness tests passed!')
"
test-package-structure:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v4
- name: Upload test results
if: always()
uses: actions/upload-artifact@v7
with:
python-version: "3.10"
name: python-${{ matrix.os }}-${{ matrix.python-version }}
path: test-results/
if-no-files-found: ignore
retention-days: 14
- name: Test package structure
comfyui:
name: real ComfyUI compatibility
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: "24"
cache: npm
cache-dependency-path: tools/testing/package-lock.json
- name: Read the reviewed runtime pins
id: runtime
run: |
# Check required files exist
test -f README.md || (echo "README.md missing" && exit 1)
test -f LICENSE || (echo "LICENSE missing" && exit 1)
test -f requirements-dev.txt || (echo "requirements-dev.txt missing" && exit 1)
node --input-type=module <<'JS' >> "$GITHUB_OUTPUT"
import runtime from './tools/testing/comfyui-runtime.json' with { type: 'json' };
for (const [key, value] of Object.entries(runtime)) console.log(`${key}=${value}`);
JS
- name: Check out pinned ComfyUI
uses: actions/checkout@v7
with:
repository: ${{ steps.runtime.outputs.repository }}
ref: ${{ steps.runtime.outputs.revision }}
path: .ci-comfyui
persist-credentials: false
- uses: actions/setup-python@v7
with:
python-version: ${{ steps.runtime.outputs.pythonVersion }}
cache: pip
cache-dependency-path: |
requirements-dev.txt
.ci-comfyui/requirements.txt
tools/testing/comfyui-runtime.json
- name: Install CPU runtime and test dependencies
run: |
python -m pip install --upgrade pip
pip install "torch==${{ steps.runtime.outputs.torchVersion }}" "torchvision==${{ steps.runtime.outputs.torchvisionVersion }}" --index-url https://download.pytorch.org/whl/cpu
pip install -r .ci-comfyui/requirements.txt -r requirements-dev.txt "torch==${{ steps.runtime.outputs.torchVersion }}" "torchvision==${{ steps.runtime.outputs.torchvisionVersion }}"
- run: npm ci --prefix tools/testing --ignore-scripts
- run: npx --prefix tools/testing playwright install --with-deps chromium
- name: Classic and Nodes 2.0 startup and compatibility
env:
KIKO_COMFY_ROOT: ${{ github.workspace }}/.ci-comfyui
KIKO_TEST_PYTHON: python
run: npm run --prefix tools/testing test:comfyui
- name: Upload real-host reports and startup logs
if: always()
uses: actions/upload-artifact@v7
with:
name: real-comfyui-reports
path: tools/testing/test-results/
if-no-files-found: ignore
retention-days: 14
# Check directory structure
test -d kikotools || (echo "kikotools directory missing" && exit 1)
test -d kikotools/base || (echo "kikotools/base directory missing" && exit 1)
test -d kikotools/tools || (echo "kikotools/tools directory missing" && exit 1)
test -d kikotools/tools/resolution_calculator || (echo "resolution_calculator directory missing" && exit 1)
test -d kikotools/tools/width_height_selector || (echo "width_height_selector directory missing" && exit 1)
test -d kikotools/tools/sampler_combo || (echo "sampler_combo directory missing" && exit 1)
test -d kikotools/tools/seed_history || (echo "seed_history directory missing" && exit 1)
test -d tests || (echo "tests directory missing" && exit 1)
test -d examples || (echo "examples directory missing" && exit 1)
test -d web || (echo "web directory missing" && exit 1)
# Check key files
test -f kikotools/__init__.py || (echo "kikotools/__init__.py missing" && exit 1)
test -f kikotools/base/base_node.py || (echo "base_node.py missing" && exit 1)
# Resolution Calculator files
test -f kikotools/tools/resolution_calculator/node.py || (echo "resolution_calculator node.py missing" && exit 1)
test -f kikotools/tools/resolution_calculator/logic.py || (echo "resolution_calculator logic.py missing" && exit 1)
# Width Height Selector files
test -f kikotools/tools/width_height_selector/node.py || (echo "width_height_selector node.py missing" && exit 1)
test -f kikotools/tools/width_height_selector/logic.py || (echo "width_height_selector logic.py missing" && exit 1)
test -f kikotools/tools/width_height_selector/presets.py || (echo "width_height_selector presets.py missing" && exit 1)
# Sampler Combo files
test -f kikotools/tools/sampler_combo/node.py || (echo "sampler_combo node.py missing" && exit 1)
test -f kikotools/tools/sampler_combo/logic.py || (echo "sampler_combo logic.py missing" && exit 1)
# Seed History files
test -f kikotools/tools/seed_history/node.py || (echo "seed_history node.py missing" && exit 1)
test -f kikotools/tools/seed_history/logic.py || (echo "seed_history logic.py missing" && exit 1)
# Web files
test -f web/width_height_swap.js || (echo "width_height_swap.js missing" && exit 1)
test -f web/seed_history_ui.js || (echo "seed_history_ui.js missing" && exit 1)
echo "✓ Package structure tests passed"
test-documentation:
coverage:
name: coverage gate (85%)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v7
- name: Test documentation completeness
- uses: actions/setup-python@v7
with:
python-version: "3.12"
cache: pip
cache-dependency-path: requirements-dev.txt
- name: Install dependencies
run: |
# Check documentation files for all tools
test -f examples/documentation/resolution_calculator.md || (echo "Resolution calculator docs missing" && exit 1)
test -f examples/workflows/resolution_calculator_example.json || (echo "Resolution calculator workflow missing" && exit 1)
test -f examples/documentation/width_height_selector.md || (echo "Width height selector docs missing" && exit 1)
test -f examples/workflows/width_height_selector_example.json || (echo "Width height selector workflow missing" && exit 1)
test -f examples/documentation/sampler_combo.md || (echo "Sampler combo docs missing" && exit 1)
test -f examples/workflows/sampler_combo_example.json || (echo "Sampler combo workflow missing" && exit 1)
test -f examples/documentation/seed_history.md || (echo "Seed history docs missing" && exit 1)
test -f examples/workflows/seed_history_example.json || (echo "Seed history workflow missing" && exit 1)
python -m pip install --upgrade pip
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements-dev.txt
# Check README has key sections
grep -q "Installation" README.md || (echo "README missing Installation section" && exit 1)
grep -q "Quick Start" README.md || (echo "README missing Quick Start section" && exit 1)
grep -q "Development" README.md || (echo "README missing Development section" && exit 1)
grep -q "Contributing" README.md || (echo "README missing Contributing section" && exit 1)
# fail_under = 85 lives in pyproject.toml [tool.coverage.report]
- name: Measure coverage
run: python -m pytest tests --cov=kikotools --cov-report=term-missing --cov-report=html --cov-report=xml
echo "✓ Documentation tests passed"
- name: Upload coverage reports
if: always()
uses: actions/upload-artifact@v7
with:
name: python-coverage
path: |
htmlcov/
coverage.xml
if-no-files-found: ignore
retention-days: 14
js-check:
name: frontend syntax
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: "24"
# Check every shipped module, including files without behavioral tests.
- name: Check all shipped JavaScript modules parse
run: |
shopt -s globstar nullglob
for f in web/**/*.js web/**/*.mjs; do
node --input-type=module --check < "$f" || { echo "Syntax error in $f"; exit 1; }
done
frontend:
name: frontend workflows and coverage
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.12"
cache: pip
cache-dependency-path: requirements-dev.txt
- name: Install Python test dependencies
run: |
python -m pip install --upgrade pip
pip install torch --index-url https://download.pytorch.org/whl/cpu
pip install -r requirements-dev.txt
- uses: actions/setup-node@v7
with:
node-version: "24"
cache: npm
cache-dependency-path: tools/testing/package-lock.json
- run: npm ci --prefix tools/testing --ignore-scripts
- run: npx --prefix tools/testing playwright install --with-deps chromium firefox webkit
- name: JavaScript regressions, Chromium workflows, and coverage gate
run: npm run --prefix tools/testing test:coverage
- name: Firefox and WebKit workflows
run: npm run --prefix tools/testing test:browser -- --project firefox --project webkit
- name: Upload frontend reports
if: always()
uses: actions/upload-artifact@v7
with:
name: frontend-test-reports
path: |
tools/testing/test-results/
coverage/js/
!coverage/js/raw/
if-no-files-found: ignore
retention-days: 14
+20 -1
View File
@@ -158,4 +158,23 @@ input/
test_images/
test_outputs/
experiments/
.claude/
.claude/
.serena
# Gemini model cache
.gemini_models_cache.json
referance/
# Runtime state written inside the package (repo is symlinked into custom_nodes)
kikotools/tools/local_image_loader/config.json
kikotools/tools/local_image_loader/selections.json
gemini_config.json
# Frontend test tooling and reports
node_modules/
coverage/
playwright-report/
test-results/
# Disposable pinned host checkout used by CI
.ci-comfyui/
+101
View File
@@ -0,0 +1,101 @@
# Pre-commit hooks configuration for ComfyUI-KikoTools
# This ensures code quality checks are run before each commit
repos:
# Python code formatting with Black
- repo: https://github.com/psf/black
rev: 26.10.1
hooks:
- id: black
args: ['--line-length=88'] # Match CI configuration
# Python linting with flake8
- repo: https://github.com/pycqa/flake8
rev: 7.3.0
hooks:
- id: flake8
args: ['--max-line-length=88', '--max-complexity=10']
exclude: '^tests/'
# Python type checking with mypy
# Note: Mypy is disabled in pre-commit due to package name issue
# Run manually with: mypy kikotools/
# - repo: https://github.com/pre-commit/mirrors-mypy
# rev: v1.8.0
# hooks:
# - id: mypy
# args: ['--config-file=mypy.ini']
# files: '^kikotools/'
# exclude: '^tests/'
# additional_dependencies: ['types-requests']
# Security checks with bandit
- repo: https://github.com/PyCQA/bandit
rev: 1.8.6
hooks:
- id: bandit
args: ['-ll', '-r']
files: '^kikotools/'
# General file checks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v5.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
args: ['--maxkb=1000']
- id: check-case-conflict
- id: check-merge-conflict
- id: check-docstring-first
- id: debug-statements
- id: mixed-line-ending
# Check for hardcoded secrets
- repo: https://github.com/Yelp/detect-secrets
rev: v1.5.0
hooks:
- id: detect-secrets
args: ['--baseline', '.secrets.baseline']
exclude: '^(tests/|\.git/)'
- repo: local
hooks:
- id: python-regressions
name: Python regression suite
entry: python -m pytest tests
language: system
pass_filenames: false
always_run: true
stages: [pre-push]
- id: frontend-regressions
name: JavaScript regression suite
entry: npm test --prefix tools/testing
language: system
pass_filenames: false
always_run: true
stages: [pre-push]
# Configuration for specific hooks
default_stages: [pre-commit]
default_language_version:
python: python3
# Run hooks on all files by default
fail_fast: false
# Exclude patterns
exclude: |
(?x)^(
\.git/|
\.mypy_cache/|
\.pytest_cache/|
__pycache__/|
build/|
dist/|
\.eggs/|
.*\.egg-info/|
venv/|
env/
)
+164
View File
@@ -0,0 +1,164 @@
{
"version": "1.5.0",
"plugins_used": [
{
"name": "ArtifactoryDetector"
},
{
"name": "AWSKeyDetector"
},
{
"name": "AzureStorageKeyDetector"
},
{
"name": "Base64HighEntropyString",
"limit": 4.5
},
{
"name": "BasicAuthDetector"
},
{
"name": "CloudantDetector"
},
{
"name": "DiscordBotTokenDetector"
},
{
"name": "GitHubTokenDetector"
},
{
"name": "GitLabTokenDetector"
},
{
"name": "HexHighEntropyString",
"limit": 3.0
},
{
"name": "IbmCloudIamDetector"
},
{
"name": "IbmCosHmacDetector"
},
{
"name": "IPPublicDetector"
},
{
"name": "JwtTokenDetector"
},
{
"name": "KeywordDetector",
"keyword_exclude": ""
},
{
"name": "MailchimpDetector"
},
{
"name": "NpmDetector"
},
{
"name": "OpenAIDetector"
},
{
"name": "PrivateKeyDetector"
},
{
"name": "PypiTokenDetector"
},
{
"name": "SendGridDetector"
},
{
"name": "SlackDetector"
},
{
"name": "SoftlayerDetector"
},
{
"name": "SquareOAuthDetector"
},
{
"name": "StripeDetector"
},
{
"name": "TelegramBotTokenDetector"
},
{
"name": "TwilioKeyDetector"
}
],
"filters_used": [
{
"path": "detect_secrets.filters.allowlist.is_line_allowlisted"
},
{
"path": "detect_secrets.filters.common.is_ignored_due_to_verification_policies",
"min_level": 2
},
{
"path": "detect_secrets.filters.heuristic.is_indirect_reference"
},
{
"path": "detect_secrets.filters.heuristic.is_likely_id_string"
},
{
"path": "detect_secrets.filters.heuristic.is_lock_file"
},
{
"path": "detect_secrets.filters.heuristic.is_not_alphanumeric_string"
},
{
"path": "detect_secrets.filters.heuristic.is_potential_uuid"
},
{
"path": "detect_secrets.filters.heuristic.is_prefixed_with_dollar_sign"
},
{
"path": "detect_secrets.filters.heuristic.is_sequential_string"
},
{
"path": "detect_secrets.filters.heuristic.is_swagger_file"
},
{
"path": "detect_secrets.filters.heuristic.is_templated_secret"
}
],
"results": {
"examples/workflows/resolution_calculator_example.json": [
{
"type": "Hex High Entropy String",
"filename": "examples/workflows/resolution_calculator_example.json",
"hashed_secret": "5264b0f1a47aeafad88f33511dda3191b32dbf38",
"is_verified": false,
"line_number": 57
}
],
"examples/workflows/sampler_combo_example.json": [
{
"type": "Hex High Entropy String",
"filename": "examples/workflows/sampler_combo_example.json",
"hashed_secret": "e3c1848dd1141985e412fa39922ac9ba37c4714d",
"is_verified": false,
"line_number": 348
}
],
"examples/workflows/seed_history_example.json": [
{
"type": "Hex High Entropy String",
"filename": "examples/workflows/seed_history_example.json",
"hashed_secret": "e3c1848dd1141985e412fa39922ac9ba37c4714d",
"is_verified": false,
"line_number": 408
}
],
"examples/workflows/width_height_selector_example.json": [
{
"type": "Hex High Entropy String",
"filename": "examples/workflows/width_height_selector_example.json",
"hashed_secret": "e3c1848dd1141985e412fa39922ac9ba37c4714d",
"is_verified": false,
"line_number": 425
}
]
},
"generated_at": "2025-07-31T23:51:20Z"
}
+46
View File
@@ -0,0 +1,46 @@
# Repository Guidelines
## Project Structure & Module Organization
`kikotools/tools/<tool>/logic.py` contains reusable logic; `node.py` defines the
ComfyUI node. Shared classes live in `kikotools/base/`. Register nodes in
`kikotools/__init__.py`; the root `__init__.py` exposes the plugin and API routes.
`web/` contains browser extensions. Tests live in `tests/unit/`, `tests/js/`, and
`tests/browser/`. Examples are in `examples/`.
## Development Commands
Install Python dependencies with `python -m pip install -r requirements-dev.txt`.
Run `python -m pytest tests` or `make test` for the complete Python suite.
Use `python -m pytest tests --cov=kikotools` for the 85% statement/branch gate.
Format with `black .`; use `flake8 .` for lint findings.
Frontend tooling needs Node.js 24 and `npm ci --prefix tools/testing
--ignore-scripts`. Run `npm test --prefix tools/testing` for JavaScript tests,
`npm run --prefix tools/testing test:coverage` for coverage and Chromium workflows,
and `npm run --prefix tools/testing test:browser` for all browser projects.
See `TESTING.md` for browser installation, environment overrides, and artifacts.
## Coding Style & Naming
Use four-space Python indentation and Black's 88-character formatting.
Use `snake_case` for Python functions/modules, `PascalCase` for classes, and
`UPPER_CASE` for constants. Keep node interfaces separate from logic. Preserve
existing ComfyUI lifecycle hooks when extending widgets and clean up timers,
listeners, and observers when nodes are removed.
## Testing Guidelines
Write regression tests first and verify failure before implementing fixes.
Name Python tests `test_*.py` and JavaScript tests `*.test.mjs`; put browser
workflows in `tests/browser/*.spec.mjs`. Test behavior and persistence, including
failure recovery. Keep fixtures disposable and external services mocked.
Never lower coverage floors to make a change pass.
## Commits & Pull Requests
Use Conventional Commits, such as `fix(local_image_loader): preserve error
status`. Link the tracked issue, describe observable behavior, and report the
commands and results used for validation. Include screenshots for visual changes.
Required checks must pass before merging. Keep credentials and runtime files out
of commits; restart ComfyUI after installing Python changes.
+58
View File
@@ -0,0 +1,58 @@
# Changelog
## 1.3.0
### Model Resolution Picker
- New `Model Resolution Picker` node (🖼️ Resolution): pick a native size for a
model family from a chip grid of aspect ratios and output an empty latent plus
width/height. Fourteen shipped families (SD 1.5, SDXL, Illustrious XL, Anima,
Flux.1, Z-Image, Qwen-Image, Flux.2 / Klein / MOSS, Qwen-Image 2.1,
HunyuanImage 2.1 and four video keyframe rows), each rounded to its VAE step.
- Native `resolution` dropdown that mirrors the chip grid both ways; typed or
wired sizes show `custom`.
- Shipped family table in `families.json` with a validator, and a user override
file at `user/kikotools/model_resolutions.json` that can replace, add or remove
families and set the default. Invalid files are ignored whole with a warning
in the panel.
- The node height follows the panel content, and workflows saved before the
dropdown existed still load.
- `GET /kikotools/model_resolution/families` serves the merged table; the shared
dimension swap now works on nodes without a preset widget.
## 1.2.0
### Unified ComfyAssets UI
- Consistent blue controls and strong red destructive actions that follow
ComfyUI's light/dark theme.
- Updated Local Image Loader, Save Image viewer, Seed History, Workflow Timer,
dimension swaps, Node Dimensions, Display Text/Any and Embedding Autocomplete.
- Larger adjustable gallery thumbnails and saved-image previews that use the
available space; responsive layouts, keyboard controls and lifecycle cleanup.
- Shared Custom Colors picker with hex entry, HSV sliders and presets.
- Restored autocomplete tag management, four-column CSV parsing, worker-based
imports and persistent tag caching. Tag packs remain a separate download;
see [tag-pack provenance and terms](docs/tag-packs/README.md).
- Follow Execution honors live enablement, avoids duplicate subscriptions and
safely groups navigation menus.
### Compatibility and testing
- Classic and Nodes 2.0 coverage includes fresh startup in each renderer,
switching both ways, themes, saved workflows and removal cleanup.
- Required CPU-only real-ComfyUI CI joins the existing Python, JavaScript,
Chromium, touch, Firefox and WebKit suites. Runtime pins and failure artifacts
make compatibility checks repeatable.
- Retained [screenshots](docs/screenshots/README.md) and documented
[testing commands](TESTING.md).
### Deprecation and upgrade
Gemini Prompt Engineer is deprecated and hidden from normal new-node discovery.
Its registration, schema and execution remain available for saved workflows;
ongoing Google API/model compatibility maintenance has ended.
Restart ComfyUI and hard-refresh the browser after upgrading. Existing node
interfaces, workflow values and setting IDs are retained. No workflow conversion
is required. Follow Execution remains opt-in through its Enable setting.
+288
View File
@@ -0,0 +1,288 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
ComfyUI-KikoTools is a planned modular collection of custom ComfyUI nodes that will provide essential tools missing from the standard ComfyUI release. All nodes will be grouped under "ComfyAssets" in the ComfyUI interface. The project is designed for extensibility, allowing new tools to be added easily while maintaining clean separation of concerns.
**Current Status**: Project is in initial planning phase. Only documentation and licensing files exist.
## Architecture
### Design Principles
- **Modular Design**: Each tool is a separate, self-contained module
- **ComfyAssets Grouping**: All nodes appear under the "ComfyAssets" category
- **Test-Driven Development**: Every tool includes comprehensive tests
- **Clean Interfaces**: Standardized input/output patterns across tools
### Core Components
- **Tool Registry**: Central registration system for all KikoTools nodes
- **Base Classes**: Shared functionality for consistent tool behavior
- **Individual Tools**: Self-contained modules for specific functionality
### Current Tools
#### 1. Resolution Calculator (First Tool)
- **Purpose**: Calculate upscale resolution from image or latent inputs
- **Inputs**:
- Image or Latent tensor
- Scale factor (1, 2, 3, 1.2, 1.5, 2.0)
- **Outputs**:
- Width (INT)
- Height (INT)
- **Target Models**: Flux and SDXL optimized
- **Use Case**: Connect calculated dimensions to upscaler nodes
## Technology Stack
- **Backend**: Python with ComfyUI node patterns
- **Node Framework**: ComfyUI INPUT_TYPES, RETURN_TYPES, execute() patterns
- **Testing**: pytest with ComfyUI test fixtures
- **Code Quality**: black, flake8, mypy
- **Integration**: ComfyUI execution queue and tensor systems
## Development Commands
**Note**: These commands are planned for when the project structure is implemented.
### Initial Setup
```bash
# Create basic project structure
mkdir -p kikotools/{base,tools} tests/{unit,integration,fixtures} scripts examples
# Create entry point files
touch __init__.py kikotools/__init__.py
```
### Code Quality (Future)
```bash
# Format Python code
black .
# Python linting
flake8 .
# Type checking
mypy .
```
### Testing (Future TDD Workflow)
```bash
# Run all tests
pytest tests/
# Run tests for specific tool
pytest tests/unit/tools/test_{tool_name}.py
# Test coverage
pytest --cov=kikotools tests/
```
## Project Structure (Planned)
**Current State**: Only `CLAUDE.md` and `LICENSE` files exist.
**Planned Structure**:
```
├── __init__.py # ComfyUI node registration entry point
├── kikotools/ # Main package
│ ├── __init__.py # Package initialization and tool registry
│ ├── base/ # Base classes and shared utilities
│ │ ├── __init__.py
│ │ ├── base_node.py # Base node class with ComfyAssets grouping
│ │ └── utils.py # Shared utility functions
│ ├── tools/ # Individual tool implementations
│ │ ├── __init__.py
│ │ ├── resolution_calculator/ # First planned tool
│ │ │ ├── __init__.py
│ │ │ ├── node.py # ResolutionCalculatorNode implementation
│ │ │ └── logic.py # Core calculation logic
│ │ └── template/ # Template for new tools
│ │ ├── __init__.py
│ │ ├── node.py
│ │ └── logic.py
├── tests/ # Comprehensive test suite (TDD approach)
│ ├── __init__.py
│ ├── conftest.py # pytest fixtures and ComfyUI test setup
│ ├── unit/ # Unit tests for individual components
│ │ ├── test_base_node.py
│ │ └── tools/
│ │ └── test_resolution_calculator.py
│ ├── integration/ # ComfyUI integration tests
│ │ ├── test_node_registration.py
│ │ └── test_workflow_execution.py
│ └── fixtures/ # Test data and workflow files
│ ├── workflows/ # .json workflow files for testing
│ ├── images/ # Test images
│ └── latents/ # Test latent tensors
├── scripts/ # Development automation
│ ├── create_tool.py # Tool template generator
│ ├── register_tool.py # Tool registration helper
│ └── validate_nodes.py # Node validation script
├── examples/ # Usage examples and demonstrations
│ ├── workflows/ # Example workflow .json files
│ └── documentation/ # Usage documentation per tool
└── requirements-dev.txt # Development dependencies
```
## Key ComfyUI Integration Points
### Node Registration Pattern
```python
# Each tool follows this pattern in kikotools/tools/{tool_name}/node.py
class ResolutionCalculatorNode:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"scale_factor": ("FLOAT", {"default": 2.0, "min": 1.0, "max": 8.0, "step": 0.1}),
},
"optional": {
"image": ("IMAGE",),
"latent": ("LATENT",),
}
}
RETURN_TYPES = ("INT", "INT")
RETURN_NAMES = ("width", "height")
FUNCTION = "calculate_resolution"
CATEGORY = "ComfyAssets" # All tools use this category
def calculate_resolution(self, scale_factor, image=None, latent=None):
# Implementation here
pass
```
### Base Node Class
- Provides consistent "ComfyAssets" categorization
- Standardizes error handling and logging
- Implements common validation patterns
- Ensures consistent return type handling
### Tool Registry System
- Automatic discovery of tools in `kikotools/tools/`
- Dynamic node registration during ComfyUI startup
- Version compatibility checking
- Dependency validation
## Test-Driven Development (TDD) Workflow
### 1. Write Tests First
```python
# tests/unit/tools/test_resolution_calculator.py
def test_resolution_calculator_with_image():
"""Test resolution calculation with image input."""
# Arrange
node = ResolutionCalculatorNode()
test_image = create_test_image(512, 512) # fixture
scale_factor = 2.0
# Act
width, height = node.calculate_resolution(scale_factor, image=test_image)
# Assert
assert width == 1024
assert height == 1024
def test_resolution_calculator_with_latent():
"""Test resolution calculation with latent input."""
# Similar pattern for latent inputs
pass
```
### 2. Run Tests (Should Fail)
```bash
pytest tests/unit/tools/test_resolution_calculator.py -v
```
### 3. Implement Minimal Code
```python
# kikotools/tools/resolution_calculator/logic.py
def calculate_upscale_resolution(input_tensor, scale_factor):
"""Calculate new resolution based on input and scale factor."""
# Minimal implementation to pass tests
pass
```
### 4. Refactor and Expand
- Add error handling
- Optimize for Flux/SDXL specific requirements
- Add comprehensive validation
- Implement edge case handling
### 5. Integration Testing
```python
# tests/integration/test_workflow_execution.py
def test_resolution_calculator_in_workflow():
"""Test resolution calculator in full ComfyUI workflow."""
workflow = load_test_workflow("resolution_calculator_example.json")
result = execute_comfyui_workflow(workflow)
assert result.success
```
## Tool-Specific Implementation Notes
### Resolution Calculator
- **Input Validation**: Handle both image and latent tensors
- **Scale Factors**: Support integer (1, 2, 3) and float (1.2, 1.5, 2.0) multipliers
- **Model Optimization**: Consider Flux and SDXL specific resolution requirements
- **Output Format**: Integer width/height suitable for upscaler node connections
- **Error Handling**: Graceful handling of invalid inputs or edge cases
### Future Tools (Planned)
- Batch Image Processor
- Advanced Prompt Utilities
- Model Management Tools
- Custom Sampling Methods
## Development Workflow
### Adding a New Tool
1. **Plan**: Define tool purpose, inputs, outputs, and test cases
2. **Generate**: Use `python scripts/create_tool.py --name "NewTool"`
3. **Test**: Write comprehensive tests following TDD principles
4. **Implement**: Build tool logic with proper ComfyUI integration
5. **Register**: Add tool to registry and validate registration
6. **Document**: Update examples and documentation
7. **Validate**: Test in real ComfyUI environment with actual workflows
### Code Quality Standards
- **Type Hints**: Full type annotation for all functions
- **Documentation**: Docstrings for all public methods and classes
- **Testing**: Minimum 90% test coverage for all tools
- **Linting**: Pass all flake8 and mypy checks
- **Formatting**: Auto-formatted with black
### Release Process
1. Run full test suite: `pytest tests/`
2. Validate in ComfyUI: `python scripts/validate_nodes.py`
3. Update version numbers and changelog
4. Create example workflows demonstrating new features
5. Update ComfyUI-Manager compatibility metadata
## Critical Implementation Notes
### ComfyUI Compatibility
- Follow ComfyUI tensor format conventions
- Implement proper memory management for large tensors
- Handle ComfyUI execution context correctly
- Ensure compatibility with ComfyUI's automatic typing system
### Performance Considerations
- Optimize for real-time workflow execution
- Minimize memory allocation during processing
- Cache expensive computations when appropriate
- Profile performance with typical Flux/SDXL workflows
### User Experience
- Clear, descriptive node names and parameter labels
- Helpful tooltips and parameter descriptions
- Consistent visual styling within ComfyAssets group
- Robust error messages with actionable guidance
### Extensibility
- Plugin architecture for easy tool addition
- Shared utilities for common operations
- Consistent API patterns across all tools
- Future-proof design for ComfyUI updates
+128
View File
@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
+4 -1
View File
@@ -357,9 +357,12 @@ Brief description of changes.
### Review Process
See [TESTING.md](TESTING.md) for automated suites, coverage gates, required merge
checks, and the remaining ComfyUI/GPU/service boundaries.
1. **Automated Checks**: CI runs tests and quality checks
2. **Code Review**: Maintainer reviews code and provides feedback
3. **Testing**: Manual testing in ComfyUI environment
3. **Testing**: Run relevant automated suites; use live ComfyUI checks for host, GPU, service, or visual changes
4. **Merge**: After approval, changes are merged to main
## 🎨 Code Style Guidelines
+66 -35
View File
@@ -1,6 +1,22 @@
# ComfyUI-KikoTools Development Makefile
.PHONY: help install test test-fast lint format type-check quality-check clean setup dev-test release-test
.PHONY: coverage frontend-test browser-test frontend-coverage help install test test-fast lint format type-check quality-check clean setup dev-test release-test
# Python and virtual environment setup
PYTHON := python3
VENV_DIR := venv
VENV_BIN := $(VENV_DIR)/bin
VENV_PYTHON := $(VENV_BIN)/python
VENV_PIP := $(VENV_BIN)/pip
# Check if we're in a virtual environment, if not use venv
ifeq ($(VIRTUAL_ENV),)
PYTHON_CMD := $(VENV_PYTHON)
PIP_CMD := $(VENV_PIP)
else
PYTHON_CMD := python
PIP_CMD := pip
endif
# Default target
help:
@@ -18,7 +34,11 @@ help:
@echo " quality-check - Run all quality checks"
@echo ""
@echo "Testing:"
@echo " test - Run comprehensive tests"
@echo " test - Run the complete pytest suite"
@echo " coverage - Python statement/branch coverage (85% gate)"
@echo " frontend-test - JavaScript regression tests"
@echo " browser-test - Extension workflows in four browser configurations"
@echo " frontend-coverage - JavaScript tests, browser tests, and coverage gate"
@echo " test-fast - Run core functionality tests"
@echo " dev-test - Quick development test"
@echo " release-test - Full release validation"
@@ -28,44 +48,48 @@ help:
@echo " help - Show this help message"
# Setup and installation
setup:
@echo "Setting up ComfyUI-KikoTools development environment..."
python -m venv venv
@echo "Virtual environment created. Activate with:"
@echo " source venv/bin/activate (Linux/Mac)"
@echo " venv\\Scripts\\activate (Windows)"
@echo "Then run: make install"
setup: $(VENV_DIR)
@echo "✅ ComfyUI-KikoTools development environment ready"
@echo "Virtual environment created. Dependencies installed."
install:
$(VENV_DIR):
@echo "Creating virtual environment..."
$(PYTHON) -m venv $(VENV_DIR)
@echo "Installing development dependencies..."
pip install --upgrade pip
pip install -r requirements-dev.txt
$(VENV_PIP) install --upgrade pip
$(VENV_PIP) install -r requirements-dev.txt
@echo "✅ Virtual environment created and dependencies installed"
install: $(VENV_DIR)
@echo "Installing/updating development dependencies..."
$(PIP_CMD) install --upgrade pip
$(PIP_CMD) install -r requirements-dev.txt
@echo "✅ Dependencies installed"
# Code quality
format:
format: $(VENV_DIR)
@echo "Formatting code with black..."
black .
$(PYTHON_CMD) -m black .
@echo "✅ Code formatted"
lint:
lint: $(VENV_DIR)
@echo "Linting with flake8..."
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
$(PYTHON_CMD) -m flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude=venv
$(PYTHON_CMD) -m flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --exclude=venv
@echo "✅ Linting completed"
type-check:
type-check: $(VENV_DIR)
@echo "Type checking with mypy..."
mypy kikotools/ --ignore-missing-imports --no-strict-optional || true
$(PYTHON_CMD) -m mypy kikotools/ --ignore-missing-imports --no-strict-optional || true
@echo "✅ Type checking completed"
quality-check: format lint type-check
@echo "✅ All quality checks completed"
# Testing
dev-test:
dev-test: $(VENV_DIR)
@echo "Running quick development test..."
@python -c "\
@$(PYTHON_CMD) -c "\
import sys, os; \
sys.path.insert(0, os.getcwd()); \
from kikotools.tools.resolution_calculator.node import ResolutionCalculatorNode; \
@@ -75,9 +99,9 @@ dev-test:
print(f'✅ Development test passed! Result: {result[0]}x{result[1]}'); \
"
test-fast:
test-fast: $(VENV_DIR)
@echo "Running core functionality tests..."
@python -c "\
@$(PYTHON_CMD) -c "\
import sys, os; \
sys.path.insert(0, os.getcwd()); \
from kikotools.base import ComfyAssetsBaseNode; \
@@ -100,13 +124,20 @@ test-fast:
print('🎉 All core tests passed!'); \
"
test: test-fast
@echo "Running comprehensive test suite..."
@echo "✅ Test case 1: 512×512 → 1024×1024 (scale: 2.0)"
@echo "✅ Test case 2: 1024×1024 → 1536×1536 (scale: 1.5)"
@echo "✅ Test case 3: 832×1216 → 1272×1864 (scale: 1.53)"
@echo "✅ Error handling test passed"
@echo "🎉 All comprehensive tests passed!"
test: $(VENV_DIR)
@$(PYTHON_CMD) -m pytest tests
coverage: $(VENV_DIR)
@$(PYTHON_CMD) -m pytest tests --cov=kikotools --cov-report=term-missing --cov-report=html
frontend-test:
npm test --prefix tools/testing
browser-test:
npm run --prefix tools/testing test:browser
frontend-coverage:
npm run --prefix tools/testing test:coverage
release-test: quality-check test
@echo "Running release validation..."
@@ -146,13 +177,13 @@ ci: quality-check test
@echo "✅ CI checks passed!"
# Tool-specific commands (can be extended for new tools)
test-resolution-calculator:
test-resolution-calculator: $(VENV_DIR)
@echo "Testing Resolution Calculator specifically..."
@python -c "import sys, os; sys.path.insert(0, os.getcwd()); from kikotools.tools.resolution_calculator.node import ResolutionCalculatorNode; import torch; node = ResolutionCalculatorNode(); scenarios = [('SDXL Portrait', torch.randn(1, 1216, 832, 3), 1.5), ('FLUX Square', torch.randn(1, 1024, 1024, 3), 2.0), ('User Scenario', torch.randn(1, 1216, 832, 3), 1.53)]; [print(f'✅ {name}: {image.shape[2]}×{image.shape[1]} → {node.calculate_resolution(scale, image=image)[0]}×{node.calculate_resolution(scale, image=image)[1]} ({scale}x)') for name, image, scale in scenarios]; print('🎉 Resolution Calculator tests completed!')"
@$(PYTHON_CMD) -c "import sys, os; sys.path.insert(0, os.getcwd()); from kikotools.tools.resolution_calculator.node import ResolutionCalculatorNode; import torch; node = ResolutionCalculatorNode(); scenarios = [('SDXL Portrait', torch.randn(1, 1216, 832, 3), 1.5), ('FLUX Square', torch.randn(1, 1024, 1024, 3), 2.0), ('User Scenario', torch.randn(1, 1216, 832, 3), 1.53)]; [print(f'✅ {name}: {image.shape[2]}×{image.shape[1]} → {node.calculate_resolution(scale, image=image)[0]}×{node.calculate_resolution(scale, image=image)[1]} ({scale}x)') for name, image, scale in scenarios]; print('🎉 Resolution Calculator tests completed!')"
test-width-height-selector:
test-width-height-selector: $(VENV_DIR)
@echo "Testing Width Height Selector specifically..."
@python -c "\
@$(PYTHON_CMD) -c "\
import sys, os; \
sys.path.insert(0, os.getcwd()); \
from kikotools.tools.width_height_selector.node import WidthHeightSelectorNode; \
@@ -176,4 +207,4 @@ test-width-height-selector:
"
test-all-tools: test-resolution-calculator test-width-height-selector
@echo "🎉 All tool-specific tests completed!"
@echo "🎉 All tool-specific tests completed!"
+800 -30
View File
@@ -1,19 +1,84 @@
# ComfyUI-KikoTools
![License](https://img.shields.io/badge/license-MIT-blue.svg)
![Python](https://img.shields.io/badge/python-3.8%2B-blue.svg)
![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)
![ComfyUI](https://img.shields.io/badge/ComfyUI-compatible-green.svg)
![Tests](https://github.com/ComfyAssets/ComfyUI-KikoTools/workflows/Tests/badge.svg)
![Code Quality](https://github.com/ComfyAssets/ComfyUI-KikoTools/workflows/Code%20Quality/badge.svg)
> A modular collection of essential custom ComfyUI nodes missing from the standard release.
ComfyUI-KikoTools provides carefully crafted, production-ready nodes grouped under the **"ComfyAssets"** category. Each tool is designed with clean interfaces, comprehensive testing, and optimized performance for SDXL and FLUX workflows.
ComfyUI-KikoTools provides carefully crafted, production-ready nodes under the "ComfyAssets" category.
Each tool is built with clean interfaces, thorough testing, and optimized performance for SDXL and FLUX workflows.
This project started out of frustration with keeping ComfyUI up to date and waiting for dozens of custom nodes to update—most of which I didn’t even use. After taking a hard look at my workflow, I realized I only needed one or two features from these nodes, many of which were abandoned or stuck in maintenance mode.
I tried forking, patching, and submitting merge requests, but eventually decided to create my own curated collection of tools—fully supported and maintained by me. That’s how Kiko’s Tools was born.
I’m sharing them here with the community, and I hope you find them as useful as I do.
## 1.3.0 — Model Resolution Picker
A new node that picks a native resolution for a model family from a chip grid of
aspect ratios, with a synced `resolution` dropdown, a shipped family table and a
user override file. See the [release notes](CHANGELOG.md) and the
[node documentation](examples/documentation/model_resolution_picker.md).
## 1.2.0 — unified ComfyAssets UI
A consistent interface across the KikoTools controls, with ComfyUI-following
light/dark styling and Classic/Nodes 2.0 support. Existing workflow interfaces
and settings are retained. See the [release notes](CHANGELOG.md),
[migration overview](docs/ui-migration.md) and [screenshots](docs/screenshots/).
## ComfyUI Classic and Nodes 2.0
KikoTools supports both ComfyUI Classic and Nodes 2.0. Our shared ComfyAssets
controls follow ComfyUI's dark/light theme, with automated tests covering renderer
switching, workflow persistence and cleanup. See the retained
[UI screenshots](docs/screenshots/) for examples from the migrations.
## 🚀 Features
### ✨ Current Tools
| Tool | Description | Category |
|------|-------------|----------|
| [📐 Resolution Calculator](#-resolution-calculator) | Calculate upscaled dimensions with model optimization | 🖼️ Resolution |
| [📏 Width Height Selector](#-width-height-selector) | Preset-based dimension selection with visual swap | 🖼️ Resolution |
| [🎲 Seed History](#-seed-history) | Advanced seed tracking with interactive history | 🌱 Seeds |
| [⚙️ Sampler Combo](#️-sampler-combo) | Unified sampling configuration interface | 🌀 Samplers |
| [📦 Empty Latent Batch](#-empty-latent-batch) | Create empty latent batches with preset support | 📦 Latents |
| [🎯 Model Resolution Picker](#-model-resolution-picker) | Pick a model family and size from ratio chips, with latent and width/height outputs | 🖼️ Resolution |
| [💾 Kiko Save Image](#-kiko-save-image) | Enhanced image saving with popup viewer | 💾 Images |
| [📋 Display Text](#-display-text) | Smart text display with prompt detection | 👁️ Display |
| [🤖 Gemini Prompt Engineer](#-gemini-prompt-engineer) | Deprecated; retained for existing workflows | 🧠 Prompts |
| [🔍 Display Any](#-display-any) | Universal debugging tool for any data type | 👁️ Display |
| [🖼️ Image to Multiple Of](#️-image-to-multiple-of) | Adjust dimensions to multiples for compatibility | 🖼️ Resolution |
| [📉 Image Scale Down By](#-image-scale-down-by) | Scale images down by a factor with quality preservation | 🖼️ Resolution |
| [🎬 Film Grain](#-film-grain) | Add realistic film grain effects to images | 💾 Images |
| [🔤 Embedding Autocomplete](#-embedding-autocomplete) | Smart autocomplete for embeddings, LoRAs, and tags | 🔧 Utils |
| [🧹 Kiko Purge VRAM](#-kiko-purge-vram) | Intelligent VRAM management with detailed reporting | 🛠️ Utils |
| [📂 Local Image Loader](#-local-image-loader) | Visual gallery browser for local media files | 💾 Images |
| [🌐 Model Downloader](#-model-downloader) | Download models from CivitAI, HuggingFace, and custom URLs | 🛠️ Utils |
| [⏱️ Workflow Timer](#️-workflow-timer) | Real-time execution timer with customizable display | 🛠️ Utils |
| [🗂️ Project File Path](#️-project-file-path) | Build safe, organized save prefixes like `MyProject/images/image` | 🛠️ Utils |
### 🧰 xyz-helpers Tools
Advanced parameter management tools adapted from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) (now in maintenance mode):
| Tool | Description | Category |
|------|-------------|----------|
| [🎛️ Flux Sampler Params](#️-flux-sampler-params) | FLUX-optimized parameter generator with batch support | 🧰 xyz-helpers |
| [📁 LoRA Folder Batch](#-lora-folder-batch) | Batch process multiple LoRAs from folders | 🧰 xyz-helpers |
| [🔁 Checkpoint Folder Iterator](#-checkpoint-folder-iterator) | Load one checkpoint per queued run from a folder | 🧰 xyz-helpers |
| [🧩 Model Compare Sheet](#-model-compare-sheet) | Save each run's image and rebuild a labeled contact sheet | 🧰 xyz-helpers |
| [📊 Plot Parameters](#-plot-parameters) | Visualize parameter effects with graphs | 🧰 xyz-helpers |
| [🎯 Sampler Select Helper](#-sampler-select-helper) | Intelligent sampler selection with recommendations | 🧰 xyz-helpers |
| [📅 Scheduler Select Helper](#-scheduler-select-helper) | Optimal scheduler selection for samplers | 🧰 xyz-helpers |
| [✍️ Text Encode Sampler Params](#️-text-encode-sampler-params) | Combined text encoding and parameter management | 🧰 xyz-helpers |
#### 📐 Resolution Calculator
Calculate upscaled dimensions from image or latent inputs with precision.
@@ -25,10 +90,12 @@ Calculate upscaled dimensions from image or latent inputs with precision.
**Use Cases:**
- Calculate target dimensions for upscaler nodes
- Plan memory usage for large generations
- Plan memory usage for large generations
- Ensure ComfyUI tensor compatibility
- Optimize batch processing workflows
![Resolution Calculator Example](examples/workflows/resolution_calculator_example.png)
#### 📏 Width Height Selector
Advanced preset-based dimension selection with visual swap button.
@@ -60,6 +127,8 @@ Advanced seed tracking with interactive history management and UI.
- Maintain reproducibility across sessions
- Compare results from different seeds efficiently
![Seed History functionality is shown in various workflow examples]
#### ⚙️ Sampler Combo
Unified sampling configuration interface combining sampler, scheduler, steps, and CFG.
@@ -76,10 +145,477 @@ Unified sampling configuration interface combining sampler, scheduler, steps, an
- Reduce node clutter in workflows
- Quick sampling parameter experimentation
#### 📦 Empty Latent Batch
Advanced empty latent creation with preset support and batch processing capabilities.
- **Preset Integration**: 26 curated resolution presets with model optimization
- **Batch Processing**: Create multiple empty latents (1-64) in a single operation
- **Visual Swap Button**: Interactive blue button for quick dimension swapping
- **Smart Validation**: Automatic dimension sanitization for VAE compatibility
- **Memory Estimation**: Built-in memory usage calculation and warnings
- **Model-Aware Presets**: SDXL (~1MP), FLUX (high-res), and Ultra-wide options
**Use Cases:**
- Initialize batch processing workflows efficiently
- Create consistent latent dimensions across model types
- Optimize memory usage with batch size planning
- Quick preset-based latent generation for different aspect ratios
![Empty Latent Batch Example](examples/workflows/empty_latent_batch_example.png)
#### 🎯 Model Resolution Picker
Choose a model family and a size from ratio chips, and get an empty latent plus width and height.
- **Model Dropdown**: 14 shipped families (SD 1.5, SDXL, Illustrious XL, Flux.1, Flux.2, Qwen-Image and more) with their preferred sizes
- **Chips and List**: Pick an aspect ratio chip, then a size from the list under it; the header shows `W × H · ratio · MP`
- **Resolution Dropdown**: A native `resolution` dropdown (`custom` plus size labels like `1216×832 - 3:2 (1.0MP)`) lists the active chip's sizes and stays in sync with the chips; a listed size wins over width and height when the node runs
- **Swap W×H**: Exchanges width and height and lands on the mirrored chip
- **Custom Ratio and Resolution**: Enter a ratio like 16:9 or type width and height; sizes are rounded to the family's step
- **Your Own Sizes**: An optional override file at `ComfyUI/user/kikotools/model_resolutions.json` (under the default user directory) can replace a family by label, add new ones, hide shipped ones with `remove`, or set `default`; an invalid file is ignored and the panel shows why ([details](examples/documentation/model_resolution_picker.md#user-override-file))
- **Outputs**: `latent` (4-channel 8x, resizable for 16x and 32x VAEs), `width` and `height`
- **Keyframe Rows**: Video families (Wan 2.2, LTX-2, HunyuanVideo 1.5, MiniMax H3) are first/last-frame sizes; the latent is an image latent, never fed to a video model
**Use Cases:**
- Pick the right size for each model without remembering bucket lists
- Switch families while keeping a listed size
- Feed one node to both the latent and the size inputs of a workflow
![Model Resolution Picker](docs/screenshots/resolution-picker/ModelResolutionPicker-dark.png)
#### 💾 Kiko Save Image
Enhanced image saving with format selection, quality control, and floating popup viewer.
- **Multiple Format Support**: Save as PNG, JPEG, or WebP with format-specific optimizations
- **Advanced Quality Controls**: JPEG/WebP quality (1-100), PNG compression (0-9), WebP lossless mode
- **Floating Popup Viewer**: Draggable, resizable window that shows saved images immediately
- **Interactive Previews**: Click any image to open in new tab, download individual images
- **Batch Selection**: Multi-select images for bulk actions (open all, download all)
- **Format-Specific Settings**: Quality indicators, file size display, compression info
- **Smart UI**: Auto-hide/show, minimize/maximize, roll-up functionality
- **Popup Toggle**: Enable/disable popup viewer per save operation
![Kiko Save Image Example](examples/workflows/kiko_save_image_example.png)
#### 📋 Display Text
Advanced text display node with intelligent formatting and enhanced user interaction.
- **Smart Prompt Detection**: Automatically detects positive/negative prompt pairs and displays in split view
- **Text Wrapping**: Proper word wrapping that reflows when node is resized
- **Scrollable Content**: Mouse wheel scrolling for long texts with visual scroll indicators
- **Copy Functionality**: Always-visible copy button with visual feedback
- **Split View Mode**: Automatic detection and formatting of SDXL-style prompts
- **Responsive Design**: Content adapts to node resizing with proper text reflow
- **Clean Formatting**: Strips prompt labels when copying for direct use
**Use Cases:**
- Display generated prompts with proper formatting
- Compare positive and negative prompts side-by-side
- Copy prompts without manual label removal
- View long text content with proper wrapping
- Debug prompt generation workflows
![Display Text Example](examples/workflows/display_text_example.png)
#### 🤖 Gemini Prompt Engineer
**Deprecated — retained for existing workflows only.** ComfyUI hides this node
from normal new-node discovery using its deprecated-node metadata. The
`GeminiPrompt` identifier, inputs, outputs and execution remain available so
saved workflows can still load.
Google API/model compatibility updates and UI migration have ended. Existing
workflows still depend on Google's service and the optional `google-genai`
package; deprecation does not guarantee future API compatibility. See the
[legacy Gemini documentation](examples/documentation/gemini_prompt.md) for the
existing configuration and behavior.
#### 🔍 Display Any
Universal debugging node that displays any type of input value or tensor information.
- **Universal Input Acceptance**: Works with any data type (tensors, strings, numbers, lists, dicts)
- **Two Display Modes**: Raw value showing string representation, or tensor shape extraction
- **Nested Structure Support**: Finds tensors within complex nested data structures
- **Debugging Focus**: Essential tool for understanding data flow and tensor dimensions
- **Clean Output**: Formatted display directly in ComfyUI interface
**Use Cases:**
- Debug tensor dimensions at any point in workflow
- Inspect latent space data structures
- View metadata and configuration objects
- Track shape changes through processing nodes
- Understand complex data types in ComfyUI
![Display Any Example](examples/workflows/display_any_example.png)
#### 🖼️ Image to Multiple Of
Adjusts image dimensions to be multiples of a specified value for model compatibility.
- **Dimension Adjustment**: Ensures image dimensions are multiples of specified value (e.g., 64, 128)
- **Two Processing Methods**: Center crop for minimal loss, or rescale to fit
- **Model Compatibility**: Essential for models requiring specific dimension constraints
- **Flexible Multiple Values**: Support from 1 to 256 with 16-step increments
- **Preserves Quality**: Smart processing maintains image quality
**Use Cases:**
- Prepare images for VAE encoding (multiple of 8 requirement)
- Ensure compatibility with specific model architectures
- Standardize dimensions across image batches
- Fix dimension errors in complex workflows
- Optimize for tiled processing requirements
![Image to Multiple Of Example](examples/workflows/image_to_multiple_of_example.png)
#### 📉 Image Scale Down By
Efficiently scale images down by a specified factor with quality preservation.
- **Proportional Scaling**: Reduces both width and height by the same factor
- **Quality Preservation**: Uses bilinear interpolation with antialiasing
- **Batch Support**: Process multiple images simultaneously
- **Memory Efficient**: Optimized for large image batches
- **Flexible Factor**: Scale from 0.01x to 1.0x with 0.01 precision
**Use Cases:**
- Create thumbnails or preview images
- Reduce memory usage for large workflows
- Generate image pyramids for multi-scale processing
- Quick downsampling for performance optimization
- Prepare images for web display or transmission
#### 🎬 Film Grain
Add realistic analog film grain effects to generated images.
- **Realistic Grain Simulation**: Mimics actual film photography characteristics
- **Grain Size Control**: Fine to coarse grain patterns (0.25x to 2.0x)
- **Intensity Adjustment**: Variable strength from subtle to pronounced (0-10)
- **Color Saturation**: Monochrome to full color grain (0-2)
- **Shadow Lifting (Toe)**: Film-like shadow response curves
- **Red Multiplier**: Adjust red channel independently for vintage looks
- **Alpha Preservation**: Maintains transparency when present
- **ITU-R BT.709 Color Space**: Professional color handling
**Use Cases:**
- Add vintage film aesthetic to AI-generated images
- Create cinematic looks with authentic grain patterns
- Simulate different film stocks (35mm, 16mm, etc.)
- Add texture to overly smooth AI renders
- Match grain from reference photography
#### 🎛️ Flux Sampler Params
FLUX-optimized parameter generator with intelligent batch processing capabilities.
- **FLUX-Specific Tuning**: Optimized guidance, shift values, and step counts for FLUX models
- **Batch Parameter Testing**: Generate multiple parameter sets for comparative analysis
- **LoRA Integration**: Seamlessly combine with LoRA Folder Batch for comprehensive testing
- **Smart Defaults**: Pre-configured optimal settings based on extensive FLUX testing
- **Range Syntax Support**: Use `start...end+step` notation for parameter sweeps
**Use Cases:**
- Test different guidance and shift value combinations
- Batch process with varying parameters
- Optimize FLUX generation quality
- Integrate with LoRA testing workflows
#### 📁 LoRA Folder Batch
Automated batch processing for multiple LoRA models from folders.
- **Automatic Scanning**: Discovers all .safetensors files in specified folders
- **Natural Epoch Sorting**: Intelligently sorts training epochs (epoch_004, epoch_020, etc.)
- **Pattern Filtering**: Include/exclude LoRAs using powerful regex patterns
- **Flexible Strength Control**: Single, multiple, or range-based strength values
- **Batch Modes**: Sequential or combinatorial strength application
- **Epoch Detection**: Automatically extracts epoch numbers from filenames
**Use Cases:**
- Test all epochs from a training run
- Compare different LoRA versions
- Evaluate strength variations
- Batch process style transfers
![LoRA Folder Batch Example](examples/workflows/xyz_helpers_lora_testing.png)
#### 🔁 Checkpoint Folder Iterator
Run the same prompt through every checkpoint in a folder, one model per queued run.
- **folder**: relative to `models/checkpoints`, or an absolute path anywhere on disk; regex include/exclude filters
- **index** with `control_after_generate`: set 0 + *increment*, then queue a batch count equal to the checkpoint count
- Queuing past the last checkpoint fails that run with a clear message instead of wrapping around, so the finished sweep is never restarted or archived by accident
- Outputs MODEL/CLIP/VAE for the indexed file plus `ckpt_name`, `label`, `index`, `count`
- Only one checkpoint is loaded at a time, so large folders do not exhaust RAM
#### 🧩 Model Compare Sheet
Output node that pairs with Checkpoint Folder Iterator.
- Saves each run's image(s) to `output/model_compare/<run_name>/<NN>_<label>.png`
- Rebuilds `grid.png` from every tile saved so far, each labeled with its filename
- `columns`, `tile_width`, `font_size` control the layout; the grid is also returned as IMAGE
- The node preview (and anything else that reads it) gets `grid_preview.png`, at most 1024 px wide; `grid.png` keeps full resolution
- After the last queued run the preview shows the complete sheet
- Wire Project File Path into `run_name` for `project/date/sweep` folders
- A run at index 0 moves tiles already in the folder to `<run_name>_<timestamp>`, so an interrupted sweep never leaks into the new grid
- Rerunning a nonzero index replaces its complete batch, even when the label or batch size changes; other indexes are preserved
- Replacement PNGs are fully written before existing tiles are changed; a failed tile write preserves the prior tiles and grid
#### 📊 Plot Parameters
Visual analysis tool for understanding parameter relationships and effects.
- **Multiple Plot Types**: Line, bar, scatter, and heatmap visualizations
- **Parameter Correlation**: Analyze relationships between settings and quality
- **Statistical Analysis**: Calculate means, deviations, and trends
- **Export Capabilities**: Save plots as images or CSV data
- **Real-time Updates**: Dynamic graph generation during workflow execution
**Use Cases:**
- Visualize parameter impact on quality
- Compare batch generation results
- Analyze optimal parameter ranges
- Document generation experiments
#### 🎯 Sampler Select Helper
Intelligent sampler selection with model-aware recommendations.
- **Model Detection**: Automatic identification of SDXL, SD1.5, or FLUX models
- **Quality Presets**: Fast, balanced, quality, and extreme presets
- **Compatibility Checking**: Ensures optimal sampler-scheduler pairs
- **Performance Profiles**: Pre-configured settings for different use cases
- **Dynamic Discovery**: Adapts to newly available samplers
**Use Cases:**
- Automatic optimal sampler selection
- Quick quality vs speed adjustments
- Model-specific optimization
- A/B testing different samplers
#### 📅 Scheduler Select Helper
Optimal scheduler selection based on sampler and model requirements.
- **Sampler-Aware**: Recommends best schedulers for each sampler
- **Noise Schedule Visualization**: Preview and compare schedule curves
- **Model Optimization**: Specific tuning for SDXL, SD1.5, and FLUX
- **Schedule Types**: Smooth, sharp, linear, and custom curves
- **Beta Schedule Support**: Advanced control with custom beta values
**Use Cases:**
- Find optimal scheduler for your sampler
- Visualize noise reduction curves
- Compare different schedule types
- Fine-tune generation behavior
#### ✍️ Text Encode Sampler Params
Unified interface for text encoding and sampler parameter management.
- **All-in-One Node**: Combine prompt encoding with sampling configuration
- **Template System**: Pre-configured settings for portraits, landscapes, etc.
- **Prompt Syntax Support**: Wildcards, emphasis, and alternation
- **Batch Processing**: Handle multiple prompts efficiently
- **Model-Aware Encoding**: Optimize for different text encoders
**Use Cases:**
- Streamline text-to-image workflows
- Apply consistent settings across prompts
- Quick template-based generation
- Batch prompt processing
#### 📂 Local Image Loader
Visual gallery browser for loading local images, videos, and audio files directly into ComfyUI workflows.
- **Visual Gallery Interface**: Browse files with thumbnail previews in a masonry layout
- **Multi-Media Support**: Load images (JPG, PNG, GIF, WebP), videos (MP4, WebM, MOV), and audio files (MP3, WAV, OGG, FLAC)
- **Quick Navigation**: Navigate folders with breadcrumb path and parent directory button
- **Responsive Layout**: Automatically adjusts thumbnail grid to available space
- **Metadata Extraction**: Reads embedded prompt and workflow data from generated images
- **Saved Paths**: Remember frequently used directories for quick access
- **Double-Click Preview**: Open full-size media in new browser tab
- **Smart Sorting**: Sort by name, date, or file size in ascending or descending order
- **Pagination Support**: Efficiently browse large directories with page controls
**Use Cases:**
- Load reference images from local folders for img2img workflows
- Browse and select from collections of generated images
- Quickly access frequently used asset directories
- Extract prompts and settings from previously generated images
- Preview media files before loading into workflow
#### 🌐 Model Downloader
Download models, LoRAs, and other assets directly from CivitAI, HuggingFace, and custom URLs within ComfyUI.
- **Multi-Platform Support**: CivitAI, HuggingFace, and direct download URLs
- **Smart URL Detection**: Automatic detection of download source and file handling
- **API Token Support**: Optional authentication for private/gated models
- **Progress Reporting**: Real-time download progress with speed indicators
- **Resume Support**: Skip existing files or force re-download
- **Interrupt Handling**: Respects ComfyUI's "Cancel current run" button
- **Automatic Cleanup**: Removes partial downloads on cancellation
- **Custom Filenames**: Override auto-detected filenames when needed
**Platform Features:**
- **CivitAI**: Model page URLs, version-specific downloads, API authentication
- **HuggingFace**: Blob and resolve URLs, branch/revision support, gated model access
- **Custom URLs**: Direct download links with bearer token authentication
**Use Cases:**
- Download models without leaving ComfyUI
- Automate asset acquisition in workflows
- Access private or gated models with API tokens
- Build reproducible workflows with automatic model fetching
- Quickly test new models from the community
#### ⏱️ Workflow Timer
Real-time execution timer that displays workflow duration with millisecond precision.
- **Live Timing**: Updates in real-time during workflow execution (MM:SS:mmm format)
- **Customizable Color**: Choose your preferred display color via KikoTools settings
- **Glow Effect**: Optional pulsing glow animation (can be enabled/disabled in settings)
- **Global Settings**: Color and glow preferences apply to all timer nodes
- **Persistent Display**: Shows final execution time after workflow completes
- **Multi-Node Sync**: All timer nodes stay synchronized during execution
**Use Cases:**
- Monitor workflow execution performance
- Compare generation times across different settings
- Identify slow nodes by adding timers at different workflow stages
- Track optimization improvements over time
**Settings (KikoTools Settings Panel):**
- **Workflow Timer: Color** - Custom color picker for timer display
- **Workflow Timer: Enable Glow** - Toggle pulsing glow effect on/off
#### 🎨 Custom Colors
Right-click a node and choose **🫶 Custom Colors** to change its full color,
title or background. The shared picker supports hex entry, HSV sliders and
presets, and follows ComfyUI's dark/light theme. Changes apply immediately;
**Done** or **Escape** closes the picker. Multiple selected nodes receive the
same change. Right-click a group for its color option.
Existing Custom Colors settings control the available menu options and automatic
title shading. Node and group colors are saved with the workflow.
Screenshots: [Dark](docs/screenshots/custom-colors/dark.png) ·
[Light](docs/screenshots/custom-colors/light.png).
#### 🧭 Follow Execution
Enable **Follow Execution: Enable** in ComfyUI settings, then right-click the
canvas to follow/stop execution, show the executing node, or navigate to a node
by type. **Auto-start** begins following when a workflow starts. Disabling the
feature stops following immediately; enabling it again waits for a manual start
or the next workflow start when Auto-start is enabled. The same host menus work
in Classic and Nodes 2.0. See the [Classic](docs/screenshots/follow-execution/classic.png)
and [Nodes 2.0](docs/screenshots/follow-execution/nodes2.png) menu screenshots.
#### 🗂️ Project File Path
Builds an organized save prefix to wire into a save node's `filename_prefix`.
- **Structure**: `root/project_name/[YYYY-MM-DD/]subfolder/filename`, e.g. `MyProject/2026-10-13/images/image`
- **Date Folder**: Optional toggle inserts today's date after the project; the node re-runs when the date changes
- **Safe Paths**: Unsafe characters are removed, spaces become underscores, and `..` segments are dropped so paths stay inside the output folder
- **Nested Folders**: Use `a/b` in `subfolder` for deeper structure
- **Extensions**: Known file extensions (`.png`, `.mp4`, ...) are stripped from `filename`; `render_v1.5` is left as is
- **Separator**: `auto`, `forward_slash` or `backslash`
Based on the idea of the Project File Path Generator from djz-nodes.
### 🔤 Embedding Autocomplete
**Intelligent autocomplete for embeddings, LoRAs, and custom tags in text prompts.**
<div align="center">
<img src="https://github.com/ComfyAssets/ComfyUI-KikoTools/blob/main/examples/ac-emb.png?raw=true" width="30%" alt="Embedding Autocomplete" />
<img src="https://github.com/ComfyAssets/ComfyUI-KikoTools/blob/main/examples/ac-lora.png?raw=true" width="30%" alt="LoRA Autocomplete" />
<img src="https://github.com/ComfyAssets/ComfyUI-KikoTools/blob/main/examples/ac-tag.png?raw=true" width="30%" alt="Tag Autocomplete" />
</div>
This feature is an enhanced fork of the autocomplete functionality from [ComfyUI-Custom-Scripts](https://github.com/pythongosssss/ComfyUI-Custom-Scripts) by pythongosssss. We've modernized the codebase, fixed existing bugs, and added robust security features.
**Key Features:**
- **Smart Triggers**: Type `embedding:` for embeddings, `<lora:` for LoRAs, or just start typing for tags
- **Custom Word Lists**: Import CSV/TXT files or browser-accessible URLs, including [versioned Danbooru tag packs](docs/tag-packs/README.md), with local offline caching
- **Safe Rendering**: Tag data stays plain text, with validated CSV fields and file-size limits
- **Flexible Settings**: Customize triggers, auto-insert commas, replace underscores, and more
- **Performance Optimized**: Handles 100,000+ tags smoothly with frequency-based sorting
- **Visual Polish**: Clean UI with proper scrolling, keyboard navigation, and type indicators
**Settings Include:**
- Enable/disable autocomplete for embeddings, LoRAs, and custom tags
- Configurable trigger phrases (e.g., `emb:`, `lora:`, custom shortcuts)
- Auto-insert comma after completion
- Replace underscores with spaces in tags
- Choose insertion keys (Tab, Enter, or both)
- Load custom word lists from URLs with security validation
**Security Features:**
- Validates CSV structure, tag names, categories, counts, and file size
- Renders tag data as plain text rather than executable HTML or JavaScript
- Rejects control characters and HTML error documents
- File size limits to prevent memory exhaustion
- Clear error messages for rejected content
**Credits:**
- Original autocomplete concept by [pythongosssss](https://github.com/pythongosssss/ComfyUI-Custom-Scripts)
- Enhanced and modernized by KikoTools team
### 🧹 Kiko Purge VRAM
**Intelligent GPU memory management with threshold-based triggering and detailed reporting.**
**Key Features:**
- **4 Purge Modes**:
- `soft`: Basic garbage collection and cache clearing
- `aggressive`: Multiple GC passes with full CUDA cache clearing
- `models_only`: Unload all models and clear model cache
- `cache_only`: Clear CUDA cache without garbage collection
- **Smart Thresholds**: Only purge when memory usage exceeds specified MB limit
- **Detailed Reporting**: Shows before/after memory usage, freed MB, and timing
- **Passthrough Design**: Acts as workflow checkpoint without disrupting data flow
- **CPU Fallback**: Gracefully handles non-CUDA environments
**Use Cases:**
- Free memory between heavy processing stages
- Prevent OOM errors in complex workflows
- Debug memory usage patterns
- Optimize multi-model workflows
- Clean up after batch processing
**Parameters:**
- **anything**: Any input (passed through unchanged)
- **mode**: Purge strategy selection
- **report_memory**: Generate detailed memory statistics
- **memory_threshold_mb**: Only purge if usage exceeds (0 = always purge)
**Example Output:**
```
Memory usage (5000.0 MB) exceeds threshold (4000 MB)
Memory Purge Report
-------------------
Mode: soft
Memory Freed: 2500.0 MB
Before: 5000.0 MB used (62.5%)
After: 2500.0 MB used (31.3%)
Time: 150.0ms
```
### 💾 Kiko Save Image Features
**Use Cases:**
- Quick preview and management of saved images without file browser navigation
- Compare multiple format outputs side-by-side (PNG vs JPEG vs WebP)
- Batch download or open selected images efficiently
- Monitor file sizes and compression effectiveness in real-time
- Streamlined workflow for iterative image generation and saving
**Why Better Than Standard Save Image:**
- **Immediate Visual Feedback**: See your saved images instantly without opening file explorer
- **Multi-Format Flexibility**: Choose optimal format for your use case (PNG for quality, JPEG for size, WebP for modern efficiency)
- **Advanced Compression Control**: Fine-tune file sizes with format-specific quality settings
- **Batch Operations**: Handle multiple images efficiently with selection and bulk actions
- **Modern UI**: Floating, draggable interface that doesn't interrupt your workflow
- **Smart Memory Usage**: File size indicators help optimize storage and sharing
- **One-Click Access**: Direct image opening in browser tabs for quick sharing or review
### 🔧 Architecture Highlights
- **Modular Design**: Each tool is self-contained and independently testable
- **Test-Driven Development**: 100% test coverage with comprehensive unit tests
- **Test-Driven Development**: Regression tests for Python logic, JavaScript extensions, and browser workflows
- **Clean Interfaces**: Standardized input/output patterns across all tools
- **Separation of Concerns**: Clear distinction between logic, UI, and integration layers
- **SOLID Principles**: Extensible architecture following software engineering best practices
@@ -113,8 +649,8 @@ Image Loader → Resolution Calculator → Upscaler
↘ scale_factor: 1.5 ↗
```
**Input:** 832×1216 (SDXL portrait format)
**Scale:** 1.5x
**Input:** 832×1216 (SDXL portrait format)
**Scale:** 1.5x
**Output:** 1248×1824 (ready for upscaling)
### Width Height Selector Example
@@ -125,8 +661,8 @@ preset: "1920×1080" ↘ 1920×1080 ↗
[swap button]
```
**Preset:** FLUX HD (1920×1080)
**Output:** 1920×1080 (16:9 cinematic)
**Preset:** FLUX HD (1920×1080)
**Output:** 1920×1080 (16:9 cinematic)
**Swap Button:** Click to get 1080×1920 (9:16 portrait)
### Seed History Example
@@ -137,8 +673,8 @@ Seed History → KSampler → VAE Decode → Save Image
[History UI: 54321, 99999, 11111...]
```
**Current Seed:** 12345
**History:** Auto-tracked previous seeds with timestamps
**Current Seed:** 12345
**History:** Auto-tracked previous seeds with timestamps
**Interaction:** Click any historical seed to reload instantly
### Sampler Combo Example
@@ -148,10 +684,92 @@ Sampler Combo → KSampler → VAE Decode → Save Image
⚙️ All Settings ↘ sampler/scheduler/steps/cfg ↗
```
**Configuration:** euler, normal, 20 steps, CFG 7.0
**Output:** Complete sampling configuration in one node
**Configuration:** euler, normal, 20 steps, CFG 7.0
**Output:** Complete sampling configuration in one node
**Smart Features:** Recommendations and compatibility validation
### Empty Latent Batch Example
```
Empty Latent Batch → KSampler → VAE Decode → Kiko Save Image
📦 preset: "1024×1024" ↘ batch latents ↗ ↘ popup viewer ↗
batch_size: 4
[swap button]
```
**Preset:** SDXL Square (1024×1024)
**Batch Size:** 4 empty latents
**Output:** 4×4×128×128 latent tensor ready for sampling
**Swap Button:** Click to switch to any available swapped preset
### Kiko Save Image Example
```
Generate Image → Kiko Save Image → Floating Popup Viewer
📷 output ↘ format: WEBP ↘ draggable window ↗
quality: 85
[popup: enabled]
```
**Format:** WebP (efficient compression, modern format)
**Quality:** 85% (balanced size/quality)
**Popup Viewer:** Floating, draggable window with saved images
**Features:** Click images to open in new tabs, download individual files, batch selection
**Advantages:** Immediate preview without file explorer, multi-format comparison, advanced quality controls
### Display Text Example
```
Gemini Prompt → Display Text → Copy to Clipboard
📋 SDXL prompt ↘ auto-split ↘ [📋 Positive] [📋 Negative]
view → formatted display
```
**Input:** Text with "Positive prompt:" and "Negative prompt:" sections
**Output:** Split view with individual copy buttons
**Features:** Text wrapping, scrolling, responsive resizing
**Smart Detection:** Automatically formats SDXL-style prompts
### Gemini Prompt Engineer Example
**Legacy example for the deprecated node; existing workflows only.**
```
Load Image → Gemini Prompt → Display Text → Text Generation Model
🖼️ reference ↘ type: SDXL ↘ split view ↘ "detailed portrait..."
[Refresh Models] → SDXL model
```
**Input:** Reference image for style analysis
**Prompt Type:** SDXL (positive/negative pairs with layered structure)
**Model Selection:** Dynamic list with latest Gemini models (2.0, 2.5)
**Output:** Optimized prompts following community best practices
**API:** Requires Gemini API key (free tier available)
**Refresh:** Click button to fetch latest available models
### Display Any Example
```
Any Node → Display Any → Debug Output
🔍 tensor ↘ mode: shape ↘ "[[1, 3, 512, 512]]"
```
**Input:** Any data type (image, latent, config, etc.)
**Mode:** "raw value" or "tensor shape"
**Output:** Formatted display of value or tensor dimensions
**Use Case:** Debug workflows, inspect data structures
### Image to Multiple Of Example
```
Load Image → Image to Multiple Of → VAE Encode → KSampler
🖼️ 513×769 ↘ multiple: 64 ↘ 512×768 → latent
method: crop
```
**Input:** Image with arbitrary dimensions
**Multiple Of:** 64 (common for VAE compatibility)
**Method:** "center crop" or "rescale"
**Output:** Adjusted image with compatible dimensions
### Common Workflows
<details>
@@ -160,7 +778,7 @@ Sampler Combo → KSampler → VAE Decode → Save Image
```json
{
"workflow": "Load SDXL portrait → Calculate 1.5x dimensions → Feed to upscaler",
"input_resolution": "832×1216",
"input_resolution": "832×1216",
"scale_factor": 1.5,
"output_resolution": "1248×1824",
"memory_efficient": true
@@ -175,13 +793,28 @@ Sampler Combo → KSampler → VAE Decode → Save Image
{
"workflow": "Generate latents → Calculate target size → Batch upscale",
"input_resolution": "1024×1024",
"scale_factor": 2.0,
"scale_factor": 2.0,
"output_resolution": "2048×2048",
"batch_optimized": true
}
```
</details>
<details>
<summary><b>LoRA Testing with xyz-helpers</b></summary>
```json
{
"workflow": "Scan LoRA folder → Apply strength ranges → Generate grid → Plot parameters",
"strength_range": "0.9...1.2+0.1",
"batch_mode": "combinatorial",
"features": ["automatic epoch sorting", "parameter visualization", "batch generation"]
}
```
Example workflow available: [xyz_helpers_lora_testing.json](examples/workflows/xyz_helpers_lora_testing.json)
</details>
## 📚 Documentation
### Available Tools
@@ -192,6 +825,24 @@ Sampler Combo → KSampler → VAE Decode → Save Image
| **Width Height Selector** | Preset-based dimension selection with 26 curated options | ✅ Complete | [Docs](examples/documentation/width_height_selector.md) |
| **Seed History** | Advanced seed tracking with interactive history management | ✅ Complete | [Docs](examples/documentation/seed_history.md) |
| **Sampler Combo** | Unified sampling configuration with smart recommendations | ✅ Complete | [Docs](examples/documentation/sampler_combo.md) |
| **Empty Latent Batch** | Create empty latent batches with preset support | ✅ Complete | [Docs](examples/documentation/empty_latent_batch.md) |
| **Model Resolution Picker** | Model family size picker with ratio chips, empty latent and width/height outputs | ✅ Complete | [Docs](examples/documentation/model_resolution_picker.md) |
| **Kiko Save Image** | Enhanced image saving with popup viewer and multi-format support | ✅ Complete | [Docs](examples/documentation/kiko_save_image.md) |
| **Display Text** | Advanced text display with smart prompt detection and split view | ✅ Complete | [Docs](examples/documentation/display_text.md) |
| **Gemini Prompt Engineer** | Legacy image analysis and prompt generation | Deprecated | [Docs](examples/documentation/gemini_prompt.md) |
| **Display Any** | Universal debugging tool for any data type or tensor shapes | ✅ Complete | [Docs](examples/documentation/display_any.md) |
| **Image to Multiple Of** | Adjust image dimensions to multiples for model compatibility | ✅ Complete | [Docs](examples/documentation/image_to_multiple_of.md) |
| **Image Scale Down By** | Efficiently scale images down by a specified factor | ✅ Complete | [Docs](#-image-scale-down-by) |
| **Film Grain** | Add realistic analog film grain effects to images | ✅ Complete | [Docs](#-film-grain) |
| **Flux Sampler Params** | FLUX-optimized parameter generator with batch support | ✅ Complete | [Docs](examples/documentation/flux_sampler_params.md) |
| **LoRA Folder Batch** | Batch process multiple LoRAs from folders | ✅ Complete | [Docs](examples/documentation/lora_folder_batch.md) |
| **Plot Parameters** | Visualize parameter effects with graphs | ✅ Complete | [Docs](examples/documentation/plot_parameters.md) |
| **Sampler Select Helper** | Intelligent sampler selection with recommendations | ✅ Complete | [Docs](examples/documentation/sampler_select_helper.md) |
| **Scheduler Select Helper** | Optimal scheduler selection for samplers | ✅ Complete | [Docs](examples/documentation/scheduler_select_helper.md) |
| **Text Encode Sampler Params** | Combined text encoding and parameter management | ✅ Complete | [Docs](examples/documentation/text_encode_sampler_params.md) |
| **Local Image Loader** | Visual gallery browser for local media files | ✅ Complete | [Docs](examples/documentation/local_image_loader.md) |
| **Model Downloader** | Download models from CivitAI, HuggingFace, and custom URLs | ✅ Complete | [Docs](#-model-downloader) |
| **Workflow Timer** | Real-time execution timer with customizable display | ✅ Complete | [Docs](#️-workflow-timer) |
| **Batch Image Processor** | Process multiple images with consistent settings | 🚧 Planned | Coming Soon |
| **Advanced Prompt Utilities** | Enhanced prompt manipulation and generation | 🚧 Planned | Coming Soon |
@@ -201,7 +852,7 @@ Sampler Combo → KSampler → VAE Decode → Save Image
**Inputs:**
- `scale_factor` (FLOAT): 1.0-8.0, default 2.0
- `image` (IMAGE, optional): Input image tensor
- `image` (IMAGE, optional): Input image tensor
- `latent` (LATENT, optional): Input latent tensor
**Outputs:**
@@ -223,7 +874,7 @@ Sampler Combo → KSampler → VAE Decode → Save Image
**Outputs:**
- `width` (INT): Selected or calculated width
- `height` (INT): Selected or calculated height
- `height` (INT): Selected or calculated height
**UI Features:**
- Visual blue swap button in bottom-right corner
@@ -267,7 +918,7 @@ Sampler Combo → KSampler → VAE Decode → Save Image
**Outputs:**
- `sampler_name` (STRING): Selected sampler algorithm
- `scheduler` (STRING): Selected scheduler algorithm
- `scheduler` (STRING): Selected scheduler algorithm
- `steps` (INT): Validated step count
- `cfg` (FLOAT): Validated CFG scale
@@ -278,11 +929,76 @@ Sampler Combo → KSampler → VAE Decode → Save Image
- Graceful error handling with safe defaults
- Comprehensive tooltips for user guidance
#### Empty Latent Batch
**Inputs:**
- `preset` (DROPDOWN): 26 preset options + custom with formatted metadata display
- `width` (INT): 64-8192, step 8, default 1024
- `height` (INT): 64-8192, step 8, default 1024
- `batch_size` (INT): 1-64, default 1
**Outputs:**
- `latent` (LATENT): Batch of empty latent tensors in ComfyUI format
- `width` (INT): Final sanitized width (divisible by 8)
- `height` (INT): Final sanitized height (divisible by 8)
**UI Features:**
- Visual blue swap button with hover and click feedback
- Intelligent preset switching when swapping dimensions
- Memory usage estimation and warnings for large batches
- Auto-update width/height widgets when presets change
**Batch Processing:**
- Creates tensors with shape: [batch_size, 4, height//8, width//8]
- Efficient memory allocation with torch.zeros
- Validates batch size limits (1-64) with performance warnings
- Compatible with all ComfyUI latent processing nodes
**Preset Integration:**
- Full access to 26 curated resolution presets from Width Height Selector
- Model-aware categorization (SDXL, FLUX, Ultra-wide)
- Formatted display with aspect ratio and megapixel information
- Intelligent fallback to custom dimensions for invalid presets
#### Kiko Save Image
**Inputs:**
- `images` (IMAGE): Batch of images to save
- `filename_prefix` (STRING): Prefix for saved filenames, default "KikoSave"
- `format` (DROPDOWN): Output format (PNG, JPEG, WEBP), default PNG
- `quality` (INT): JPEG/WebP quality (1-100), default 90
- `png_compress_level` (INT): PNG compression level (0-9), default 4
- `webp_lossless` (BOOLEAN): Use lossless WebP compression, default False
- `popup` (BOOLEAN): Enable popup viewer window, default True
**Outputs:**
- `UI`: Enhanced image preview data with popup viewer functionality
**UI Features:**
- Floating, draggable popup window showing saved images immediately
- Interactive image grid with click-to-open functionality
- Individual image download buttons with format-specific quality indicators
- Batch selection with multi-select checkboxes for bulk operations
- Window controls: minimize, maximize, roll-up, close, and dragging
- Auto-hide/show behavior with smart positioning
**Format Support:**
- **PNG**: Lossless compression with metadata preservation, configurable compression levels
- **JPEG**: Quality-controlled lossy compression with automatic transparency handling
- **WebP**: Modern format with both lossy and lossless modes, superior compression ratios
**Advanced Features:**
- File size monitoring and display for optimization feedback
- Format-specific quality indicators (PNG compression level, JPEG/WebP quality percentage)
- Smart filename sanitization with timestamp-based uniqueness
- Persistent popup viewer across multiple save operations
- Toggle button integration in node UI for manual viewer control
## 🛠️ Development
### Prerequisites
- Python 3.8+
- Python 3.10+
- ComfyUI installation
- PyTorch 2.0+
@@ -300,6 +1016,9 @@ source venv/bin/activate # On Windows: venv\Scripts\activate
# Install development dependencies
pip install -r requirements-dev.txt
# Install pre-commit hooks
pre-commit install
# Run tests
python -c "
import sys, os
@@ -316,13 +1035,32 @@ print(f'✅ Development setup successful! Test result: {result[0]}x{result[1]}')
### Code Quality
We maintain high code quality standards:
We maintain high code quality standards with automated pre-commit hooks:
#### Pre-commit Hooks
Our pre-commit configuration automatically runs:
- **Black**: Code formatting (127 char line length)
- **Flake8**: Linting and style checks
- **Bandit**: Security vulnerability scanning
- **detect-secrets**: Prevents accidental secret commits
- File checks: trailing whitespace, YAML validation, merge conflicts
```bash
# Run all pre-commit hooks manually
pre-commit run --all-files
# Update hooks to latest versions
pre-commit autoupdate
```
#### Manual Code Quality Checks
```bash
# Format code
black .
# Lint code
# Lint code
flake8 .
# Type checking
@@ -345,8 +1083,8 @@ Following **Test-Driven Development (TDD)**:
# Test structure
tests/
├── unit/ # Individual component tests
├── integration/ # ComfyUI workflow tests
└── fixtures/ # Test data and workflows
├── js/ # Extension and coverage-gate regression tests
└── browser/ # Browser workflows and disposable host fixtures
```
### Adding New Tools
@@ -387,7 +1125,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
## 🏷️ Tags
`comfyui` `custom-nodes` `image-processing` `ai-tools` `sdxl` `flux` `upscaling` `resolution` `batch-processing` `python` `pytorch`
`comfyui` `custom-nodes` `image-processing` `ai-tools` `sdxl` `flux` `upscaling` `resolution` `batch-processing` `model-downloader` `civitai` `huggingface` `python` `pytorch`
## 🔗 Links
@@ -398,13 +1136,40 @@ MIT License - see [LICENSE](LICENSE) file for details.
## 📈 Stats
- **Nodes**: 4 (Resolution Calculator, Width Height Selector, Seed History, Sampler Combo)
- **Registered Nodes**: 33, including the deprecated Gemini node
- **Extensions**: Embedding Autocomplete, Custom Colors and Follow Execution
- **Organization**: Tools grouped under ComfyAssets
- **Download Platforms**: 3 (CivitAI, HuggingFace, Custom URLs)
- **Format Support**: 3 (PNG, JPEG, WebP with advanced controls)
- **Presets**: 26 curated resolution presets
- **Interactive Features**: 2 (Swap Button, History UI)
- **Test Coverage**: 100% (180+ comprehensive tests)
- **Python Version**: 3.8+
- **ComfyUI Compatibility**: Latest
- **Dependencies**: Minimal (PyTorch, NumPy)
- **Interactive Features**: 8+ (swap buttons, history UI, popup viewers, parameter visualization)
- **Legacy AI Integration**: Deprecated Gemini node retained for saved workflows
- **Test Coverage**: Enforced 85% Python statement/branch coverage and per-file frontend coverage floors
- **Python Version**: 3.10+
- **ComfyUI Compatibility**: Classic and Nodes 2.0; tested runtime pins in [TESTING.md](TESTING.md)
- **Dependencies**: Minimal (PyTorch, NumPy, Pillow, google-genai for Gemini)
## 🙏 Attribution
### UI inspiration
[Pixaroma's ComfyUI toolkit](https://github.com/pixaroma/ComfyUI-Pixaroma)
inspired the reusable controls and editor organization. ComfyAssets uses its own
implementation and styling; no Pixaroma toolkit code or assets are vendored.
### xyz-helpers Tools
The xyz-helpers collection was adapted from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) by cubiq. The original project is in maintenance-only mode, and we've adopted these essential tools to ensure continued support and compatibility with modern ComfyUI workflows. We're grateful for cubiq's original work and contributions to the ComfyUI community.
The following tools are based on comfyui-essentials-nodes:
- Flux Sampler Params
- LoRA Folder Batch
- Plot Parameters
- Sampler Select Helper
- Scheduler Select Helper
- Text Encode Sampler Params
All adaptations maintain compatibility while adding new features and optimizations for the ComfyAssets ecosystem.
---
@@ -414,4 +1179,9 @@ MIT License - see [LICENSE](LICENSE) file for details.
[⭐ Star this repo](https://github.com/ComfyAssets/ComfyUI-KikoTools) • [🐛 Report Bug](https://github.com/ComfyAssets/ComfyUI-KikoTools/issues) • [💡 Request Feature](https://github.com/ComfyAssets/ComfyUI-KikoTools/issues)
</div>
</div>
## Automated testing
See [TESTING.md](TESTING.md) for Python and JavaScript tests, cross-browser
workflows, coverage gates, CI reports, and required merge checks.
+66
View File
@@ -0,0 +1,66 @@
# Security Policy
## Supported Versions
ComfyUI-KikoTools is actively maintained. We provide security updates for the following versions:
| Version | Supported |
| ------- | ------------------ |
| 1.x.x | :white_check_mark: |
| < 1.0 | :x: |
## Reporting a Vulnerability
We take the security of ComfyUI-KikoTools seriously. If you believe you have found a security vulnerability, please report it to us as described below.
### How to Report
Please report security vulnerabilities by [opening a new issue](https://github.com/ComfyAssets/ComfyUI-KikoTools/issues/new) with the following:
- Use the title prefix `[SECURITY]`
- Provide a clear description of the vulnerability
- Include steps to reproduce the issue
- Specify the version(s) affected
- If possible, suggest a fix or mitigation
### What to Expect
- **Response Time**: We aim to acknowledge receipt within 48 hours
- **Investigation**: We will investigate and validate the reported vulnerability
- **Updates**: We will keep you informed about the progress
- **Resolution**: Once verified, we will work on a fix and release it as soon as possible
- **Credit**: We will acknowledge your contribution in the release notes (unless you prefer to remain anonymous)
### Scope
Security vulnerabilities in scope include:
- Code execution vulnerabilities in node implementations
- Path traversal or file system access issues
- API key or credential exposure
- Dependency vulnerabilities that affect the project
- Any issue that could compromise user data or system security
### Out of Scope
The following are generally not considered security vulnerabilities:
- Issues in ComfyUI core (report these to the ComfyUI project)
- Performance issues
- Bugs that don't have security implications
- Feature requests
## Security Best Practices
When using ComfyUI-KikoTools:
- Keep your installation up to date
- Store API keys (like Gemini API keys) securely using environment variables
- Review generated files before sharing them
- Be cautious with custom prompts that might expose sensitive information
## Contact
For urgent security matters, you can also reach out to the maintainers directly through GitHub.
Thank you for helping keep ComfyUI-KikoTools secure!
+166
View File
@@ -0,0 +1,166 @@
# Testing and merge confidence
Use regression tests before fixing a bug. Confirm that the test fails on the
original implementation, then run the relevant suites after the fix.
## What runs
| Layer | Real components | Replaced boundaries |
| --- | --- | --- |
| Python | Tool logic, tensors, image files, API validation | ComfyUI services and external models where needed |
| JavaScript | Production extension hooks and callback behavior | Minimal host objects; controlled timers for focused regressions |
| Browser fixtures | All shipped extension imports and lifecycle registration; real plugin startup, HTTP routes, PNG thumbnails, selection persistence, and image loading | ComfyUI graph/widgets/settings/event interfaces and its saved-image serving endpoint |
| Real ComfyUI | Pinned server/frontend, LiteGraph, native menus, DOM widgets, initial renderers, switching and workflow serialization | Disposable media routes; simulated execution/progress events; model/service boundaries |
Browser workflows cover swaps, local image browsing/selection, pagination,
sorting, hidden folders, directory autocomplete, saved paths,
denied paths and network recovery, display output, seed persistence/cleanup, and
workflow timing, autocomplete insertion, and saved-image previews. They run in Chromium, Firefox, WebKit, and touch Chromium.
The harness binds to localhost and writes configuration, selections, and fixture
images only inside a temporary directory. External browser requests fail tests;
custom-word downloads are disabled in the fixture. No credentials, model
weights, running ComfyUI, or GPU are required.
## Run locally
Use Python 3.10–3.13 and Node.js 24. Install the CPU PyTorch wheel on Linux if you
do not need CUDA, then install the development dependencies:
```sh
python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
python -m pip install -r requirements-dev.txt
python -m pytest tests
python -m pytest tests --cov=kikotools --cov-report=term-missing
npm ci --prefix tools/testing --ignore-scripts
npx --prefix tools/testing playwright install --with-deps chromium firefox webkit
npm test --prefix tools/testing
npm run --prefix tools/testing test:coverage
npm run --prefix tools/testing test:browser -- --project firefox --project webkit
```
`make test` runs the full pytest suite and propagates failures. `make coverage`,
`make frontend-test`, `make browser-test`, and `make frontend-coverage` provide
shortcuts. The Python Make targets use the existing virtual-environment setup.
Install local commit and push hooks with `pre-commit install --hook-type
pre-commit --hook-type pre-push`. The push hooks run Python and JavaScript
regressions; CI additionally runs all browser workflows and coverage gates.
For a quick browser check, use `test:browser -- --project chromium`.
`KIKO_TEST_PYTHON` selects a Python executable with the development dependencies;
`KIKO_TEST_PORT` changes the default port 8766. Servers are never reused.
Unsupported Linux distributions may lack WebKit libraries; Ubuntu CI validates
all engines.
## Coverage and diagnosis
Python CI checks all twelve Python/OS combinations and enforces 85% combined
statement/branch coverage. Every production `web/*.js` file appears in frontend
coverage, including code without behavioral coverage. V8 unit coverage and
Chromium desktop/touch coverage are merged through c8. The committed per-file
floors are an honest starting point, not a claim that every feature is tested.
`npm run --prefix tools/testing coverage:update` reruns the suites and raises
floors with two percentage points of runtime allowance. It cannot lower existing
floors; new source files require 80% line coverage when added with this command.
Missing measurements, missing baselines, and per-file regressions fail CI.
CI artifacts include Python JUnit results, Python HTML/XML coverage, and
`frontend-test-reports` with frontend HTML coverage and browser failure traces
and screenshots. Open traces using Playwright's `show-trace` command. Local
frontend coverage is at `coverage/js/index.html`.
The fixture suite covers plugin behavior without a real ComfyUI frontend. The
separate required real-host job covers the integration described below. GPU/model
execution, cloud inference and model downloads still need targeted checks when
changed; the compatibility suite does not queue workflows or download weights.
Type checking and security jobs retain their existing advisory behavior.
## Pinned real-ComfyUI compatibility
The required **real ComfyUI compatibility** CI job provisions a CPU-only host.
`tools/testing/comfyui-runtime.json` pins upstream commit
`e8665ca8e4089ab917567cdb491308e1397663d4` (server 0.39.0), frontend 1.55.14,
Python 3.12 and the CPU PyTorch/torchvision pair. The launcher rejects a different
revision or modified tracked upstream files. The suite verifies server/frontend
versions and attaches them to each test's report.
Two projects, `classic-start` and `nodes2-start`, select the renderer during
startup, before creating nodes. Both run the complete suite, including live
renderer switching, zoom/resize, palette changes, workflow save/reload and
cleanup. Test-local settings start from a blank workspace and skip first-run
onboarding. They do not change the server's saved preferences.
The migrated controls covered are Local Image Loader, Save Image, Seed History,
Workflow Timer and its color picker, both dimension swap nodes, Node Dimensions,
Display Text/Any, Embedding Autocomplete/settings/tag imports, Custom Colors and
Follow Execution. Native canvas menus are opened with actual right clicks.
Execution/progress events are simulated; generation is never queued.
### Managed local host
Use a separate clean checkout of the pinned ComfyUI commit and a Python 3.12
environment containing its `requirements.txt` and this repository's
`requirements-dev.txt`. Install the CPU torch/torchvision versions from the
runtime manifest first, using the official CPU wheel index. The CI workflow in
`.github/workflows/tests.yml` contains the full provisioning recipe.
```sh
KIKO_COMFY_ROOT=/path/to/pinned/ComfyUI \
KIKO_TEST_PYTHON=/path/to/cpu-venv/bin/python \
npm run --prefix tools/testing test:comfyui
```
Playwright owns this host on `127.0.0.1:8189` and a fixture server on port 8771.
The launcher creates a temporary base/user/model directory, installs only a
symlink to the current KikoTools checkout, disables other custom nodes and
partner services, and uses an in-memory database. It removes owned state after
normal shutdown, startup failure or process termination. Existing installations
and model folders are not used. An occupied port fails instead of reusing an
unrelated server.
### Existing local host
An opt-in mode still supports an already running host with the same pinned
versions:
```sh
KIKO_COMFY_URL=http://127.0.0.1:8188/ \
npm run --prefix tools/testing test:comfyui
```
Only loopback URLs are accepted. A separate browser context serves the current
checkout's extensions. Media requests use real plugin Python routes with
disposable fixture data. Other HTTP writes are intercepted, preserving server
settings, saved workflows and selections. This mode does not start or stop the
existing server.
### Reports and pin updates
CI retains `real-comfyui-reports` for 14 days, including server startup logs at
`tools/testing/test-results/comfyui-host.log`, browser traces, screenshots and
version attachments. Startup logs survive failure. Reviewed captures copied to
[docs/screenshots](docs/screenshots/README.md) are committed separately from
throwaway test artifacts.
To update the host, review and change the runtime manifest, provision the new
checkout/dependencies, and run both startup projects before merging. A version
assertion update alone is not compatibility validation. Cross-pack installation
and shared-source version compatibility remain tracked in #81.
## Required merge checks
The manifest in `.github/branch-protection.json` requires all Python matrix jobs,
Python coverage, frontend syntax, frontend workflows/coverage, real ComfyUI
compatibility, and the existing
`code-quality` job (including Black and fatal lint checks). Administrators follow
the same checks and branches must be current with main. No extra human approval
is required. Workflows run on every pull request without path filters.
After the checks pass, an administrator applies and verifies the manifest:
```sh
gh api --method PUT repos/ComfyAssets/ComfyUI-KikoTools/branches/main/protection \
--input .github/branch-protection.json
gh api repos/ComfyAssets/ComfyUI-KikoTools/branches/main/protection/required_status_checks
```
Keep the manifest synchronized with job names when changing CI.
+115 -4
View File
@@ -3,15 +3,126 @@ ComfyUI-KikoTools: Modular collection of custom ComfyUI nodes
All nodes are grouped under the "ComfyAssets" category
"""
from .kikotools import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
import re
from pathlib import Path
try:
from .kikotools import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
except ImportError:
# Fallback for testing environment
from kikotools import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
# Tell ComfyUI where to find our JavaScript extensions
WEB_DIRECTORY = "./web"
import os
WEB_DIRECTORY = os.path.join(os.path.dirname(os.path.abspath(__file__)), "web")
# Import server components at module level to ensure they're available
try:
from aiohttp import web
from server import PromptServer
import folder_paths
print("[KikoTools] Server imports successful")
# Register autocomplete endpoints directly
@PromptServer.instance.routes.get("/kikotools/autocomplete/embeddings")
async def get_embeddings(request):
"""API endpoint for getting list of embeddings with full paths."""
print("[KikoTools] Embeddings endpoint called")
try:
embedding_files = folder_paths.get_filename_list("embeddings")
print(f"[KikoTools] Found {len(embedding_files)} embedding files")
# Return embeddings with their subdirectory paths, without extensions
embeddings = []
for f in embedding_files:
# Remove extension but keep subdirectory path
clean_path = os.path.splitext(f)[0]
embeddings.append(
{
"file_name": clean_path,
"model_name": clean_path,
"name": os.path.basename(clean_path),
"path": clean_path,
}
)
if len(embeddings) > 0:
print(f"[KikoTools] Sample embedding: {embeddings[0]}")
print(f"[KikoTools] Returning {len(embeddings)} embeddings with paths")
return web.json_response(embeddings)
except Exception as e:
print(f"[KikoTools] Error getting embeddings: {e}")
import traceback
traceback.print_exc()
return web.json_response([])
@PromptServer.instance.routes.get("/kikotools/autocomplete/loras")
async def get_loras(request):
"""API endpoint for getting list of LoRAs."""
print("[KikoTools] LoRA endpoint called")
try:
lora_files = folder_paths.get_filename_list("loras")
print(f"[KikoTools] Found {len(lora_files)} LoRA files")
# Return LoRAs with paths
loras = []
for f in lora_files:
clean_path = os.path.splitext(f)[0]
loras.append(
{
"name": os.path.basename(clean_path),
"path": clean_path,
"file": f,
}
)
print(f"[KikoTools] Returning {len(loras)} LoRAs")
return web.json_response(loras)
except Exception as e:
print(f"[KikoTools] Error getting LoRAs: {e}")
import traceback
traceback.print_exc()
return web.json_response([])
print("[KikoTools] Autocomplete API endpoints registered successfully")
print(
"[KikoTools] Routes available: /kikotools/autocomplete/embeddings and /kikotools/autocomplete/loras"
)
except ImportError as e:
print(f"[KikoTools] Could not import server components: {e}")
except Exception as e:
print(f"[KikoTools] Unexpected error setting up API: {e}")
import traceback
traceback.print_exc()
# API endpoints are registered above at module import time
def get_version():
"""Parse version from pyproject.toml"""
try:
pyproject_path = Path(__file__).parent / "pyproject.toml"
if pyproject_path.exists():
content = pyproject_path.read_text(encoding="utf-8")
match = re.search(r'version\s*=\s*["\']([^"\']+)["\']', content)
if match:
return match.group(1)
except Exception:
pass
return "unknown"
# Print startup message with loaded tools
print("\033[94m[ComfyUI-KikoTools]\033[0m")
print()
print(f"\033[94m[ComfyUI-KikoTools] Version:\033[0m {get_version()}")
for node_key, display_name in NODE_DISPLAY_NAME_MAPPINGS.items():
print(f"🫶 \033[94m[Loaded:\033[0m {display_name}")
# ASCII only: a Windows console using a legacy code page cannot print emoji,
# and a UnicodeEncodeError here would abort loading the whole node.
ascii_name = display_name.encode("ascii", "ignore").decode().strip()
print(f" \033[94mLoaded:\033[0m {ascii_name}")
print(f"\033[94mTotal: {len(NODE_CLASS_MAPPINGS)} tools loaded\033[0m")
print()
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]
+22
View File
@@ -0,0 +1,22 @@
# ComfyAssets UI screenshots
Retained captures from the KikoTools UI migrations. Real-ComfyUI tests cover
Classic and Nodes 2.0, renderer switching, saved workflows and cleanup.
| Interface | Captures |
| --- | --- |
| Local Image Loader | [Classic](live-image-loader/classic.png), [Nodes 2.0](live-image-loader/nodes2.png) |
| Saved-image viewer | [Dark](save-image-viewer/dark.png), [Light](save-image-viewer/light.png) |
| Seed History | [Dark](seed-history/dark.png), [Light](seed-history/light.png) |
| Workflow Timer | [Dark](workflow-timer/dark.png), [Light](workflow-timer/light.png), [Color picker](workflow-timer/color-picker.png) |
| Dimension swap | [Selector dark](dimension-swap/selector-dark.png), [Selector light](dimension-swap/selector-light.png), [Latent dark](dimension-swap/latent-dark.png), [Latent light](dimension-swap/latent-light.png) |
| Node Dimensions | [Dark](node-dimensions/dark.png), [Light](node-dimensions/light.png) |
| Display Text | [Dark](display-output/text-dark.png), [Light](display-output/text-light.png) |
| Display Any | [Dark](display-output/any-dark.png), [Light](display-output/any-light.png) |
| Embedding Autocomplete | [Suggestions dark](embedding-autocomplete/suggestions-dark.png), [Suggestions light](embedding-autocomplete/suggestions-light.png), [Settings dark](embedding-autocomplete/settings-dark.png), [Settings light](embedding-autocomplete/settings-light.png) |
| Custom Colors | [Dark](custom-colors/dark.png), [Light](custom-colors/light.png) |
| Follow Execution | [Classic](follow-execution/classic.png), [Nodes 2.0](follow-execution/nodes2.png) |
Screenshots record the tested appearance at capture time. Updated Playwright
captures are temporary test artifacts until copied into this directory and
committed for review.
Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

@@ -0,0 +1,734 @@
# Model Resolution Picker Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** A new ComfyAssets node whose model dropdown drives a ratio-chip grid and size list, outputting width, height and an empty latent, with the toolkit Swap W×H.
**Architecture:** The family table lives in Python (`families.py`) and is served by a GET route; the Python node rounds to the family step and builds the latent with the existing `create_empty_latent_batch`. The frontend is a pure derivation module (`web/ui/resolution-picker.js`), a DOM view (`web/views/resolution-picker.js`) and an extension (`web/model_resolution_picker.js`) that projects the native `model`/`width`/`height` widgets into the panel and writes back through widget callbacks. No panel state is serialized.
**Tech Stack:** Python 3.10–3.13, torch (CPU), aiohttp routes on `PromptServer`; ES modules on the `web/ui` toolkit; pytest, `node --test`, Playwright fixture host (`tests/browser`), pinned real ComfyUI (`tests/comfyui`).
**Spec:** `docs/superpowers/specs/2026-10-27-model-resolution-picker-design.md`
## Global Constraints
- Node id `ModelResolutionPicker`, display name `Model Resolution Picker`, category `🫶 ComfyAssets/🖼️ Resolution`.
- Combo value is the family **label**; labels are stable ids; each family carries `aliases`; unknown label → step 8, logged warning, no re-rounding to another family's step.
- Chips, in grid order: `1:1`, `4:5`, `5:4`, `3:4`, `4:3`, `2:3`, `3:2`, `9:16`, `16:9`, `21:9`, `9:21`, then `Custom ratio`. Mirror pairs: 4:5↔5:4, 3:4↔4:3, 2:3↔3:2, 9:16↔16:9, 21:9↔9:21, 1:1↔1:1.
- Chip tolerance for derived (non-listed) sizes: **7%** relative to the chip ratio. Authored list membership always wins.
- 1 MP = 1,048,576 px. Custom-ratio sizes use core Resolution Selector math: `scale = sqrt(mp·1048576/(rw·rh))`, each side `round(side/step)·step`, never below 64.
- Default family `SDXL`, default size 1024×1024, `batch_size` default 1, min 1, max 64. `width`/`height` widgets: default 1024, min 64, max 8192, step 8.
- Route: `GET /kikotools/model_resolution/families`, registered at module import like `local_image_loader/node.py`, guarded by `try/except ImportError`.
- Only `createScope()`, `mountTheme()`, `createButton()` and `controls.css` tokens for UI; new CSS classes are prefixed `.ca-resolution-`.
- Panel DOM widget is added with `serialize: false`; it mounts after the native widgets; new nodes start at 340 × 600.
- Never vendor Pixaroma code or assets. No external HTTP.
- Python coverage ≥ 85% overall; every new `web/*.js` file gets a row in `tools/testing/frontend-coverage-baseline.json`.
- Width Height Selector and Empty Latent Batch tests must keep passing unchanged.
- Commits: conventional type with scope, imperative subject, bullet body. No AI attribution lines.
## Review Focus
1. Typed width/height below 64 or not on the family step (e.g. 70×1000 on HunyuanImage 2.1): Python emits ≥ 64 on the step, the header shows the same effective size. Pinned in Task 1 (`round_to_step`) and Task 3 (`test_execution_rounds_to_family_step`).
2. Families route unavailable (network error or 500): panel shows one status line, native widgets and Swap still work, no page error. Pinned in Task 7 (`route failure keeps native widgets usable`).
3. Workflow saved with a former label or an unknown label: former label resolves through `aliases`; unknown label keeps the saved size (step 8). Pinned in Task 2 (`test_resolve_family_by_alias`) and Task 3 (`test_unknown_label_keeps_size_and_warns`).
4. `width` and `height` converted to inputs: chips and list disabled, header says "dimensions are wired in", Swap disabled. Pinned in Task 7 (`wired-in dimensions disable the picker`).
5. Custom ratio text such as `0:5`, `16:`, `a:b`, `99999999:1`: rejected with the inline error, previous list kept. Pinned in Task 5 (`parseRatio rejects malformed input`) and Task 7 (`custom ratio field`).
---
## File structure
| Path | Responsibility |
|---|---|
| `kikotools/tools/model_resolution_picker/logic.py` | step rounding, size generation, ratio label |
| `kikotools/tools/model_resolution_picker/families.py` | `Family` record, `CHIPS`, `MIRROR`, `FAMILIES`, lookup, JSON payload |
| `kikotools/tools/model_resolution_picker/node.py` | node class, route handler, module-level registration |
| `kikotools/tools/model_resolution_picker/__init__.py` | exports `ModelResolutionPickerNode` |
| `kikotools/__init__.py` | registration (modify) |
| `web/ui/dimensions.js` | swap without a `preset` widget (modify) |
| `web/ui/resolution-picker.js` | pure derivation and ratio math |
| `web/views/resolution-picker.js` | DOM view built from toolkit primitives |
| `web/model_resolution_picker.js` | extension: fetch families, mount view + swap, bind widgets |
| `web/ui/controls.css` | `.ca-resolution-*` rules (modify) |
| `tests/unit/tools/model_resolution_picker/` | `test_logic.py`, `test_families.py`, `test_node.py` |
| `tests/js/resolution-picker.test.mjs`, `tests/js/dimensions.test.mjs` | JS unit tests |
| `tests/browser/workflows.spec.mjs` | fixture-host workflows (modify) |
| `tests/comfyui/host-setup.mjs`, `tests/comfyui/resolution-picker.spec.mjs` | real-host coverage |
| `tools/testing/frontend-coverage-baseline.json` | floors for the 3 new JS files (modify) |
| `README.md`, `docs/ui-migration.md`, `examples/documentation/model_resolution_picker.md`, `docs/screenshots/resolution-picker/` | docs |
All pytest runs use the scratch venv: `V=/tmp/claude-1000/-home-vito-code-personal-ComfyUI-KikoTools/f79925f2-d12d-4383-ad2e-85d80c922cf7/scratchpad/venv; $V/bin/python -m pytest … -p no:cacheprovider`. Frontend: `npm test --prefix tools/testing`, `npm run --prefix tools/testing test:browser -- --project chromium`.
---
### Task 1: Rounding and size generation (`logic.py`)
**Files:**
- Create: `kikotools/tools/model_resolution_picker/__init__.py` (empty docstring for now), `kikotools/tools/model_resolution_picker/logic.py`
- Test: `tests/unit/tools/model_resolution_picker/__init__.py` (empty), `tests/unit/tools/model_resolution_picker/test_logic.py`
**Interfaces:**
- Produces: `round_to_step(value: int, step: int, minimum: int = 64) -> int`; `generate_size(ratio_w: int, ratio_h: int, megapixels: float, step: int) -> tuple[int, int]`; `ratio_label(width: int, height: int) -> str` (reduced `W:H` with aliases `8:5→16:10`, `5:8→10:16`, `7:3→21:9`, `3:7→9:21`); `MEGAPIXEL = 1_048_576`.
- [ ] **Step 1: Write the failing tests**
```python
from kikotools.tools.model_resolution_picker.logic import (
MEGAPIXEL, generate_size, ratio_label, round_to_step)
def test_round_to_step_rounds_nearest_and_never_below_minimum():
assert round_to_step(1000, 32) == 992
assert round_to_step(1232, 16) == 1232
assert round_to_step(70, 32) == 64
assert round_to_step(10, 64) == 64
assert round_to_step(1016, 64) == 1024
def test_generate_size_matches_core_resolution_selector():
assert generate_size(1, 1, 1.0, 8) == (1024, 1024)
assert generate_size(16, 9, 2.0, 16) == (1936, 1088)
assert generate_size(3, 2, 4.0, 32) == (2496, 1664)
assert generate_size(21, 9, 0.25, 64) == (768, 320)
def test_ratio_label_reduces_and_aliases():
assert ratio_label(1024, 1024) == "1:1"
assert ratio_label(1216, 832) == "19:13"
assert ratio_label(1280, 800) == "16:10"
assert ratio_label(1344, 576) == "21:9"
assert MEGAPIXEL == 1024 * 1024
```
- [ ] **Step 2: Run tests to verify they fail**
Run: `$V/bin/python -m pytest tests/unit/tools/model_resolution_picker/test_logic.py -p no:cacheprovider -q`
Expected: FAIL with `ModuleNotFoundError`
- [ ] **Step 3: Implement the three functions in `logic.py`**
`round_to_step`: nearest multiple, then `max(result, ceil(minimum/step)*step)`. `generate_size`: the Global Constraints formula, each side through `round_to_step`. `ratio_label`: `math.gcd`, then the alias dict.
- [ ] **Step 4: Run tests to verify they pass**
Run: same command. Expected: `3 passed`
- [ ] **Step 5: Commit**
```bash
git add kikotools/tools/model_resolution_picker/__init__.py kikotools/tools/model_resolution_picker/logic.py tests/unit/tools/model_resolution_picker/
git commit -m "feat(model_resolution_picker): add step rounding and size generation"
```
---
### Task 2: The family table (`families.py`)
**Files:**
- Create: `kikotools/tools/model_resolution_picker/families.py`
- Test: `tests/unit/tools/model_resolution_picker/test_families.py`
**Interfaces:**
- Consumes: `generate_size` from Task 1 (used once, by hand, to author the generated lists; not imported by `families.py`).
- Produces:
- `CHIPS: tuple[str, ...]` in the Global Constraints order (11 chips, without `Custom ratio`).
- `MIRROR: dict[str, str]`.
- `class Family(NamedTuple)`: `label: str`, `aliases: tuple[str, ...]`, `models: str`, `group: str` (`"image"`|`"video"`), `vae_ratio: int`, `step: int`, `megapixels: tuple[float, ...]`, `default: tuple[int, int]`, `sizes: dict[str, tuple[tuple[int, int], ...]]`.
- `FAMILIES: tuple[Family, ...]`, `DEFAULT_FAMILY = "SDXL"`.
- `family_labels() -> list[str]`, `resolve_family(label: str) -> Family | None` (label or alias), `families_payload() -> dict` with keys `chips`, `mirror`, `default`, `families` (each: `label, aliases, models, group, vaeRatio, step, megapixels, default: [w, h], sizes: {chip: [[w, h], …]}`).
Authored data (copy verbatim; order of `FAMILIES` is this order):
| label | group | vae | step | MP tiers | default | official sizes to list verbatim; everything else generated with `generate_size` at each tier and chip |
|---|---|---|---|---|---|---|
| `SD 1.5` | image | 8 | 64 | 0.25, 0.5 | 512×512 | must include 512², 768², 512×768, 768×512 |
| `SDXL` (models: "SDXL, Pony and other SDXL finetunes") | image | 8 | 64 | 1.0 | 1024×1024 | 1:1 1024²; 4:5 832×1024; 5:4 1024×832; 3:4 896×1152; 4:3 1152×896; 2:3 832×1216; 3:2 1216×832; 9:16 768×1344; 16:9 1344×768; 21:9 1536×640; 9:21 640×1536. No generated entries. |
| `Illustrious XL` (aliases: none; models: "Illustrious XL v1.0 and later, NoobAI, WAI and other Illustrious finetunes") | image | 8 | 32 | 1.0, 1.5, 2.25 | 1024×1024 | all SDXL entries, plus 1:1 1536²; 4:5 1248×1536; 5:4 1536×1248; 3:4 1344×1792; 4:3 1792×1344; 2:3 1024×1536, 1248×1824; 3:2 1536×1024, 1824×1248; 9:16 1152×2048; 16:9 2048×1152. No generated entries. |
| `Anima` | image | 8 | 16 | 0.5, 1.0, 1.5 | 1024×1024 | generated; drop any entry with a side < 512 or > 1536 |
| `Flux.1` (models: "Flux.1 dev / schnell / Krea, Chroma, HiDream I1, SD3.5, Lumina 2") | image | 8 | 16 | 0.5, 1.0, 1.5, 2.0 | 1024×1024 | generated; must include 16:9 1920×1088 and 9:16 1088×1920 (replace the 2.0 MP generated entry) |
| `Z-Image` (models: "Z-Image Turbo and Z-Image Base") | image | 8 | 16 | 1.0, 1.6 | 1024×1024 | 1:1 1024², 1280²; 4:3 1152×896, 1152×864, 1440×1120, 1472×1104; 3:4 896×1152, 864×1152, 1120×1440, 1104×1472; 3:2 1248×832, 1536×1024; 2:3 832×1248, 1024×1536; 16:9 1280×720, 1600×896; 9:16 720×1280, 896×1600; 21:9 1344×576, 1680×720; 9:21 576×1344, 720×1680. 4:5 and 5:4 empty. |
| `Qwen-Image` (models: "Qwen-Image and Qwen-Image 2512") | image | 8 | 16 | 1.76 | 1328×1328 | 1:1 1328²; 16:9 1664×928; 9:16 928×1664; 4:3 1472×1104; 3:4 1104×1472; 3:2 1584×1056; 2:3 1056×1584. Other chips empty. |
| `Flux.2 / Klein / MOSS` (models: "Flux.2 dev, Flux.2 Klein, Flux.3 Klein, MOSS-Image, Ideogram 4") | image | 16 | 16 | 1.0, 2.0, 4.0 | 1024×1024 | generated; must include 3:2 1248×832, 2:3 832×1248, 1:1 2048², 16:9 1920×1088, 9:16 1088×1920 |
| `Qwen-Image 2.1` (models: "Qwen-Image 2.1 and 2.1 Turbo") | image | 16 | 32 | 1.0, 4.0 | 1024×1024 | 1:1 2048²; 4:3 2400×1792; 3:4 1792×2400; 3:2 2528×1696; 2:3 1696×2528; 16:9 2752×1536; 9:16 1536×2752; plus generated 1.0 MP entries for every chip |
| `HunyuanImage 2.1` | image | 32 | 32 | 4.0 | 2048×2048 | 1:1 2048²; 16:9 2560×1536; 9:16 1536×2560; 4:3 2304×1792; 3:4 1792×2304; 3:2 2496×1664; 2:3 1664×2496. Other chips empty. |
| `Wan 2.2 (keyframes)` | video | 16 | 16 | 0.4, 0.9 | 1280×720 | 16:9 832×480, 1280×720, 1280×704; 9:16 480×832, 720×1280, 704×1280; 1:1 640×640, 960×960; other chips generated |
| `LTX-2 (keyframes)` | video | 32 | 32 | 0.4, 0.9 | 768×512 | 3:2 768×512; 2:3 512×768; 16:9 960×544; 9:16 544×960; other chips generated |
| `HunyuanVideo 1.5 (keyframes)` | video | 16 | 16 | 0.4, 0.9 | 1280×720 | 16:9 848×480, 1280×720; 9:16 480×848, 720×1280; other chips generated |
| `MiniMax H3 (keyframes)` | video | 16 | 32 | 0.4, 1.0 | 864×480 | generated |
Every list is sorted by pixel count ascending; mirrored chips hold mirrored entries.
- [ ] **Step 1: Write the failing tests**
```python
from kikotools.tools.model_resolution_picker.families import (
CHIPS, DEFAULT_FAMILY, FAMILIES, MIRROR, families_payload, family_labels, resolve_family)
CHIP_RATIO = {c: int(c.split(":")[0]) / int(c.split(":")[1]) for c in CHIPS}
def test_chip_order_and_mirrors():
assert CHIPS == ("1:1", "4:5", "5:4", "3:4", "4:3", "2:3", "3:2", "9:16", "16:9", "21:9", "9:21")
assert all(MIRROR[MIRROR[c]] == c for c in CHIPS)
def test_every_size_is_on_step_and_within_seven_percent_of_its_chip():
for family in FAMILIES:
for chip, entries in family.sizes.items():
for w, h in entries:
assert w % family.step == 0 and h % family.step == 0, (family.label, w, h)
assert abs(w / h - CHIP_RATIO[chip]) / CHIP_RATIO[chip] <= 0.07, (family.label, chip, w, h)
def test_every_entry_has_its_mirror_and_lists_are_sorted():
for family in FAMILIES:
for chip, entries in family.sizes.items():
assert list(entries) == sorted(entries, key=lambda s: s[0] * s[1])
for w, h in entries:
assert (h, w) in family.sizes.get(MIRROR[chip], ()), (family.label, chip, w, h)
def test_defaults_labels_and_must_includes():
labels = family_labels()
assert labels[0] == "SD 1.5" and "SDXL" in labels and DEFAULT_FAMILY == "SDXL"
assert len(set(labels)) == len(labels)
for family in FAMILIES:
assert any(family.default in entries for entries in family.sizes.values()), family.label
must = {"SDXL": ("16:9", (1344, 768)), "Flux.1": ("16:9", (1920, 1088)),
"Flux.2 / Klein / MOSS": ("3:2", (1248, 832)), "Z-Image": ("21:9", (1680, 720)),
"Qwen-Image": ("16:9", (1664, 928)), "Qwen-Image 2.1": ("16:9", (2752, 1536)),
"HunyuanImage 2.1": ("16:9", (2560, 1536)), "Illustrious XL": ("2:3", (1248, 1824)),
"Wan 2.2 (keyframes)": ("16:9", (1280, 704)), "MiniMax H3 (keyframes)": ("16:9", (864, 480))}
for label, (chip, size) in must.items():
assert size in resolve_family(label).sizes[chip], label
def test_resolve_family_by_alias_and_unknown():
assert resolve_family("SDXL").step == 64
assert resolve_family("nope") is None
def test_payload_is_json_shaped():
payload = families_payload()
assert payload["default"] == "SDXL" and payload["chips"] == list(CHIPS)
sdxl = next(f for f in payload["families"] if f["label"] == "SDXL")
assert sdxl["sizes"]["3:2"] == [[1216, 832]] and sdxl["vaeRatio"] == 8 and sdxl["group"] == "image"
```
- [ ] **Step 2: Run tests to verify they fail**
Run: `$V/bin/python -m pytest tests/unit/tools/model_resolution_picker/test_families.py -p no:cacheprovider -q`
Expected: FAIL with `ModuleNotFoundError`
- [ ] **Step 3: Author `families.py`**
Produce generated entries with a one-off snippet calling `generate_size` for each chip and tier, apply the "must include" replacements and the Anima range filter, sort, then paste the literals. `families_payload()` converts tuples to lists.
- [ ] **Step 4: Run tests to verify they pass**
Run: same command. Expected: `6 passed`
- [ ] **Step 5: Commit**
```bash
git add kikotools/tools/model_resolution_picker/families.py tests/unit/tools/model_resolution_picker/test_families.py
git commit -m "feat(model_resolution_picker): add the model family resolution table"
```
---
### Task 3: Node, route and registration
**Files:**
- Create: `kikotools/tools/model_resolution_picker/node.py`
- Modify: `kikotools/tools/model_resolution_picker/__init__.py`, `kikotools/__init__.py` (import + both mapping dicts, next to `EmptyLatentBatch`)
- Test: `tests/unit/tools/model_resolution_picker/test_node.py`
**Interfaces:**
- Consumes: `resolve_family`, `family_labels`, `families_payload`, `DEFAULT_FAMILY` (Task 2); `round_to_step` (Task 1); `create_empty_latent_batch(width, height, batch_size)` from `kikotools/tools/empty_latent_batch/logic.py`.
- Produces: `class ModelResolutionPickerNode(ComfyAssetsBaseNode)` with `INPUT_TYPES` (`model` COMBO of `family_labels()` default `DEFAULT_FAMILY`, tooltip "Model family; the chips list its preferred sizes", `width`, `height`, `batch_size` per Global Constraints), `RETURN_TYPES = ("LATENT", "INT", "INT")`, `RETURN_NAMES = ("latent", "width", "height")`, `FUNCTION = "create"`, `create(self, model, width, height, batch_size) -> tuple[dict, int, int]`; `async def get_families(request) -> web.Response` (JSON of `families_payload()`); module-level registration of `GET /kikotools/model_resolution/families` in a `try: import server … except ImportError: pass` block.
- [ ] **Step 1: Write the failing tests**
```python
import asyncio, json, logging
from kikotools.tools.model_resolution_picker.node import ModelResolutionPickerNode, get_families
def test_inputs_and_outputs():
inputs = ModelResolutionPickerNode.INPUT_TYPES()["required"]
assert inputs["model"][1]["default"] == "SDXL" and "HunyuanImage 2.1" in inputs["model"][0]
width = inputs["width"][1]
assert (width["default"], width["min"], width["max"], width["step"]) == (1024, 64, 8192, 8)
assert ModelResolutionPickerNode.RETURN_TYPES == ("LATENT", "INT", "INT")
assert ModelResolutionPickerNode.CATEGORY == "🫶 ComfyAssets/🖼️ Resolution"
def test_execution_rounds_to_family_step_and_marks_latent():
latent, w, h = ModelResolutionPickerNode().create("HunyuanImage 2.1", 70, 1000, 2)
assert (w, h) == (64, 992)
assert latent["samples"].shape == (2, 4, 124, 8) and latent["downscale_ratio_spacial"] == 8
def test_exact_sizes_pass_through():
_, w, h = ModelResolutionPickerNode().create("Flux.2 / Klein / MOSS", 1248, 832, 1)
assert (w, h) == (1248, 832)
def test_unknown_label_keeps_size_and_warns(caplog):
with caplog.at_level(logging.WARNING):
_, w, h = ModelResolutionPickerNode().create("Renamed Family", 1000, 1000, 1)
assert (w, h) == (1000, 1000) and "Renamed Family" in caplog.text
def test_families_route_returns_payload():
response = asyncio.run(get_families(None))
assert response.status == 200 and json.loads(response.text)["default"] == "SDXL"
def test_registered_in_kikotools():
from kikotools import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
assert NODE_CLASS_MAPPINGS["ModelResolutionPicker"] is ModelResolutionPickerNode
assert NODE_DISPLAY_NAME_MAPPINGS["ModelResolutionPicker"] == "Model Resolution Picker"
```
- [ ] **Step 2: Run tests to verify they fail**
Run: `$V/bin/python -m pytest tests/unit/tools/model_resolution_picker/test_node.py -p no:cacheprovider -q`
Expected: FAIL with `ImportError`
- [ ] **Step 3: Implement `node.py`, export it, register it**
`create`: resolve → `step = family.step if family else 8`, warning via `logger.warning` when `None`; round both sides; `log_info` when a value changed; build the latent; return. `get_families` returns `web.json_response(families_payload())`.
- [ ] **Step 4: Run the new tests, then the whole Python suite with coverage**
Run: `$V/bin/python -m pytest tests/unit/tools/model_resolution_picker -p no:cacheprovider -q` → `6 passed`
Run: `$V/bin/python -m pytest tests -p no:cacheprovider -q --cov=kikotools | tail -3` → all passed, coverage ≥ 85%
Run: `$V/bin/black --check kikotools tests && $V/bin/flake8 kikotools/tools/model_resolution_picker` → clean
- [ ] **Step 5: Commit**
```bash
git add kikotools/tools/model_resolution_picker kikotools/__init__.py tests/unit/tools/model_resolution_picker/test_node.py
git commit -m "feat(model_resolution_picker): add node, families route and registration"
```
---
### Task 4: Swap without a preset widget (`web/ui/dimensions.js`)
**Files:**
- Modify: `web/ui/dimensions.js:904-932` (`swapDimensions`)
- Test: `tests/js/dimensions.test.mjs`
**Interfaces:**
- Produces: `swapDimensions(node, app)` now swaps when the node has `width` and `height` widgets but no `preset` widget, taking the existing custom path (direct swap, callbacks once each, dirty canvas). Behavior with a `preset` widget is unchanged.
- [ ] **Step 1: Write the failing test**
```js
test('nodes without a preset widget swap width and height directly', () => {
const calls = [];
const node = { widgets: [
{ name: 'width', value: 1344, callback(value) { calls.push(['width', value]); } },
{ name: 'height', value: 768, callback(value) { calls.push(['height', value]); } },
], graph: { setDirtyCanvas() { calls.push(['dirty']); } } };
swapDimensions(node, app);
assert.deepEqual(node.widgets.map(widget => widget.value), [768, 1344]);
assert.deepEqual(calls, [['width', 768], ['height', 1344], ['dirty']]);
});
```
- [ ] **Step 2: Run to verify it fails**
Run: `npm test --prefix tools/testing -- ../../tests/js/dimensions.test.mjs` (or `node --test tests/js/dimensions.test.mjs`)
Expected: FAIL, values unchanged
- [ ] **Step 3: Change the guard**
`if (!width || !height || swapping.has(node)) return; const custom = !preset || preset.value === 'custom';` and leave every `preset.*` access behind `!custom`.
- [ ] **Step 4: Run the JS unit tests**
Run: `npm test --prefix tools/testing` Expected: all pass, including the existing dimensions tests
- [ ] **Step 5: Commit**
```bash
git add web/ui/dimensions.js tests/js/dimensions.test.mjs
git commit -m "feat(ui): let swapDimensions work on nodes without a preset widget"
```
---
### Task 5: Derivation module (`web/ui/resolution-picker.js`)
**Files:**
- Create: `web/ui/resolution-picker.js`
- Test: `tests/js/resolution-picker.test.mjs`
**Interfaces:**
- Consumes: the families payload shape from Task 2.
- Produces (all pure, no DOM):
- `parseRatio(text) -> [w, h] | null` — accepts `W:H`, `W/H`, `WxH`, `W×H`, surrounding whitespace; integers 1–10000; else `null`.
- `reducedRatio(width, height) -> string` — same rule and aliases as Python `ratio_label`.
- `findFamily(payload, label) -> family | null` — label or alias.
- `deriveSelection(payload, label, width, height) -> { family, chip, entryIndex, custom, ratioText }` — `chip` is the chip whose list holds `[width, height]`; else nearest chip within 7%; else `null` with `custom: true` and `ratioText` from `reducedRatio`. `entryIndex` is the index in `family.sizes[chip]` or `-1`.
- `sizesForRatio(family, [rw, rh]) -> [[w, h], …]` — one per tier, Global Constraints formula, deduped, ascending.
- `defaultEntry(family, chip) -> [w, h] | null` — entry of `family.sizes[chip]` with pixel count closest to `family.default`'s.
- `effectiveSize(family, width, height) -> [w, h]` — rounded to `family.step` (8 when `family` is null), ≥ 64.
- `megapixels(width, height) -> string` — one decimal, e.g. `"1.0"`.
- `sizeOnModelChange(payload, newLabel, width, height, chip) -> [w, h]` — keep if listed in the new family; else `defaultEntry(newFamily, chip)` when non-null; else `newFamily.default`.
- [ ] **Step 1: Write the failing tests** (use a fixture payload with `SDXL` (step 64, default 1024², sizes 1:1 1024², 3:2 1216×832, 2:3 832×1216, 16:9 1344×768, 9:16 768×1344) and `HunyuanImage 2.1` (step 32, default 2048², 1:1 2048², 16:9 2560×1536, 9:16 1536×2560), plus `aliases: ['SDXL 1.0']` on SDXL)
```js
test('parseRatio accepts the documented forms and rejects malformed input', () => {
for (const text of ['16:9', ' 16 / 9 ', '16x9', '16×9']) assert.deepEqual(parseRatio(text), [16, 9]);
for (const text of ['0:5', '16:', 'a:b', '99999999:1', '', null]) assert.equal(parseRatio(text), null);
});
test('deriveSelection prefers authored membership, then the 7% band, then custom', () => {
assert.deepEqual(deriveSelection(payload, 'HunyuanImage 2.1', 2560, 1536), { family: payload.families[1], chip: '16:9', entryIndex: 0, custom: false, ratioText: '5:3' });
assert.equal(deriveSelection(payload, 'SDXL', 1200, 800).chip, '3:2'); // 1.5 exactly, not listed
assert.equal(deriveSelection(payload, 'SDXL', 1200, 800).entryIndex, -1);
const custom = deriveSelection(payload, 'SDXL', 1600, 800); // 2:1 is >7% from every chip
assert.deepEqual([custom.chip, custom.custom, custom.ratioText], [null, true, '2:1']);
assert.equal(deriveSelection(payload, 'SDXL 1.0', 1024, 1024).chip, '1:1'); // alias
assert.equal(deriveSelection(payload, 'nope', 1024, 1024).family, null);
});
test('sizesForRatio follows the core formula per tier and effectiveSize rounds to the step', () => {
assert.deepEqual(sizesForRatio({ step: 16, megapixels: [1, 2] }, [16, 9]), [[1360, 768], [1936, 1088]]);
assert.deepEqual(effectiveSize(payload.families[1], 70, 1000), [64, 992]);
assert.deepEqual(effectiveSize(null, 1001, 1001), [1000, 1000]);
assert.equal(megapixels(1216, 832), '1.0');
});
test('model change keeps listed sizes, else the same chip default, else the family default', () => {
assert.deepEqual(sizeOnModelChange(payload, 'HunyuanImage 2.1', 2560, 1536, '16:9'), [2560, 1536]);
assert.deepEqual(sizeOnModelChange(payload, 'HunyuanImage 2.1', 1344, 768, '16:9'), [2560, 1536]);
assert.deepEqual(sizeOnModelChange(payload, 'HunyuanImage 2.1', 1216, 832, '3:2'), [2048, 2048]);
assert.deepEqual(defaultEntry(payload.families[0], '21:9'), null);
});
```
- [ ] **Step 2: Run to verify failure** — `node --test tests/js/resolution-picker.test.mjs` → FAIL (module missing)
- [ ] **Step 3: Implement the module** — plain functions; `sizesForRatio` uses `Math.round(side / step) * step` with the 64 floor; nearest-chip selection picks the smallest relative error.
- [ ] **Step 4: Run** — `npm test --prefix tools/testing` → all pass
- [ ] **Step 5: Commit**
```bash
git add web/ui/resolution-picker.js tests/js/resolution-picker.test.mjs
git commit -m "feat(ui): add resolution picker derivation module"
```
---
### Task 6: View, extension and first browser workflows
**Files:**
- Create: `web/views/resolution-picker.js`, `web/model_resolution_picker.js`
- Modify: `web/ui/controls.css` (append `.ca-resolution-*` rules), `tests/browser/workflows.spec.mjs`
**Interfaces:**
- Consumes: Task 5 functions; `createScope`, `mountTheme`, `createButton` from `web/ui/core.js`; `mountDimensionSwap` from `web/ui/dimension-swap.js`; `swapDimensions` (Task 4); `api.fetchApi` from `../../scripts/api.js`.
- Produces:
- `createResolutionPickerView(scope, actions) -> { element, render(state), setStatus(text, isError) }` in `web/views/resolution-picker.js`. `actions = { selectChip(chip), openCustom(), commitRatio(text), selectSize(w, h) }`. `state = { disabled: boolean, header: string, chips: [{ id, label, active, disabled }], customOpen: boolean, ratioText: string, entries: [{ w, h, active }], error: string | null }`. Markup: `element.className = 'ca-tools ca-resolution-picker'`; header `<p class="ca-resolution-header">`; chips in `<div class="ca-resolution-chips" role="group" aria-label="Aspect ratio">` as `<button type="button" aria-pressed>` with text `1:1` … and a last chip `Custom ratio`; sizes in `<div class="ca-resolution-sizes" role="group" aria-label="Resolution">` as `<button type="button" aria-pressed>` with text `1216 × 832`; custom mode shows `<input aria-label="Custom ratio" placeholder="16:9">` (commit on Enter and on `change`) above the generated sizes; status `<p class="ca-resolution-status" role="status">`.
- Extension `comfyassets.ModelResolutionPicker` in `web/model_resolution_picker.js`: `beforeRegisterNodeDef` for `nodeData.name === 'ModelResolutionPicker'`; in `onNodeCreated`: `this.setSize([340, 600])`, create scope, mount view with `this.addDOMWidget('resolution_picker', 'div', view.element, { serialize: false, hideOnZoom: false, getMinHeight: () => 320 })`, `mountTheme`, `this.swapDimensions = () => { swapDimensions(this, app); render(); }`, `this.createSwapButton = () => mountDimensionSwap(this, app)` then call it, wrap the `model`/`width`/`height` widget callbacks to call the original then `render()`, `onRemoved` disposes the scope and preserves the previous hook's return. Families are loaded once per page via a module-level `loadFamilies(api)` promise using `api.fetchApi('/kikotools/model_resolution/families')`; while loading the view renders `disabled: true` with header `Loading sizes…`; on failure header `Sizes unavailable; type width and height` via `setStatus(…, true)` and chips stay disabled. Custom-ratio open flag and pending text live on the node instance (`this.resolutionCustom = { open, text }`), never serialized. Wired-in check: `node.inputs?.some(input => input.widget?.name === name && input.link != null)` for `width` or `height` → `disabled: true`, header `Dimensions are wired in`, and the swap button `disabled`.
- Writing a size: set `width.value`/`height.value`, call each widget's `callback(value, app.canvas, node, [0, 0], null)` once, `node.graph?.setDirtyCanvas?.(true, true)`.
- [ ] **Step 1: Write the failing browser tests** (append to `tests/browser/workflows.spec.mjs`; the fixture host creates text inputs for the initial keys and serves the real route because `node.py` registered it on the stubbed `PromptServer`)
```js
test('resolution picker renders chips from the families route and writes sizes to native widgets', async ({ page, request }) => {
await openHost(page);
const payload = await (await request.get('/kikotools/model_resolution/families')).json();
expect(payload.default).toBe('SDXL');
await createNode(page, 'ModelResolutionPicker', { model: 'SDXL', width: 1024, height: 1024, batch_size: 1 });
const panel = page.locator('.ca-resolution-picker');
await expect(panel.getByRole('button', { name: '1:1', exact: true })).toHaveAttribute('aria-pressed', 'true');
await expect(panel.locator('.ca-resolution-header')).toHaveText('1024 × 1024 · 1:1 · 1.0 MP');
await panel.getByRole('button', { name: '3:2', exact: true }).click();
const node = page.locator('[data-node="ModelResolutionPicker"]');
await expect(node.getByLabel('width', { exact: true })).toHaveValue('1216');
await expect(node.getByLabel('height', { exact: true })).toHaveValue('832');
await expect(panel.getByRole('button', { name: '1216 × 832', exact: true })).toHaveAttribute('aria-pressed', 'true');
await expect(panel.getByRole('button', { name: '21:9', exact: true })).toBeEnabled();
for (const theme of ['light', 'dark']) {
await page.evaluate(theme => document.body.style.setProperty('--bg-color', theme === 'light' ? '#fff' : '#202020'), theme);
await expect(panel).toHaveAttribute('data-ca-theme', theme);
}
expect(await panel.evaluate(element => element.scrollWidth <= element.clientWidth)).toBe(true);
});
test('resolution picker mounting is idempotent and removal disposes its scope', async ({ page }) => {
await openHost(page);
const id = await createNode(page, 'ModelResolutionPicker', { model: 'SDXL', width: 1024, height: 1024, batch_size: 1 });
await page.evaluate(id => { const node = kikoHost.nodes.get(String(id)); node.createSwapButton(); node.createSwapButton(); }, id);
await expect(page.locator('.ca-resolution-picker')).toHaveCount(1);
await expect(page.locator('.ca-dimension-swap')).toHaveCount(1);
const aborted = await page.evaluate(id => { const node = kikoHost.nodes.get(String(id)); kikoHost.removeNode(id); return node.resolutionScope.signal.aborted; }, id);
expect(aborted).toBe(true);
await expect(page.locator('.ca-resolution-picker')).toHaveCount(0);
});
```
- [ ] **Step 2: Run to verify failure** — `npm run --prefix tools/testing test:browser -- --project chromium -g "resolution picker"` → FAIL (no panel)
- [ ] **Step 3: Implement the view, the CSS and the extension**
CSS: chips grid `grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px`; active chip and active size use `--ca-accent` background with `#fff` text (same as `.ca-primary`); size list is a single column of full-width buttons; header uses `--ca-muted`; everything inherits the `.ca-tools` tokens so light/dark follow `data-ca-theme`. Expose the scope as `node.resolutionScope` for the disposal test.
- [ ] **Step 4: Run the browser suite and the JS unit suite** — `npm run --prefix tools/testing test:browser -- --project chromium` and `npm test --prefix tools/testing` → all pass, no page errors
- [ ] **Step 5: Commit**
```bash
git add web/views/resolution-picker.js web/model_resolution_picker.js web/ui/controls.css tests/browser/workflows.spec.mjs
git commit -m "feat(model_resolution_picker): add the chip grid panel and extension"
```
---
### Task 7: Swap, model change, custom ratio, wired-in, route failure; coverage floors
**Files:**
- Modify: `web/model_resolution_picker.js`, `web/views/resolution-picker.js` (only as the tests demand), `tests/browser/workflows.spec.mjs`, `tools/testing/frontend-coverage-baseline.json`
**Interfaces:**
- Consumes: Task 6 extension and view; `sizeOnModelChange`, `sizesForRatio`, `parseRatio` (Task 5).
- [ ] **Step 1: Write the failing browser tests**
```js
test('resolution picker swap lands on the mirrored chip and entry', async ({ page }) => {
await openHost(page);
await createNode(page, 'ModelResolutionPicker', { model: 'SDXL', width: 1344, height: 768, batch_size: 1 });
const panel = page.locator('.ca-resolution-picker');
await page.locator('.ca-dimension-swap').getByRole('button', { name: 'Swap width and height', exact: true }).click();
await expect(panel.getByRole('button', { name: '9:16', exact: true })).toHaveAttribute('aria-pressed', 'true');
await expect(panel.getByRole('button', { name: '768 × 1344', exact: true })).toHaveAttribute('aria-pressed', 'true');
});
test('resolution picker model change keeps listed sizes or falls back to the chip default', async ({ page }) => {
await openHost(page);
const id = await createNode(page, 'ModelResolutionPicker', { model: 'SDXL', width: 1344, height: 768, batch_size: 1 });
const node = page.locator('[data-node="ModelResolutionPicker"]');
await node.getByLabel('model', { exact: true }).fill('HunyuanImage 2.1');
await node.getByLabel('model', { exact: true }).dispatchEvent('change');
await expect(node.getByLabel('width', { exact: true })).toHaveValue('2560');
await expect(node.getByLabel('height', { exact: true })).toHaveValue('1536');
await expect(page.locator('.ca-resolution-header')).toHaveText('2560 × 1536 · 16:9 · 3.8 MP');
});
test('resolution picker custom ratio field computes tiers and rejects bad input', async ({ page }) => {
await openHost(page);
await createNode(page, 'ModelResolutionPicker', { model: 'Flux.1', width: 1024, height: 1024, batch_size: 1 });
const panel = page.locator('.ca-resolution-picker');
await panel.getByRole('button', { name: 'Custom ratio', exact: true }).click();
const field = panel.getByLabel('Custom ratio', { exact: true });
await expect(field).toHaveValue('1:1');
await field.fill('16:10'); await field.press('Enter');
await expect(panel.getByRole('button', { name: '1296 × 816', exact: true })).toBeVisible(); // 1.0 MP, step 16
await panel.getByRole('button', { name: '1296 × 816', exact: true }).click();
await expect(page.locator('[data-node="ModelResolutionPicker"]').getByLabel('width', { exact: true })).toHaveValue('1296');
await field.fill('a:b'); await field.press('Enter');
await expect(panel.locator('.ca-resolution-status')).toHaveText(/Enter a ratio like 16:9/);
await expect(panel.getByRole('button', { name: '1296 × 816', exact: true })).toBeVisible();
});
test('wired-in dimensions disable the picker and swap', async ({ page }) => {
await openHost(page);
const id = await createNode(page, 'ModelResolutionPicker', { model: 'SDXL', width: 1024, height: 1024, batch_size: 1 });
await page.evaluate(id => { const node = kikoHost.nodes.get(String(id)); node.inputs.push({ name: 'width', widget: { name: 'width' }, link: 7 }); node.widgets.find(w => w.name === 'width').callback('1024', kikoHost.app.canvas, node, [0, 0], null); }, id);
const panel = page.locator('.ca-resolution-picker');
await expect(panel.locator('.ca-resolution-header')).toHaveText('Dimensions are wired in');
await expect(panel.getByRole('button', { name: '3:2', exact: true })).toBeDisabled();
await expect(page.locator('.ca-dimension-swap').getByRole('button')).toBeDisabled();
});
test('route failure keeps native widgets usable', async ({ page }) => {
await page.route('**/kikotools/model_resolution/families', route => route.fulfill({ status: 500, body: 'nope' }));
await openHost(page);
await createNode(page, 'ModelResolutionPicker', { model: 'SDXL', width: 1024, height: 1024, batch_size: 1 });
const panel = page.locator('.ca-resolution-picker');
await expect(panel.locator('.ca-resolution-status')).toHaveText('Sizes unavailable; type width and height');
await expect(panel.getByRole('button', { name: '1:1', exact: true })).toBeDisabled();
await page.locator('.ca-dimension-swap').getByRole('button').click();
await expect(page.locator('[data-node="ModelResolutionPicker"]').getByLabel('width', { exact: true })).toHaveValue('1024');
});
```
The 500 response must not be counted as a page error: the fixture's `afterEach` collects responses ≥ 500, so this test registers its route before `openHost` and removes the 500 URL from `page.errors` at the end (`page.errors = page.errors.filter(e => !e.includes('families'))`).
- [ ] **Step 2: Run to verify failure** — `npm run --prefix tools/testing test:browser -- --project chromium -g "resolution picker|wired-in|route failure"` → the new tests FAIL
- [ ] **Step 3: Implement the remaining behavior** — model-change handler uses `sizeOnModelChange`; custom field uses `parseRatio` + `sizesForRatio`, error copy `Enter a ratio like 16:9`; the fixture model widget is a text input, so the extension must listen to the widget callback, not to a `<select>`.
- [ ] **Step 4: Add coverage floors and run everything frontend**
Add rows for `model_resolution_picker.js`, `ui/resolution-picker.js`, `views/resolution-picker.js` to `tools/testing/frontend-coverage-baseline.json`, then:
Run: `npm test --prefix tools/testing && npm run --prefix tools/testing test:browser -- --project chromium && npm run --prefix tools/testing test:coverage`
Expected: all pass; set each new row to the measured values rounded down to the nearest whole percent (never below 80 for lines/statements).
- [ ] **Step 5: Commit**
```bash
git add web/model_resolution_picker.js web/views/resolution-picker.js tests/browser/workflows.spec.mjs tools/testing/frontend-coverage-baseline.json
git commit -m "feat(model_resolution_picker): model change, custom ratio, wired-in and failure handling"
```
---
### Task 8: Real-host coverage (both renderers, workflow round-trip)
**Files:**
- Create: `tests/comfyui/host-setup.mjs`, `tests/comfyui/resolution-picker.spec.mjs`
- Modify: `tests/comfyui/image-browser.spec.mjs:1-60` (import the shared `beforeEach` instead of its inline copy)
**Interfaces:**
- Produces: `installHost(test, expect)` in `host-setup.mjs` that registers exactly the existing `beforeEach` (versions check, fixture reset, route proxying, settings override, extension filter, `app.graph.clear()`), with `/kikotools/model_resolution/` added to the list of prefixes proxied to the fixture server so the route is deterministic.
- [ ] **Step 1: Extract the shared setup and confirm the existing spec still passes**
Run: `KIKO_COMFY_ROOT=… npm run --prefix tools/testing test:comfyui` (or against `KIKO_COMFY_URL`; see `TESTING.md`). Expected: unchanged results for `image-browser.spec.mjs`.
- [ ] **Step 2: Write the failing real-host test** (mirrors the sibling swap test at `image-browser.spec.mjs:61-150`)
```js
test('resolution picker survives renderers, swap, themes and workflow reload', async ({ page }, testInfo) => {
await page.evaluate(async () => { const { app } = await import('/scripts/app.js'); const node = LiteGraph.createNode('ModelResolutionPicker'); app.graph.add(node); app.canvas.centerOnNode(node); });
const panel = page.locator('.ca-resolution-picker');
await expect(panel.getByRole('button', { name: '1:1', exact: true })).toHaveAttribute('aria-pressed', 'true');
await panel.getByRole('button', { name: '16:9', exact: true }).click();
for (const renderer of [false, true, false]) {
await page.evaluate(async renderer => { const { app } = await import('/scripts/app.js'); await app.ui.settings.setSettingValue('Comfy.VueNodes.Enabled', renderer); }, renderer);
await expect.poll(() => page.evaluate(() => LiteGraph.vueNodesMode)).toBe(renderer);
await expect(panel).toHaveCount(1);
await page.locator('.ca-dimension-swap').getByRole('button', { name: 'Swap width and height', exact: true }).click();
}
await expect(panel.getByRole('button', { name: '9:16', exact: true })).toHaveAttribute('aria-pressed', 'true'); // three swaps from 16:9
for (const theme of ['light', 'dark']) {
await page.evaluate(async theme => { const { app } = await import('/scripts/app.js'); await app.ui.settings.setSettingValue('Comfy.ColorPalette', theme); }, theme);
await expect(panel).toHaveAttribute('data-ca-theme', theme);
await page.screenshot({ path: testInfo.outputPath(`ModelResolutionPicker-${theme}.png`) });
}
const restored = await page.evaluate(async () => {
const { app } = await import('/scripts/app.js');
const workflow = app.graph.serialize();
const saved = workflow.nodes.find(node => node.type === 'ModelResolutionPicker');
if (saved.widgets_values.slice(4).some(value => value !== '' && value != null)) throw new Error('Picker UI leaked into workflow values');
await app.loadGraphData(workflow);
const node = app.graph._nodes.find(node => node.type === 'ModelResolutionPicker');
return { saved: saved.widgets_values.slice(0, 4), restored: node.widgets.filter(w => ['model', 'width', 'height', 'batch_size'].includes(w.name)).map(w => w.value), size: Array.from(node.size), savedSize: Array.from(saved.size) };
});
expect(restored.restored).toEqual(restored.saved);
expect(restored.size).toEqual(restored.savedSize);
await expect(panel.getByRole('button', { name: '9:16', exact: true })).toHaveAttribute('aria-pressed', 'true');
await page.evaluate(async () => { const { app } = await import('/scripts/app.js'); const node = app.graph._nodes.find(node => node.type === 'ModelResolutionPicker'); window.removedPicker = node; app.graph.remove(node); });
await expect(panel).toHaveCount(0);
expect(await page.evaluate(() => removedPicker.resolutionScope.signal.aborted)).toBe(true);
});
```
- [ ] **Step 3: Run it against the pinned host for both projects** — `npm run --prefix tools/testing test:comfyui` → `classic-start` and `nodes2-start` pass. Fix extension behavior if a renderer differs; do not special-case the test.
- [ ] **Step 4: Copy the two screenshots** from the test output to `docs/screenshots/resolution-picker/ModelResolutionPicker-{light,dark}.png` (used by Task 9).
- [ ] **Step 5: Commit**
```bash
git add tests/comfyui/host-setup.mjs tests/comfyui/resolution-picker.spec.mjs tests/comfyui/image-browser.spec.mjs docs/screenshots/resolution-picker
git commit -m "test(model_resolution_picker): cover both renderers and workflow round-trip on the pinned host"
```
---
### Task 9: Documentation
**Files:**
- Modify: `README.md` (tools table after the `Empty Latent Batch` row at line 44; a `#### 🎯 Model Resolution Picker` section after `#### 📦 Empty Latent Batch`; the status table near line 801), `docs/ui-migration.md` (table row `Model Resolution Picker | views/resolution-picker.js, ui/resolution-picker.js, shared swap | [Picker](screenshots/resolution-picker/)`), `web/ui/README.md` (a short "Model Resolution Picker" section stating: derived state only, in-memory custom-ratio flag, families route, disabled chips for empty lists)
- Create: `examples/documentation/model_resolution_picker.md` (inputs, outputs, the family table with labels and steps, the keyframe note for video families, the manual GPU check procedure from the spec's "Verification before merge")
- [ ] **Step 1: Write the docs** — copy the family labels and steps from `families.py`; README section bullets: model dropdown; chips and list; Swap; custom ratio and custom resolution rounding; outputs; "video rows are keyframe sizes, the latent is an image latent".
- [ ] **Step 2: Verify links** — `grep -n "resolution-picker\|model_resolution_picker" README.md docs/ui-migration.md web/ui/README.md` shows every referenced path exists (`ls` each).
- [ ] **Step 3: Commit**
```bash
git add README.md docs/ui-migration.md web/ui/README.md examples/documentation/model_resolution_picker.md
git commit -m "docs(model_resolution_picker): document the node, families and keyframe use"
```
---
### Task 10: Full verification and manual GPU check
**Files:** none new.
- [ ] **Step 1: Run every suite**
```
$V/bin/python -m pytest tests -p no:cacheprovider -q --cov=kikotools | tail -3
$V/bin/black --check . && $V/bin/flake8 kikotools
npm test --prefix tools/testing && npm run --prefix tools/testing test:coverage
npm run --prefix tools/testing test:browser
npm run --prefix tools/testing test:comfyui
```
Expected: all green; Python coverage ≥ 85%; frontend floors met; Width Height Selector and Empty Latent Batch tests untouched and passing.
- [ ] **Step 2: Manual generation check on the user's ComfyUI** (spec: "Verification before merge"): one queued image each for `Flux.2 / Klein / MOSS` at 1248×832 and `HunyuanImage 2.1` at 2560×1536 through this node → KSampler → VAE Decode → Save; confirm the saved PNGs are exactly those sizes. Record both results in the PR description. This step needs the user's GPU box; if it cannot run, say so in the PR and mark it as a pre-merge TODO.
- [ ] **Step 3: Hand off** — use `superpowers:finishing-a-development-branch` (push with the explicit refspec, PR against `main`).
---
## Addendum tasks (spec addendum 2026-10-27: JSON table, user override, resolution dropdown)
Global Constraints additions: the shipped file is `kikotools/tools/model_resolution_picker/families.json`; the override is `<user_dir>/kikotools/model_resolutions.json`; dropdown label format `{w}×{h} - {ratio} ({mp:.1f}MP)` with `custom` first; `resolution` COMBO sits between `model` and `width`; `VALIDATE_INPUTS(cls, model, resolution)` returns True; an invalid override is ignored whole, logged, and surfaced as `warnings` in the payload; workflows serialize five native widgets.
### Task 11: Shipped JSON table with a runtime validator
**Files:**
- Create: `kikotools/tools/model_resolution_picker/families.json`, `kikotools/tools/model_resolution_picker/table.py`
- Modify: `kikotools/tools/model_resolution_picker/families.py` (becomes a thin loader), `pyproject.toml` (package-data for `*.json` under `kikotools`)
- Test: `tests/unit/tools/model_resolution_picker/test_table.py`; `test_families.py` keeps passing unchanged
**Interfaces:**
- Produces in `table.py`: `load_table(path: Path) -> dict` (JSON load; raises `ValueError` with the path on bad JSON); `validate_table(data: dict) -> list[str]` (returns human-readable problems, empty when valid; checks: top-level keys and `version == 1`, chips equal `CHIPS`, mirror consistent, unique labels/aliases across families, every chip key present per family, sizes on step, 7% tolerance, exact mirrors, ascending order, one chip per size, default present in sizes, `group` in {image, video}, `vaeRatio` in {8, 16, 32}, `step` > 0, non-empty `megapixels`); `build_families(data: dict) -> tuple[Family, ...]`.
- `families.py` keeps exporting `CHIPS`, `MIRROR`, `Family`, `FAMILIES`, `DEFAULT_FAMILY`, `family_labels`, `resolve_family`, `families_payload`, now built from `families.json` at import; a validation failure raises `RuntimeError` listing the problems.
- [ ] **Step 1: Write the failing tests** — `test_validate_table_accepts_the_shipped_file` (loads the shipped path, `validate_table` returns `[]`), `test_validate_table_reports_each_invariant` (parametrized: a copy of the shipped data mutated to break one rule each — off-step size, >7% size, missing mirror, unsorted list, duplicate size under two chips, duplicate label, missing chip key, default not listed, bad vaeRatio — must produce exactly one message naming the family and chip), `test_load_table_bad_json_names_the_path`, `test_families_module_is_built_from_json` (`FAMILIES` labels equal the JSON labels in order).
- [ ] **Step 2: Run to verify failure** — `$V/bin/python -m pytest tests/unit/tools/model_resolution_picker/test_table.py -p no:cacheprovider -q` → ImportError.
- [ ] **Step 3: Generate `families.json` from the current `FAMILIES`** with a one-off snippet (`families_payload()` plus `"version": 1`), indent 2, then rewrite `families.py` to load/validate/build; delete the literals; keep the source comments as a `"notes"` string per family (the validator ignores unknown keys).
- [ ] **Step 4: Run** the new tests, `test_families.py`, the full suite with coverage, black and flake8.
- [ ] **Step 5: Commit** — `refactor(model_resolution_picker): move the family table to families.json with a validator`.
### Task 12: User override file
**Files:**
- Create: `kikotools/tools/model_resolution_picker/override.py`
- Modify: `families.py` (resolution and payload consult the merged table), `node.py` (route payload includes `warnings`)
- Test: `tests/unit/tools/model_resolution_picker/test_override.py`, `test_node.py`
**Interfaces:**
- Produces: `override_path() -> Path | None` (uses `folder_paths.get_user_directory()`; `None` when `folder_paths` or the function is unavailable); `merge_override(shipped: dict, override: dict) -> dict` (rules from the spec: replace by label, append new, `remove`, `default`); `current_table() -> tuple[dict, list[str]]` (shipped merged with a valid override, cached by override mtime; returns `(merged_data, warnings)`; an unreadable/invalid override yields the shipped data plus one `"Override ignored: <reason>"` warning and a `logger.warning`).
- `families.py`: `resolve_family(label)` and `families_payload()` use `current_table()`; `families_payload()["warnings"]` is the warnings list; `FAMILIES` remains the shipped tuple for tests.
- [ ] **Step 1: Write the failing tests** — with `tmp_path` as a fake user directory (patch `folder_paths` in `sys.modules` with `get_user_directory`): override replaces SDXL's `sizes` and `resolve_family("SDXL")` reflects it; a new family `My Model` appears last in `families_payload()["families"]` and resolves; `"remove": ["SD 1.5"]` hides it; invalid JSON → shipped data, `warnings == ["Override ignored: <path>: <json error>"]` and a warning log; an override whose merged result breaks an invariant (e.g. off-step size) → ignored whole with the validator message in the warning; editing the file (newer mtime) is picked up on the next call; no `folder_paths` → no override, no warnings; `get_families` route JSON includes `warnings`.
- [ ] **Step 2: Run to verify failure.**
- [ ] **Step 3: Implement** `override.py` and wire `families.py`/`node.py`. `current_table()` caches on `(path, mtime_ns)`.
- [ ] **Step 4: Run** the picker tests, full suite with coverage, black, flake8.
- [ ] **Step 5: Commit** — `feat(model_resolution_picker): merge a user override file into the family table`.
### Task 13: Native `resolution` dropdown synced with the panel
**Files:**
- Modify: `node.py` (input, `VALIDATE_INPUTS`, execution precedence), `logic.py` (`size_label(w, h, ratio_label) -> str`, `parse_size_label(text) -> tuple[int, int] | None`), `web/ui/resolution-picker.js` (`sizeLabel`, `parseSizeLabel`, `dropdownOptions(payload, label, chip) -> string[]`), `web/model_resolution_picker.js` (rebuild options and sync value; dropdown callback writes size), `web/views/resolution-picker.js` (status shows the first payload warning), `tests/js/resolution-picker.test.mjs`, `tests/browser/workflows.spec.mjs`, `tests/comfyui/resolution-picker.spec.mjs` (five native widgets: `slice(5)`, include `resolution` in the round-trip), `tools/testing/frontend-coverage-baseline.json` if floors move
- Test: `test_node.py`, `test_logic.py`
**Interfaces:**
- Python: `INPUT_TYPES` adds `resolution` COMBO (`["custom"] + sorted distinct labels across shipped families`, default `custom`, tooltip "Sizes for the selected model and ratio; custom uses width/height") between `model` and `width`; `create(self, model, resolution, width, height, batch_size)`: when `resolution != "custom"` and `parse_size_label` succeeds, those dims replace `width`/`height` before rounding; `VALIDATE_INPUTS(cls, model, resolution) -> bool`.
- JS: `dropdownOptions` returns `['custom', ...labels]` for the active chip's sizes, or all family sizes when `chip` is null/custom; the extension sets `resolution.options.values` (array) on every render where model or active chip changed, sets `resolution.value` to the current size's label or `custom`, and wraps the `resolution` widget callback: a label value writes the parsed size through `width`/`height` callbacks (re-entrancy guarded), `custom` leaves sizes alone.
- Fixture host note: the fixture renders `resolution` as a text input; tests set its value and dispatch `change`.
- [ ] **Step 1: Write the failing tests** — Python: `test_resolution_label_overrides_width_height` (`create("SDXL", "1216×832 - 3:2 (1.0MP)", 1024, 1024, 1)` → `(1216, 832)`), `test_custom_uses_width_height`, `test_validate_inputs_accepts_unlisted_resolution`, `size_label(1216, 832, "3:2") == "1216×832 - 3:2 (1.0MP)"`, `parse_size_label("1216×832 - 3:2 (1.0MP)") == (1216, 832)` and `None` for `custom`/garbage. JS unit: `dropdownOptions(payload, 'SDXL', '3:2')` → `['custom', '1216×832 - 3:2 (1.0MP)']`; `dropdownOptions(payload, 'SDXL', null)` lists all five SDXL sizes with ratio labels; `parseSizeLabel` round-trips `sizeLabel`. Browser: creating the node with `resolution: 'custom'` and clicking `3:2` sets the `resolution` input to `1216×832 - 3:2 (1.0MP)`; setting `resolution` to `1344×768 - 16:9 (1.0MP)` and dispatching `change` writes width/height 1344/768 and activates `16:9`; a swap sets `resolution` to the mirrored label; a typed width of 1000 sets it to `custom`; with the families route fulfilled by a payload carrying `warnings: ['Override ignored: test']` the status line shows that text. Real host: the existing spec asserts `widgets_values.slice(5)` is empty and that `resolution` survives reload.
- [ ] **Step 2: Run to verify failure** (Python, JS unit, chromium focused).
- [ ] **Step 3: Implement** Python then JS. The option list is set as a plain array on `widget.options.values`; the sibling `dimensions.js` already reads array or function options.
- [ ] **Step 3b: Ratio shapes on chips** (spec addendum part 4): in `web/views/resolution-picker.js` prepend to each ratio chip a `<span class="ca-resolution-shape" aria-hidden="true">` with inline `width`/`height` computed from the chip ratio (longest side 18 px, shortest at least 6 px, whole pixels), `1:1` square, none on `Custom ratio`; CSS in `controls.css`: `display:inline-block; border:1px solid currentColor; border-radius:2px; margin-inline-end:6px; vertical-align:middle`. Browser assertion in the existing render test: the `16:9` chip's shape is wider than tall and the `9:16` chip's is taller than wide; the chips' accessible names are unchanged.
- [ ] **Step 4: Run** Python suite with coverage, black/flake8, JS unit, chromium+touch browser suite (`env KIKO_TEST_PYTHON=…`), coverage gate, real host on both projects.
- [ ] **Step 5: Commit** — `feat(model_resolution_picker): add a resolution dropdown synced with the chip grid`.
### Task 14: Docs and final verification for the addendum
- [ ] Update `README.md` section bullets (dropdown; override file path and rules), `examples/documentation/model_resolution_picker.md` (JSON schema, override example with `remove` and a new family, warning behavior, dropdown precedence), `web/ui/README.md` (dropdown sync rule). Verify links.
- [ ] Run every suite as in Task 10 Step 1 (Python, black/flake8, JS unit, coverage gate, chromium+touch, real host). WebKit is a known local environment limitation.
- [ ] Commit — `docs(model_resolution_picker): document the JSON table, override file and resolution dropdown`.
@@ -0,0 +1,399 @@
# Model Resolution Picker — design
Date: 2026-10-27. Status: approved 2026-10-27.
## Intent
A new ComfyAssets node that picks an image size the way Pixaroma's
"Resolution" node does — a grid of aspect-ratio chips with a list of concrete
sizes under the active chip — but driven by a **model dropdown**. Pick the model
family first and every chip's size list becomes that family's preferred
resolutions. The node outputs `width`, `height` and an empty `LATENT`, and has
the toolkit's Swap W×H button, so one node replaces Width Height Selector plus
Empty Latent Batch in a workflow.
This is a **new node**. Width Height Selector and Empty Latent Batch stay as
they are.
Why now: every new model family (Flux.2, Klein, Z-Image, Qwen-Image 2.1,
MOSS-Image, Anima, HunyuanImage) has its own preferred sizes and its own VAE
step. The existing preset dropdown is one flat SDXL/FLUX list; keeping it
current for a dozen families makes a 60-entry combo nobody can scan. The chip
grid makes the choice two clicks, and the model dropdown keeps the wrong sizes
out of reach.
## Goals
- Two clicks from "I loaded Illustrious" to a correct size: model, then chip,
then (optionally) a different entry in the list.
- Swap W×H flips the dimensions, moves the highlight to the mirrored chip
(16:9 → 9:16) and to the matching entry in that chip's list.
- Custom Ratio: type any `W:H` and get sizes computed at the family's
megapixel tiers, rounded to the family's step.
- Custom Resolution: type exact width/height; the node rounds to the family's
step and shows the effective size before you queue.
- The empty latent is correct for every listed family, including 16x and 32x
VAEs, by carrying the `downscale_ratio_spacial` mark that core
`EmptyLatentImage` sets (the fix from commit 297f2b1).
- Looks and behaves the same in the Classic and Nodes 2.0 frontends, using the
existing `web/ui` toolkit.
## Non-goals
- Replacing or changing Width Height Selector or Empty Latent Batch, their
presets, JS, or tests.
- Video-length or frame-count handling. Video families are listed for their
**frame size** only: the latent output is an image latent for generating
first/last keyframes with an image model at the video model's native size.
- Per-checkpoint detection. The user picks the family; the node does not
inspect the loaded model.
- Snap/step toggles on the node. The step is a property of the family.
## Anti-goals (would make this a failure even if it works)
- Vendoring or adapting Pixaroma's JS, CSS or assets. `docs/ui-migration.md`
records that only the organization was an inspiration; that stays true.
- Storing UI state anywhere except ComfyUI's native widgets. The DOM panel is
a projection of `model`, `width`, `height`; it must never need its own
serialized state to reconstruct itself.
- A size table that is "generated from the formula" for a family that has an
official list. Official lists are used verbatim.
- Breaking the real-host CI (pinned ComfyUI, both renderers).
## How it looks
Top to bottom inside the node:
1. Native `model` combo widget.
2. The DOM panel (`.ca-tools`):
- A header line: effective size, ratio label and megapixels, e.g.
`1216 × 832 · 3:2 · 1.0 MP`. "Effective" means after rounding to the
family step, so what you see is what Python will emit.
- The chip grid: `1:1`, `4:5`, `5:4`, `3:4`, `4:3`, `2:3`, `3:2`, `9:16`,
`16:9`, `21:9`, `9:21`, and `Custom ratio`. Three columns. Chips with no
sizes for the selected family are disabled, not hidden, so the grid never
reflows. The active chip uses the toolkit's primary (blue) style.
- Below the grid, the size list for the active chip, one entry highlighted.
With `Custom ratio` active, the list is replaced by a `W:H` text field
and the computed sizes for that ratio.
- The toolkit Swap W×H button.
3. Native `width`, `height`, `batch_size` widgets. `width`/`height` double as
the Custom Resolution fields and as the inputs you convert when wiring
dimensions in from another node.
Placement: the panel mounts after the native widgets, which is the toolkit
convention used by the two sibling nodes and the behavior `addDOMWidget` gives
for free in both renderers. **My call**; the builder may move the panel
directly under `model` only if it proves stable in both renderers under the
real-host tests.
New nodes are 340 px wide. The node height is fitted to the panel content (the
chip grid plus the active chip's list) after every render, so no empty space is
left under the list; it grows and shrinks with the list. Saved workflows keep
their width and are re-fitted on load. **Changed by the owner after testing**: the
original 340 × 600 default left a large gap under one-entry lists.
## Behavior
### State model
The only persisted state is the four native widgets: `model` (COMBO),
`width` (INT), `height` (INT), `batch_size` (INT). Everything the panel
shows is derived:
- **Active chip**: the chip under which the family table lists
`width × height` (authored membership always wins; that is how HunyuanImage's
2560×1536, a 5:3 size, lives under 16:9). If no list contains it, the
nearest chip whose ratio is within 7% of `width / height` (so 1216×832
reads as 3:2 and 1536×640 as 21:9). If none, `Custom ratio` is active and
the ratio field shows the reduced fraction, with aliases so 8:5 displays as
16:10 and 7:3 as 21:9.
- **Custom-ratio mode flag**: the panel keeps one in-memory, non-serialized
flag "custom ratio open" plus the pending text in the field. Clicking the
`Custom ratio` chip sets the flag and prefills the field with the current
reduced ratio; it does not change `width`/`height`. Committing a ratio and
picking a tier size sets `width`/`height` and keeps the custom list open
until another chip is clicked. On reload the flag is gone and the pure
derivation above decides, which is the intended behavior. This is the only
panel-owned state and it is never needed to reconstruct anything.
- **Highlighted list entry**: the entry equal to `width × height`, else none.
- **Header**: computed from `width`, `height` and the family step.
Consequence: reload, undo, copy/paste, and converting `width`/`height` to
inputs all work with zero code, because there is nothing to restore. When
`width` or `height` is converted to an input, the panel disables the chips
and list and shows "dimensions are wired in" in the header; the Swap button
stays enabled only when both are widgets.
### Interactions
- **Chip click**: sets `width`/`height` to that chip's default entry for the
family (the entry closest to the family's default megapixels), unless the
current size already belongs to that chip's list.
- **List click**: sets `width`/`height` to that entry.
- **Custom ratio field**: accepts `W:H` (integers, also `W/H` and `W x H`);
on commit, the list shows one size per family megapixel tier, computed with
ComfyUI core's Resolution Selector formula (`scale = sqrt(MP·1024² /
(w·h))`, each side rounded to the family step). Invalid input keeps the
previous list and shows an inline error in the panel's status line.
- **Swap W×H**: swaps `width`/`height` through the native widgets with
callbacks (so converted inputs and other extensions see it), then the panel
re-derives. Because every family list is authored with mirrored pairs, the
swapped size lands on the mirrored chip with a highlighted entry.
- **Model change**: if the current size is in the new family's lists, keep it.
Otherwise pick, in order: the same chip's default entry in the new family;
the family default size. Log nothing; the header reflects the change.
- **Width/height edited directly**: panel re-derives; the header shows the
rounded effective size if the typed value is not on the family step.
Every width/height change goes through the widgets' callbacks exactly once,
as `ui/dimensions.js` already guarantees for the sibling nodes.
### Python execution
Inputs: `model`, `width`, `height`, `batch_size`.
Outputs: `LATENT` (`latent`), `INT` (`width`), `INT` (`height`).
1. Resolve the family record. The combo value **is the label**, because a
ComfyUI COMBO stores the displayed string in both renderers; labels are
therefore short and treated as stable ids (`SDXL`, `Illustrious XL`,
`Flux.1`, `Z-Image`, `Qwen-Image`, `Flux.2 / Klein / MOSS`,
`Qwen-Image 2.1`, `HunyuanImage 2.1`, `Anima`, `SD 1.5`, and the four
video rows suffixed ` (keyframes)`). The model list that a label covers
goes in the combo tooltip and the README, not in the label. Each record
carries an `aliases` list of former labels so a rename maps forward.
An unknown value falls back to a generic 8-pixel step with a logged
warning: it never re-rounds a saved size to another family's step.
2. Round `width`/`height` to the nearest multiple of the family step, never
below 64; log at info level when a value changed.
3. Build the latent with the existing
`empty_latent_batch.logic.create_empty_latent_batch` (4 channels, 8x,
carries `downscale_ratio_spacial: 8`). ComfyUI's sampler resizes it to the
family's real latent ratio because of that mark; for 16x and 32x families
the step rounding in step 2 is what keeps that resize exact.
4. Return `(latent, width, height)` as rounded.
The family table lives in Python
(`kikotools/tools/model_resolution_picker/families.py`) and is the single
source of truth. The frontend fetches it once from a GET route
`/kikotools/model_resolution/families` registered on `PromptServer` (the
project already registers routes for Local Image Loader) and caches it per
page. If the fetch fails the panel shows one status line and the native
widgets remain fully usable. **My call**; the alternative is duplicating the
table in JS, which is exactly the drift this node exists to avoid.
### Family table schema
```
id: stable string used as the combo value, e.g. "sdxl"
label: combo text, e.g. "SDXL / Pony"
vae_ratio: 8 | 16 | 32 (informational; the latent is always built at 8)
step: pixel multiple for rounding and custom sizes
megapixels: list of tiers for Custom ratio, in 1024² units
default: (w, h)
sizes: { chip: [(w, h), ...] } authored, mirrored pairs across chips
```
The `sizes` lists are **authored literals checked into Python**. For the
"generated" families they are produced once by a helper script using the
Resolution Selector formula and then hand-adjusted to include the official
examples named below (for instance 1920×1088 for Flux 16:9 does not fall out
of the formula; it is added by hand). The validator never regenerates them.
The live Custom-ratio list does use the formula, so it may offer a slightly
different size than the chip list for the same ratio; that is acceptable and
the header shows the exact size either way.
Python unit tests validate the table: every size is a multiple of `step`,
every size's ratio is within 7% of its chip, every landscape entry has its
portrait mirror under the mirrored chip, defaults are present in `sizes`,
labels and aliases are unique, and every "must include" size named in the
family table below is present.
## The families (v1)
Sources are listed per row. "Official list" means the sizes are copied; for
families whose guidance is "any size, multiples of N, within a megapixel
range", the sizes are generated with the Resolution Selector formula at the
stated tiers and step, then spot-checked against the official examples
named. 1 MP = 1,048,576 px.
| id | Label | VAE | Step | Tiers (MP) | Default | Sizes |
|---|---|---|---|---|---|---|
| `sd15` | SD 1.5 | 8 | 64 | 0.25, 0.5 | 512×512 | generated; 512-class, plus 768×768 |
| `sdxl` | SDXL / Pony | 8 | 64 | 1.0 | 1024×1024 | official 1 MP buckets: 1024², 1152×896, 1216×832, 1344×768, 1536×640 and mirrors; 4:5/5:4 as 832×1024 / 1024×832 |
| `illustrious` | Illustrious XL (v1+) | 8 | 32 | 1.0, 1.5, 2.25 | 1024×1024 | SDXL buckets plus the 1536 class from the v1.0 model card and paper: 1536², 1024×1536, 1248×1824, 1344×1792, 1152×2048 and mirrors |
| `anima` | Anima | 8 | 16 | 0.5, 1.0, 1.5 | 1024×1024 | generated within the documented 512–1536 range; Wan 2.1 latent format in ComfyUI |
| `flux1` | Flux.1 dev / schnell / Krea, Chroma, HiDream I1, SD3.5, Lumina 2 | 8 | 16 | 0.5, 1.0, 1.5, 2.0 | 1024×1024 | generated; must include 1920×1088 / 1088×1920 (BFL's 16:9 example) |
| `zimage` | Z-Image Turbo / Base | 8 | 16 | 1.0, 1.6 | 1024×1024 | official 1024 and 1280 sets (11 sizes each) confirmed by Tongyi on the model's HF discussion #28 |
| `qwen_image` | Qwen-Image / 2512 | 8 | 16 | 1.76 | 1328×1328 | official training-stage list: 1328², 1664×928, 1472×1104, 1584×1056 and mirrors |
| `flux2` | Flux.2 dev / Klein, Flux.3 Klein, MOSS-Image, Ideogram 4 | 16 | 16 | 1.0, 2.0, 4.0 | 1024×1024 | generated; must include 1248×832 (Flux.2 template default) and 2048² (4 MP max) |
| `qwen_image_21` | Qwen-Image 2.1 / Turbo | 16 | 32 | 1.0, 4.0 | 1024×1024 | official 2K presets 2048², 2400×1792, 2528×1696, 2752×1536 and mirrors, plus generated 1 MP set (ComfyUI template default) |
| `hunyuan_image_21` | HunyuanImage 2.1 | 32 | 32 | 4.0 | 2048×2048 | official: 2048², 2560×1536, 2304×1792 and mirrors; 3:2/2:3 generated at 4 MP. No 1 MP tier: the card says 1K output artifacts |
Families named in one row share a VAE and step; the combo label lists them so
users find their model. Adding a family later is one Python record plus its
test row.
### Video families (frame sizes for first/last-frame generation)
These rows exist so a keyframe can be generated at the exact size the video
model will be run at. The combo groups them under a "Video (keyframes)"
suffix in the label. The latent output is still an 8x image latent, which is
what the keyframe image model needs; it is never fed to the video model.
| id | Label | Step | Tiers (MP) | Default | Sizes |
|---|---|---|---|---|---|
| `wan22` | Wan 2.2 (keyframes) | 16 | 0.4, 0.9 | 1280×720 | official 480p/720p classes: 832×480, 1280×720, 1280×704 (5B TI2V template), 640×640, 960×960 and mirrors |
| `ltx2` | LTX-2 (keyframes) | 32 | 0.4, 0.9 | 768×512 | template sizes 768×512, 960×544 (ComfyUI templates), plus generated at 0.9 MP with the 32 step the LTX templates use |
| `hunyuan_video_15` | HunyuanVideo 1.5 (keyframes) | 16 | 0.4, 0.9 | 1280×720 | generated; 480p and 720p classes |
| `minimax_h3` | MiniMax H3 (keyframes) | 32 | 0.4, 1.0 | 864×480 | generated at 0.4 MP, step 32, which is what every official H3 template sets on Resolution Selector; 1.0 MP tier covers H3-Base's 768p ceiling |
Video family sizes are validated by the same table tests. They carry no
frame-count data by design.
Deliberately out of v1: Ming Image, SenseNova U1.5 and other pixel-space
models (ratio 1, needs its own latent path), HunyuanImage 3.0 (no native
ComfyUI support), Flux.3 Image (API only).
## Decisions
- Default family on first add: **SDXL** at 1024×1024 (decided 2026-10-27).
Decided 2026-10-27: video families are in, as keyframe sizes (see above).
Builder-check calls made by the author, 2026-10-27: authored chip membership
wins over ratio tolerance (7%); combo value is the label with an alias list;
custom-ratio mode is an in-memory flag; size lists are authored literals;
manual 16x/32x generation check before merge.
## Verification before merge
The real-host CI never queues a generation, so the latent path is only
unit-tested. Core `EmptyLatentImage` is the same 4-channel, 8x, marked latent
this node emits, and the official Anima template already runs that path
(channel repeat 4 → 16). The two cases to confirm by hand on the user's GPU
box before merge, each with one queued image: a 16x family (Flux.2 or
Qwen-Image 2.1) and the 32x family (HunyuanImage 2.1) at a non-square size,
checking the saved image's dimensions equal the node's outputs. Record the
result in the PR.
## Constraints
- Node id `ModelResolutionPicker`, display name "Model Resolution Picker",
category `🫶 ComfyAssets/🖼️ Resolution`. Registered in
`kikotools/__init__.py` like the others.
- Frontend: `web/model_resolution_picker.js` extension plus a view under
`web/views/` built only from `web/ui/core.js` primitives and
`controls.css` tokens; styling added to `controls.css` under a new
`.ca-resolution-*` family. `createScope()` owns listeners, the fetch
signal and theme observers; node removal disposes it and preserves the
previous `onRemoved` hook's context and return value, as every migrated
node does.
- Swap reuses `ui/dimension-swap.js` for the button and `ui/dimensions.js`
for the swap; the latter gains support for nodes that have `width`/`height`
but no `preset` widget (today it returns early).
- Tests, per `TESTING.md` tiers: Python unit tests for the table validator,
rounding and execution (including the latent mark and the unknown-id
fallback); JS unit tests for derivation, ratio parsing, custom-ratio
generation and model-change selection; browser-fixture workflows for chip
→ widgets, list → widgets, swap → mirrored chip, model change, custom
ratio, wired-in dimensions, theme switch, idempotent mount and disposal;
real-host coverage of both renderers and workflow round-trip. Python
coverage stays above the 85% gate; new JS files get floors in
`tools/testing/frontend-coverage-baseline.json`.
- README gets a tools-table row and a section with a screenshot under
`docs/screenshots/`; `docs/ui-migration.md` gets a row.
- Version bump to 1.3.0 (new node), in the release PR, not this one.
- No external requests. The only HTTP call is the local families route.
## Left to the builder
- Exact chip grid markup (`createSegments` vs buttons with
`aria-pressed`), keyboard order, and how disabled chips announce
themselves.
- The generated-size algorithm's tie-breaking and the spot-check test list.
- Whether the families route also serves ETag/caching headers.
- Node default size fine-tuning and the status line's wording.
- File split inside `kikotools/tools/model_resolution_picker/` (`node.py`,
`logic.py`, `families.py`, `routes.py`).
## Addendum (2026-10-27, after first hands-on test): JSON table, user override, resolution dropdown
Decided with the owner after testing the first build. Three changes, same branch.
### 1. The family table is a shipped JSON file
`kikotools/tools/model_resolution_picker/families.json` replaces the Python
literals. Its shape is exactly the payload the route already serves:
```json
{ "version": 1, "chips": [...], "mirror": {...}, "default": "SDXL",
"families": [ { "label": "SDXL", "aliases": [], "models": "...", "group": "image",
"vaeRatio": 8, "step": 64, "megapixels": [1.0], "default": [1024, 1024],
"sizes": { "1:1": [[1024, 1024]], "4:5": [[832, 1024]], ... } } ] }
```
`families.py` loads it at import, validates it with a runtime validator that
enforces the same invariants the tests enforce today (step multiples, 7% chip
tolerance, exact mirrors, ascending order, one chip per size, unique labels
and aliases, default present, every chip key present), and builds the same
`Family` records. A shipped file that fails validation is a startup error.
The file is package data (`[tool.setuptools.package-data]`), and the git
checkout is what ComfyUI loads, so the file simply lives next to the module.
### 2. A user override file
`<ComfyUI user directory>/kikotools/model_resolutions.json` (via
`folder_paths.get_user_directory()`; when that is unavailable there is no
override). Same schema as the shipped file; top-level `chips`/`mirror` are
ignored if present. Merge rules, by `label`:
- same label as a shipped family → the override record **replaces** it whole;
- new label → appended after the shipped families, in file order;
- optional `"remove": ["label", ...]` hides shipped families;
- `default` in the override, if present, becomes the combo default.
The merged table is validated with the same validator. An unreadable or
invalid override is **ignored entirely** (never partially applied), logged at
warning level, and reported to the panel: the payload gains
`"warnings": ["Override ignored: <reason>"]` and the panel shows the first
warning in its status line. The override is re-read when its mtime changes,
so edits are picked up on a page reload (or ComfyUI's "Refresh node definitions"), which also refreshes the `model` dropdown, without a restart. The frontend caches the families payload per page.
The shipped file is never modified by the node.
### 3. A native `resolution` dropdown
A COMBO widget named `resolution` sits between `model` and `width`. It lists
the sizes that fit the **active ratio chip** for the selected model, plus
`custom` first; with `Custom ratio` active or no chip matched it lists every
size for the model. Labels use the old node's format:
`1216×832 - 3:2 (1.0MP)` (ratio label = the chip the size is listed under,
else the reduced ratio; megapixels to one decimal, 1 MP = 1024²).
Sync rules (the panel remains a projection of the native widgets):
- Picking from the dropdown writes `width`/`height` through their callbacks,
exactly like a size-list click.
- Any size change (chip list, swap, typed width/height, model change) sets
`resolution` to the matching label, or `custom` when the size is not in the
active list. The dropdown's option list is rebuilt whenever the model or the
active chip changes.
- Python: when `resolution` is not `custom` and parses as `W×H`, those
dimensions win over `width`/`height` (same precedence as the old preset
dropdown); then the family-step rounding applies as before. The combo's
Python option list is `custom` plus every distinct size label across all
shipped families, and `VALIDATE_INPUTS` accepts `resolution` as it does
`model`, so override-added sizes and old labels still execute.
- Workflows now serialize five native widgets; nothing else changes.
Non-goals unchanged. The chip grid, Custom ratio and Swap stay as built.
### 4. Ratio shape on each chip
Each ratio chip shows a small outlined box shaped like its ratio before the
label (wide for 16:9, tall for 9:16, square for 1:1). It is drawn with the
toolkit's own markup and tokens: a `<span class="ca-resolution-shape">`
sized from the ratio (longest side 18 px, shortest side at least 6 px,
rounded to whole pixels), `border: 1px solid currentColor`, with the active
chip's white text colour showing through. Purely decorative: `aria-hidden`,
no effect on the accessible name, hidden on `Custom ratio`. No Pixaroma
code or assets.
+48
View File
@@ -0,0 +1,48 @@
# Autocomplete tag packs
ComfyAssets prepares filtered autocomplete data from Danbooru's public
`tags.json` and `tag_aliases.json` APIs. Dataset releases are versioned separately
from KikoTools and stored as release assets, keeping bulk data out of Git history.
## Danbooru — Illustrious / NoobAI, 2026-10-16
[Download the CSV and manifest](https://github.com/ComfyAssets/ComfyUI-KikoTools/releases/tag/tags-2026-10-16).
This snapshot contains 165,158 canonical tags, their categories, post counts, and
aliases. The adjacent manifest records the retrieval date, model targets,
selection filters, schema, byte size, and SHA-256 checksum.
The UTF-8 CSV has no header. Columns are `tag,category,post_count,aliases`.
Aliases share a comma-separated field; use a CSV parser that respects quoting.
An empty alias field means no aliases. Category codes are 0 (general), 1
(artist), 3 (copyright), 4 (character), and 5 (meta).
In ComfyUI Settings, search **Embedding Autocomplete**, open its settings,
then download the pack and choose the CSV under **Tag list file**. Enable
**Show Custom Words** and select **Save Settings**. Importing stages the list;
Cancel leaves the active list unchanged. The browser caches the saved file in
IndexedDB for offline use and restores it after a refresh. Clearing browser site
data removes this cache. There is no server-side autocomplete folder to populate.
Alternatively, enter a CSV/TXT URL and select **Load URL**, then save. Remote
servers must allow browser cross-origin requests; for GitHub release assets,
download and import the file locally. Existing one-column word lists and
`tag,frequency` lists remain supported. The default URL is unchanged.
Parsing runs in a Web Worker with a 32 MB UTF-8 limit. Search checks canonical
names and aliases, inserts the canonical tag, and ranks exact matches ahead of
prefixes and substrings, then by descending post count. The number of displayed
results is bounded by Maximum suggestions. Categories are retained in records;
category-specific controls are not currently exposed.
## Source and terms
Source data: [Danbooru](https://danbooru.donmai.us/), collected 2026-10-16.
Selection and packaging: ComfyAssets. This is an independent filtered snapshot,
not an official Danbooru release or a guarantee of model compatibility.
Refer to [Danbooru's Terms of Service](https://danbooru.donmai.us/terms) and the
[official terms template](https://github.com/danbooru/danbooru/blob/master/app/views/static/terms_of_service.html.erb).
The template's Copyright Infringement section identifies tags as factual
information that is generally not copyrightable. Its API Usage section requires
compliance with API rate limits. This reference does not claim an explicit
dataset license or extend KikoTools' software license to upstream data.
@@ -0,0 +1,35 @@
{
"source": "danbooru.donmai.us tags.json + tag_aliases.json",
"pulled": "2026-10-16",
"target_models": "Illustrious 1.0-3.x, NoobAI-XL",
"filters": {
"created_before": "2025-01-01",
"min_post_count": {
"general": 50,
"artist": 30,
"copyright": 20,
"character": 20
},
"artists_kept": true,
"deprecated_removed": true,
"meta_tags": "removed except small allowlist"
},
"tag_count": 165158,
"schema_version": 1,
"file": "danbooru-illustrious-noobai-2026-10-16.csv",
"encoding": "UTF-8",
"header_row": false,
"columns": [
"tag",
"category",
"post_count",
"aliases"
],
"aliases_format": "Comma-separated inside a CSV-quoted field when necessary",
"size_bytes": 3979593,
"sha256": "733531a60f2e2d38925f53381b18cff21513ef14550d1f2ca670a0f55b86b798",
"prepared_by": "ComfyAssets",
"terms_url": "https://danbooru.donmai.us/terms",
"terms_source_url": "https://github.com/danbooru/danbooru/blob/master/app/views/static/terms_of_service.html.erb",
"release_tag": "tags-2026-10-16"
}
+45
View File
@@ -0,0 +1,45 @@
# KikoTools UI migration
KikoTools' custom controls use the original ComfyAssets foundation in `web/ui/`.
They share blue primary actions, red destructive actions, keyboard controls and
ComfyUI-following dark/light colors. Layouts resize with the available space.
Pixaroma's toolkit inspired the organization; its code/assets are not vendored.
| Interface | Shared implementation | Retained examples |
| --- | --- | --- |
| Local Image Loader | `views/image-browser.js`, controls and scoped controller | [Loader](screenshots/live-image-loader/) |
| Save Image | Floating window, batch selection and responsive preview | [Viewer](screenshots/save-image-viewer/) |
| Seed History | `views/seed-history.js`, scoped watcher and disclosure | [History](screenshots/seed-history/) |
| Workflow Timer | Shared clock, controls and color picker | [Timer](screenshots/workflow-timer/) |
| Width Height / Empty Latent swaps | `ui/dimension-swap.js`, `ui/dimensions.js` | [Swaps](screenshots/dimension-swap/) |
| Model Resolution Picker | `views/resolution-picker.js`, `ui/resolution-picker.js`, shared swap | [Picker](screenshots/resolution-picker/) |
| Node Dimensions | `views/node-dimensions.js`, native dialog shell | [Dimensions](screenshots/node-dimensions/) |
| Display Text / Any | Shared output view, controller and widget adapter | [Output](screenshots/display-output/) |
| Embedding Autocomplete | Shared suggestions/settings, worker parsing and cache | [Autocomplete](screenshots/embedding-autocomplete/) |
| Custom Colors | Shared color picker, native node/group menus | [Colors](screenshots/custom-colors/) |
| Follow Execution | Native host menus/settings, idempotent execution listeners | [Menus](screenshots/follow-execution/) |
`createScope()` owns listeners, observers and delayed work. Node removal preserves
previous lifecycle hooks while disposing owned resources. Host-specific text
widget handling lives in `ui/text-widget-adapter.js`; floating windows and native
dialogs manage their own focus and cleanup. See [module notes](../web/ui/README.md).
Pure Python nodes continue using ComfyUI's native widgets. Gemini Prompt
Engineer retains its legacy UI for saved workflows under the agreed deprecation;
its UI migration was cancelled.
The [test guide](../TESTING.md) distinguishes fixture coverage, real-host
compatibility and model/GPU behavior. Required real-host CI uses a reviewed
upstream commit, fresh Classic/Nodes 2.0 starts and disposable CPU-only state.
## Remaining shared-toolset work
Before PromptManager adopts the foundation, extract a versioned shared source
package with reproducible browser artifacts. Then test both node packs alone and
together, including different bundled foundation versions, isolated settings
and identical styling. These requirements remain open in issue #81; the KikoTools
migration does not establish cross-pack compatibility by itself.
The approved design-preview draft [#82](https://github.com/ComfyAssets/ComfyUI-KikoTools/pull/82)
records the original direction. This inventory describes the shipped interfaces
and supersedes that draft's implementation sequence.
+366
View File
@@ -0,0 +1,366 @@
import os
from typing import Tuple
import comfy.sd
import comfy.utils
import torch
import torch.nn.functional as F
from comfy.sd import CLIP
from diffusers import ConsistencyDecoderVAE
from folder_paths import get_folder_paths
from huggingface_hub import hf_hub_download
from torch import Tensor
def find_or_create_cache():
cwd = os.getcwd()
if os.path.exists(os.path.join(cwd, "ComfyUI")):
cwd = os.path.join(cwd, "ComfyUI")
if os.path.exists(os.path.join(cwd, "models")):
cwd = os.path.join(cwd, "models")
if not os.path.exists(os.path.join(cwd, "huggingface_cache")):
print("Creating huggingface_cache directory within comfy")
os.mkdir(os.path.join(cwd, "huggingface_cache"))
return str(os.path.join(cwd, "huggingface_cache"))
class ConsistencyDecoder:
@classmethod
def INPUT_TYPES(s):
return {"required": {"latent": ("LATENT",)}}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "decode"
CATEGORY = "latent"
def __init__(self):
self.vae = (
ConsistencyDecoderVAE.from_pretrained(
"openai/consistency-decoder",
torch_dtype=torch.float16,
variant="fp16",
use_safetensors=True,
cache_dir=find_or_create_cache(),
)
.eval()
.to("cuda")
)
def _decode(self, latent):
"""Used when patching another vae."""
return self.vae.decode(latent.half().cuda()).sample
def decode(self, latent):
"""Used for standalone decoding."""
sample = self._decode(latent["samples"])
sample = sample.clamp(-1, 1).movedim(1, -1).add(1.0).mul(0.5).cpu()
return (sample,)
class PatchDecoderTiled:
@classmethod
def INPUT_TYPES(s):
return {"required": {"vae": ("VAE",)}}
RETURN_TYPES = ("VAE",)
FUNCTION = "patch"
category = "vae"
def __init__(self):
self.vae = ConsistencyDecoder()
def patch(self, vae):
del vae.first_stage_model.decoder
vae.first_stage_model.decode = self.vae._decode
vae.decode = (
lambda x: vae.decode_tiled_(
x,
tile_x=512,
tile_y=512,
overlap=64,
)
.to("cuda")
.movedim(1, -1)
)
return (vae,)
# quick node to set SDXL-friendly aspect ratios in 1024^2
# adapted from throttlekitty
class SDXLAspectRatio:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
}
}
RETURN_TYPES = ("INT", "INT")
RETURN_NAMES = ("width", "height")
FUNCTION = "run"
CATEGORY = "image"
def run(self, image: Tensor) -> Tuple[int, int]:
_, height, width, _ = image.shape
aspect_ratio = width / height
aspect_ratios = (
(1 / 1, 1024, 1024),
(2 / 3, 832, 1216),
(3 / 4, 896, 1152),
(5 / 8, 768, 1216),
(9 / 16, 768, 1344),
(9 / 19, 704, 1472),
(9 / 21, 640, 1536),
(3 / 2, 1216, 832),
(4 / 3, 1152, 896),
(8 / 5, 1216, 768),
(16 / 9, 1344, 768),
(19 / 9, 1472, 704),
(21 / 9, 1536, 640),
)
# find the closest aspect ratio
closest = min(aspect_ratios, key=lambda x: abs(x[0] - aspect_ratio))
return (closest[1], closest[2])
class ImageToMultipleOf:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
"multiple_of": (
"INT",
{
"default": 64,
"min": 1,
"max": 256,
"step": 16,
"display": "number",
},
),
"method": (["center crop", "rescale"],),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("image",)
FUNCTION = "run"
CATEGORY = "image"
def run(self, image: Tensor, multiple_of: int, method: str) -> Tuple[Tensor]:
"""Center crop the image to a specific multiple of a number."""
_, height, width, _ = image.shape
new_height = height - (height % multiple_of)
new_width = width - (width % multiple_of)
if method == "rescale":
return (
F.interpolate(
image.unsqueeze(0),
size=(new_height, new_width),
mode="bilinear",
align_corners=False,
).squeeze(0),
)
else:
top = (height - new_height) // 2
left = (width - new_width) // 2
bottom = top + new_height
right = left + new_width
return (image[:, top:bottom, left:right, :],)
class HFHubLoraLoader:
def __init__(self):
self.loaded_lora = None
self.loaded_lora_path = None
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"model": ("MODEL",),
"clip": ("CLIP",),
"repo_id": ("STRING", {"default": ""}),
"subfolder": ("STRING", {"default": ""}),
"filename": ("STRING", {"default": ""}),
"strength_model": (
"FLOAT",
{"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01},
),
"strength_clip": (
"FLOAT",
{"default": 1.0, "min": -20.0, "max": 20.0, "step": 0.01},
),
}
}
RETURN_TYPES = ("MODEL", "CLIP")
FUNCTION = "load_lora"
CATEGORY = "loaders"
def load_lora(
self,
model,
clip,
repo_id: str,
subfolder: str,
filename: str,
strength_model: float,
strength_clip: float,
):
if strength_model == 0 and strength_clip == 0:
return (model, clip)
lora_path = hf_hub_download(
repo_id=repo_id.strip(),
subfolder=(
None
if subfolder is None or subfolder.strip() == ""
else subfolder.strip()
),
filename=filename.strip(),
cache_dir=find_or_create_cache(),
)
lora = None
if self.loaded_lora is not None:
if self.loaded_lora_path == lora_path:
lora = self.loaded_lora
else:
self.loaded_lora = None
self.loaded_lora_path = None
if lora is None:
lora = comfy.utils.load_torch_file(lora_path, safe_load=True)
self.loaded_lora = lora
self.loaded_lora_path = lora_path
model_lora, clip_lora = comfy.sd.load_lora_for_models(
model, clip, lora, strength_model, strength_clip
)
return (model_lora, clip_lora)
class HFHubEmbeddingLoader:
"""Load a text model embedding from Huggingface Hub.
The connected CLIP model is not manipulated."""
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"clip": ("CLIP",),
"repo_id": ("STRING", {"default": ""}),
"subfolder": ("STRING", {"default": ""}),
"filename": ("STRING", {"default": ""}),
}
}
RETURN_TYPES = ("CLIP",)
FUNCTION = "download_embedding"
CATEGORY = "n/a"
def download_embedding(
self,
clip: CLIP, # added to signify it's best put in between nodes
repo_id: str,
subfolder: str,
filename: str,
):
hf_hub_download(
repo_id=repo_id.strip(),
subfolder=(
None
if subfolder is None or subfolder.strip() == ""
else subfolder.strip()
),
filename=filename.strip(),
local_dir=get_folder_paths("embeddings")[0],
)
return (clip,)
class GlifVariable:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"variable": (
[
"",
],
),
"fallback": (
"STRING",
{
"default": "",
"single_line": True,
},
),
}
}
RETURN_TYPES = ("STRING", "INT", "FLOAT")
FUNCTION = "do_it"
CATEGORY = "glif/variables"
@classmethod
def VALIDATE_INPUTS(cls, variable: str, fallback: str):
# Since we populate dynamically, comfy will report invalid inputs. Override to always return True
return True
def do_it(self, variable: str, fallback: str):
variable = variable.strip()
fallback = fallback.strip()
if variable == "" or (variable.startswith("{") and variable.endswith("}")):
variable = fallback
int_val = 0
float_val = 0.0
string_val = f"{variable}"
try:
int_val = int(variable)
except Exception:
pass
try:
float_val = float(variable)
except Exception:
pass
return (string_val, int_val, float_val)
NODE_CLASS_MAPPINGS = {
"GlifConsistencyDecoder": ConsistencyDecoder,
"GlifPatchConsistencyDecoderTiled": PatchDecoderTiled,
"SDXLAspectRatio": SDXLAspectRatio,
"ImageToMultipleOf": ImageToMultipleOf,
"HFHubLoraLoader": HFHubLoraLoader,
"HFHubEmbeddingLoader": HFHubEmbeddingLoader,
"GlifVariable": GlifVariable,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"GlifConsistencyDecoder": "Consistency VAE Decoder",
"GlifPatchConsistencyDecoderTiled": "Patch Consistency VAE Decoder",
"SDXLAspectRatio": "Image to SDXL compatible WH",
"ImageToMultipleOf": "Image to Multiple of",
"HFHubLoraLoader": "Load HF Lora",
"HFHubEmbeddingLoader": "Load HF Embedding",
"GlifVariable": "Glif Variable",
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

+106
View File
@@ -0,0 +1,106 @@
# Batch Prompts Node
The **Batch Prompts** node loads and processes prompts from text files for batch generation workflows. It automatically cycles through prompts with each execution, making it perfect for testing multiple prompts in queue batches.
## Features
- **File-based prompt loading** - Load prompts from text files with `---` separators
- **Auto-increment mode** - Automatically advance to the next prompt with each execution
- **Positive/Negative splitting** - Automatically splits prompts at "Negative:" markers
- **Persistent state** - Maintains position across ComfyUI restarts
- **Wrap-around support** - Loop back to the first prompt after the last one
- **Progress tracking** - Shows current position and total prompts
## Input Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `prompt_file` | STRING | "prompts.txt" | Path to text file containing prompts |
| `index` | INT | 0 | Manual prompt index (when auto_increment is off) |
| `auto_increment` | BOOLEAN | True | Automatically advance to next prompt |
| `wrap_around` | BOOLEAN | True | Loop back to start after last prompt |
| `split_negative` | BOOLEAN | True | Split prompts at "Negative:" marker |
| `reload_file` | BOOLEAN | False | Force reload file from disk |
| `show_preview` | BOOLEAN | True | Show prompt preview in console |
## Output Values
| Output | Type | Description |
|--------|------|-------------|
| `positive` | STRING | The positive prompt text |
| `negative` | STRING | The negative prompt text (if split) |
| `full_prompt` | STRING | Complete prompt including negative |
| `next_prompt` | STRING | Preview of the next prompt |
| `current_index` | INT | Current prompt index (0-based) |
| `total_prompts` | INT | Total number of prompts |
| `batch_info` | STRING | Progress information string |
## Prompt File Format
Create a text file with prompts separated by `---` on its own line:
```
A beautiful sunset over the ocean
Negative: blurry, dark, low quality
---
Mountain landscape with snow peaks
Negative: foggy, unclear
---
Futuristic city at night
Negative: old, vintage, sepia
```
## Usage Examples
### Basic Queue Batch Processing
1. Create a prompt file in your ComfyUI `input` folder
2. Add the Batch Prompts node to your workflow
3. Set `prompt_file` to your file name
4. Enable `auto_increment` and `wrap_around`
5. Connect `positive` to your text encoder
6. Connect `negative` to your negative text encoder
7. Set Queue Batch to desired number (e.g., 10)
8. Run the queue - prompts will cycle automatically
### Manual Index Control
For manual control over which prompt to use:
1. Set `auto_increment` to False
2. Control the `index` parameter manually
3. Use with other nodes that provide index values
### Monitoring Progress
The node provides several ways to track progress:
- `batch_info` output shows "Prompt X of Y (Z% complete)"
- Console logging shows current prompt preview (when `show_preview` is True)
- `current_index` and `total_prompts` for custom progress displays
## Tips
- Place prompt files in the ComfyUI `input` folder for easy access
- Use relative paths like "prompts.txt" for files in the input folder
- Use absolute paths for files elsewhere on your system
- The node maintains state across ComfyUI restarts
- Set `reload_file` to True to force re-reading after editing the file
- Empty sections (between `---` markers) are automatically skipped
## Troubleshooting
### Prompts not changing in queue batch
- Ensure `auto_increment` is set to True
- Check console for "[BatchPrompts] Auto-increment" messages
- Restart ComfyUI after installing/updating the node
### File not found errors
- Check that the file exists in the ComfyUI `input` folder
- Try using an absolute path to test
- Ensure file has read permissions
### State persistence
- State is stored in your system's temp directory
- Clear `/tmp/comfyui_batch_prompts/` to reset all counters
- Use `reload_file` to reset counter for a specific file
+120
View File
@@ -0,0 +1,120 @@
# Display Any
The Display Any node is a debugging and inspection tool that can display any type of input value in ComfyUI. It's particularly useful for understanding data structures and tensor shapes during workflow development.
## Features
- **Universal Input**: Accepts any type of input data (tensors, strings, numbers, lists, dictionaries, etc.)
- **Two Display Modes**:
- **Raw Value**: Shows the string representation of the input
- **Tensor Shape**: Extracts and displays the shapes of any tensors found in the input
- **Nested Structure Support**: Can find tensors within nested dictionaries and lists
- **UI Output**: Displays results directly in the ComfyUI interface
## Inputs
- **input** (*): Any value you want to display or inspect
- **mode** (DROPDOWN): Display mode selection
- `raw value`: Shows the complete string representation of the input
- `tensor shape`: Extracts and shows shapes of any tensors in the input
## Outputs
- **display_text** (STRING): The formatted display text
## Usage Examples
### 1. Display Simple Values
Connect any output to see its raw value:
```
String Input: "Hello, ComfyUI!"
Mode: raw value
Output: "Hello, ComfyUI!"
```
### 2. Inspect Tensor Shapes
Great for debugging image processing pipelines:
```
Image Tensor: [1, 3, 512, 512]
Mode: tensor shape
Output: "[[1, 3, 512, 512]]"
```
### 3. Debug Complex Data Structures
View nested data structures with multiple tensors:
```python
Input: {
"images": tensor([1, 3, 256, 256]),
"masks": [tensor([256, 256]), tensor([256, 256, 1])],
"config": {"steps": 20}
}
Mode: tensor shape
Output: "[[1, 3, 256, 256], [256, 256], [256, 256, 1]]"
```
### 4. Workflow Debugging
Use Display Any nodes at various points in your workflow to understand data flow:
- After loading images to verify dimensions
- Before/after processing nodes to track shape changes
- To inspect conditioning or latent data structures
- To view metadata or configuration dictionaries
## Use Cases
### Image Pipeline Debugging
Place Display Any nodes after image loading and processing nodes to track dimension changes:
```
Load Image → Display Any (tensor shape) → Resize → Display Any (tensor shape)
```
### Latent Space Inspection
Understand latent dimensions in your workflows:
```
VAE Encode → Display Any (tensor shape) → KSampler → Display Any (raw value)
```
### Configuration Verification
Display complex configuration objects to ensure correct settings:
```
Config Node → Display Any (raw value) → Processing Node
```
## Tips
1. **Multiple Display Nodes**: You can use multiple Display Any nodes in a single workflow to track data at different stages
2. **Tensor Shape Mode**: Particularly useful when working with:
- Image batches to verify batch size
- Latent tensors to understand dimensions
- Mask arrays to check compatibility
3. **Raw Value Mode**: Best for:
- String prompts and text
- Configuration dictionaries
- Debugging node outputs
- Understanding data structure
4. **No Tensors Found**: If you see "No tensors found in input" in tensor shape mode, the input doesn't contain any tensor-like objects (numpy arrays, torch tensors, etc.)
## Technical Notes
- The node uses `str()` for raw value display, providing Python's string representation
- Tensor shape detection works with any object that has a `shape` attribute
- Nested structure traversal supports dictionaries, lists, and tuples
- The output is both displayed in the UI and available as a string output for further processing
## Example Workflow Integration
```
[Load Image] → [Image Processing] → [Display Any (tensor shape)]
↓
"[[1, 3, 512, 512]]"
↓
[Text Multiline] ← [Concatenate] ← "Image dimensions: "
```
This creates a text output showing the current image dimensions that can be used elsewhere in your workflow.
+147
View File
@@ -0,0 +1,147 @@
# Display Text
The Display Text node provides advanced text display capabilities with smart formatting, interactive features, and responsive design for ComfyUI workflows.
## Features
- **Smart Prompt Detection**: Automatically detects and formats SDXL-style positive/negative prompt pairs
- **Text Wrapping**: Proper word wrapping that reflows when node is resized
- **Scrollable Content**: Mouse wheel scrolling for long texts with visual indicators
- **Copy Functionality**: Always-visible copy button with visual feedback
- **Split View Mode**: Side-by-side display for prompt pairs
- **Responsive Design**: Content adapts to node resizing
## Inputs
- **text** (STRING): The text to display
- Can be a single text block
- Can contain "Positive prompt:" and "Negative prompt:" sections for automatic split view
## Outputs
- **text** (STRING): Pass-through of the input text
## Display Modes
### Single Text Mode
When the input is regular text without prompt markers, it displays as a single scrollable text area with:
- Word wrapping at word boundaries
- Vertical scrolling for long content
- Single copy button for the entire text
### Split View Mode
Automatically activated when text contains both "Positive prompt:" and "Negative prompt:" sections:
- Side-by-side display with 50/50 split
- Independent scrolling for each section
- Separate copy buttons for each prompt
- Labels are stripped when copying (clean prompts)
## Usage Examples
### 1. Display Generated Prompts
```
Gemini Prompt → Display Text → Copy to workflow
```
The node automatically detects SDXL format and shows positive/negative prompts side-by-side.
### 2. Debug Text Processing
```
Text Processing → Display Text → Further Processing
```
View intermediate text processing results with proper formatting.
### 3. Show Long Descriptions
```
Load Text → Display Text → Review
```
Display long text content with scrolling and word wrapping.
## Interactive Features
### Copy Button
- Always visible in the top-right corner
- Shows "✓ Copied!" feedback on click
- In split view: separate buttons for each section
- Strips prompt labels for clean copying
### Scrolling
- Mouse wheel scrolling when hovering over text
- Visual indicators appear when content is scrollable
- Smooth scrolling with proper boundaries
- Independent scrolling in split view mode
### Resizing
- Text reflows when node width changes
- Maintains readability at different sizes
- Split view maintains 50/50 proportions
- Minimum height ensures usability
## Smart Prompt Detection
The node intelligently detects prompt formats:
1. **SDXL Format**:
- Looks for "Positive prompt:" and "Negative prompt:" markers
- Case-insensitive detection
- Handles various formatting styles
2. **Label Stripping**:
- When copying from split view, labels are removed
- "Positive prompt: beautiful sunset" → "beautiful sunset"
- Clean prompts ready for direct use
## Styling
- **Font**: Monospace for consistent alignment
- **Colors**:
- Text: Light gray (#ddd) on dark background
- Background: Semi-transparent dark (#1a1a1a)
- Borders: Subtle gray (#333)
- **Spacing**: Comfortable padding and line height
- **Visual Feedback**: Hover effects on interactive elements
## Use Cases
### Prompt Engineering Workflows
- Display AI-generated prompts with proper formatting
- Compare positive and negative prompts side-by-side
- Copy refined prompts without manual cleanup
### Text Processing Pipelines
- Debug text transformations at each step
- View formatted outputs from text nodes
- Monitor prompt construction workflows
### Documentation and Notes
- Display workflow instructions
- Show generation parameters
- Present formatted metadata
## Technical Details
- **Text Processing**: Preserves original text while adding display formatting
- **Responsive Design**: CSS-based layout adapts to node dimensions
- **Event Handling**: Proper event propagation for ComfyUI compatibility
- **Memory Efficient**: Only renders visible text portions
## Tips
1. **For Long Prompts**: The scrolling feature handles texts of any length efficiently
2. **Quick Copy**: Use the copy buttons to quickly grab prompts for other nodes
3. **Resizing**: Drag node edges to find optimal display width for your content
4. **Split View**: Works best with SDXL-format prompts but handles any dual-section text
## Integration Example
```
[Gemini Prompt Engineer] → [Display Text] → [Copy Button Click]
↓ ↓ ↓
SDXL Format Split View Display Clean Prompts
```
This creates a seamless workflow from prompt generation to usage, with the Display Text node providing the visual interface for review and interaction.
@@ -0,0 +1,222 @@
# Empty Latent Batch Documentation
## Overview
The Empty Latent Batch is a ComfyUI node that creates empty latent tensors with batch support and preset integration. It combines the preset functionality of Width Height Selector with efficient batch processing capabilities, making it ideal for batch workflows and optimized generation pipelines.
## Features
### 🎯 **Preset Integration**
- **26 Curated Presets**: Full access to SDXL, FLUX, and Ultra-wide presets
- **Formatted Display**: Shows aspect ratio, megapixels, and model group
- **Smart Fallback**: Automatic fallback to custom dimensions for invalid presets
- **Model Optimization**: Preset categories optimized for different model types
### 📦 **Batch Processing**
- **Configurable Batch Size**: Create 1-64 empty latents in single operation
- **Memory Efficient**: Uses torch.zeros for optimal memory allocation
- **Batch Validation**: Prevents excessive memory usage with warnings
- **ComfyUI Compatible**: Standard latent format for seamless integration
### 🔄 **Visual Swap Button**
- **Interactive UI**: Blue swap button with hover and click feedback
- **Preset-Aware Swapping**: Intelligent switching between matching presets
- **Custom Dimension Support**: Simple value swapping for custom inputs
- **Visual Feedback**: Button state changes during interaction
### ✅ **Smart Validation**
- **Dimension Sanitization**: Automatic adjustment to divisible-by-8 constraint
- **Memory Estimation**: Built-in memory usage calculation
- **Error Handling**: Graceful handling of invalid inputs with helpful messages
- **Logging**: Detailed operation logging for debugging
## Node Interface
### Inputs
- **preset**: Dropdown with 26 formatted preset options + custom
- **width**: Custom width (64-8192, step 8, default 1024)
- **height**: Custom height (64-8192, step 8, default 1024)
- **batch_size**: Number of latents to create (1-64, default 1)
### Outputs
- **latent**: Dictionary containing batch of empty latent tensors
- **width**: Final sanitized width (guaranteed divisible by 8)
- **height**: Final sanitized height (guaranteed divisible by 8)
## Preset Reference
The Empty Latent Batch node uses the same 26 curated presets as the Width Height Selector:
### SDXL Presets (~1 Megapixel)
Optimized for SDXL models with ~1MP resolution constraint.
### FLUX Presets (High Resolution)
Higher resolution presets optimized for FLUX models with better quality/speed balance.
### Ultra-Wide Presets (Modern Ratios)
Modern aspect ratios for ultra-wide and panoramic generation.
*For complete preset details, see [Width Height Selector Documentation](width_height_selector.md#preset-reference)*
## Usage Examples
### Basic Empty Latent Creation
1. **Select Preset**: Choose from dropdown (e.g., "1024×1024 - 1:1 (1.0MP) - SDXL")
2. **Set Batch Size**: Enter desired number of latents (e.g., 4)
3. **Connect Output**: Link latent output to KSampler or other processing nodes
### Custom Batch Creation
1. **Set Preset**: Select "custom"
2. **Enter Dimensions**: Input width and height manually
3. **Set Batch Size**: Configure number of latents needed
4. **Validation**: Automatic sanitization ensures compatibility
### Orientation Swapping
1. **Choose Preset**: Any preset (e.g., "1920×1080")
2. **Click Swap Button**: Blue button in bottom-right corner
3. **Result**: Gets swapped preset if available, or custom dimensions with swapped values
4. **Widget Update**: Width/height widgets automatically update
### Memory-Aware Batch Processing
1. **Large Batch**: Set batch_size to 16 or higher
2. **Memory Warning**: Node provides memory usage estimation
3. **Optimization**: Choose appropriate resolution preset for available VRAM
## Common Workflows
### Batch Generation Pipeline
```
Empty Latent Batch → KSampler → VAE Decode → Save Image
(batch_size: 4) ↓ ↓ ↓
4 samples 4 images 4 files
```
- Create 4 empty latents at once
- Process all through sampling
- Generate 4 images in single operation
- Efficient for parameter exploration
### Model Comparison Workflow
```
Empty Latent Batch → [Multiple KSamplers] → [Multiple VAE Decoders] → Compare Results
(batch_size: 8) ↓ ↓ ↓
Split batch Process variants Side-by-side
```
- Create consistent batch of empty latents
- Split across different samplers/models
- Compare results with identical starting conditions
### Upscaling Preparation
```
Empty Latent Batch → KSampler → VAE Decode → Resolution Calculator → Upscaler
(832×1216, batch:4) ↓ ↓ ↓ ↓
Sample Decode Calculate 2x Upscale batch
```
- Generate batch at base resolution
- Calculate upscale dimensions
- Process entire batch through upscaler
### Aspect Ratio Exploration
```
Empty Latent Batch → [Clone to multiple orientations] → Parallel Processing
(1920×1080) ↓ ↓
[Swap Button] → Portrait & Landscape versions Compare orientations
```
- Start with base preset
- Use swap button to create orientation variants
- Process both simultaneously
## Advanced Features
### Memory Estimation
The node provides built-in memory estimation for batch operations:
```python
# Example memory calculations
Batch Size: 4, Resolution: 1024×1024
Latent Tensor: 4 × 4 × 128 × 128 = 262,144 elements
Memory Usage: 262,144 × 4 bytes = 1.0 MB per batch
```
### Intelligent Preset Handling
- **Formatted Display**: Shows full metadata in dropdown
- **Original Extraction**: Extracts original preset name from formatted strings
- **Validation**: Verifies preset exists before processing
- **Fallback Logic**: Uses custom dimensions if preset is invalid
### Batch Size Optimization
- **Performance Warnings**: Alerts for large batch sizes
- **Memory Limits**: Prevents excessive memory allocation
- **Hardware Awareness**: Considers available system resources
## Tips and Best Practices
### Batch Size Selection
- **Small Batches (1-4)**: Good for testing and development
- **Medium Batches (5-16)**: Efficient for most production workflows
- **Large Batches (17-64)**: Only for high-memory systems and specific use cases
### Preset Selection
- **SDXL Projects**: Use SDXL presets for memory efficiency
- **FLUX Projects**: Use FLUX presets for optimal quality
- **Ultra-wide Projects**: Ensure sufficient VRAM for large resolutions
- **Custom Projects**: Use custom dimensions for specific requirements
### Memory Management
- Monitor memory usage with large batches
- Use appropriate resolution presets for available VRAM
- Consider splitting very large batches across multiple nodes
- Clear GPU memory between large batch operations
### Workflow Integration
- Always connect all three outputs (latent, width, height)
- Use width/height outputs for downstream dimension calculations
- Combine with Resolution Calculator for upscaling workflows
- Leverage batch processing for efficient parameter exploration
## Troubleshooting
### Common Issues
- **Out of Memory**: Reduce batch_size or use lower resolution presets
- **Invalid Dimensions**: Node automatically sanitizes to valid values
- **Preset Not Found**: Falls back to custom dimensions with warning
- **Swap Button Not Working**: Ensure node is not collapsed and button is visible
### Performance Optimization
- **Batch Size**: Start with smaller batches and increase as needed
- **Resolution**: Use appropriate presets for your model and VRAM
- **Memory Monitoring**: Watch for memory warnings and adjust accordingly
- **Cleanup**: Clear unused tensors between large batch operations
### Error Handling
- **Dimension Validation**: Automatic rounding to nearest valid values
- **Batch Size Limits**: Clamped to 1-64 range with warnings
- **Memory Allocation**: Graceful handling of insufficient memory
- **Preset Fallbacks**: Automatic fallback to custom dimensions
## Technical Details
### Latent Tensor Format
- **Shape**: [batch_size, 4, height//8, width//8]
- **Data Type**: torch.float32
- **Initialization**: torch.zeros for clean empty state
- **Memory Layout**: Contiguous tensor for optimal performance
### Validation Pipeline
1. **Preset Extraction**: Parse formatted preset strings
2. **Dimension Calculation**: Get base dimensions from preset or custom
3. **Sanitization**: Ensure divisible-by-8 constraint
4. **Batch Validation**: Check batch size limits
5. **Memory Estimation**: Calculate expected memory usage
6. **Tensor Creation**: Allocate and initialize latent tensor
### UI Integration
- **JavaScript Extension**: Custom UI for swap button functionality
- **Widget Synchronization**: Auto-update width/height when preset changes
- **Visual Feedback**: Hover effects and click animations
- **Event Handling**: Proper mouse event management
### Swap Button Implementation
- **Position Calculation**: Dynamic positioning based on node size
- **State Management**: Visual feedback for button interactions
- **Preset Intelligence**: Smart switching between compatible presets
- **Fallback Logic**: Custom dimension swapping when preset not available
@@ -0,0 +1,152 @@
# Flux Sampler Params
## Overview
The **Flux Sampler Params** node provides a specialized parameter generator for FLUX model sampling. This tool was adapted from the excellent [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) project (now in maintenance mode) and enhanced for the ComfyAssets ecosystem.
## Attribution
This node is based on work from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) by cubiq. The original project is in maintenance-only mode, and we've adopted and enhanced these tools to ensure continued support and compatibility with modern ComfyUI workflows.
## Features
- **FLUX-Optimized Parameters**: Specifically tuned for FLUX model requirements
- **Batch Processing Support**: Generate multiple parameter sets for comparative testing
- **Interactive UI Elements**: Visual controls for quick parameter adjustments
- **Smart Defaults**: Pre-configured optimal settings for FLUX workflows
- **Comprehensive Parameter Control**: Fine-tune all aspects of FLUX sampling
## Node Properties
- **Category**: `ComfyAssets/🧰 xyz-helpers`
- **Node Name**: `FluxSamplerParams`
- **Function**: `get_value`
## Inputs
### Required
| Parameter | Type | Default | Range | Description |
|-----------|------|---------|-------|-------------|
| `scheduler` | DROPDOWN | normal | [normal, simple, sgm_uniform] | Scheduler algorithm for sampling |
| `steps` | INT | 20 | 1-100 | Number of sampling steps |
| `guidance` | FLOAT | 3.5 | 0.0-100.0 | Guidance scale for conditioning |
| `max_shift` | FLOAT | 1.0 | 0.0-100.0 | Maximum shift value for FLUX |
| `base_shift` | FLOAT | 0.5 | 0.0-100.0 | Base shift value for FLUX |
| `denoise` | FLOAT | 1.0 | 0.0-1.0 | Denoising strength |
| `batch_mode` | DROPDOWN | single | [single, batch] | Single value or batch processing |
### Optional
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `batch_count` | INT | 1 | Number of batch variations (1-100) |
| `batch_seed_mode` | DROPDOWN | incremental | Seed generation mode for batches |
| `variation_seed` | INT | None | Optional seed for variations |
| `lora_params` | LORA_PARAMS | None | LoRA parameters from LoRAFolderBatch |
## Outputs
| Name | Type | Description |
|------|------|-------------|
| `sampler_params` | SAMPLER_PARAMS | Complete FLUX sampling parameters |
| `scheduler` | STRING | Selected scheduler algorithm |
| `steps` | INT | Number of sampling steps |
| `guidance` | FLOAT | Guidance scale value |
## Usage Examples
### Basic FLUX Sampling
```
FluxSamplerParams → KSampler → VAE Decode → Save Image
scheduler: normal
steps: 20
guidance: 3.5
```
### Batch Parameter Testing
```
FluxSamplerParams → KSampler → Image Grid → Save
batch_mode: batch
batch_count: 5
guidance: 2.0...5.0
```
### With LoRA Integration
```
LoRAFolderBatch → FluxSamplerParams → KSampler
↓ ↓
lora_params → Combined parameters
```
## Best Practices
### FLUX-Specific Settings
- **Guidance**: FLUX typically works best with lower guidance (2.0-5.0)
- **Steps**: 15-25 steps usually sufficient for FLUX
- **Scheduler**: `normal` or `sgm_uniform` recommended for FLUX
- **Shift Values**: Adjust for different quality/speed tradeoffs
### Batch Testing Workflow
1. Set `batch_mode` to `batch`
2. Configure parameter ranges using `...` syntax
3. Set appropriate `batch_count`
4. Use with image grid nodes for comparison
### Memory Optimization
- Start with smaller batch counts for testing
- Monitor VRAM usage with high batch counts
- Use incremental seed mode for reproducibility
## Integration with Other Nodes
### Works Well With
- **LoRA Folder Batch**: Combine multiple LoRAs with FLUX parameters
- **Plot Parameters**: Visualize parameter effects
- **Sampler Select Helper**: Dynamic sampler selection
- **Text Encode Sampler Params**: Add text conditioning
### Common Workflows
1. **Parameter Sweep**: Test multiple guidance/step combinations
2. **LoRA Testing**: Evaluate different LoRA strengths with FLUX
3. **Quality Comparison**: Compare different shift values
4. **Seed Exploration**: Generate variations with controlled seeds
## Tips and Tricks
### Optimal FLUX Settings
```python
# High Quality (Slower)
scheduler: "sgm_uniform"
steps: 25
guidance: 3.5
max_shift: 1.0
base_shift: 0.5
# Fast Preview
scheduler: "simple"
steps: 12
guidance: 2.5
max_shift: 0.8
base_shift: 0.4
```
### Batch Parameter Ranges
- Steps: `15...25+5` (test 15, 20, 25)
- Guidance: `2.0...5.0+0.5` (test 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0)
- Denoise: `0.8...1.0+0.1` (test 0.8, 0.9, 1.0)
## Troubleshooting
### Common Issues
1. **Out of Memory**: Reduce batch_count or image resolution
2. **Poor Quality**: Increase steps or adjust guidance
3. **Artifacts**: Check shift values aren't too high
4. **Slow Generation**: Use `simple` scheduler for previews
### Parameter Guidelines
- Don't set guidance too high (>10) for FLUX
- Keep denoise at 1.0 for initial generation
- Adjust shift values gradually for best results
## Version History
- **1.0.0**: Initial adaptation from comfyui-essentials-nodes
- **1.0.1**: Added batch processing support
- **1.0.2**: Enhanced FLUX-specific optimizations
- **1.0.3**: Improved UI elements and parameter validation
## Credits
Original implementation by cubiq in [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials). Adapted and maintained by the ComfyAssets team.
+220
View File
@@ -0,0 +1,220 @@
# Gemini Prompt Engineer (Deprecated)
**Legacy documentation for existing workflows.** This node is soft-deprecated
and hidden from normal new-node discovery through ComfyUI's deprecated-node
metadata. Users who enable deprecated-node visibility may still find it.
The `GeminiPrompt` registration, display name, input/output schema and execution
remain available for saved workflows. Existing configuration and model-refresh
behavior remain in place. Google API/model compatibility updates and UI migration
have ended; the historical model and setup details below may no longer match
Google's current service. No replacement or automatic workflow conversion is
provided.
The Gemini Prompt Engineer node uses Google's Gemini AI to analyze images and generate optimized prompts for various AI image generation models.
## Features
- **Multi-Model Support**: Generate prompts optimized for FLUX, SDXL, Danbooru, and Video generation
- **Custom Prompts**: Override templates with your own system prompts
- **Visual Feedback**: UI shows processing status and error states
- **Flexible API Key Management**: Multiple ways to provide API credentials
- **Dynamic Model Selection**: Fetch and use latest Gemini models with refresh button
- **Model Caching**: Persistent storage of available models for offline access
- **Help Integration**: Built-in setup guide accessible via help button
## Setup
### 1. Get API Key
Get your free Gemini API key from [Google AI Studio](https://makersuite.google.com/app/apikey)
### 2. Install Dependencies
```bash
pip install google-genai
```
### 3. Configure API Key
Choose one of these methods:
1. **Environment Variable** (Recommended):
```bash
export GEMINI_API_KEY="your-api-key-here"
```
2. **Config File**:
Create `gemini_config.json` in your ComfyUI root directory:
```json
{
"api_key": "your-api-key-here"
}
```
3. **Node Input**:
Enter the API key directly in the node's `api_key` field
## Inputs
- **image** (IMAGE): The image to analyze
- **prompt_type** (DROPDOWN): Type of prompt to generate
- `flux`: Detailed artistic prompts with quality markers
- `sdxl`: Positive/negative prompt pairs with weight emphasis
- `danbooru`: Anime-style booru tags with underscores
- `video`: Motion and temporal descriptions for video generation
- **model** (DROPDOWN): Gemini model selection
- Dynamically populated list of available models
- Includes latest models like gemini-2.0-flash-exp
- Click refresh button to update model list
- **api_key** (STRING, optional): Gemini API key if not set elsewhere
- **custom_prompt** (STRING, optional): Override template with custom system prompt
## Outputs
- **prompt** (STRING): Generated prompt text
- **negative_prompt** (STRING): Negative prompt (only populated for SDXL format)
## Prompt Type Details
### FLUX Format
Generates detailed prompts optimized for FLUX models:
- Starts with main subject and action
- Includes style and medium descriptors
- Adds lighting and atmosphere details
- Uses quality markers like "4K", "highly detailed", "award-winning"
Example output:
```
majestic mountain landscape at golden hour, oil painting style, dramatic lighting with sun rays piercing through clouds, wide angle composition, warm color palette with orange and purple hues, highly detailed, 4K resolution, trending on ArtStation, photorealistic rendering
```
### SDXL Format
Generates positive and negative prompt pairs with enhanced structure:
- Layered positive prompts: main subject → style → composition → technical
- Comprehensive negative prompts to avoid common issues
- Uses parentheses for emphasis: `(detailed eyes:1.2)`
- Includes quality boosters and technical specifications
Example output:
```
Positive prompt:
beautiful woman with flowing red hair, elegant pose, (detailed eyes:1.2), serene expression
oil painting style, renaissance art influence, classical portraiture
golden hour lighting, warm color palette, soft shadows, dramatic chiaroscuro
centered composition, rule of thirds, shallow depth of field, bokeh background
masterpiece, best quality, highly detailed, 8k uhd, professional artwork
Negative prompt:
low quality, worst quality, blurry, out of focus, pixelated, low resolution
bad anatomy, deformed features, extra limbs, missing limbs, disconnected limbs
poorly drawn face, poorly drawn hands, amateur drawing, bad proportions
oversaturated, overexposed, underexposed, bad lighting, harsh shadows
jpeg artifacts, watermark, signature, text, cropped, duplicate
```
### Danbooru Format
Generates booru-style tags for anime artwork:
- Uses underscores for multi-word concepts
- Includes character count descriptors (1girl, 2boys)
- Orders tags from most to least important
Example output:
```
1girl, solo, long_hair, blue_eyes, blonde_hair, school_uniform, serafuku, pleated_skirt, thighhighs, smile, looking_at_viewer, classroom, sitting, desk, window, sunlight, highres, masterpiece
```
### Video Format
Generates prompts for video generation models:
- Describes motion and camera movements
- Includes temporal markers and transitions
- Specifies technical details like fps and duration
Example output:
```
Aerial shot slowly descending toward a misty forest at dawn, camera smoothly transitions to tracking shot following a deer through the trees, photorealistic style, soft golden hour lighting with fog, 10 second duration, 4K resolution 24fps, ending with close-up of deer looking at camera
```
## Custom System Prompts
You can override any template by providing your own system prompt. This is useful for:
- Specialized use cases
- Different language outputs
- Custom formatting requirements
- Integration with specific workflows
Example custom prompt:
```
You are an expert at analyzing images and creating simple, concise descriptions.
Focus only on the main subject and primary colors.
Keep your response under 50 words.
```
## Error Handling
The node provides clear error messages for common issues:
- Missing API key
- API request failures
- Invalid image inputs
- Rate limiting
Errors are displayed in the prompt output for easy debugging.
## Model Selection
### Dynamic Model List
- Click the refresh button (🔄) next to the model dropdown to fetch latest models
- Models are fetched from Google's API and include all available versions
- Common models include:
- `gemini-2.0-flash-exp`: Latest experimental flash model
- `gemini-1.5-pro`: Advanced model with larger context
- `gemini-1.5-flash`: Fast and efficient for most tasks
### Model Caching
- Available models are cached locally for offline access
- Cache persists across ComfyUI sessions
- Refresh button updates the cache with latest models
## UI Features
### Help Button
- Click the help button (?) for quick setup instructions
- Shows API key setup methods
- Links to Google AI Studio for key generation
### Status Indicators
- Processing spinner during API calls
- Error messages displayed in red
- Success feedback when prompt is generated
## Tips
1. **API Usage**: Gemini has generous free tier limits, but be mindful of rate limits
2. **Image Quality**: Higher resolution images provide better analysis results
3. **Prompt Refinement**: You can chain multiple Gemini nodes with different custom prompts
4. **Caching**: Results are not cached, so identical images will make new API calls
5. **Model Selection**: Use flash models for faster responses, pro models for complex analysis
## Example Workflow
1. Load an image using Load Image node
2. Connect to Gemini Prompt Engineer
3. Select appropriate prompt_type for your target model
4. Connect prompt output to your generation model
5. For SDXL, connect both prompt and negative_prompt outputs
## Troubleshooting
**"API key not found" error**:
- Check environment variable is set correctly
- Verify config file path and JSON format
- Try entering key directly in node
**"No response generated" error**:
- Check internet connection
- Verify API key is valid
- Image might be too large (resize if needed)
**Import error for google-genai**:
- Run `pip install google-genai` in your ComfyUI environment
- Restart ComfyUI after installation
@@ -0,0 +1,82 @@
# Image to Multiple Of
## Overview
The **Image to Multiple Of** node adjusts image dimensions to be multiples of a specified value. This is particularly useful for models that require input dimensions to be multiples of certain values (e.g., 8, 16, 32, 64) for optimal performance or compatibility.
## Purpose
Many AI models, especially diffusion models and VAEs, require input dimensions to be multiples of specific values due to their architecture (e.g., downsampling layers). This node ensures your images meet these requirements without manual calculation.
## Inputs
- **image** (IMAGE, required): The input image to process
- **multiple_of** (INT, required): The value that dimensions should be multiple of
- Default: 64
- Range: 1-256
- Step: 16
- **method** (COMBO, required): Processing method
- Options: "center crop", "rescale"
## Outputs
- **image** (IMAGE): Processed image with dimensions adjusted to multiples of the specified value
## Processing Methods
### Center Crop
- Crops the image from the center to achieve the target dimensions
- Preserves image quality but may lose edge content
- Best for images where the important content is centered
### Rescale
- Resizes the image to the target dimensions using bilinear interpolation
- Keeps all content but may slightly affect image quality
- Best when you need to preserve all image content
## Usage Examples
### Example 1: Prepare for VAE Encoding
```
Load Image → Image to Multiple Of (multiple_of: 64) → VAE Encode
```
### Example 2: Prepare for Specific Model Requirements
```
Load Image → Image to Multiple Of (multiple_of: 32) → Model Processing
```
### Example 3: Batch Processing
```
Load Images → Image to Multiple Of (multiple_of: 16, method: rescale) → Batch Process
```
## Technical Details
- Supports batch processing (processes all images in a batch)
- Works with any number of channels (RGB, RGBA, grayscale, etc.)
- Calculates the largest dimensions that are less than or equal to the original size
- For center crop: crops equally from all sides to maintain centering
- For rescale: uses bilinear interpolation with align_corners=False
## Common Use Cases
1. **VAE Preprocessing**: Ensure images are compatible with VAE encoders that require dimensions divisible by 64
2. **Model Compatibility**: Adjust images for models with specific architectural requirements
3. **Batch Uniformity**: Ensure all images in a batch have dimensions that meet model requirements
4. **Performance Optimization**: Some models perform better with dimensions that are powers of 2
## Tips
- Use **center crop** when your subject is centered and you don't mind losing edge details
- Use **rescale** when you need to preserve all image content
- Common multiple_of values: 8, 16, 32, 64, 128
- For Stable Diffusion models, 64 is typically recommended
- For some upscaling models, 32 or 16 may be sufficient
## Error Handling
The node will raise an error if:
- The image dimensions are smaller than the specified multiple_of value
- Invalid input types are provided
- The resulting dimensions would be 0 or negative
+125
View File
@@ -0,0 +1,125 @@
# Kiko Film Grain
## Overview
The **Kiko Film Grain** node applies realistic film grain effects to images, simulating the aesthetic of analog film photography. It provides comprehensive controls for grain size, intensity, color saturation, and shadow lifting to achieve various film looks.
## Node Details
- **Category**: ComfyAssets/image
- **Node Name**: KikoFilmGrain
- **Display Name**: Kiko Film Grain
## Inputs
### Required
- **image** (`IMAGE`)
- The input image to apply film grain to
- Supports batch processing
- Preserves alpha channel if present
### Parameters
- **scale** (`FLOAT`)
- Controls the size of the grain pattern
- Range: 0.25 to 2.0
- Default: 0.5
- Lower values = finer grain, higher values = coarser grain
- **strength** (`FLOAT`)
- Intensity of the grain effect
- Range: 0.0 to 10.0
- Default: 0.5
- 0.0 = no grain, higher values = more pronounced grain
- **saturation** (`FLOAT`)
- Color saturation of the grain
- Range: 0.0 to 2.0
- Default: 0.7
- 0.0 = monochrome grain, 1.0 = full color, >1.0 = oversaturated
- **toe** (`FLOAT`)
- Lifts blacks/shadows for a film-like look
- Range: -0.2 to 0.5
- Default: 0.0
- Positive values lift shadows, negative values crush blacks
- **seed** (`INT`)
- Random seed for grain pattern generation
- Range: 0 to maximum integer
- Default: 0
- Use for reproducible grain patterns
## Outputs
- **image** (`IMAGE`)
- The processed image with film grain applied
- Same dimensions and batch size as input
- Alpha channel preserved if present
## Usage Examples
### Subtle Film Look
```
Scale: 0.5
Strength: 0.3
Saturation: 0.8
Toe: 0.05
```
Creates a subtle, fine-grained film aesthetic suitable for portraits.
### Vintage Film
```
Scale: 1.0
Strength: 0.8
Saturation: 0.5
Toe: 0.15
```
Simulates vintage film with moderate grain and lifted shadows.
### High ISO Film
```
Scale: 0.75
Strength: 1.5
Saturation: 0.6
Toe: 0.1
```
Emulates high ISO film stock with pronounced grain.
### Black & White Film
```
Scale: 0.6
Strength: 0.6
Saturation: 0.0
Toe: 0.08
```
Creates monochrome grain perfect for black and white photography.
## Technical Details
### Improvements Over Standard Implementations
1. **Pure PyTorch Operations**: No OpenCV dependencies, better GPU utilization
2. **ITU-R BT.709 Color Space**: Accurate color conversion for grain application
3. **Screen Blend Mode**: Preserves highlights better than multiply blending
4. **Channel-Specific Weighting**: Film grain is stronger in blue channel (3x), moderate in red (2x), matching real film characteristics
5. **Efficient Memory Management**: Minimizes tensor copies and conversions
### Algorithm Overview
1. Generate random noise at specified scale
2. Convert to YCbCr color space for realistic grain distribution
3. Apply different blur kernels to each channel:
- Y (luminance): 3x3 kernel for fine detail
- Cb (blue-yellow): 15x15 kernel for color noise
- Cr (red-green): 11x11 kernel for color noise
4. Convert back to RGB and apply strength/saturation
5. Use screen blend mode to combine with original image
6. Apply toe adjustment for film-like shadow response
## Tips
- Start with low strength values (0.2-0.5) and adjust upward
- For color images, saturation between 0.5-0.8 looks most natural
- Combine with color grading nodes for complete film emulation
- Use consistent seed values across batch for uniform grain
- Scale parameter affects both grain size and render performance (smaller scale = more computation)
## Compatibility
- Works with any image format supported by ComfyUI
- Preserves image properties (alpha channel, batch size)
- Compatible with both RGB and RGBA images
- Efficient batch processing support
+213
View File
@@ -0,0 +1,213 @@
# Kiko Save Image
Enhanced image saving node with multiple format support, quality controls, and an interactive floating popup viewer for ComfyUI.
## Features
- **Multiple Format Support**: Save as PNG, JPEG, or WebP with format-specific optimizations
- **Advanced Quality Controls**: Fine-tune compression settings per format
- **Floating Popup Viewer**: Interactive window showing saved images immediately
- **Batch Operations**: Multi-select images for bulk actions
- **File Size Display**: Real-time feedback on compression effectiveness
- **Smart UI**: Auto-hide, draggable, resizable popup window
## Inputs
- **images** (IMAGE): Batch of images to save
- **filename_prefix** (STRING): Prefix for saved filenames
- Default: "KikoSave"
- Supports subfolder paths (e.g., "outputs/renders/final")
- **format** (DROPDOWN): Output format selection
- `PNG`: Lossless compression, best quality
- `JPEG`: Lossy compression, smaller files
- `WEBP`: Modern format, best compression ratio
- **quality** (INT): JPEG/WebP quality level
- Range: 1-100 (default: 90)
- Higher values = better quality, larger files
- **png_compress_level** (INT): PNG compression level
- Range: 0-9 (default: 4)
- Higher values = smaller files, slower saving
- **webp_lossless** (BOOLEAN): Use lossless WebP compression
- Default: False (lossy)
- True: Lossless compression like PNG
- **popup** (BOOLEAN): Enable popup viewer window
- Default: True
- Toggle per save operation
## Outputs
- **UI**: Enhanced preview data with interactive popup viewer
## Popup Viewer Features
### Window Controls
- **Drag Handle**: Click and drag the header to move window
- **Minimize Button**: Collapse to title bar only
- **Maximize Button**: Expand to larger viewing size
- **Roll-up Button**: Show/hide content area
- **Close Button**: Hide the popup (can reopen with toggle)
### Image Grid
- **Thumbnails**: Click any image to open full-size in new tab
- **File Info**: Shows filename and size for each image
- **Quality Indicators**:
- PNG: Compression level (0-9)
- JPEG/WebP: Quality percentage
- **Batch Selection**: Checkboxes for multi-select operations
### Bulk Actions
- **Open All Selected**: Opens selected images in new tabs
- **Download All Selected**: Downloads selected images as a batch
- **Individual Downloads**: Download button per image
### Smart Behavior
- **Auto-positioning**: Appears in convenient screen location
- **Persistence**: Stays open across multiple saves
- **Auto-hide**: Can be minimized when not needed
- **Responsive**: Adapts to different image counts
## Format Details
### PNG Format
- **Pros**: Lossless quality, transparency support, wide compatibility
- **Cons**: Larger file sizes
- **Best for**: Final outputs, images with transparency, archival
- **Compression**: 0 (none) to 9 (maximum)
- Level 4 (default) balances size and speed
- Level 9 for maximum compression (slow)
### JPEG Format
- **Pros**: Smaller files, fast loading, universal support
- **Cons**: Lossy compression, no transparency
- **Best for**: Web images, previews, photos
- **Quality**: 1-100%
- 90% (default) excellent quality with good compression
- 95%+ for near-lossless quality
- 70-85% for web optimization
### WebP Format
- **Pros**: Best compression ratios, supports transparency, modern
- **Cons**: Limited software support
- **Best for**: Web deployment, storage optimization
- **Modes**:
- Lossy (default): Excellent compression with quality control
- Lossless: PNG-like quality with better compression
## Usage Examples
### High-Quality Archive
```
Format: PNG
Compression: 0-2
Use Case: Final renders for portfolio or client delivery
```
### Web Optimization
```
Format: JPEG or WebP
Quality: 80-85
Use Case: Website images, social media posts
```
### Balanced Storage
```
Format: WebP
Quality: 90
Lossless: False
Use Case: Large batches with storage constraints
```
### Transparency Preservation
```
Format: PNG or WebP (lossless)
Use Case: Logos, UI elements, cutout images
```
## Workflow Integration
### Basic Save
```
Generate → Kiko Save Image
format: PNG
popup: enabled
```
### Format Comparison
```
Generate → Kiko Save Image (PNG) → Compare file sizes
↘ Kiko Save Image (JPEG) ↗
↘ Kiko Save Image (WebP) ↗
```
### Batch Processing
```
Batch Generate → Kiko Save Image → Popup Viewer
↓ ↓
4 images Select best results
```
## Tips and Best Practices
1. **Format Selection**:
- Use PNG for maximum quality and transparency
- Use JPEG for photographs without transparency
- Use WebP for modern web deployment
2. **Quality Settings**:
- Start with defaults (90 for JPEG/WebP, 4 for PNG)
- Adjust based on file size requirements
- Preview results in popup before finalizing
3. **Popup Management**:
- Drag to second monitor for larger workspace
- Use roll-up to save screen space
- Disable popup for automated workflows
4. **Batch Operations**:
- Use checkboxes to select multiple images
- Open all in tabs for side-by-side comparison
- Download all for quick collection
5. **File Organization**:
- Use subfolders in filename_prefix
- Include descriptive prefixes
- Let ComfyUI handle timestamp suffixes
## Advantages Over Standard Save Image
- **Immediate Preview**: No need to navigate file system
- **Format Flexibility**: Choose optimal format per use case
- **Quality Control**: Fine-tune compression settings
- **Batch Management**: Handle multiple images efficiently
- **Modern UI**: Floating interface doesn't interrupt workflow
- **File Size Awareness**: See compression effectiveness immediately
- **Quick Access**: One-click opening and downloading
## Technical Details
- **Image Processing**: Uses Pillow for format conversion
- **Metadata**: Preserves ComfyUI metadata in saved files
- **File Naming**: Automatic timestamp and counter suffixes
- **Memory Efficiency**: Processes images individually
- **Thread Safety**: Proper handling of concurrent saves
## Troubleshooting
**Popup not appearing**:
- Check that popup input is enabled
- Look for minimized window
- Try toggling the popup button in node
**WebP not working**:
- Ensure Pillow has WebP support
- Update Pillow: `pip install --upgrade pillow`
**Large file sizes**:
- Increase compression (PNG) or reduce quality (JPEG/WebP)
- Consider switching formats
- Check image dimensions
**Can't see all images**:
- Scroll within the popup grid
- Maximize the popup window
- Images are shown newest first
@@ -0,0 +1,158 @@
# Local Image Loader
## Overview
The Local Image Loader node provides a visual gallery interface for browsing and selecting images, videos, and audio files from your local filesystem directly within ComfyUI. This streamlined version focuses on essential functionality without the complexity of tagging or metadata management.
## Features
- **Visual Gallery Browser**: Browse local directories with thumbnail previews
- **Multi-Media Support**: Load images, videos, and audio files
- **Directory Navigation**: Navigate through folders with ease
- **Sorting Options**: Sort by name, date, or file size
- **Saved Paths**: Save frequently used directory paths for quick access
- **Pagination**: Handle large directories with paginated display
- **Lightbox Preview**: Full-size preview with zoom and pan capabilities
## Node Inputs
### Required Inputs
None - The node uses a visual interface for file selection
### Hidden Inputs
- `unique_id`: Automatically assigned node identifier
## Node Outputs
| Output | Type | Description |
|--------|------|-------------|
| `image` | IMAGE | The selected image as a tensor |
| `video_path` | STRING | Path to the selected video file |
| `audio_path` | STRING | Path to the selected audio file |
| `info` | STRING | JSON metadata about the selected image |
## Usage
### Basic Workflow
1. **Add the Node**: Search for "Local Image Loader" in the node menu
2. **Browse Directory**: Enter a directory path or use saved paths
3. **Select Media**: Click on thumbnails to select files
4. **Connect Outputs**: Use the outputs in your workflow
### Interface Controls
#### Path Management
- **Directory Input**: Enter or paste a directory path
- **Saved Paths Dropdown**: Quick access to saved directories
- **Save Path Button** (💾): Save current directory to favorites
- **Browse Button** (📁): Load the entered directory
#### View Options
- **Videos Checkbox**: Show/hide video files
- **Audio Checkbox**: Show/hide audio files
- **Sort By**: Choose between Name, Date, or Size
- **Sort Order**: Ascending (↑) or Descending (↓)
- **Refresh Button** (🔄): Reload current directory
#### Gallery Display
- **Thumbnail Grid**: Visual preview of files
- **Blue Border**: Selected items are highlighted
- **Folder Icons**: Navigate into subdirectories
- **Video Overlay**: Visual indicator for video files
- **Pagination**: Navigate through pages of results
## File Support
### Supported Image Formats
- `.jpg`, `.jpeg`
- `.png`
- `.bmp`
- `.gif`
- `.webp`
### Supported Video Formats
- `.mp4`
- `.webm`
- `.mov`
- `.mkv`
- `.avi`
### Supported Audio Formats
- `.mp3`
- `.wav`
- `.ogg`
- `.flac`
## Image Metadata
When an image is selected, the node extracts and returns metadata including:
- **Basic Info**: Filename, width, height, format, mode
- **Embedded Parameters**: Generation parameters if present
- **Workflow Data**: Embedded ComfyUI workflow if present
- **Prompt Data**: Embedded prompt information if present
## Examples
### Loading an Image for Processing
```
Local Image Loader → Load Image → Image Processing Node
↓
[info] → Display Text (to show metadata)
```
### Setting Up a Multi-Media Workflow
```
Local Image Loader → [image] → Image Preview
↓
[video_path] → Video Player Node
↓
[audio_path] → Audio Player Node
```
## Tips and Best Practices
1. **Save Frequently Used Paths**: Use the save button to bookmark directories you use often
2. **Use Sorting**: Sort by date to find recent files quickly
3. **Keyboard Navigation**: Press Enter in the path field to load a directory
4. **Performance**: For directories with thousands of files, use pagination to navigate efficiently
5. **Thumbnail Generation**: Thumbnails are generated on-demand and cached for performance
## Differences from Original
This version simplifies the original ComfyUI_Local_Image_Gallery by removing:
- Tag filtering and management
- Rating system
- Global tag search
- Metadata editing capabilities
These features were removed to focus on the core functionality of browsing and selecting files, making the tool simpler and more straightforward to use.
## Troubleshooting
### Common Issues
**Directory Not Loading**
- Verify the path exists and you have read permissions
- Check for special characters in the path
- Try using absolute paths instead of relative ones
**Thumbnails Not Showing**
- Ensure the files are in supported formats
- Check if the images are corrupted
- Try refreshing the gallery
**Large Directories Slow to Load**
- Use sorting and pagination to manage large folders
- Consider organizing files into subdirectories
- Enable only the media types you need (images, videos, audio)
## Technical Details
The node creates a visual widget that runs in the ComfyUI interface and communicates with the backend through API endpoints to:
- List directory contents
- Generate thumbnails
- Save user preferences
- Handle file selection
All file operations are performed server-side for security, with proper path validation to prevent directory traversal attacks.
+264
View File
@@ -0,0 +1,264 @@
# LoRA Folder Batch
## Overview
The **LoRA Folder Batch** node automates the process of testing multiple LoRA models from a folder. This tool was adapted from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) (now in maintenance mode) and enhanced with batch processing capabilities for efficient LoRA evaluation workflows.
## Attribution
This node is based on work from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) by cubiq. The original project is in maintenance-only mode, and we've adopted and enhanced these tools to ensure continued support and compatibility with modern ComfyUI workflows.
## Features
- **Automatic Folder Scanning**: Discovers all .safetensors files in specified folders
- **Natural Sorting**: Intelligently sorts epochs (e.g., epoch_004, epoch_020, epoch_100)
- **Pattern Filtering**: Include/exclude LoRAs using regex patterns
- **Flexible Strength Control**: Single, multiple, or range-based strength values
- **Batch Modes**: Sequential or combinatorial strength application
- **Epoch Detection**: Automatically extracts epoch numbers from filenames
- **Auto-Batching**: Automatically splits large LoRA collections into manageable chunks to prevent UI disconnection
## Node Properties
- **Category**: `ComfyAssets/🧰 xyz-helpers`
- **Node Name**: `LoRAFolderBatch`
- **Function**: `batch_loras`
## Inputs
### Required
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `folder_path` | STRING | "." | Folder path relative to models/loras (or absolute) |
| `strength` | STRING | "1.0" | Strength values (see formats below) |
| `batch_mode` | DROPDOWN | sequential | [sequential, combinatorial] processing mode |
### Optional
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `include_pattern` | STRING | "" | Regex pattern to include files |
| `exclude_pattern` | STRING | "" | Regex pattern to exclude files |
| `max_loras` | INT | 50 | Maximum LoRAs to process (when auto_batch disabled) |
| `sort_order` | DROPDOWN | natural | Sorting method [natural, alphabetical, newest, oldest] |
| `auto_batch` | DROPDOWN | disabled | Enable auto-batching for large collections [disabled, enabled] |
| `batch_size` | INT | 25 | Number of LoRAs per batch when auto-batching |
| `batch_index` | INT | 0 | Which batch to output (0-based) when auto-batching |
### Strength Format Options
- **Single**: `"1.0"` - Apply same strength to all LoRAs
- **Multiple**: `"0.5, 0.75, 1.0"` - Comma-separated values
- **Range**: `"0.5...1.0+0.25"` - Start...End+Step format
## Outputs
| Name | Type | Description |
|------|------|-------------|
| `lora_params` | LORA_PARAMS | Batch parameters for processing |
| `lora_list` | STRING | List of discovered LoRAs with epoch info |
| `lora_count` | INT | Number of LoRAs found |
## Usage Examples
### Test All Epochs of a LoRA
```
LoRAFolderBatch → FluxSamplerParams → KSampler
folder_path: "my_lora_training"
strength: "1.0"
batch_mode: sequential
```
### Strength Testing for Each LoRA
```
LoRAFolderBatch → KSampler → Image Grid
folder_path: "test_loras"
strength: "0.5, 0.75, 1.0"
batch_mode: combinatorial
```
### Filter Specific Epochs
```
LoRAFolderBatch → Processing Pipeline
folder_path: "training_results"
include_pattern: "epoch_0[2-5]0"
strength: "0.8...1.2+0.1"
```
### Auto-Batch Large Collections
```
LoRAFolderBatch → FluxSamplerParams → KSampler
folder_path: "massive_lora_collection" # 100+ files
strength: "1.0"
auto_batch: enabled
batch_size: 25
batch_index: 0 # Change to 1, 2, 3... for subsequent batches
```
## Batch Modes Explained
### Sequential Mode
Each LoRA gets one strength value in order:
- LoRA1 → strength[0]
- LoRA2 → strength[1]
- LoRA3 → strength[0] (cycles if fewer strengths than LoRAs)
### Combinatorial Mode
Each LoRA is tested with ALL strength values:
- LoRA1 → [0.5, 0.75, 1.0]
- LoRA2 → [0.5, 0.75, 1.0]
- LoRA3 → [0.5, 0.75, 1.0]
## Auto-Batching for Large Collections
### Overview
When testing large numbers of LoRAs (e.g., 75+ files), ComfyUI can experience UI disconnections or memory issues. Auto-batching solves this by automatically splitting your LoRA collection into smaller, manageable chunks.
### How It Works
1. **Enable Auto-Batching**: Set `auto_batch` to "enabled"
2. **Set Batch Size**: Configure `batch_size` (default: 25, range: 5-100)
3. **Select Batch**: Use `batch_index` to choose which batch to process
### Example: Testing 75 LoRAs
With 75 LoRAs and batch_size=25, the system creates 3 batches:
- **Batch 0**: LoRAs 1-25 (set batch_index=0)
- **Batch 1**: LoRAs 26-50 (set batch_index=1)
- **Batch 2**: LoRAs 51-75 (set batch_index=2)
Run your workflow 3 times, changing only the `batch_index` each time.
### Visual Feedback
When auto-batching is enabled, the `lora_list` output includes batch information:
```
=== Batch 1/3 (LoRAs 1-25) ===
style-epoch-001
style-epoch-002
...
```
### Best Practices for Auto-Batching
1. **Start with Default**: Use batch_size=25 for most scenarios
2. **Adjust for Memory**: Decrease batch_size if you still experience issues
3. **Combinatorial Mode**: Be extra careful - 25 LoRAs × 3 strengths = 75 combinations
4. **Save Between Batches**: Save your results after each batch to avoid data loss
5. **Use Plot Parameters**: The batch info appears in plot visualizations for easy tracking
## File Naming Patterns
### Supported Epoch Formats
- `model-v1-000004.safetensors` → Epoch 4
- `style_epoch_020.safetensors` → Epoch 20
- `lora-000100.safetensors` → Epoch 100
### Natural Sorting Examples
Files are sorted intelligently:
1. `model-000004.safetensors`
2. `model-000020.safetensors`
3. `model-000100.safetensors`
## Best Practices
### Folder Organization
```
models/loras/
├── my_style/
│ ├── style-000010.safetensors
│ ├── style-000020.safetensors
│ └── style-000030.safetensors
└── character/
├── char-v2-000005.safetensors
└── char-v2-000010.safetensors
```
### Testing Workflows
1. **Initial Testing**: Use single strength (1.0) to evaluate all epochs
2. **Fine-tuning**: Use combinatorial mode with multiple strengths
3. **Final Selection**: Filter to specific epochs and test strength range
### Pattern Filtering Examples
```python
# Include only specific versions
include_pattern: "v2|v3"
# Exclude test/backup files
exclude_pattern: "test|backup|old"
# Include specific epoch range
include_pattern: "epoch_0[3-7]0"
```
## Integration with Other Nodes
### Common Pipelines
1. **LoRA Comparison Grid**:
```
LoRAFolderBatch → KSampler → Image Grid → Save
```
2. **Strength Testing**:
```
LoRAFolderBatch → PlotParameters → Graph Display
```
3. **Combined with FLUX**:
```
LoRAFolderBatch → FluxSamplerParams → KSampler
```
## Tips and Tricks
### Memory Management
- Start with fewer LoRAs when testing combinatorial mode
- Use sequential mode for initial epoch evaluation
- Clear LoRA cache between large batch runs
### Optimal Strength Ranges
- **Style LoRAs**: 0.5-1.0
- **Character LoRAs**: 0.7-1.2
- **Detail LoRAs**: 0.3-0.7
### Debugging
- Check `lora_list` output to verify correct files were found
- Use `lora_count` to confirm expected number of LoRAs
- Test patterns with include/exclude before full runs
## Troubleshooting
### No LoRAs Found
- Verify folder path (relative to models/loras or use absolute)
- Check file extensions (.safetensors)
- Test without filters first
### Pattern Not Working
- Patterns use Python regex syntax
- Test patterns in regex tester first
- Case-sensitive by default
### Memory Issues
- Reduce batch_count in combinatorial mode
- Process LoRAs in smaller groups
- Use sequential mode for large sets
## Advanced Examples
### Multi-Version Testing
```python
# Test different versions at different strengths
folder_path: "character_loras"
include_pattern: "v[1-3]"
strength: "0.6, 0.8, 1.0"
batch_mode: combinatorial
```
### Epoch Progression Analysis
```python
# Test every 10th epoch
folder_path: "training_output"
include_pattern: "0[0-9]0\\.safetensors$"
strength: "1.0"
batch_mode: sequential
```
## Version History
- **1.0.0**: Initial adaptation from comfyui-essentials-nodes
- **1.0.1**: Added natural sorting for epochs
- **1.0.2**: Enhanced pattern filtering
- **1.0.3**: Improved batch modes and strength parsing
- **1.0.4**: Added auto-batching for large LoRA collections
## Credits
Original implementation by cubiq in [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials). Adapted and maintained by the ComfyAssets team.
@@ -0,0 +1,190 @@
# Model Resolution Picker Documentation
## Overview
The Model Resolution Picker is a ComfyUI node that picks a model family and a size for it, then outputs an empty latent plus the width and height. A chip grid of aspect ratios and a size list under the active chip replace hunting through a long preset dropdown. The sizes come from each family's official lists or from megapixel tiers rounded to the family's grid step.
![Model Resolution Picker](../../docs/screenshots/resolution-picker/ModelResolutionPicker-dark.png)
A light-theme capture is at `docs/screenshots/resolution-picker/ModelResolutionPicker-light.png`.
## Node Interface
- **Node id**: `ModelResolutionPicker`
- **Display name**: Model Resolution Picker
- **Category**: `🫶 ComfyAssets/🖼️ Resolution`
### Inputs
- **model**: Dropdown of the 14 shipped family labels (default `SDXL`)
- **resolution**: Dropdown of `custom` plus size labels such as `1216×832 - 3:2 (1.0MP)` (default `custom`); see [Resolution dropdown](#resolution-dropdown)
- **width**: Width in pixels (64-8192, step 8)
- **height**: Height in pixels (64-8192, step 8)
- **batch_size**: Number of empty latents (1-64)
### Outputs
- **latent**: Empty latent batch at the rounded size
- **width**: Rounded width (INT)
- **height**: Rounded height (INT)
A `resolution` label other than `custom` that parses as `W×H` takes precedence over width and height; `custom` or an unparseable label uses width and height. Width and height are rounded to the selected family's step at execution and never go below 64. An unknown family label (for example from an old workflow) falls back to step 8 and logs a warning.
The latent is the shared 4-channel 8x empty latent. It carries `downscale_ratio_spacial: 8`, which lets ComfyUI resize it for 16x and 32x VAEs, so the same output works with every family below.
The frontend reads the family table from `GET /kikotools/model_resolution/families`. The response carries the table, plus a `warnings` list that is empty unless an override file was ignored.
## Model Families
| Label | Models covered | VAE ratio | Step | Megapixel tiers | Default size | Group |
|---|---|---|---|---|---|---|
| SD 1.5 | SD 1.5 and SD 1.5 finetunes | 8x | 64 | 0.25, 0.5 | 512×512 | image |
| SDXL | SDXL, Pony and other SDXL finetunes | 8x | 64 | 1.0 | 1024×1024 | image |
| Illustrious XL | Illustrious XL v1.0 and later, NoobAI, WAI and other Illustrious finetunes | 8x | 32 | 1.0, 1.5, 2.25 | 1024×1024 | image |
| Anima | Anima | 8x | 16 | 0.5, 1.0, 1.5 | 1024×1024 | image |
| Flux.1 | Flux.1 dev / schnell / Krea, Chroma, HiDream I1, SD3.5, Lumina 2 | 8x | 16 | 0.5, 1.0, 1.5, 2.0 | 1024×1024 | image |
| Z-Image | Z-Image Turbo and Z-Image Base | 8x | 16 | 1.0, 1.6 | 1024×1024 | image |
| Qwen-Image | Qwen-Image and Qwen-Image 2512 | 8x | 16 | 1.76 | 1328×1328 | image |
| Flux.2 / Klein / MOSS | Flux.2 dev, Flux.2 Klein, Flux.3 Klein, MOSS-Image, Ideogram 4 | 16x | 16 | 1.0, 2.0, 4.0 | 1024×1024 | image |
| Qwen-Image 2.1 | Qwen-Image 2.1 and 2.1 Turbo | 16x | 32 | 1.0, 4.0 | 1024×1024 | image |
| HunyuanImage 2.1 | HunyuanImage 2.1 | 32x | 32 | 4.0 | 2048×2048 | image |
| Wan 2.2 (keyframes) | Wan 2.2 (first/last-frame sizes) | 16x | 16 | 0.4, 0.9 | 1280×720 | video |
| LTX-2 (keyframes) | LTX-2 (first/last-frame sizes) | 32x | 32 | 0.4, 0.9 | 768×512 | video |
| HunyuanVideo 1.5 (keyframes) | HunyuanVideo 1.5 (first/last-frame sizes) | 16x | 16 | 0.4, 0.9 | 1280×720 | video |
| MiniMax H3 (keyframes) | MiniMax H3 (first/last-frame sizes) | 16x | 32 | 0.4, 1.0 | 864×480 | video |
The family steps and megapixel tiers are the grid the node rounds to and the tiers custom ratios use. Video families are suffixed `(keyframes)`; see the keyframe note below.
## Family Table File
The families live in `kikotools/tools/model_resolution_picker/families.json`, loaded and checked by `table.py` when ComfyUI starts. A shipped file that fails the check is a startup error. Top-level keys:
- `version`: must be `1`
- `chips`: exactly the 11 chips listed above, in that order
- `mirror`: pairs every chip with its mirrored chip (`4:5` with `5:4`, `1:1` with itself)
- `default`: label or alias of the family selected by default
- `families`: a non-empty list of family objects
Each family object has:
- `label`: unique name shown in the dropdown; `aliases` (optional list) are old names that still resolve to it
- `models`: description string; `notes` (optional string) is ignored by the node
- `group`: `image` or `video`
- `vaeRatio`: `8`, `16` or `32`
- `step`: positive integer grid step, a multiple of 8 (at most 4096), so rounded sizes land on the 8-pixel latent grid
- `megapixels`: non-empty list of tiers used by custom ratios (each above 0, at most 64)
- `default`: a `[width, height]` pair that appears in `sizes`
- `sizes`: an object with one key per chip, each a list of `[width, height]` pairs in ascending pixel order (an empty list disables the chip)
Every size must be a multiple of `step`, within 7% of its chip's ratio, listed under one chip only, and have its swapped size under the mirrored chip. `validate_table` returns every problem as text and never raises.
## User Override File
To change sizes without editing the shipped file, create `model_resolutions.json` in a `kikotools` folder inside ComfyUI's user directory (`folder_paths.get_user_directory()`; typically `ComfyUI/user/kikotools/model_resolutions.json`). It uses the same schema as the shipped file, and any top-level `chips` or `mirror` are ignored. Rules:
- Families are matched by `label`. A family with a shipped label replaces that family whole; a new label is appended after the shipped ones, in file order.
- `remove` is a list of labels that deletes shipped families. It wins over `families` when both name the same label.
- `default` is an optional label or alias that becomes the default family; it must name a family that survives the merge.
- The file is read again when its modification time changes, so a page reload (or ComfyUI's "Refresh node definitions") picks up edits, including the `model` dropdown, without restarting ComfyUI.
- The shipped file is never modified.
This example replaces SDXL with a shorter list, adds a new family, hides two shipped ones and makes the new family the default:
```json
{
"version": 1,
"default": "My SDXL",
"remove": ["SD 1.5", "Anima"],
"families": [
{
"label": "SDXL",
"aliases": [],
"models": "SDXL with a trimmed size list",
"group": "image",
"vaeRatio": 8,
"step": 64,
"megapixels": [1.0],
"default": [1024, 1024],
"sizes": {
"1:1": [[1024, 1024]],
"4:5": [[832, 1024]],
"5:4": [[1024, 832]],
"3:4": [], "4:3": [], "2:3": [], "3:2": [],
"9:16": [], "16:9": [], "21:9": [], "9:21": []
}
},
{
"label": "My SDXL",
"aliases": ["my-sdxl"],
"models": "My own SDXL finetune",
"group": "image",
"vaeRatio": 8,
"step": 64,
"megapixels": [1.0],
"default": [1152, 896],
"sizes": {
"1:1": [[1024, 1024]],
"4:5": [[896, 1152]], "5:4": [[1152, 896]],
"3:4": [], "4:3": [], "2:3": [], "3:2": [],
"9:16": [], "16:9": [], "21:9": [], "9:21": []
}
}
]
}
```
The merged table is checked with the same `validate_table`. If the file is unreadable, is not valid JSON, nests too deeply, or fails the check, the whole file is ignored and the shipped table is used. ComfyUI logs a warning `Override ignored: <path>: <reason>`, the same text is returned in the `warnings` list of `GET /kikotools/model_resolution/families`, and the panel's status line shows the first warning. Fix the file and reload to clear it.
## How to Use
### Chips and size list
1. **Pick a model** in the dropdown.
2. **Click a ratio chip** (`1:1 4:5 5:4 3:4 4:3 2:3 3:2 9:16 16:9 21:9 9:21`; each shows a small outline of its shape). The size list under the active chip shows that family's sizes for the ratio.
3. **Click a size** to write it to width and height. The header shows `W × H · ratio · MP` for the current values.
A chip is disabled when the family lists no sizes for that ratio.
### Resolution dropdown
The native `resolution` dropdown sits between `model` and `width` and mirrors the panel. Its options are `custom` plus every size label from the shipped families (sorted as text), written `{w}×{h} - {ratio} ({mp}MP)` with 1 MP = 1,048,576 pixels, for example `1216×832 - 3:2 (1.0MP)`.
- In the UI the dropdown lists the sizes of the active ratio chip, or every size of the family when `Custom ratio` is active or no chip matches.
- Picking a label writes width and height and activates the matching chip.
- Clicking a chip or a size, or swapping, sets the label to match.
- Typing a size that is not in the list, wiring width or height, a families table that is still loading, or a failed load shows `custom`.
- On execution a non-`custom` label that parses wins over width and height; `custom` or an unparseable label uses width and height. Labels that only an override file adds are accepted too.
### Swap
The Swap W×H button (the same control Width Height Selector and Empty Latent Batch use) exchanges width and height and lands on the mirrored chip, for example 3:4 becomes 4:3.
### Changing the model
Switching the model keeps the current size if the new family lists it. Otherwise it falls back to the active chip's default size in the new family, then to the family default.
### Custom ratio
Choose the `Custom ratio` chip and type a ratio such as `16:9`. The panel computes sizes at the family's megapixel tiers, rounded to the family's step. Bad input is rejected with "Enter a ratio like 16:9".
### Custom resolution
Type width and height directly in the native widgets. The panel re-derives its chip, list and header from those values. They are rounded to the family's step when the node runs.
### Wired-in width or height
When width or height is driven by another node, the panel and Swap are disabled.
### Sizes unavailable
If the families route fails, the panel shows "Sizes unavailable; type width and height" with the chips disabled. The node still works through the native widgets.
Panel state is derived from the native widgets, so nothing extra is saved in the workflow. New nodes are 340 px wide and the node height follows the panel content: it is re-fitted whenever the list changes (a chip click, a model change, opening Custom ratio), so there is no empty space under the list. A saved workflow keeps its width; the height is fitted again on load.
## Keyframe Note for Video Families
`Wan 2.2`, `LTX-2`, `HunyuanVideo 1.5` and `MiniMax H3` (all suffixed `(keyframes)`) are listed for first/last-frame generation at the video model's frame size. The latent output is still an image latent and is never fed to a video model. The image latent decodes to the listed size when the keyframe image model's VAE ratio divides the video family's step (for example an 8x or 16x image model for Wan 2.2's 1280×720; a 32x model would not land on 720).
## Manual GPU Check Before Merge
The real-host CI never queues a generation, so the latent path is only unit-tested. Core `EmptyLatentImage` is the same 4-channel, 8x, marked latent this node emits, and the official Anima template already runs that path (channel repeat 4 → 16). Confirm two cases by hand on a GPU box. Each is one queued image through this node → KSampler → VAE Decode → Save:
1. `Flux.2 / Klein / MOSS` (16x) at 1248×832.
2. `HunyuanImage 2.1` (32x) at 2560×1536.
For each case, the saved PNG's dimensions must equal the node's width and height outputs. Record both results in the PR description.
## Related Nodes
- [Width Height Selector](width_height_selector.md): preset-based dimensions with swap
- [Empty Latent Batch](empty_latent_batch.md): empty latent batches with presets
+234
View File
@@ -0,0 +1,234 @@
# Plot Parameters
## Overview
The **Plot Parameters** node creates visual graphs and plots from sampler parameters, enabling data-driven analysis of generation settings. Adapted from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) (now in maintenance mode), this tool helps visualize the relationship between parameters and output quality.
## Attribution
This node is based on work from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) by cubiq. The original project is in maintenance-only mode, and we've adopted and enhanced these tools to ensure continued support and compatibility with modern ComfyUI workflows.
## Features
- **Multi-Parameter Plotting**: Visualize multiple parameters simultaneously
- **Comparison Graphs**: Compare settings across batch runs
- **Statistical Analysis**: Calculate means, deviations, and trends
- **Export Capabilities**: Save plots as images or data files
- **Real-time Updates**: Dynamic graph generation during workflow execution
## Node Properties
- **Category**: `ComfyAssets/🧰 xyz-helpers`
- **Node Name**: `PlotParameters`
- **Function**: `plot`
## Inputs
### Required
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `sampler_params` | SAMPLER_PARAMS | - | Parameters to plot |
| `plot_type` | DROPDOWN | line | [line, bar, scatter, heatmap] |
| `x_axis` | DROPDOWN | steps | Parameter for X axis |
| `y_axis` | DROPDOWN | quality | Metric for Y axis |
### Optional
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `title` | STRING | "Parameter Analysis" | Graph title |
| `show_grid` | BOOLEAN | True | Display grid lines |
| `show_legend` | BOOLEAN | True | Display legend |
| `color_scheme` | DROPDOWN | default | Color palette selection |
## Outputs
| Name | Type | Description |
|------|------|-------------|
| `plot_image` | IMAGE | Generated plot as image |
| `data_csv` | STRING | Plot data in CSV format |
| `statistics` | STRING | Statistical summary |
## Usage Examples
### Basic Parameter Visualization
```
FluxSamplerParams → PlotParameters → Display Image
plot_type: line
x_axis: steps
y_axis: guidance
```
### Batch Comparison Plot
```
LoRAFolderBatch → PlotParameters → Save Image
plot_type: scatter
x_axis: lora_strength
y_axis: quality_score
```
### Heatmap Analysis
```
Parameter Grid → PlotParameters → Analysis Display
plot_type: heatmap
x_axis: cfg
y_axis: steps
```
## Plot Types Explained
### Line Plot
- Best for continuous parameter changes
- Shows trends and relationships
- Ideal for time series or progression
### Bar Chart
- Compares discrete values
- Good for categorical comparisons
- Shows distribution clearly
### Scatter Plot
- Reveals correlations
- Identifies outliers
- Best for large datasets
### Heatmap
- Two-dimensional parameter analysis
- Color-coded intensity values
- Perfect for grid searches
## Best Practices
### Parameter Selection
- Choose related parameters for meaningful plots
- Use consistent scales for comparison
- Consider parameter ranges when plotting
### Visual Clarity
- Limit number of series to 5-7 for readability
- Use contrasting colors for multiple lines
- Enable grid for precise value reading
### Data Analysis
```python
# Effective parameter combinations
x_axis: "guidance"
y_axis: "perceived_quality"
# Step efficiency analysis
x_axis: "steps"
y_axis: "generation_time"
# LoRA impact assessment
x_axis: "lora_strength"
y_axis: "style_adherence"
```
## Integration Examples
### Complete Analysis Pipeline
```
1. Generate with parameters
2. Plot results
3. Export data
4. Statistical analysis
```
### Multi-Plot Workflow
```
Params → Plot1 (steps vs quality)
↘ Plot2 (guidance vs coherence)
↘ Plot3 (strength vs style)
→ Combined Analysis
```
## Advanced Features
### Custom Metrics
- Define custom Y-axis metrics
- Import external quality scores
- Calculate derived values
### Export Options
- PNG/SVG image formats
- CSV data export
- JSON statistics export
### Styling Options
```python
# Professional presentation
color_scheme: "scientific"
show_grid: True
show_legend: True
# Minimal style
color_scheme: "minimal"
show_grid: False
show_legend: False
```
## Statistical Analysis
### Available Metrics
- Mean, Median, Mode
- Standard Deviation
- Correlation Coefficients
- Trend Lines
- R-squared Values
### Interpretation Guide
- **Positive Correlation**: Parameters increase together
- **Negative Correlation**: Inverse relationship
- **No Correlation**: Independent parameters
## Tips and Tricks
### Optimal Visualization
1. Start with scatter plots for exploration
2. Use line plots for trends
3. Apply heatmaps for 2D parameter spaces
4. Bar charts for final comparisons
### Data Preparation
- Normalize scales when comparing different metrics
- Remove outliers for cleaner plots
- Group similar parameters
### Performance Tips
- Cache plot images for repeated viewing
- Export data for external analysis
- Use lower resolution for preview plots
## Troubleshooting
### Empty Plots
- Verify sampler_params contains data
- Check axis parameter selection
- Ensure valid parameter ranges
### Scaling Issues
- Use logarithmic scale for wide ranges
- Normalize data if needed
- Adjust plot dimensions
### Export Problems
- Check file permissions
- Verify export path exists
- Ensure sufficient disk space
## Use Cases
### Hyperparameter Optimization
Track and visualize the effect of different sampling parameters on output quality.
### LoRA Strength Analysis
Plot the relationship between LoRA strength and style transfer effectiveness.
### Efficiency Studies
Analyze generation time vs quality trade-offs across different settings.
### Batch Comparison
Compare multiple generation runs to identify optimal parameters.
## Version History
- **1.0.0**: Initial adaptation from comfyui-essentials-nodes
- **1.0.1**: Added heatmap visualization
- **1.0.2**: Enhanced statistical analysis
- **1.0.3**: Improved export capabilities
## Credits
Original implementation by cubiq in [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials). Adapted and maintained by the ComfyAssets team.
@@ -98,4 +98,4 @@ The Resolution Calculator integrates seamlessly with:
- Standard ComfyUI image loaders
- VAE encode/decode operations
- Upscaler nodes (ESRGAN, Real-ESRGAN, etc.)
- Custom latent processing workflows
- Custom latent processing workflows
+7 -7
View File
@@ -40,7 +40,7 @@ The Sampler Combo is a unified ComfyUI node that combines sampler, scheduler, st
### Outputs
- **sampler_name**: Selected sampler algorithm
- **scheduler**: Selected scheduler algorithm
- **scheduler**: Selected scheduler algorithm
- **steps**: Number of sampling steps
- **cfg**: CFG scale value
@@ -75,7 +75,7 @@ The Sampler Combo is a unified ComfyUI node that combines sampler, scheduler, st
- **linear**: Basic linear distribution
- **sgm_uniform**: Uniform distribution
### Advanced Schedulers
### Advanced Schedulers
- **karras**: Karras noise schedule (recommended)
- **exponential**: Exponential decay
- **polyexponential**: Polynomial exponential
@@ -99,7 +99,7 @@ Steps: 15-25
CFG: 6.0-8.0
```
#### Quality Optimized
#### Quality Optimized
```
Sampler: dpmpp_2m_sde or dpmpp_3m_sde
Scheduler: karras
@@ -138,7 +138,7 @@ CFG: 7.0-8.5
### Basic Configuration
```
sampler_name: euler
scheduler: normal
scheduler: normal
steps: 20
cfg: 7.0
```
@@ -164,7 +164,7 @@ cfg: 6.5
### Compatibility Analysis
The node provides real-time analysis of parameter compatibility:
- Scheduler compatibility with selected sampler
- Steps optimization for sampler type
- Steps optimization for sampler type
- CFG scale recommendations
- Performance impact assessment
@@ -200,9 +200,9 @@ The node provides real-time analysis of parameter compatibility:
The Sampler Combo node outputs are compatible with all standard ComfyUI sampling nodes:
- KSampler
- KSamplerAdvanced
- KSamplerAdvanced
- Custom sampling workflows
- Upscaling pipelines
- Img2img workflows
Connect the outputs directly to your sampling node inputs for streamlined configuration.
Connect the outputs directly to your sampling node inputs for streamlined configuration.
@@ -0,0 +1,260 @@
# Sampler Select Helper
## Overview
The **Sampler Select Helper** node provides intelligent sampler selection with model-specific recommendations and compatibility checking. Adapted from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) (now in maintenance mode), this tool ensures optimal sampler-scheduler combinations for different model architectures.
## Attribution
This node is based on work from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) by cubiq. The original project is in maintenance-only mode, and we've adopted and enhanced these tools to ensure continued support and compatibility with modern ComfyUI workflows.
## Features
- **Model-Aware Selection**: Automatic recommendations based on model type
- **Compatibility Validation**: Ensures sampler-scheduler pairs work well together
- **Performance Profiles**: Pre-configured settings for quality vs speed
- **Dynamic Updates**: Adapts to newly available samplers
- **Batch Support**: Test multiple samplers in sequence
## Node Properties
- **Category**: `ComfyAssets/🧰 xyz-helpers`
- **Node Name**: `SamplerSelectHelper`
- **Function**: `select_sampler`
## Inputs
### Required
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `model_type` | DROPDOWN | auto | [auto, sdxl, sd15, flux, custom] |
| `quality_preset` | DROPDOWN | balanced | [fast, balanced, quality, extreme] |
| `sampler_override` | DROPDOWN | auto | Specific sampler selection |
### Optional
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `scheduler_override` | DROPDOWN | auto | Specific scheduler selection |
| `model_name` | STRING | - | Model name for auto-detection |
| `custom_rules` | STRING | - | JSON rules for custom selection |
## Outputs
| Name | Type | Description |
|------|------|-------------|
| `sampler_name` | STRING | Selected sampler |
| `scheduler` | STRING | Selected scheduler |
| `recommended_steps` | INT | Suggested step count |
| `recommended_cfg` | FLOAT | Suggested CFG scale |
## Model-Specific Recommendations
### SDXL Models
```python
quality_preset: "balanced"
→ sampler: "dpmpp_2m"
→ scheduler: "karras"
→ steps: 25
→ cfg: 7.0
```
### SD 1.5 Models
```python
quality_preset: "quality"
→ sampler: "dpmpp_2m_sde"
→ scheduler: "exponential"
→ steps: 30
→ cfg: 7.5
```
### FLUX Models
```python
quality_preset: "fast"
→ sampler: "euler"
→ scheduler: "simple"
→ steps: 15
→ cfg: 3.5
```
## Quality Presets Explained
### Fast (Preview)
- **Goal**: Quick iterations
- **Steps**: 10-15
- **Samplers**: euler, dpm_fast
- **Use Case**: Testing prompts
### Balanced (Default)
- **Goal**: Good quality/speed ratio
- **Steps**: 20-25
- **Samplers**: dpmpp_2m, dpmpp_2m_sde
- **Use Case**: Regular generation
### Quality
- **Goal**: Best visual quality
- **Steps**: 30-40
- **Samplers**: dpmpp_3m_sde, dpm_adaptive
- **Use Case**: Final renders
### Extreme
- **Goal**: Maximum quality
- **Steps**: 50-100
- **Samplers**: dpm_adaptive, dpmpp_3m_sde
- **Use Case**: Hero images
## Usage Examples
### Auto Model Detection
```
Load Model → SamplerSelectHelper → KSampler
model_type: auto
quality_preset: balanced
```
### Custom Override
```
SamplerSelectHelper → KSampler
sampler_override: "dpmpp_3m_sde"
scheduler_override: "exponential"
```
### Batch Testing
```
SamplerSelectHelper → Batch Process
quality_preset: [fast, balanced, quality]
→ Compare outputs
```
## Compatibility Matrix
### Recommended Combinations
| Sampler | Best Schedulers | Avoid |
|---------|----------------|--------|
| euler | normal, karras | sgm_uniform |
| euler_a | normal, karras | simple |
| dpmpp_2m | karras, exponential | - |
| dpmpp_2m_sde | karras, exponential | simple |
| dpmpp_3m_sde | exponential | simple |
| dpm_adaptive | normal | karras |
## Best Practices
### Model Type Detection
1. Use `auto` for automatic detection
2. Override only when necessary
3. Provide model_name for better accuracy
### Performance Optimization
```python
# Quick preview workflow
quality_preset: "fast"
→ 10 steps, euler sampler
# Final production
quality_preset: "quality"
→ 35 steps, dpmpp_3m_sde
# Experimental/artistic
quality_preset: "extreme"
→ 75 steps, dpm_adaptive
```
### Custom Rules Format
```json
{
"model_pattern": "anime.*",
"sampler": "dpmpp_2m_sde",
"scheduler": "karras",
"steps": 28,
"cfg": 7.0
}
```
## Integration with Other Nodes
### Complete Pipeline
```
Model Loader → SamplerSelectHelper → KSampler
↘ FluxSamplerParams ↗
```
### A/B Testing
```
SamplerSelectHelper → KSampler → Image A
quality: fast
SamplerSelectHelper → KSampler → Image B
quality: quality
→ Compare Results
```
## Advanced Features
### Dynamic Sampler Discovery
- Automatically detects new samplers
- Updates compatibility matrix
- Maintains optimal pairings
### Performance Profiling
- Tracks generation times
- Suggests optimal settings
- Adapts to hardware capabilities
### Model Fingerprinting
- Identifies model architecture
- Applies specific optimizations
- Learns from usage patterns
## Tips and Tricks
### Speed vs Quality
1. Start with "fast" for prompt testing
2. Move to "balanced" for iteration
3. Use "quality" for final output
4. Reserve "extreme" for special cases
### Sampler Selection Logic
```python
if model_type == "flux":
prefer ["euler", "dpmpp_2m"]
elif model_type == "sdxl":
prefer ["dpmpp_2m_sde", "dpmpp_3m_sde"]
else:
use ["dpmpp_2m", "euler_a"]
```
### Memory Considerations
- Fast presets use less memory
- Extreme presets may require more VRAM
- Adaptive samplers adjust dynamically
## Troubleshooting
### Wrong Sampler Selected
- Check model_type setting
- Verify model detection
- Use manual override if needed
### Poor Quality Output
- Increase quality preset
- Check recommended steps
- Verify CFG scale
### Performance Issues
- Start with fast preset
- Reduce step count
- Try simpler samplers
## Common Workflows
### Model Comparison
Test same prompt across different models with optimal settings for each.
### Quality Ladder
Progress from fast to extreme quality to find optimal balance.
### Sampler Shootout
Compare all compatible samplers for specific model/prompt combination.
## Version History
- **1.0.0**: Initial adaptation from comfyui-essentials-nodes
- **1.0.1**: Added FLUX model support
- **1.0.2**: Enhanced compatibility matrix
- **1.0.3**: Improved auto-detection
## Credits
Original implementation by cubiq in [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials). Adapted and maintained by the ComfyAssets team.
@@ -0,0 +1,300 @@
# Scheduler Select Helper
## Overview
The **Scheduler Select Helper** node provides intelligent scheduler selection with sampler-aware recommendations and model-specific optimizations. Adapted from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) (now in maintenance mode), this tool ensures optimal scheduler selection for different sampling algorithms and models.
## Attribution
This node is based on work from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) by cubiq. The original project is in maintenance-only mode, and we've adopted and enhanced these tools to ensure continued support and compatibility with modern ComfyUI workflows.
## Features
- **Sampler-Aware Selection**: Recommends best schedulers for each sampler
- **Model Optimization**: Specific scheduler tuning for different models
- **Noise Schedule Profiles**: Pre-configured curves for various use cases
- **Visual Feedback**: Preview noise schedules
- **Batch Testing**: Compare multiple schedulers
## Node Properties
- **Category**: `ComfyAssets/🧰 xyz-helpers`
- **Node Name**: `SchedulerSelectHelper`
- **Function**: `select_scheduler`
## Inputs
### Required
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `sampler_name` | STRING | - | Current sampler being used |
| `model_type` | DROPDOWN | auto | [auto, sdxl, sd15, flux] |
| `schedule_type` | DROPDOWN | smooth | [smooth, sharp, linear, custom] |
### Optional
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `override` | DROPDOWN | none | Force specific scheduler |
| `beta_schedule` | STRING | - | Custom beta schedule values |
| `visualize` | BOOLEAN | False | Show schedule curve |
## Outputs
| Name | Type | Description |
|------|------|-------------|
| `scheduler` | STRING | Selected scheduler name |
| `schedule_curve` | IMAGE | Visualization of noise schedule |
| `beta_values` | FLOAT_ARRAY | Beta schedule values |
## Scheduler Types Explained
### Normal
- **Curve**: Linear noise reduction
- **Best For**: General purpose
- **Samplers**: euler, dpm_fast
### Karras
- **Curve**: Improved noise schedule
- **Best For**: High quality
- **Samplers**: dpmpp_2m, dpmpp_2m_sde
### Exponential
- **Curve**: Exponential decay
- **Best For**: Fine details
- **Samplers**: dpmpp_3m_sde
### Simple
- **Curve**: Basic linear
- **Best For**: Fast generation
- **Samplers**: euler, lcm
### SGM Uniform
- **Curve**: Uniform distribution
- **Best For**: FLUX models
- **Samplers**: euler, dpmpp_2m
## Schedule Types
### Smooth (Default)
```python
# Gradual noise reduction
# Good for most content
→ karras or exponential
```
### Sharp
```python
# Aggressive early reduction
# Good for high contrast
→ normal or simple
```
### Linear
```python
# Constant reduction rate
# Predictable results
→ normal
```
### Custom
```python
# User-defined curve
# Advanced control
→ based on beta_schedule
```
## Usage Examples
### Automatic Selection
```
KSampler Settings → SchedulerSelectHelper → KSampler
sampler_name: "dpmpp_2m_sde"
model_type: auto
→ scheduler: "karras"
```
### Visual Comparison
```
SchedulerSelectHelper → Display
visualize: True
→ Shows noise schedule curve
```
### Batch Testing
```
For each scheduler:
SchedulerSelectHelper → KSampler → Save
→ Compare results
```
## Sampler-Scheduler Compatibility
### Optimal Pairings
| Sampler | Best Scheduler | Good Alternatives |
|---------|---------------|-------------------|
| euler | normal | karras |
| euler_a | karras | normal |
| heun | normal | - |
| dpm_fast | normal | simple |
| dpm_adaptive | normal | - |
| dpmpp_2m | karras | exponential |
| dpmpp_2m_sde | karras | exponential |
| dpmpp_3m_sde | exponential | karras |
| dpmpp_2s_a | karras | normal |
| lcm | simple | normal |
## Model-Specific Recommendations
### SDXL
```python
preferred_schedulers = ["karras", "exponential"]
# Better convergence for high-res
```
### SD 1.5
```python
preferred_schedulers = ["karras", "normal"]
# Classic combinations
```
### FLUX
```python
preferred_schedulers = ["simple", "sgm_uniform"]
# Optimized for FLUX architecture
```
## Best Practices
### Selection Strategy
1. Let auto-detection handle defaults
2. Override for specific artistic goals
3. Test multiple schedulers for hero images
4. Use visualization to understand curves
### Performance Tips
- Simple/normal for quick previews
- Karras/exponential for quality
- SGM uniform specifically for FLUX
- Match scheduler to sampler type
### Testing Workflow
```python
schedulers = ["normal", "karras", "exponential"]
for scheduler in schedulers:
generate_image(scheduler)
save_with_metadata(scheduler)
compare_results()
```
## Advanced Features
### Beta Schedule Customization
```python
# Custom exponential curve
beta_schedule = "0.00085, 0.0012, 0.0018, ..."
# Sharp early reduction
beta_schedule = "0.001, 0.002, 0.004, 0.006, ..."
```
### Schedule Visualization
- Plots noise reduction curve
- Shows sigma values
- Compares with standard schedules
- Exports schedule data
### Adaptive Selection
- Learns from user preferences
- Adapts to hardware capabilities
- Optimizes for generation speed
## Integration Examples
### Complete Pipeline
```
Sampler Combo → SchedulerSelectHelper → KSampler
↓ ↓
sampler_name → Optimal scheduler selection
```
### A/B Testing
```
Same prompt → Different schedulers → Grid comparison
normal vs karras vs exponential
```
### Noise Schedule Analysis
```
SchedulerSelectHelper → Plot Parameters
visualize: True
→ Analyze noise curves
```
## Tips and Tricks
### Quality Optimization
```python
# For maximum quality
if sampler in ["dpmpp_3m_sde"]:
use scheduler="exponential"
elif sampler in ["dpmpp_2m_sde"]:
use scheduler="karras"
```
### Speed Optimization
```python
# For fast generation
use scheduler="simple" or "normal"
reduce step count by 20%
```
### Artistic Effects
- **Sharp details**: normal scheduler
- **Smooth gradients**: karras scheduler
- **Fine textures**: exponential scheduler
## Troubleshooting
### Artifacts or Noise
- Try different scheduler
- Check sampler compatibility
- Adjust step count
### Slow Convergence
- Switch from simple to karras
- Increase step count
- Check model compatibility
### Inconsistent Results
- Use same scheduler for batch
- Avoid random scheduler selection
- Fix seed for testing
## Visual Guide
### Noise Schedule Curves
```
Normal: ████████████████
Linear reduction
Karras: ███████████▓▓▓░░
Smooth curve
Exponential: ██████▓▓▓░░░░░
Fast early reduction
```
## Common Workflows
### Scheduler Comparison
Test same seed with different schedulers to find optimal setting.
### Model Migration
When switching models, automatically adjust scheduler for best results.
### Quality Ladder
Progress through schedulers from fast to quality for different use cases.
## Version History
- **1.0.0**: Initial adaptation from comfyui-essentials-nodes
- **1.0.1**: Added visualization features
- **1.0.2**: Enhanced model detection
- **1.0.3**: Improved compatibility matrix
## Credits
Original implementation by cubiq in [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials). Adapted and maintained by the ComfyAssets team.
+1 -1
View File
@@ -164,4 +164,4 @@ See the `examples/workflows/` directory for complete workflow examples demonstra
- Basic seed tracking workflow
- Creative iteration with history
- Technical reproducibility setup
- Batch processing with seed management
- Batch processing with seed management
@@ -0,0 +1,310 @@
# Text Encode Sampler Params
## Overview
The **Text Encode Sampler Params** node combines text encoding with sampler parameter management, providing a unified interface for prompt processing and sampling configuration. Adapted from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) (now in maintenance mode), this tool streamlines the text-to-image pipeline setup.
## Attribution
This node is based on work from [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials) by cubiq. The original project is in maintenance-only mode, and we've adopted and enhanced these tools to ensure continued support and compatibility with modern ComfyUI workflows.
## Features
- **Unified Interface**: Combine text encoding and sampler params in one node
- **Dynamic Prompt Processing**: Support for wildcards and syntax
- **Parameter Templates**: Pre-configured settings for common scenarios
- **Batch Text Processing**: Handle multiple prompts efficiently
- **Model-Aware Encoding**: Optimize for different text encoders
## Node Properties
- **Category**: `ComfyAssets/🧰 xyz-helpers`
- **Node Name**: `TextEncodeSamplerParams`
- **Function**: `encode_and_params`
## Inputs
### Required
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `text` | STRING | - | Prompt text to encode |
| `clip` | CLIP | - | CLIP model for encoding |
| `sampler_name` | DROPDOWN | dpmpp_2m | Sampling algorithm |
| `scheduler` | DROPDOWN | karras | Noise scheduler |
| `steps` | INT | 20 | Sampling steps |
| `cfg` | FLOAT | 7.0 | CFG scale |
### Optional
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `negative_text` | STRING | "" | Negative prompt |
| `seed` | INT | -1 | Random seed (-1 for random) |
| `denoise` | FLOAT | 1.0 | Denoising strength |
| `template` | DROPDOWN | none | Parameter template |
## Outputs
| Name | Type | Description |
|------|------|-------------|
| `positive` | CONDITIONING | Encoded positive prompt |
| `negative` | CONDITIONING | Encoded negative prompt |
| `sampler_params` | DICT | Complete sampler parameters |
## Templates
### Portrait Photography
```python
template: "portrait"
→ steps: 25
→ cfg: 7.5
→ sampler: dpmpp_2m_sde
→ scheduler: karras
```
### Landscape Art
```python
template: "landscape"
→ steps: 30
→ cfg: 8.0
→ sampler: dpmpp_3m_sde
→ scheduler: exponential
```
### Quick Preview
```python
template: "preview"
→ steps: 12
→ cfg: 6.0
→ sampler: euler
→ scheduler: normal
```
### High Detail
```python
template: "detailed"
→ steps: 40
→ cfg: 7.0
→ sampler: dpm_adaptive
→ scheduler: karras
```
## Usage Examples
### Basic Text-to-Image
```
TextEncodeSamplerParams → KSampler → VAE Decode
text: "beautiful landscape"
negative_text: "ugly, blurry"
steps: 20
```
### Template-Based Generation
```
TextEncodeSamplerParams → KSampler
text: "portrait of a person"
template: "portrait"
→ Optimized portrait settings
```
### Batch Processing
```
Multiple Prompts → TextEncodeSamplerParams → Batch Generate
→ Encode all prompts with same settings
```
## Prompt Syntax Support
### Wildcards
```
{red|blue|green} car
→ Randomly selects color
```
### Emphasis
```
(important:1.2) detail
→ Increases weight to 1.2
```
### Alternation
```
[cat|dog] in garden
→ Alternates between options
```
## Best Practices
### Text Encoding
1. Keep prompts concise and descriptive
2. Use emphasis for important elements
3. Structure prompts logically
4. Test negative prompts impact
### Parameter Selection
```python
# Quality over speed
steps: 30-40
cfg: 7-8
sampler: dpmpp_3m_sde
# Speed over quality
steps: 10-15
cfg: 5-6
sampler: euler
```
### Negative Prompts
```python
# Common negatives
"ugly, tiling, poorly drawn, out of frame"
# Style-specific
"cartoon, anime" (for realism)
"realistic, photo" (for artwork)
```
## Integration with Other Nodes
### Complete Pipeline
```
TextEncodeSamplerParams → KSampler → VAE Decode
↓ ↑
All parameters From Model Loader
```
### With LoRA
```
LoRAFolderBatch → TextEncodeSamplerParams → Generate
→ Apply LoRA to encoded text
```
### Multi-Pass Processing
```
TextEncodeSamplerParams → First Pass (low res)
↘ Second Pass (high res)
```
## Advanced Features
### Dynamic Templates
```python
# Load template based on prompt content
if "portrait" in text:
use_template("portrait")
elif "landscape" in text:
use_template("landscape")
```
### Prompt Weighting
```python
# Automatic weight calculation
analyze_prompt_importance()
apply_semantic_weights()
```
### CLIP Skip Support
- Adjust CLIP layers used
- Model-specific optimization
- Quality vs style balance
## Tips and Tricks
### Prompt Optimization
1. Front-load important elements
2. Use commas for separation
3. Avoid contradictions
4. Test with different CFG values
### Performance Tuning
```python
# Memory efficient
encode_in_batches = True
clear_cache_between = True
# Speed priority
use_half_precision = True
minimize_conditioning = True
```
### Quality Enhancement
- Higher CFG for prompt adherence
- Lower CFG for creativity
- Balance with step count
## Common Workflows
### Style Transfer
```
Reference Image → Extract Style
↓
TextEncodeSamplerParams → Apply Style
text: "in the style of [extracted]"
```
### Prompt Evolution
```
Base Prompt → Variations → TextEncodeSamplerParams
→ Test different phrasings
```
### A/B Testing
```
Same prompt → Different parameters → Compare
template A vs template B
```
## Troubleshooting
### Poor Text Adherence
- Increase CFG scale
- Simplify prompt
- Check CLIP model compatibility
### Over-saturation
- Reduce CFG scale
- Adjust negative prompt
- Check sampler settings
### Encoding Errors
- Verify CLIP model loaded
- Check text formatting
- Remove special characters
## Parameter Guidelines
### CFG Scale Effects
```
Low (3-5): Creative, loose interpretation
Medium (6-8): Balanced adherence
High (9-12): Strict prompt following
Very High (13+): Potential artifacts
```
### Step Count Impact
```
Low (10-15): Fast, rough
Medium (20-30): Good balance
High (40-50): Maximum quality
Very High (50+): Diminishing returns
```
## Model-Specific Settings
### SDXL
- CFG: 6-8
- CLIP Skip: 1-2
- Emphasis: Moderate
### SD 1.5
- CFG: 7-9
- CLIP Skip: 1-2
- Emphasis: Standard
### FLUX
- CFG: 3-5
- CLIP Skip: 0
- Emphasis: Subtle
## Version History
- **1.0.0**: Initial adaptation from comfyui-essentials-nodes
- **1.0.1**: Added template system
- **1.0.2**: Enhanced prompt syntax support
- **1.0.3**: Improved batch processing
## Credits
Original implementation by cubiq in [comfyui-essentials-nodes](https://github.com/cubiq/ComfyUI_essentials). Adapted and maintained by the ComfyAssets team.
@@ -147,7 +147,7 @@ Width Height Selector → EmptyLatentImage → Resolution Calculator → Upscale
### Aspect Ratio Considerations
- **Portrait**: 3:4, 2:3, 13:19 work well for people
- **Landscape**: 16:9, 19:13, 7:4 for scenes and objects
- **Landscape**: 16:9, 19:13, 7:4 for scenes and objects
- **Square**: 1:1 for centered compositions
- **Ultra-wide**: 21:9+ for panoramic and cinematic shots
@@ -192,4 +192,4 @@ Width Height Selector → EmptyLatentImage → Resolution Calculator → Upscale
### Preset Organization
- Categorized by model optimization
- Sorted by aspect ratio within categories
- Comprehensive tooltips for each preset
- Comprehensive tooltips for each preset
+14
View File
@@ -0,0 +1,14 @@
A beautiful sunset over the ocean, golden hour lighting, professional photography, vibrant colors, high detail
Negative: blurry, dark, low quality, distorted, oversaturated
---
Majestic mountain landscape with snow-capped peaks, dramatic clouds, alpine scenery, crystal clear air, epic composition
Negative: foggy, flat lighting, boring composition, low contrast
---
Futuristic cityscape at night, neon lights, cyberpunk aesthetic, rain-slicked streets, atmospheric, blade runner style
Negative: daylight, rural, old fashioned, low tech, empty streets
---
Enchanted forest with magical glowing mushrooms, fairy lights, mystical atmosphere, ancient trees, fantasy art style
Negative: desert, urban, modern, realistic, mundane
---
Space station orbiting Earth, detailed mechanical structures, astronauts performing spacewalk, realistic sci-fi, NASA photography
Negative: fantasy, medieval, underwater, cartoon style
+379
View File
@@ -0,0 +1,379 @@
{
"id": "display-any-example",
"revision": 0,
"last_node_id": 11,
"last_link_id": 9,
"nodes": [
{
"id": 1,
"type": "DisplayAny",
"pos": [
400,
270
],
"size": [
210,
60
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "input",
"type": "*",
"link": 1
}
],
"outputs": [
{
"name": "display_text",
"type": "STRING",
"links": [
8
]
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "DisplayAny"
},
"widgets_values": [
"tensor shape"
]
},
{
"id": 2,
"type": "LoadImage",
"pos": [
50,
270
],
"size": [
315,
314
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
1,
5
]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.47",
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"example.png",
"image"
]
},
{
"id": 4,
"type": "DisplayAny",
"pos": [
400,
520
],
"size": [
210,
70
],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "input",
"type": "*",
"link": 7
}
],
"outputs": [
{
"name": "display_text",
"type": "STRING",
"links": [
9
]
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "DisplayAny"
},
"widgets_values": [
"raw value"
]
},
{
"id": 7,
"type": "VAELoader",
"pos": [
-250,
410
],
"size": [
270,
58
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "VAE",
"type": "VAE",
"links": [
6
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.47",
"Node name for S&R": "VAELoader"
},
"widgets_values": [
"ae.safetensors"
]
},
{
"id": 8,
"type": "VAEEncode",
"pos": [
430,
390
],
"size": [
140,
46
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "pixels",
"type": "IMAGE",
"link": 5
},
{
"name": "vae",
"type": "VAE",
"link": 6
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
7
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.47",
"Node name for S&R": "VAEEncode"
},
"widgets_values": []
},
{
"id": 9,
"type": "DisplayText",
"pos": [
640,
270
],
"size": [
450,
278
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 8
}
],
"outputs": [
{
"name": "text",
"type": "STRING",
"links": null
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "DisplayText"
},
"widgets_values": [
null
]
},
{
"id": 10,
"type": "DisplayText",
"pos": [
640,
610
],
"size": [
590,
278
],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 9
}
],
"outputs": [
{
"name": "text",
"type": "STRING",
"links": null
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "DisplayText"
},
"widgets_values": [
null
]
},
{
"id": 11,
"type": "MarkdownNote",
"pos": [
-320,
530
],
"size": [
350,
270
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"Display Any Example\n\nUniversal debugging tool:\n- Accepts ANY input type\n- Two modes: raw value or tensor shape\n- Finds tensors in nested structures\n\nUse cases:\n- Debug tensor dimensions\n- Inspect latent data\n- View config objects\n- Track data flow\n\nConnect anything to see its contents!"
],
"color": "#432",
"bgcolor": "#653"
}
],
"links": [
[
1,
2,
0,
1,
0,
"IMAGE"
],
[
5,
2,
0,
8,
0,
"IMAGE"
],
[
6,
7,
0,
8,
1,
"VAE"
],
[
7,
8,
0,
4,
0,
"*"
],
[
8,
1,
0,
9,
0,
"STRING"
],
[
9,
4,
0,
10,
0,
"STRING"
]
],
"groups": [
{
"id": 1,
"title": "Display Any",
"bounding": [
-400,
130,
1740,
850
],
"color": "#ffffff",
"font_size": 24,
"flags": {}
}
],
"config": {},
"extra": {
"ds": {
"scale": 0.6115909044841477,
"offset": [
689.1392030323894,
-27.435530779258137
]
},
"frontendVersion": "1.23.4",
"VHS_latentpreview": true,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 466 KiB

@@ -0,0 +1,144 @@
{
"id": "display-text-example",
"revision": 0,
"last_node_id": 6,
"last_link_id": 2,
"nodes": [
{
"id": 1,
"type": "DisplayText",
"pos": [
-160,
70
],
"size": [
500,
400
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 2
}
],
"outputs": [
{
"name": "text",
"type": "STRING",
"links": null
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "DisplayText"
},
"widgets_values": [
null
]
},
{
"id": 4,
"type": "easy positive",
"pos": [
-620,
70
],
"size": [
400,
200
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "positive",
"type": "STRING",
"links": [
2
]
}
],
"properties": {
"cnr_id": "comfyui-easy-use",
"ver": "1.3.1",
"Node name for S&R": "easy positive"
},
"widgets_values": [
"Positive prompt:\nbeautiful landscape, mountains in background, sunset lighting, golden hour, professional photography, high resolution, detailed textures, vibrant colors, masterpiece\n\nNegative prompt:\nlow quality, blurry, pixelated, bad composition, oversaturated, underexposed, amateur"
],
"color": "#232",
"bgcolor": "#353"
},
{
"id": 6,
"type": "MarkdownNote",
"pos": [
-620,
330
],
"size": [
360,
250
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"Display Text Example\n\nSmart text display with:\n- Auto-detection of SDXL prompt format\n- Split view for positive/negative prompts\n- Text wrapping and scrolling\n- Copy buttons (strips labels)\n- Responsive resizing\n\nTry editing the text to see:\n1. Single text mode (no prompt markers)\n2. Split view mode (with Positive/Negative prompts)\n\nPerfect for displaying Gemini-generated prompts!"
],
"color": "#432",
"bgcolor": "#653"
}
],
"links": [
[
2,
4,
0,
1,
0,
"STRING"
]
],
"groups": [
{
"id": 1,
"title": "Display Text",
"bounding": [
-750,
-60,
1240,
700
],
"color": "#ffffff",
"font_size": 24,
"flags": {}
}
],
"config": {},
"extra": {
"ds": {
"scale": 0.90909090909091,
"offset": [
720,
30
]
},
"frontendVersion": "1.23.4",
"VHS_latentpreview": true,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

@@ -0,0 +1,301 @@
{
"id": "empty-latent-batch-example",
"revision": 0,
"last_node_id": 9,
"last_link_id": 8,
"nodes": [
{
"id": 1,
"type": "EmptyLatentBatch",
"pos": [
350,
350
],
"size": [
370,
210
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "latent",
"type": "LATENT",
"slot_index": 0,
"links": []
},
{
"name": "width",
"type": "INT",
"slot_index": 1,
"links": [
4
]
},
{
"name": "height",
"type": "INT",
"slot_index": 2,
"links": [
5
]
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "EmptyLatentBatch"
},
"widgets_values": [
"896×1152 - 7:9 (1.0MP) - SDXL",
896,
1152,
4
]
},
{
"id": 2,
"type": "Note",
"pos": [
-50,
320
],
"size": [
350,
250
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {
"text": "Empty Latent Batch Example\n\nThis node creates empty latent tensors with:\n- 26 preset resolutions for SDXL/FLUX/Ultra-wide\n- Batch size support (1-64)\n- Interactive swap button\n- Memory usage estimation\n\nConnect to:\n- KSampler for image generation\n- Other latent processing nodes\n\nOutputs sanitized dimensions divisible by 8."
},
"widgets_values": [
"Empty Latent Batch Example\n\nThis node creates empty latent tensors with:\n- 26 preset resolutions for SDXL/FLUX/Ultra-wide\n- Batch size support (1-64)\n- Interactive swap button\n- Memory usage estimation\n\nConnect to:\n- KSampler for image generation\n- Other latent processing nodes\n\nOutputs sanitized dimensions divisible by 8."
],
"color": "#432",
"bgcolor": "#653"
},
{
"id": 5,
"type": "DisplayAny",
"pos": [
760,
280
],
"size": [
210,
60
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "input",
"type": "*",
"link": 4
}
],
"outputs": [
{
"name": "display_text",
"type": "STRING",
"links": [
7
]
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "DisplayAny"
},
"widgets_values": [
"raw value"
]
},
{
"id": 6,
"type": "DisplayAny",
"pos": [
770,
500
],
"size": [
210,
60
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "input",
"type": "*",
"link": 5
}
],
"outputs": [
{
"name": "display_text",
"type": "STRING",
"links": [
8
]
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "DisplayAny"
},
"widgets_values": [
"raw value"
]
},
{
"id": 8,
"type": "DisplayText",
"pos": [
1000,
280
],
"size": [
210,
138
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 7
}
],
"outputs": [
{
"name": "text",
"type": "STRING",
"links": null
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "DisplayText"
},
"widgets_values": [
null
]
},
{
"id": 9,
"type": "DisplayText",
"pos": [
1010,
500
],
"size": [
210,
138
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 8
}
],
"outputs": [
{
"name": "text",
"type": "STRING",
"links": null
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "DisplayText"
},
"widgets_values": [
null
]
}
],
"links": [
[
4,
1,
1,
5,
0,
"*"
],
[
5,
1,
2,
6,
0,
"*"
],
[
7,
5,
0,
8,
0,
"STRING"
],
[
8,
6,
0,
9,
0,
"STRING"
]
],
"groups": [
{
"id": 1,
"title": "Empty Latent",
"bounding": [
-110,
120,
1440,
620
],
"color": "#ffffff",
"font_size": 24,
"flags": {}
}
],
"config": {},
"extra": {
"ds": {
"scale": 0.8264462809917358,
"offset": [
156.41790912499965,
-2.272547312500285
]
},
"frontendVersion": "1.23.4",
"VHS_latentpreview": true,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

@@ -0,0 +1,207 @@
{
"id": "gemini-prompt-example",
"revision": 0,
"last_node_id": 6,
"last_link_id": 4,
"nodes": [
{
"id": 2,
"type": "LoadImage",
"pos": [
300,
30
],
"size": [
315,
314
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
3
]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.47",
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"example.png",
"image"
]
},
{
"id": 3,
"type": "DisplayText",
"pos": [
1070,
30
],
"size": [
500,
400
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 4
}
],
"outputs": [
{
"name": "text",
"type": "STRING",
"links": null
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "DisplayText"
},
"widgets_values": [
null
]
},
{
"id": 5,
"type": "GeminiPrompt",
"pos": [
640,
30
],
"size": [
400,
276
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 3
}
],
"outputs": [
{
"name": "prompt",
"type": "STRING",
"links": [
4
]
},
{
"name": "negative_prompt",
"type": "STRING",
"links": null
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "GeminiPrompt"
},
"widgets_values": [
"sdxl",
"gemini-2.0-flash",
"1234-1234-1234-1234",
"",
true,
null,
null,
null
]
},
{
"id": 6,
"type": "MarkdownNote",
"pos": [
300,
410
],
"size": [
400,
240
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"Gemini Prompt Engineer Example\n\nAI-powered image analysis:\n- Analyzes image content and style\n- Generates optimized prompts for FLUX/SDXL/Danbooru/Video\n- Dynamic model selection with refresh button\n- Custom system prompt override\n\nSetup:\n1. Get free API key from Google AI Studio\n2. Set GEMINI_API_KEY env var or use config file\n3. Click refresh button to get latest models\n\nDisplayText automatically shows split view for SDXL prompts!"
],
"color": "#432",
"bgcolor": "#653"
}
],
"links": [
[
3,
2,
0,
5,
0,
"IMAGE"
],
[
4,
5,
0,
3,
0,
"STRING"
]
],
"groups": [
{
"id": 1,
"title": "Gemini Prompt Engineer",
"bounding": [
210,
-110,
1460,
850
],
"color": "#ffffff",
"font_size": 24,
"flags": {}
}
],
"config": {},
"extra": {
"ds": {
"scale": 0.7513148009015777,
"offset": [
97.5582232217803,
133.12148846503663
]
},
"frontendVersion": "1.23.4",
"VHS_latentpreview": true,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

@@ -0,0 +1,552 @@
{
"id": "3cd0ac86-8d11-41e5-9d8b-6621bc810ee6",
"revision": 0,
"last_node_id": 21,
"last_link_id": 21,
"nodes": [
{
"id": 1,
"type": "LoadImage",
"pos": [
-80,
850
],
"size": [
315,
314.0000305175781
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"label": "IMAGE",
"name": "IMAGE",
"type": "IMAGE",
"links": [
13
]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.47",
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"example.png",
"image"
]
},
{
"id": 8,
"type": "GetNode",
"pos": [
540,
840
],
"size": [
210,
34
],
"flags": {
"collapsed": true
},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "UPSCALE_MODEL",
"type": "UPSCALE_MODEL",
"links": []
}
],
"title": "Get_upscale_skin_model",
"properties": {
"widget_ue_connectable": {}
},
"widgets_values": [
"upscale_skin_model"
]
},
{
"id": 9,
"type": "ImageUpscaleWithModel",
"pos": [
530,
810
],
"size": [
221.98202514648438,
60
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "upscale_model",
"type": "UPSCALE_MODEL",
"link": 16
},
{
"name": "image",
"type": "IMAGE",
"link": 5
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
10,
21
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.28",
"Node name for S&R": "ImageUpscaleWithModel",
"widget_ue_connectable": {}
},
"widgets_values": []
},
{
"id": 10,
"type": "ImageToMultipleOf",
"pos": [
1000,
810
],
"size": [
230,
82
],
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 6
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [
18
]
}
],
"properties": {
"cnr_id": "comfyui-glifnodes",
"ver": "1.0.0",
"Node name for S&R": "ImageToMultipleOf",
"widget_ue_connectable": {}
},
"widgets_values": [
65,
"center crop"
]
},
{
"id": 13,
"type": "ImageScaleBy",
"pos": [
760,
810
],
"size": [
220,
82
],
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 10
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
6,
19
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.28",
"Node name for S&R": "ImageScaleBy",
"widget_ue_connectable": {}
},
"widgets_values": [
"lanczos",
0.5000000000000001
]
},
{
"id": 14,
"type": "ImageUpscaleWithModel",
"pos": [
280,
830
],
"size": [
221.98202514648438,
60
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "upscale_model",
"type": "UPSCALE_MODEL",
"link": 15
},
{
"name": "image",
"type": "IMAGE",
"link": 13
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
5,
20
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.28",
"Node name for S&R": "ImageUpscaleWithModel",
"widget_ue_connectable": {}
},
"widgets_values": []
},
{
"id": 15,
"type": "UpscaleModelLoader",
"pos": [
-90,
720
],
"size": [
320,
60
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "UPSCALE_MODEL",
"type": "UPSCALE_MODEL",
"links": [
15
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.28",
"Node name for S&R": "UpscaleModelLoader",
"widget_ue_connectable": {}
},
"widgets_values": [
"4x-ClearRealityV1.pth"
],
"color": "#232",
"bgcolor": "#353"
},
{
"id": 16,
"type": "UpscaleModelLoader",
"pos": [
140,
610
],
"size": [
320,
60
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "UPSCALE_MODEL",
"type": "UPSCALE_MODEL",
"links": [
16
]
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.28",
"Node name for S&R": "UpscaleModelLoader",
"widget_ue_connectable": {}
},
"widgets_values": [
"1xSkinContrast-High-SuperUltraCompact.pth"
],
"color": "#232",
"bgcolor": "#353"
},
{
"id": 18,
"type": "PreviewImage",
"pos": [
1260,
810
],
"size": [
270,
280
],
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 18
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.47",
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 19,
"type": "PreviewImage",
"pos": [
1010,
940
],
"size": [
210,
258
],
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 19
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.47",
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 20,
"type": "PreviewImage",
"pos": [
520,
940
],
"size": [
210,
258
],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 20
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.47",
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
},
{
"id": 21,
"type": "PreviewImage",
"pos": [
750,
940
],
"size": [
210,
258
],
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 21
}
],
"outputs": [],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.47",
"Node name for S&R": "PreviewImage"
},
"widgets_values": []
}
],
"links": [
[
5,
14,
0,
9,
1,
"IMAGE"
],
[
6,
13,
0,
10,
0,
"IMAGE"
],
[
10,
9,
0,
13,
0,
"IMAGE"
],
[
13,
1,
0,
14,
1,
"IMAGE"
],
[
15,
15,
0,
14,
0,
"UPSCALE_MODEL"
],
[
16,
16,
0,
9,
0,
"UPSCALE_MODEL"
],
[
18,
10,
0,
18,
0,
"IMAGE"
],
[
19,
13,
0,
19,
0,
"IMAGE"
],
[
20,
14,
0,
20,
0,
"IMAGE"
],
[
21,
9,
0,
21,
0,
"IMAGE"
]
],
"groups": [
{
"id": 1,
"title": "Image to Multiple of",
"bounding": [
-180,
420,
1800,
830
],
"color": "#ffffff",
"font_size": 24,
"flags": {}
}
],
"config": {},
"extra": {
"ds": {
"scale": 0.6655312045216585,
"offset": [
292.51725658775155,
-375.0766486499791
]
},
"frontendVersion": "1.23.4",
"VHS_latentpreview": true,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 805 KiB

@@ -0,0 +1,165 @@
{
"id": "kiko-film-grain-example",
"revision": 0,
"last_node_id": 4,
"last_link_id": 2,
"nodes": [
{
"id": 1,
"type": "LoadImage",
"pos": [
50,
100
],
"size": [
350,
450
],
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [1],
"shape": 3,
"label": "IMAGE"
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3,
"label": "MASK"
}
],
"properties": {
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"example.png"
]
},
{
"id": 2,
"type": "KikoFilmGrain",
"pos": [
450,
100
],
"size": [
315,
202
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 1
}
],
"outputs": [
{
"name": "image",
"type": "IMAGE",
"links": [2],
"shape": 3,
"label": "image",
"slot_index": 0
}
],
"properties": {
"cnr_id": "kikotools",
"Node name for S&R": "KikoFilmGrain"
},
"widgets_values": [
0.5,
0.5,
0.7,
0.0,
0
],
"color": "#223",
"bgcolor": "#335"
},
{
"id": 3,
"type": "PreviewImage",
"pos": [
850,
100
],
"size": [
350,
450
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 2
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 4,
"type": "Note",
"pos": [
450,
350
],
"size": [
315,
150
],
"flags": {},
"order": 3,
"mode": 0,
"properties": {
"text": ""
},
"widgets_values": [
"Kiko Film Grain Example\n\nThis workflow demonstrates the film grain effect.\n\nAdjust parameters:\n- Scale: Grain size (0.25-2.0)\n- Strength: Intensity (0.0-10.0)\n- Saturation: Color amount (0.0-2.0)\n- Toe: Shadow lifting (-0.2-0.5)\n- Seed: Random pattern"
],
"color": "#432",
"bgcolor": "#653"
}
],
"links": [
[
1,
1,
0,
2,
0,
"IMAGE"
],
[
2,
2,
0,
3,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1.0,
"offset": [0, 0]
}
},
"version": 0.4
}
@@ -0,0 +1,147 @@
{
"id": "kiko-save-image-example",
"revision": 0,
"last_node_id": 4,
"last_link_id": 1,
"nodes": [
{
"id": 1,
"type": "KikoSaveImage",
"pos": [
390,
80
],
"size": [
400,
390
],
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 1
}
],
"outputs": [],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "KikoSaveImage"
},
"widgets_values": [
"KikoSave",
"PNG",
90,
4,
false,
true
]
},
{
"id": 2,
"type": "LoadImage",
"pos": [
-10,
80
],
"size": [
315,
314
],
"flags": {},
"order": 1,
"mode": 0,
"inputs": [],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
1
]
},
{
"name": "MASK",
"type": "MASK",
"links": null
}
],
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.47",
"Node name for S&R": "LoadImage"
},
"widgets_values": [
"example.png",
"image"
]
},
{
"id": 4,
"type": "MarkdownNote",
"pos": [
-10,
460
],
"size": [
370,
270
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {},
"widgets_values": [
"Kiko Save Image Example\n\nEnhanced image saving with:\n- Format selection: PNG, JPEG, WebP\n- Quality controls per format\n- Floating popup viewer (draggable)\n- Batch operations support\n- File size display\n\nPopup Features:\n- Click images to open in new tab\n- Download individual or selected images\n- Minimize/maximize/roll-up controls\n- Persistent across saves\n\nTry different formats to compare file sizes!"
],
"color": "#432",
"bgcolor": "#653"
}
],
"links": [
[
1,
2,
0,
1,
0,
"IMAGE"
]
],
"groups": [
{
"id": 1,
"title": "Kiko save Image",
"bounding": [
-110,
-60,
1020,
870
],
"color": "#ffffff",
"font_size": 24,
"flags": {}
}
],
"config": {},
"extra": {
"ds": {
"scale": 0.7513148009015777,
"offset": [
648.1018911400128,
110.02838653698085
]
},
"frontendVersion": "1.23.4",
"VHS_latentpreview": true,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

@@ -1,15 +1,15 @@
{
"id": "41469b2d-d616-479d-879a-95cdc6074a37",
"revision": 0,
"last_node_id": 6,
"last_link_id": 5,
"last_node_id": 11,
"last_link_id": 9,
"nodes": [
{
"id": 1,
"type": "ResolutionCalculator",
"pos": [
60,
430
-30,
210
],
"size": [
315,
@@ -38,8 +38,7 @@
"type": "INT",
"slot_index": 0,
"links": [
1,
3
6
]
},
{
@@ -47,15 +46,15 @@
"type": "INT",
"slot_index": 1,
"links": [
2,
4
7
]
}
],
"properties": {
"aux_id": "ComfyAssets/ComfyUI-KikoTools",
"cnr_id": "kikotools",
"ver": "965ad60c74d7f25b1acce890d9c06518e46e6d0b",
"Node name for S&R": "ResolutionCalculator",
"aux_id": "ComfyAssets/ComfyUI-KikoTools",
"widget_ue_connectable": {}
},
"widgets_values": [
@@ -66,8 +65,8 @@
"id": 6,
"type": "LoadImage",
"pos": [
-250,
430
-340,
210
],
"size": [
274.080078125,
@@ -94,8 +93,8 @@
"properties": {
"cnr_id": "comfy-core",
"ver": "0.3.40",
"widget_ue_connectable": {},
"Node name for S&R": "LoadImage"
"Node name for S&R": "LoadImage",
"widget_ue_connectable": {}
},
"widgets_values": [
"image-2025-06-13-105737.jpg",
@@ -103,80 +102,14 @@
]
},
{
"id": 4,
"type": "Display Int (rgthree)",
"id": 7,
"type": "MarkdownNote",
"pos": [
420,
360
-350,
610
],
"size": [
210,
88
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"dir": 3,
"name": "input",
"type": "INT",
"link": 3
}
],
"outputs": [],
"properties": {
"cnr_id": "rgthree-comfy",
"ver": "1.0.2506081210",
"Node name for S&R": "Display Int (rgthree)",
"widget_ue_connectable": {}
},
"widgets_values": [
""
]
},
{
"id": 5,
"type": "Display Int (rgthree)",
"pos": [
430,
530
],
"size": [
210,
88
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"dir": 3,
"name": "input",
"type": "INT",
"link": 4
}
],
"outputs": [],
"properties": {
"cnr_id": "rgthree-comfy",
"ver": "1.0.2506081210",
"widget_ue_connectable": {},
"Node name for S&R": "Display Int (rgthree)"
},
"widgets_values": [
""
]
},
{
"id": 2,
"type": "Note",
"pos": [
-150,
170
],
"size": [
400,
410,
200
],
"flags": {},
@@ -184,50 +117,167 @@
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {
"text": "Resolution Calculator Example\n\nThis node calculates upscaled dimensions from:\n- IMAGE tensors (connect from image loaders)\n- LATENT tensors (connect from VAE encode/generation)\n\nUse cases:\n- Calculate target dimensions for upscalers\n- Plan memory usage for large generations\n- Ensure dimensions are divisible by 8\n\nScale factors optimized for SDXL and FLUX models.",
"widget_ue_connectable": {}
},
"properties": {},
"widgets_values": [
"Resolution Calculator Example\n\nThis node calculates upscaled dimensions from:\n- IMAGE tensors (connect from image loaders)\n- LATENT tensors (connect from VAE encode/generation)\n\nUse cases:\n- Calculate target dimensions for upscalers\n- Plan memory usage for large generations\n- Ensure dimensions are divisible by 8\n\nScale factors optimized for SDXL and FLUX models."
],
"color": "#432",
"bgcolor": "#653"
},
{
"id": 8,
"type": "DisplayAny",
"pos": [
330,
160
],
"size": [
270,
58
],
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "input",
"type": "*",
"link": 6
}
],
"outputs": [
{
"name": "display_text",
"type": "STRING",
"links": [
8
]
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "DisplayAny"
},
"widgets_values": [
"raw value"
]
},
{
"id": 9,
"type": "DisplayAny",
"pos": [
330,
310
],
"size": [
270,
58
],
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "input",
"type": "*",
"link": 7
}
],
"outputs": [
{
"name": "display_text",
"type": "STRING",
"links": [
9
]
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "DisplayAny"
},
"widgets_values": [
"raw value"
]
},
{
"id": 10,
"type": "DisplayText",
"pos": [
670,
160
],
"size": [
210,
138
],
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 8
}
],
"outputs": [
{
"name": "text",
"type": "STRING",
"links": null
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "DisplayText"
},
"widgets_values": [
null
]
},
{
"id": 11,
"type": "DisplayText",
"pos": [
660,
400
],
"size": [
210,
138
],
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 9
}
],
"outputs": [
{
"name": "text",
"type": "STRING",
"links": null
}
],
"properties": {
"cnr_id": "kikotools",
"ver": "271cd020c1b2f78e7ee40c1f08e70191fed00012",
"Node name for S&R": "DisplayText"
},
"widgets_values": [
null
]
}
],
"links": [
[
1,
1,
0,
3,
0,
"INT"
],
[
2,
1,
1,
3,
1,
"INT"
],
[
3,
1,
0,
4,
0,
"INT"
],
[
4,
1,
1,
5,
0,
"INT"
],
[
5,
6,
@@ -235,25 +285,71 @@
1,
0,
"IMAGE"
],
[
6,
1,
0,
8,
0,
"*"
],
[
7,
1,
1,
9,
0,
"*"
],
[
8,
8,
0,
10,
0,
"STRING"
],
[
9,
9,
0,
11,
0,
"STRING"
]
],
"groups": [],
"groups": [
{
"id": 1,
"title": "Resolution Calculator",
"bounding": [
-480,
20,
1480,
880
],
"color": "#ffffff",
"font_size": 24,
"flags": {}
}
],
"config": {},
"extra": {
"ue_links": [],
"ds": {
"scale": 0.7972024500000015,
"scale": 0.45000000000000145,
"offset": [
709.6391289161842,
-90.0127389290854
1248.1498802376432,
256.0155843113725
]
},
"links_added_by_ue": [],
"frontendVersion": "1.21.7",
"frontendVersion": "1.23.4",
"VHS_latentpreview": true,
"VHS_latentpreviewrate": 0,
"VHS_MetadataImage": true,
"VHS_KeepIntermediate": true
},
"version": 0.4
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 785 KiB

@@ -534,4 +534,4 @@
"VHS_KeepIntermediate": true
},
"version": 0.4
}
}

Some files were not shown because too many files have changed in this diff Show More