* fix(dub): whisperx CUDA OOM → CPU fallback instead of a bare 500
Found while exercising the dub pipeline on an 8 GB RTX 4070 Laptop GPU: with
the TTS model + GPU worker pool resident, whisperx's CTranslate2 load of
large-v3 dies with "CUDA failed with error out of memory", and
POST /dub/transcribe surfaced it as an unhandled 500 with no guidance.
WhisperXBackend now catches a CUDA OOM at load and retries on CPU (int8, same
model + accuracy, just slower) after clearing the CUDA cache. Dubbing keeps
working on small/laptop GPUs instead of dead-ending. Only triggers on a CUDA
OOM, so the MPS/CPU paths are untouched (cross-platform parity).
Verified: /dub/transcribe on the prepped job went 500 → 200 with correct
segments. Added a deterministic unit test (forces the OOM, asserts the device
switches cuda→cpu; a non-OOM RuntimeError still propagates).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(watermark): embed invisible watermark on /generate output, not just dubs
embed_watermark was wired only into the dub pipeline (dub_generate.py), so
plain TTS from /generate came out unmarked even with invisible watermarking
enabled — i.e. the setting silently did nothing for the main generate path.
Embed it on the final audio in the generate handler too. embed_watermark
self-gates on the setting + AudioSeal availability and passes audio through
unchanged on failure, so it's a no-op when off and never breaks generation.
Verified: detector on a fresh /generate clip went is_watermarked:false →
true, confidence 1.0, message OMNI ("OM"), is_omnivoice:true.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(settings): wrap the settings sub-nav so tabs don't clip out of view
The settings sub-nav has 10 tabs (General…Privacy) but the shared .ui-tabs
primitive is a non-wrapping inline-flex row, so on a narrow Settings pane the
later tabs (Credentials/Logs/About/Privacy) overflowed the right edge and were
unreachable. Scope flex-wrap to `.ui-tabs.settings-tabs-ui` only — the bar now
grows to 2–3 rows instead of running off-screen. The shared primitive (used by
the models role tabs, log-source tabs, etc.) is unchanged.
Verified at 900px (2 rows) and 700px (3 rows): all 10 tabs visible.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(settings): connect active tab to content via accent + tighten spacing
Make the active settings tab read as connected to the panel below: each tab
carries its own semantic accent (already in TAB_DEFS — Models pink, Engines
purple, …) instead of a uniform pink, and that accent is threaded down as
--settings-accent to paint a matching hairline along the top of the content
panel. The shared colour ties tab→content subtly and wrap-proof (no fragile
positional connector). Content wrapped in .settings-content with deliberate
margin/padding so it breathes under the bar; the bar's own bottom margin is
dropped so the bridge owns that gap.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(engines): make the compatibility matrix responsive (scroll, don't overlap)
On a narrow Settings pane the matrix's fixed-width columns (status/gpu/
isolation/actions ≈ 630px) plus the flexible name column couldn't fit, so the
cells collapsed and OVERLAPPED — name text rendered under the AVAILABLE/ACTIVE
badges and GPU chips. Give the table a horizontal-scroll container with a
shared header/body min-width (840px) and stop the fixed cells from shrinking,
so columns keep their shape and stay legible at any width (scroll for the
overflow) — the same data-table treatment used elsewhere. Fills normally on
wide panes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(settings): border-connect the active tab to its content panel
Refine the tab→content connection from a single accent hairline to a
"border-connect": the pill bar opens at its bottom (flat corners, no bottom
border) into a 3-sided panel (.settings-content) framed in the active tab's
accent, with a 2px full-accent top edge at the seam. The bar + panel read as
one outlined container, and the active tab's colour visibly feeds into the
panel it opens. Accent is threaded per-tab via --settings-accent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>