- Story items can be pinned to a specific generation version via
toolbar dropdown (shows when clip is selected and has >1 version)
- version_id column on story_items with migration, validated against
the generation's versions before saving
- Split/duplicate preserve the source clip's pinned version
- Export and playback resolve version-specific audio paths
- Extracted _build_item_detail helper in stories.py (DRY cleanup)
- Story list restyled from rounded cards to flat rows with rounded
hover/active states, gradient header fade, and dynamic bottom
padding that accounts for track editor + generate box
- Add is_favorited column with toggle endpoint and star button on history
- Show sparkles icon on profile cards that have effects configured
- Gold ring on selected profile cards
- Smaller, gray action buttons with brighter hover
- Clamp player time to duration to prevent runaway playback
- Align profile card icon to top for wrapped names
- Flush bottom corners on history card when versions expanded
- Simplify .gitignore data/ rule
- Add /generate/{id}/regenerate endpoint that creates a new take version
- Wire regenerate into history dropdown with SSE progress + autoplay
- Add normalize_audio to regenerate path
- Remove duplicate Regenerate menu item
- Show disabled ellipsis menu during generation instead of hiding it
- Fix sf.write format kwarg broken by asyncio.to_thread migration
- Rename clean version label to 'original', effects to 'version-N'
- Show effects chain names in version list instead of 'N fx'
- Include all versions in export package
- Clean up version panel padding
Reload the player with the version-specific audio URL when effects are
applied to the currently playing generation. Also consolidate the
instruct/effects buttons into a single button with the effects editor
shown inline when instruct mode is open.
- Fix inverted effects toggle in FloatingGenerateBox
- Add Save button + API method for editing custom effect presets
- Return early on effects save failure in ProfileForm
- Fix no-op ternary in effectsStore
- Handle duplicate preset names with proper 400 response
- Use effects_chain is None instead of label for clean version lookup
- Move blocking audio ops to asyncio.to_thread in async endpoints
- Log warnings instead of silently swallowing parse errors
Adds a full effects pipeline powered by Spotify's pedalboard library,
enabling users to apply professional DSP effects (flanger, reverb, delay,
compressor, pitch shift, filters, gain) to generated audio.
Key features:
- Effects chain editor with drag-and-drop reordering (dnd-kit)
- Generation versions: clean copy always saved, processed versions created on top
- Built-in presets (Robotic, Radio, Echo Chamber, Deep Voice) + custom user presets
- Per-profile default effects chain (auto-applied to new generations)
- Per-generation effects override from the generation form
- Apply effects to existing generations from history (creates new version)
- Ephemeral preview endpoint for auditioning effects without persisting
- Dedicated Effects sidebar tab with preset management and live preview
- Version switcher in history cards with expandable panel
- Backward-compatible: existing generations backfilled as clean versions
- Add openPath and pickDirectory to PlatformFilesystem interface
- Remove direct @tauri-apps/plugin-shell and plugin-dialog imports from app
- Merge build-cuda.yml into release.yml as a parallel job
Generations now return immediately with a 'generating' status and appear
in history right away. TTS runs in a serial background queue to avoid
GPU contention. Users can kick off multiple generations without blocking.
- Async POST /generate creates DB record immediately, queues TTS work
- Serial generation queue prevents concurrent GPU access (Metal/CUDA/CPU)
- SSE endpoint GET /generate/{id}/status for real-time completion tracking
- Retry endpoint POST /generate/{id}/retry for failed generations
- Store engine and model_size on generation records for retry support
- History cards show animated loader (react-loaders) for generating/playing
- Failed generations show retry button instead of actions menu
- Model downloads happen inline in the queue instead of rejecting with 202
- Stale 'generating' records marked as failed on server startup
- Autoplay on generate setting (default: on)
- Show engine name on generation cards
- Remove sidebar generation spinner
- Checkbox alignment fix in settings
- Add model folder migration with byte-level progress tracking (backend + UI)
- Custom models directory support via VOICEBOX_MODELS_DIR env var passed to sidecar
- Hardcoded model descriptions displayed in model detail cards
- Open model folder button in storage location row
- Remove 'not downloaded' badge from model cards
- Fix server settings scroll offset for audio player
- Fix shell open permission to allow file paths
- Add normalize toggle to generation settings
- Split chunking/crossfade sliders into dedicated GenerationSettings card
- Merge connection status badges into ConnectionForm (remove ServerStatus card)
- 2-column grid layout for the entire settings page
- GPU Acceleration: remove icon, badge, and MLX info card
- Models: merge 'Other Voice Models' into single 'Voice Generation' list
- Model detail: remove 'Downloaded' badge, border above actions, swap
badges above stats row, match disk size font to stats
Persisted setting (default 800 chars) controls how long text is split
before generation. Lower values improve quality for long outputs by
keeping each chunk well within the model's context window.
- Slider in Server Connection settings (100–2000 chars, step 50)
- Stored in localStorage via Zustand persist
- Passed as max_chunk_chars on every generation request
- Frontend text limit raised to 50,000 to match backend
- Initialize lastSerializedRef to empty string so first-mount hydration
always runs (fixes initial value not rendering)
- Guard arrow-key menu nav against empty filteredTags (avoids NaN index)
- Disable ARIA role/multiline and detach event handlers when disabled
- Add onBlur to close autocomplete dropdown when editor loses focus
- Chain exception with 'from e' in unload endpoint for better tracebacks
Type / in the text input when using Chatterbox Turbo to open an
autocomplete dropdown with 9 supported paralinguistic tags ([laugh],
[chuckle], [gasp], [cough], [sigh], [groan], [sniff], [shush],
[clear throat]).
- contentEditable div replaces textarea for Turbo engine only
- Tags render as inline styled badges
- Pasting text with [tag] patterns auto-converts to badges
- Badges serialize back to plain [tag] text for the API
- Dropdown portalled to body, opens above caret to avoid overflow
- POST /models/{model_name}/unload — unloads a specific model from
memory without deleting from disk, supports all engine types
- Frontend: Unload button in model detail dialog when model is loaded
- Delete button remains disabled while loaded (unload first)
Both PyTorch and MLX backends silently dropped the language parameter —
it was accepted by generate() but never forwarded to the underlying
Qwen3-TTS model, causing it to default to auto-detection which
frequently confuses similar languages (e.g. Portuguese for Spanish).
- Add LANGUAGE_CODE_TO_NAME mapping (ISO 639-1 to full name) to both backends
- PyTorch: pass language= to generate_voice_clone()
- MLX: pass lang_code= to all 4 model.generate() call sites
- Frontend: auto-sync generation form language with selected voice profile
Closes#97
- New ChatterboxTurboTTSBackend wrapping ChatterboxTurboTTS (ResembleAI/chatterbox-turbo)
- English-only 350M model with paralinguistic tag support ([laugh], [cough], [chuckle])
- Bypasses upstream token=True bug by calling snapshot_download(token=None) + from_local()
- Same CPU-on-macOS forcing and torch.load monkey-patching as multilingual backend
- Full engine integration: generate, stream, model status/download/delete endpoints
- Language dropdown now shows only languages supported by the selected engine
- Per-engine language maps: Qwen (10), LuxTTS (en), Chatterbox (23), Turbo (en)
- Auto-switches to English when selecting English-only engines
- Backend language regex expanded to accept all 23 Chatterbox languages
- Add HFProgressTracker to LuxTTS and Chatterbox backends so tqdm-based
file-level download progress reaches the frontend (previously only Qwen
had this, LuxTTS/Chatterbox showed a static spinner)
- Add progress/current/total/filename fields to ActiveDownloadTask so the
/tasks/active polling endpoint carries progress data
- Show inline progress bar + bytes in the model list and detail modal,
poll at 1s during active downloads (5s otherwise)
- Fix GpuAcceleration crash: cudaStatusLoading was referenced before
initialization in its own useQuery declaration
- New ChatterboxTTSBackend wrapping ChatterboxMultilingualTTS (ResembleAI/chatterbox)
- Supports 23 languages including Hebrew, forces CPU on macOS (MPS issue)
- Monkey-patches torch.load for CPU loading, forces eager attention for compatibility
- trim_tts_output utility cuts trailing silence/hallucination from Chatterbox output
- Full engine integration: /generate, /generate/stream, model status/download/delete
- Hebrew (he) added to supported languages in frontend and backend validation
- Single flat model dropdown extended with Chatterbox option in both generation UIs
- ModelManagement UI groups LuxTTS and Chatterbox under 'Other Voice Models' section
- Fix silent Zod validation failure when LuxTTS selected (modelSize was
set to 'default' which failed enum validation, preventing form submit)
- Preserve engine, model size, and language after successful generation
instead of resetting to defaults
- Combine engine + model size into one flat dropdown (Qwen3-TTS 1.7B,
Qwen3-TTS 0.6B, LuxTTS) in both FloatingGenerateBox and GenerationForm
- Add linacodec git dep to requirements.txt (uv-only source, pip can't
resolve it from Zipvoice's pyproject.toml)
- Remove redundant transitive deps from requirements.txt
- Quiet the sidecar setup script (was printing misleading instructions)
Introduce LuxTTS (ZipVoice) alongside Qwen TTS, enabling users to choose
between engines at generation time. LuxTTS offers fast, English-focused
voice cloning at 48kHz with ~1GB VRAM.
Backend:
- Add LuxTTSBackend with encode_prompt/generate_speech integration
- Multi-engine registry (get_tts_backend_for_engine) replacing singleton
- Engine-prefixed voice prompt cache keys to avoid collisions
- Engine field on GenerationRequest (default 'qwen' for backward compat)
- Engine dispatch in /generate and /generate/stream endpoints
- LuxTTS in model status, download, and delete maps
Frontend:
- TTS Engine selector dropdown in GenerationForm (Qwen TTS / LuxTTS)
- Conditionally hide Model Size and Delivery Instructions for LuxTTS
- Engine field added to TypeScript types and Zod schema
- LuxTTS section in Model Management page
Add the ability to download a CUDA-enabled backend binary (~2.4 GB) and
swap it in via a backend-only restart, solving the #1 user pain point
(19 open 'GPU not detected' issues caused by GitHub's 2 GB asset limit).
Backend:
- cuda_download.py: download from R2 (primary) or GitHub split-parts
(fallback), SHA-256 verification, atomic writes, progress via SSE
- 4 new endpoints: GET/POST/DELETE /backend/cuda-*, GET cuda-progress
- server.py: --version flag, auto-detect variant from binary name
- build_binary.py: --cuda flag for CUDA PyInstaller builds
- split_binary.py: split large binaries into <2GB GitHub Release assets
- CI workflow for building CUDA binary
Tauri:
- restart_server command (stop -> wait -> start)
- start_server prefers CUDA binary from {data_dir}/backends/ if present
- Version mismatch check: runs --version before launching CUDA binary
Frontend:
- GpuAcceleration component: download, progress, restart, switch, delete
- API client + types for CUDA status and management
- Platform lifecycle: restartServer() on Tauri/Web
- Aggressive 1s health polling during restart for fast reconnection
- HistoryTable: skip row key handler when focus is on Actions button (Enter/Space)
- StoryList: expose selected story (aria-pressed, 'Selected' in label)
- ProfileCard: skip card key handler when focus is on Export/Edit/Delete
- VoicesTab: keep table semantics; edit button in first cell instead of role=button on row
- PR-ACCESSIBILITY.md: 'Fine-tune' wording, 'focus on the text area' phrasing
Made-with: Cursor
- Audio player: aria-labels for Play/Pause, Loop, Mute, Close; labelled playback and volume sliders
- Generation: aria-labels for Generate speech and Fine tune instructions buttons
- Voice cards: focusable, labelled, Enter/Space to select
- History rows: focusable, labelled, Enter/Space to play; transcript textarea labelled
- Voices tab: focusable rows, labelled, Enter/Space to edit; Actions button labelled
- Model management: focusable model rows and labelled Download/Delete buttons
- Server tab: regions with aria-label and tabIndex for Connection, Status, App Updates
- Stories: focusable story rows, labelled, Enter/Space to select; Actions and track editor buttons labelled
- Voice profile samples: Play/Pause/Stop and mini-player slider labelled
Tested with NVDA and Narrator on Windows. See docs/PR-ACCESSIBILITY.md for full description.
Made-with: Cursor
- Fix transcribe_audio to use whisper-large-v3 mapping (not openai/whisper-large)
- Propagate error field in progress-only fallback path for get_active_tasks
- Use removed return value in cancel endpoint to vary response message
- Add error rollback to handleCancel with toast on failure
- Make isCancelling per-model instead of global
- Fix inverted chevron icons in Problems panel
- Move all clears under lock in clear_all_tasks
- Simplify cancel_download to use dict.pop()
- Add cancel (X) button on downloading and errored model items
- Add collapsible Problems panel (VS Code-style) showing error details
- Add "Clear All" button to reset all stale download/error state
- Add POST /models/download/cancel endpoint to dismiss individual downloads
- Add POST /tasks/clear endpoint to reset all task and progress state
- Include error messages in /tasks/active response for visibility
- Capture SSE error messages client-side for immediate display
- Fix whisper-large using wrong HF repo (openai/whisper-large → openai/whisper-large-v3)
- Fix Whisper HF repo mapping in both PyTorch and MLX backends
- Shorten error toast to point users to Problems panel instead of wall of text
Exposes the existing remote server mode through a checkbox in Server
Connection settings. When enabled, the server binds to 0.0.0.0 instead
of 127.0.0.1, making it accessible from other devices on the network.
The plumbing already existed (Rust sidecar passes --host 0.0.0.0 when
remote=true, serverStore has mode state, Python backend accepts --host),
but the UI hardcoded startServer(false). This wires it up.
Closes#104
- Adds tooltips on hover for buttons of the generate box
- Replaces the message square icon with a sliders icon for the instruction mode toggle.
- Adds a tooltip to the instruction mode toggle button.
- Updates the placeholder text for the input field.