Ensure SUPPORTED_LANGUAGES in language-constants.js is populated with all 55 codes
before event handlers run, so isLanguageCode() works correctly. Prevents duplicate
language codes like [hr:|hr:|...] by properly detecting language codes vs parameters.
Language button now checks if the same language code already exists
before adding another one. Clicking the button multiple times on the
same tag now doesn't create duplicates like [pt-br:|pt-br:|...].
Only updates language if a different one is selected.
Modified /api/tts-audio-suite/available-characters to return both
character filenames and their aliases from the character alias map.
This allows the tag editor dropdown to show friendly alias names like
'Alice' alongside actual voice files.
1. Language dropdown selection now persistent - restore value AFTER options
are loaded from API, not before (fixes race condition)
2. Character button now recognizes language-prefixed characters like
[en:broom_salesman|seed:3] and replaces just the character part,
producing [en:new_char|seed:3] instead of adding duplicate character
[seed:5|steps:3] now correctly becomes [en:|seed:5|steps:3] instead of
[en:|steps:3]. The issue was only capturing the pipe and subsequent
parameters, not the entire tag content including the first parameter.
Changed /api/tts-audio-suite/available-languages endpoint to derive
languages from the authoritative LANGUAGE_ALIASES dictionary in
language_mapper.py instead of hardcoded list. This ensures the tag
editor always has all supported language codes.
Changed from static getSupportedLanguages() to dynamic API fetch,
matching the character loading pattern. Now fetches from
/api/tts-audio-suite/available-languages with fallback to hardcoded list.
Adds console message when languages are loaded.
When cursor is inside [seed:5|steps:3], language button now correctly
creates [de:|seed:5|steps:3] instead of [de:seed:5|steps:3].
For parameter-only tags, language code is inserted with pipe separator
(empty character slot) rather than prepending to parameter name.
- Added language-constants.js with API-driven language list (fallback to hardcoded)
- Added /api/tts-audio-suite/available-languages endpoint in __init__.py
- Updated widget-ui-builder.js to use getSupportedLanguages()
- Updated widget-event-handlers.js to use isLanguageCode() for detection
- Fixes issue where [seed:5|steps:3] now correctly becomes [de:|seed:5|steps:3]
instead of [de:5|steps:3] by properly detecting language codes vs parameters
Now accepts both [seed:3]* and [seed:3] * cursor positions when adding
parameters/languages. Previously only worked immediately after ] without
space. Fixes the issue where users had to place cursor directly after tag
instead of allowing normal spacing conventions.
When cursor is inside [seed:3], clicking language button now correctly
creates [de:|seed:3] instead of incorrectly replacing seed with language.
The fix properly handles tags with pipe separators, preserving all
existing parameters while prepending the language code.
Implements language tag insertion matching character/parameter buttons:
- Inserts [lang:] or [lang:Character] tags at cursor
- Supports all 9 languages (en, de, fr, ja, es, it, pt, th, no)
- Modifies existing tags (replaces language in [lang:Char] format)
- Creates new tags when cursor not in tag
- Integrates with tag editor UI builder and event handlers
Refactored 900-line main widget file to 534 lines by extracting all event listener attachments into widget-event-handlers.js (359 lines). Preserves 100% of original functionality while maintaining LLM-friendly modular architecture.
Changes:
- Extracted editor input debouncing, undo/redo, keyboard shortcuts
- Moved font controls (Ctrl+scroll, font family/size selectors)
- Consolidated character selection and parameter insertion handlers
- Preserved complex preset save/load logic with selection support
- Integrated divider resize with double-click reset
- Maintained sidebar scaling and canvas interaction events
All modules now <400 lines for optimal LLM context usage.
Major refactoring to improve code maintainability:
- Reduced main widget file from 1677→900 lines (46% reduction)
- Extracted UI builders into separate modules:
* widget-ui-builder.js (237 lines) - History, Character, Language, Validation sections
* widget-parameter-section.js (237 lines) - Parameter controls with dynamic inputs
* widget-preset-system.js (102 lines) - Preset management
- All original functionality preserved with identical behavior
- Parameter tag insertion now working with full event handler
- Note: Language selector currently only displays, doesn't auto-add to tags (design limitation)
Modules are properly organized with clear separation of concerns while maintaining all original logic and event handling.
Divider was positioned once on init but never updated when sidebar width changed.
Now setSidebarWidth() also updates the divider position, so cursor feedback
and resize handler stay accessible after resizing or double-click reset.
Instead of fixed 15 char limit, calculate max characters based on current
sidebar width. Uses formula: (sidebarWidth - 30) / 6 to estimate char count.
Minimum of 8 chars guaranteed. Automatically adjusts when sidebar is resized.
Preset titles now display the saved tag/content instead of just 'P1', 'P2', 'P3'.
Content is limited to 15 characters with ellipsis if longer. When preset is deleted,
title reverts back to P1/P2/P3. Updates both on initial load and after each save.
Changed from Ctrl+Z/Ctrl+Shift+Z to Alt+Z/Alt+Shift+Z. This avoids any conflict
with ComfyUI's global undo system since ComfyUI only listens for Ctrl+Z.
Updated button tooltips to reflect the new shortcuts.
Ctrl+Z/Ctrl+Shift+Z now stops event from bubbling to parent listeners.
Prevents ComfyUI from also trying to undo its own state when widget undo is used.
- Changed from using undefined selectionStart/selectionEnd to window.getSelection()
- Now properly detects when user has selected text in editor
- Saves complex tags from selection or character+parameters from sidebar
- Both load and save buttons now properly updated
Changed condition from '>' to '>=' when checking if caret is inside tag.
This fixes the bug where [seed:5<caret>] wasn't detected as inside the tag,
causing character/parameter additions to create duplicate tags instead of
modifying the existing one.
When creating a new parameter tag, position caret right after ] instead of
after the space. This allows subsequent parameter additions to detect and
modify the existing tag instead of creating duplicates.
Changed from 12px to 8px width, centered on the border (4px left + 4px right).
This prevents the hitbox from extending too far into the editor and blocking
text clicks while still being easy to grab.
- Create /api/tts-audio-suite/available-characters endpoint
- Make widget's populateCharacters() async and try to fetch from API
- Falls back to hardcoded defaults if API unavailable
- Endpoint returns sorted list of discovered character voices
When adding a character to a tag like [seed:2|cfg:2], now correctly prepends
the character instead of replacing the first parameter. Detects if first part
has ':' (parameter) vs no colon (character), then either unshift (prepend) or
replace accordingly.
- Extract universal tag detection (after tag, inside tag, not in tag) into reusable function
- Refactor Add Parameter and Add Character buttons to use centralized logic
- Eliminates ~200 lines of duplicate code
- Fixes nested tag creation bug in character insertion
- Reduces parameter button from ~130 to ~55 lines
- Reduces character button from ~130 to ~40 lines
Character Dropdown:
- Now uses discovered characters from state.discoveredCharacters
- Handles both array and object (key-value) formats from discovery system
- Falls back to default names if no characters discovered
- Actually loads the voices from the voices folder instead of hardcoded defaults
Add Char Button:
- Fixed undefined 'caretPos' variable - now calls getCaretPos()
- Uses same pattern as Add Parameter button with proper text insertion
- Gets current caret position correctly before modifying text
- Inserts character tag at cursor and moves caret to after tag
- Properly updates history with caret position
- Saves state and updates UI consistently
- Now actually works instead of silently failing
- Changed regex from \s* (whitespace including newlines) to * (spaces only)
- Preserves newlines before tags in SRT format subtitles
- Fixed issue where '[seed:2] Text' would appear on same line as timestamp
- Now correctly maintains SRT structure:
1
00:00:00,000 --> 00:00:20,000
[seed:2] So, I know I
- Still removes unnecessary spaces around brackets and trailing whitespace
- Detects 2 or more consecutive spaces in text
- Highlighted with slightly darker background (#2a2a2a) compared to editor (#1a1a1a)
- Maintains white text color (#eee) for readability
- Non-distractive visual cue to spot spacing in text
- Useful for intentional spacing: some TTS engines preserve spaces and generate
more natural pauses or spaced-out speech when multiple spaces are present
- Helps maintain awareness of spacing without being visually intrusive
- Commas now display in bright green (#66ff66) with bold weight
- Periods remain in golden yellow (#ffcc33) for differentiation
- Easier visual distinction between punctuation types
- Commas stand out more prominently in the text flow
- Double-click on the invisible resize divider (when cursor changes to col-resize) resets:
* Sidebar width to 220px (default)
* UI scale to 1.0x (default)
* Font size to 14px (default)
- Shows notification confirming all settings have been reset
- Detection window: 300ms between clicks with <5px mouse movement tolerance
- Prevents accidental resets while still being easy to intentionally activate
- Removed the 5px visual divider bar between sidebar and editor
- Kept the original sidebar styling with border-right: 1px solid #444
- Resizing now works by dragging the right edge of the sidebar (within 8px)
- Cursor changes to col-resize when hovering near the right edge
- No visual changes - resizing is invisible but fully functional
- Maintains backward compatibility with original panel appearance
Comprehensive Parameter System:
- Added all parameters from docs with proper grouping by engine:
* Universal: seed, temperature
* ChatterBox: cfg, exaggeration
* F5-TTS/Higgs: speed
* Higgs/VibeVoice/IndexTTS: top_p, top_k
* VibeVoice/IndexTTS: inference_steps
* IndexTTS: emotion_alpha
- Each parameter has appropriate input type:
* Seed: number input (0-4294967295)
* Temperature: slider (0.1-2.0)
* CFG: slider (0.0-20.0)
* Speed: slider (0.5-2.0)
* Exaggeration: slider (0.0-2.0)
* Top P: slider (0.0-1.0)
* Top K: number (1-100)
* Steps: number (1-100)
* Emotion Alpha: slider (0.0-1.0)
- All parameter values persist in localStorage
Tooltip System:
- Added helpful tooltips to all buttons:
* History: Undo/Redo with keyboard shortcuts
* Character: Add character tag at cursor
* Parameters: Add parameter to tag or create new
* Presets: Save/Load/Delete with descriptions
* Format: Normalize spacing and structure
* Validate: Check tag syntax
Resizable Sidebar with UI Scaling:
- Sidebar width is draggable between 150px and 400px
- Sidebar width persists in localStorage
- Ctrl+scroll on sidebar to scale UI elements (0.7x - 1.5x)
- UI scale affects all buttons, inputs, text sizes in sidebar
- UI scale persists in localStorage
- Visual resizer divider with col-resize cursor
Node Rename:
- Changed from '🏷️ String Multiline Tag Editor' to '🏷️ Multiline TTS Tag Editor'
Debounced History Grouping:
- Changed history system from capturing every keystroke to smart grouping with 500ms debounce
- User must pause typing for 500ms before that chunk is added to history
- Multiple consecutive keystrokes now group into single history entry
- Paste and cut events trigger immediate history flush for quick actions
- Results in much cleaner, more meaningful history (paragraph-level vs letter-level)
Persistent Font Size with Ctrl+Scroll:
- Added fontSize property to EditorState, persisted in localStorage
- Editor now loads with saved font size on page reload
- Ctrl+wheel (or Cmd+wheel on Mac) to zoom text up/down
- Font size constrained between 8px and 32px
- Changes are automatically saved to localStorage
- Modified EditorState.addToHistory() to store {text, caretPos} objects instead of text strings
- Updated undo() and redo() to return history entries with both text and caret position
- Updated all callers of addToHistory() to pass current caret position:
- Input event handler captures caret position after text changes
- Parameter insertion handlers capture final caret position
- Character insertion captures final position
- Format button resets caret to 0
- Preset insertion captures position after tag insertion
- Modified undo/redo button and keyboard shortcut handlers to restore caret from history
- Caret now restores to its original position in that history state, making navigation much more intuitive when text length changes
- Remove misaligned textarea+overlay architecture
- Replace with single contenteditable div for perfect text alignment
- Implement caret position preservation across highlighting updates
- Add getPlainText() and getCaretPos()/setCaretPos() helper functions
- Update all event handlers to work with contenteditable
- Fixes word wrapping alignment issues completely
- Caret position now visually matches actual character position
- Selection, copy/paste, and all interactions now work correctly
- Add proper bracket matching algorithm that counts depth when finding matching [
- Fix missing closing bracket in tag reconstruction
- Only add parameters to tags when caret is directly after ]
- Create new tags at exact caret position when not after a tag
- Simplifies logic to avoid false positives from searching distant tags