The width of the repeat input field in the LoRA cycler settings view has been increased from 40px to 50px. This change improves usability by providing more space for user input, making the control easier to interact with and reducing visual crowding.
JavaScript floating point arithmetic causes values like 1.1 to become
1.1000000000000014. Add precision limiting to 2 decimal places in
snapToStep function for both sliders.
- Add LoraListModal component with search and preview tooltip
- Make 'Next LoRA' name clickable to open selector modal
- Integrate PreviewTooltip with custom resolver for Vue widgets
- Disable selector when prompts are queued (consistent with pause button)
- Fix tooltip z-index to display above modal backdrop
Fixes issue: users couldn't easily identify which index corresponds
to specific LoRA in large lists
- Replace REP badge with segmented progress bar for repeat indicator
- Reorganize Starting Index & Repeat controls into aligned groups
- Change repeat format from '× [count] times' to '[count] ×' for better alignment
- Remove unnecessary refresh button and related logic
- Add `hasQueuedPrompts` reactive flag to track queued executions
- Pass `is-pause-disabled` prop to settings view to disable pause button
- Update pause button title to indicate why it's disabled
- Remove server queue clearing logic from pause toggle handler
- Clear `hasQueuedPrompts` flag when manually changing index or resetting
- Set `hasQueuedPrompts` to true when adding prompts to execution queue
- Update flag when processing queued executions to reflect current queue state
- Update package.json test script to run both JS and Vue tests
- Simplify LoraCyclerLM output by removing redundant lora name fallback
- Extend Vitest config to include TypeScript test files
- Add Vue testing dependencies and setup for component testing
- Implement comprehensive test suite for BatchQueueSimulator component
- Add test setup file with global mocks for ComfyUI modules
Reset execution state when user manually changes LoRA index to ensure next execution starts from the user-set index. This prevents stale execution state from interfering with user-initiated index changes.
- Add generic type parameter to ComponentWidget<T> for type-safe callbacks
- Remove LegacyLoraPoolConfig interface and migrateConfig function
- Update LoraPoolWidget to use ComponentWidget<LoraPoolConfig>
- Clean up type imports across widget files
Remove pointer event .stop modifiers from textarea to allow events
to propagate to container where forwardMiddleMouseToCanvas forwards them
to ComfyUI canvas for pan functionality
Remove multiple sources of truth and async sync chains that caused
values to be lost during load/switch workflow or reload page.
Changes:
- Remove internalValue state variable from main.ts
- Update getValue/setValue to read/write DOM directly via widget.inputEl
- Remove textValue reactive ref and v-model from Vue component
- Remove serializeValue, onSetValue, and watch callbacks
- Register textarea reference on mount, clean up on unmount
- Simplify AutocompleteTextWidgetInterface
Follows ComfyUI built-in addMultilineWidget pattern:
- Single source of truth (DOM element value only)
- Direct sync (no intermediate variables or async chains)
Also adds documentation:
- docs/dom-widgets/value-persistence-best-practices.md
- docs/dom-widgets/README.md
- Update docs/dom_widget_dev_guide.md with reference
Adds custom words autocomplete functionality similar to comfyui-custom-scripts,
with the following features:
Backend (Python):
- Create CustomWordsService for CSV parsing and priority-based search
- Add API endpoints: GET/POST /api/lm/custom-words and
GET /api/lm/custom-words/search
- Share storage with pysssss plugin (checks for their user/autocomplete.txt first)
- Fallback to Lora Manager's user directory for storage
Frontend (JavaScript/Vue):
- Add 'custom_words' and 'prompt' model types to autocomplete system
- Prompt node now supports dual-mode autocomplete:
* Type 'emb:' prefix → search embeddings
* Type normally → search custom words (no prefix required)
- Add AUTOCOMPLETE_TEXT_PROMPT widget type
- Update Vue component and composable types
Key Features:
- CSV format: word[,priority] compatible with danbooru-tags.txt
- Priority-based sorting: 20% top priority + prefix + include matches
- Preview tooltip for embeddings (not for custom words)
- Dynamic endpoint switching based on prefix detection
Breaking Changes:
- Prompt (LoraManager) node widget type changed from
AUTOCOMPLETE_TEXT_EMBEDDINGS to AUTOCOMPLETE_TEXT_PROMPT
- Removed standalone web/comfyui/prompt.js (integrated into main widgets)
Fixes comfy_dir path calculation by prioritizing folder_paths.base_path
from ComfyUI when available, with fallback to computed path.
Removed excessive top padding in DOM mode to improve visual alignment and consistency with other form elements. The change reduces the top padding from 24px to 8px, eliminating unnecessary vertical space while maintaining the same bottom padding and overall styling.
Introduce AUTOCOMPLETE_TEXT_WIDGET_MAX_HEIGHT constant and apply it to autocomplete text widgets when modelType is 'loras'. This ensures LoRA-specific widgets have a consistent maximum height of 100px, improving UI consistency and preventing excessive widget expansion.
- Change `STRING` input type to `AUTOCOMPLETE_TEXT_LORAS` in LoraManagerLoader, LoraStacker, and WanVideoLoraSelectLM nodes for LoRA syntax input
- Change `STRING` input type to `AUTOCOMPLETE_TEXT_EMBEDDINGS` in PromptLoraManager node for prompt input
- Remove manual multiline, autocomplete, and dynamicPrompts configurations in favor of built-in autocomplete types
- Update placeholder text for consistency across nodes
- Remove unused `setupInputWidgetWithAutocomplete` mock from frontend tests
- Add Vue app cleanup logic to prevent memory leaks in widget management
Add @pointerdown.stop, @pointermove.stop, @pointerup.stop modifiers to the
index input element to stop pointer event propagation to parent node.
This prevents unintended node dragging when user clicks/drags on the index
input for value adjustment or text selection.
Follows the pattern used by ComfyUI built-in widgets like
WidgetLayoutField and WidgetTextarea.
- Add execution_index and next_index fields to CyclerConfig interface
- Introduce beforeQueued hook in widget to handle index shifting for batch executions
- Use execution_index when provided, fall back to current_index for single executions
- Track execution state with Symbol to differentiate first vs subsequent executions
- Update state management to handle dual-index logic for proper LoRA cycling in batch queues
After refactoring mode change logic from lora_stacker.js to main.ts
(compiled to lora-manager-widgets.js), updateConnectedTriggerWords became
a bundled inline function, making the mock from utils.js ineffective.
Changes:
- Import Vue widgets module in test to register mode change handlers
- Call both extensions' beforeRegisterNodeDef when setting up nodes
- Fix test node structure with proper widget setup (input widget with
options property and loras widget with test data)
- Update test assertions to verify mode setter configuration via property
descriptor check instead of mocking bundled functions
Also fix Lora Cycler widget min height from 316 to 314 pixels.
Co-Authored-By: Claude <noreply@anthropic.com>
- Extract common mode change logic from lora_randomizer.js and lora_stacker.js
into new mode-change-handler.ts TypeScript module
- Add LORA_PROVIDER_NODE_TYPES constant to centralize LoRA provider node types
- Update getActiveLorasFromNode in utils.js to support Lora Cycler's
cycler_config widget (single current_lora_filename)
- Update getConnectedInputStackers and updateDownstreamLoaders to use
isLoraProviderNode helper instead of hardcoded class checks
- Register mode change handlers in main.ts for all LoRA provider nodes
(Lora Stacker, Lora Randomizer, Lora Cycler)
- Add value change callback to Lora Cycler widget to trigger
updateDownstreamLoaders when current_lora_filename changes
- Remove duplicate mode change logic from lora_stacker.js
- Delete lora_randomizer.js (logic now centralized)
Co-Authored-By: Claude <noreply@anthropic.com>
Removed the sort by selection UI from the Lora Cycler widget and
hardcoded the sorting to always use filename. This simplifies the
interface while maintaining all sorting functionality.
Changes:
- Removed sort_by prop/emit from LoraCyclerSettingsView
- Removed sort tabs UI and associated styles
- Hardcoded sort_by = "filename" in backend node
- Removed sort by handling logic from LoraCyclerWidget
- Updated widget height to accommodate removal
Add allowEqualValues prop to DualRangeSlider component (default: false for backward compatibility).
When enabled, removes the step offset constraint that prevented min and max handles from being set to the same value.
Applied to all range sliders in LoraRandomizerSettingsView:
- LoRA Count range slider
- Model Strength Range slider
- Recommended Strength Scale slider
- Clip Strength Range slider
Backend already handles equal values correctly via rng.uniform().
Add Lora Cycler node that cycles through LoRAs sequentially from a filtered pool. Supports configurable sort order, strength settings, and persists cycle progress across workflow save/load.
Backend:
- New LoraCyclerNode with cycle() method
- New /api/lm/loras/cycler-list endpoint
- LoraService.get_cycler_list() for filtered/sorted list
Frontend:
- LoraCyclerWidget with Vue.js component
- useLoraCyclerState composable
- LoraCyclerSettingsView for UI display
- Move slider handle value labels 6px upward in both DualRangeSlider and SingleSlider components
- Add consistent line-height of 14px to ensure proper text alignment
- Improves visual spacing and readability of value labels during slider interaction
- Add execution_seed and next_seed parameters to support deterministic randomization across batch executions
- Separate UI display generation from execution stack generation to maintain consistency in batch queues
- Update LoraService to accept optional seed parameter for reproducible randomization
- Ensure each execution with a different seed produces unique results without affecting global random state
Align visual design of Lora Randomizer widget with Loras widget for
consistent UI/UX across the node interface.
Changes:
- Unified border-radius system (4px→6px for containers, 6px for inputs)
- Standardized padding (12px→6px for widget container)
- Reduced slider height (32px→24px) following desktop tool best practices
- Aligned font sizes (12px→13px for labels, 11px→12px for buttons)
- Unified spacing system (16px→6px for sections, 8px→6px for gaps)
- Adjusted widget minimum height (510px→448px) to reflect layout changes
- Fix infinite reinitialization loop by only validating stale widget.inputEl when it's actually in DOM
- Improve findWidgetInputElement to specifically search for textarea for text widgets, avoiding mismatches with checkbox inputs on nodes like WanVideo Lora Select that have toggle switches
- Add data-node-id based element search as primary strategy for better reliability across rendering modes
- Fix autocomplete initialization to properly handle element DOM state transitions
Fixes autocomplete failing after Canvas ↔ Vue DOM mode switches and WanVideo node always failing to trigger autocomplete.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
- Use pointer events (pointerdown/pointermove/pointerup/pointercancel) with proper capture
- Fix drag not updating strength values by avoiding re-renders during drag
- Fix cursor stuck in resize state by ensuring proper cleanup
- Fix cursor showing wrong icon on hover (should be pointer)
- Ensure strength values display fixed width with 2 decimal places
- Remove unnecessary data-capture-wheel attribute (no wheel adjustment in loras widget)
- Add font-variant-numeric: tabular-nums for consistent number display
This ensures loras widget works consistently in both Canvas and Vue DOM render modes.
- Add folder_include parameter support in backend API handlers
- Add folder_include to FilterCriteria and implement multi-folder filtering logic
- Update frontend to send all include folders instead of only the first
- Add tests for single/multiple include folders, include with exclude, and non-recursive filtering
Add a clear button (X icon) to the search bars in BaseModelModal and TagsModal. The button appears when there is search text, and clicking it clears the search input and refocuses the search field.
Add support for respecting recommended strength values from LoRA usage_tips
when randomizing LoRA selection.
Features:
- New toggle setting to enable/disable recommended strength respect (default off)
- Scale range slider (0-2, default 0.5-1.0) to adjust recommended values
- Uses recommended strength × random(scale) when feature enabled
- Fallbacks to original Model/Clip Strength range when no recommendation exists
- Clip strength recommendations only apply when using Custom Range mode
Backend changes:
- Parse usage_tips JSON string to extract strength/clipStrength
- Apply scale factor to recommended values during randomization
- Pass new parameters through API route and node
Frontend changes:
- Update RandomizerConfig type with new properties
- Add new UI section with toggle and dual-range slider
- Wire up state management and event handlers
- No layout shift (removed description text)
Tests:
- Add tests for enabled/disabled recommended strength in API routes
- Add test verifying config passed to service
- All existing tests pass
Build: Include compiled Vue widgets
Fix issue where mouse cursor flickers between 'grabbing' and 'default'
while dragging slider handles. The cursor now remains 'grabbing'
throughout the entire drag operation regardless of mouse position.
Changes:
- Add dynamic 'is-dragging' class to SingleSlider and DualRangeSlider
- Apply cursor: grabbing to root component when dragging state is active
- Add blank line after module docstring for better PEP 8 compliance
- Reformat long lines to adhere to 88-character limit using Black-style formatting
- Improve string consistency by using double quotes consistently
- Enhance readability of complex list comprehensions and method calls
- Maintain all existing functionality while improving code structure
- Improve tag chip hover states in TagsModal with contextual colors for include/exclude modes
- Adjust toggle switch thumb vertical alignment in LicenseSection and LoraRandomizerSettingsView
- Remove debug console.log from loras widget value update
Add isMounted ref to LoraRandomizerWidget to avoid premature updates from the loras widget watch. The watch now only responds after the component is fully mounted, and the onMounted hook captures the initial loras widget value before enabling the watcher. This prevents the watch from overwriting valid initial data with empty values during component initialization.
- Add `scaleMode` and `segments` props to DualRangeSlider component
- Implement segmented scale visualization with configurable segment widths
- Define strength segments for model and clip strength sliders with expanded middle range
- Enable finer control in common value ranges via wheel step multipliers
- Add `_preprocess_loras_input` method to handle different widget input formats
- Move core randomization logic to `LoraService` for better separation of concerns
- Update `_select_loras` method to use new service-based approach
- Add comprehensive test fixtures for license filtering scenarios
- Include debug print statement for pool config inspection during development
This refactor improves code organization by centralizing business logic in the service layer while maintaining backward compatibility with existing widget inputs.
Add `forwardMiddleMouseToCanvas` utility to forward middle mouse button events from DOM widgets to the ComfyUI canvas, enabling workflow panning when the cursor is over a widget. The function is implemented in `vue-widgets/src/main.ts` and documented in the developer guide. Additionally, fix `getPoolConfigFromConnectedNode` to return null for inactive pool nodes.
- Import and register two new nodes: LoraDemoNode and LoraRandomizerNode
- Update import exception handling for better readability with multi-line formatting
- Add comprehensive documentation file `docs/custom-node-ui-output.md` for UI output usage in custom nodes
- Ensure proper node registration in NODE_CLASS_MAPPINGS for ComfyUI integration
- Maintain backward compatibility with existing node structure and import fallbacks
- Add include/exclude folder modals for advanced filtering
- Implement folder tree search with auto-expand functionality
- Add hover tooltip to preview header showing matching LoRA thumbnails
- Format match count with locale string for better readability
- Prevent event propagation on refresh button click
- Improve folder tree component with expand/collapse controls
- Add performance note explaining that providing `getMinHeight` and `getHeight` via `options` avoids expensive DOM measurements
- Expand dynamic resizing section with detailed update sequence and common scenarios table
- Update LoraPoolSummaryView.vue with `min-height: 0` to allow flex shrinking
- Update main.ts to provide `getMinHeight` via options and adjust `computeLayoutSize` for performance
- Add `onSetValue` callback to handle external updates like workflow loading
- Implement `updateConfig` method for direct widget value updates
- Add value change detection in `restoreFromConfig` to prevent unnecessary updates
- Remove debug console log on component mount
- Extend widget value type to support legacy config format
- Change primary accent color from green to blue across multiple components
- Update background colors for better visual consistency
- Improve empty state styling in TagsSection with better padding and background
- Add box-sizing to BaseModelSection for consistent layout
- Update CSS comments to reflect new color scheme
- Update documentation to reflect new widget filename `lora-manager-widgets.js`
- Remove `LoraManagerDemoNode` import and registration from `__init__.py`
- Translate development guide from Chinese to English for broader accessibility
- Clean up obsolete demo references to align with actual widget implementation