Renames what users see. The app, the installers, the window title, the
docs and all 21 locales now say VoiceStudio, with "(previously
OmniVoice-Studio)" noted near the title of each doc surface so people
recognise it.
Deliberately NOT renamed, because renaming any of them silently breaks
an existing install — there is no legacy-path fallback anywhere in this
codebase:
- bundle identifier com.debpalash.omnivoice-studio (MSI UpgradeCode,
macOS TCC grants, managed venv, WebView localStorage, the
single-instance lock)
- data directories OmniVoice / .omnivoice and omnivoice.db
- the ~150 OMNIVOICE_* environment variables
- the X-OmniVoice-* HTTP headers (a wire protocol)
- the published Docker image paths
- the OmniVoice ENGINE, which is a model name and not this product
tests/test_identity_paths_survive_the_rename.py pins every one of those
so a future well-meaning sweep cannot orphan a user's library.
Linux .deb users install a new package name and should apt remove
omnivoice-studio; that note is in the changelog.
The repository was renamed. 724 references across 59 files now point at the new URL — README badges, docs, install guides, the updater's releases API call, CONTRIBUTING, the Colab link and the probe harness. GitHub redirects the old URLs, so nothing was broken in the meantime.
Deliberately NOT renamed, because each breaks something on a user's machine: the Tauri bundle identifier (the path to every existing user's data), /usr/lib/omnivoice-studio and the compose container names, and the published Docker image paths.
The image path needed a code change to STAY still: docker.yml derived it from github.repository, so the next build would have published to ghcr.io/debpalash/voicestudio while Docker Hub, a hardcoded literal, stayed put — everyone pulling the documented GHCR path would have kept receiving the last pre-rename image forever. It is now pinned, with a test that fails if it ever derives from the repo name again.
Also makes the probe's repo-name assertion shape-based: it hardcoded the old name and failed on every PR after the rename while the code it tests worked perfectly.
* fix(macos): declare the floor the app actually delivers (13.3, not 12)
The app declared minimumSystemVersion 12.0 and the docs promised Monterey,
while the frontend required Safari 16.4 in three independent places: Vite's
default build target (baseline-widely-available = safari16.4), Tailwind v4's own
documented floor, and `@property` throughout its generated utilities. On
Monterey's WKWebView 15.6 the focus ring and accent surfaces resolve invalid,
and a bundled dependency ships a RegExp lookbehind that is a PARSE-time
SyntaxError no polyfill can reach.
Option B — actually supporting 15.6 — means setting build.target back,
replacing 64 color-mix() calls, dropping Tailwind v4 and replacing that
dependency, indefinitely, for an OS that stopped receiving security updates in
late 2024. The council was unanimous on A, and the precedent is uniform (Chrome
117, Electron 27, VS Code, Firefox 116).
minimumSystemVersion is also the guard: macOS itself refuses to launch a bundle
below it, so a Monterey user gets an explicit OS refusal rather than an app that
opens to a blank window — which matters because the Tauri updater has no
per-OS gating of its own.
Docs updated in the same change (README support table, docs/install/macos.md)
and the webCompat floor assertion re-derived to 16.4, so the post-floor API
list must be revisited the next time the floor moves.
Closes#1268
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(macos): raise the floor in the macOS overlay too, and assert it
Greptile P1, and correct: Tauri merges tauri.macos.conf.json OVER the base
config for a macOS build, and that file carried its own
minimumSystemVersion: 12.0. Changing the base config alone decided nothing —
the shipped bundle would have stayed Monterey-installable while the base
config, the README and the install docs all said 13.3.
Worse, the guard I added read only the base config, so it would have gone on
passing. A test that validates the wrong file is not a guard; it now asserts
both, with a comment saying why the overlay is the one that ships.
Also per review: the changelog entry was an editorial paragraph rather than a
one-line entry, and the section was missing ### Docs. Both fixed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* docs(webcompat): the module header still described the old 12.0 floor
The floor moved to 13.3/Safari 16.4 in this PR and the test was re-derived,
but webCompat.js still told the next reader the oldest supported WebView was
15.6 — which would make every fill here look mandatory instead of retained
for Linux's unpinnable WebKitGTK (CodeRabbit).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
* feat(contact): add the project X account alongside Discord
Adds https://x.com/fs01c137y as a channel on the in-app Contact page and in
the README, next to the existing Discord links — updates, releases, and what
is being built next, for people who would rather not sit in a chat server.
Follows the ContactPage convention: the URL is a module constant so no surface
can drift, and the card explains WHEN to use the channel rather than being a
bare link. lucide dropped its Twitter glyph, so the icon is Megaphone, which
reads as announcements anyway.
contact.follow_* translated in all 21 locales; ContactPage test extended to
pin the URL.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(contact): correct the X handle to @idebpalash
Owner's current account. All six references updated in lockstep — README nav
row, badge row, CTA block and contributing list, plus the ContactPage constant
and the test that pins it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Replace the text-heavy 8-card grid with real app imagery: the three flagship
features (Voice Cloning, Voice Design, Video Dubbing) now lead with tight
cropped screenshots of their actual UI (docs/features/*.png, uniform 2.8:1
tiles) over a one-line label; the five conceptual/no-screenshot features
(Audiobook, Stories, Dictation, 100% Local, MCP) compact to emoji + a single
line. Less text, more app. The "…and 12 more" fold is unchanged.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- vs Others: rename "Why OmniVoice?" heading + nav label
- Quickstart: collapse to one install line + a single troubleshooting fold
- System Requirements: fold three admonitions into one compact note
- Architecture: expand — Tauri/Rust shell, sidecar+IPC, data layer, engine
row (14 TTS / 11 ASR / Demucs / Pyannote / AudioSeal) + a layer legend
- OpenAI API: add a diff-block showing the one-line base_url swap
- Agent Skills: credit Claude Code, Codex, Cursor, Grok, Kimi, opencode
- Colab + Sponsor/Donate: trimmed to essentials
docs-drift green; code fences balanced.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lead with the productive path instead of a generic curl: a discover→synthesize
flow that uses your own cloned voice (the real differentiator over the cloud),
grounded in the actual router — `voice` resolves profile IDs, `model` can pin a
specific engine per request, `/v1/audio/voices` returns `{voices[],engines[]}`.
Tighter intro, accurate format list (adds aac), verbose_json word-timing note,
and a current (non-deprecated) OpenAI-SDK streaming-response TTS example.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surface what shipped in v0.4.0 and sharpen the productivity story:
- Audiobook: multi-voice cast, expressive controls, live per-chapter
progress + Stop, one-click sample (feature card + receipts row)
- Dubbing: Paste Translation ("translate or paste your own") on the card
- Gallery: its voices are now selectable in every picker — Studio,
Audiobook, Stories, Dubbing (screenshot caption + Voice receipts row)
Also fix an internal count inconsistency: the ASR registry has 11 engines
(10 on-device + the OpenAI-compatible remote client, as the ASR section
already states), but the Why-OVS table and the receipts row still said 10.
Both now read 11. docs-drift green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GitHub natively recognizes CONTRIBUTING.md, SECURITY.md, SUPPORT.md, and
CODE_OF_CONDUCT.md in .github/ (Contributing link, Security policy tab, and
the community profile all keep resolving), so relocate the four there and
drop four files from the repo root.
Reference fixes in the same commit (no broken links):
- README.md / README_CN.md → .github/CONTRIBUTING.md
- docs/migration/real-time-voice-cloning.md → ../../.github/SUPPORT.md
- SUPPORT→SECURITY link unchanged (both now in .github/, same dir)
- tests/test_issue_fixes.py Discord-link check repointed to .github/
CONTRIBUTING.md (a missing path would silently skip, dropping coverage)
- CLAUDE.md docs-sync rule paths updated to match
Kept at root by design: README/LICENSE/CHANGELOG/CLAUDE/AGENTS (required or
convention), SPONSORS.md (wired to absolute GitHub URLs in FUNDING.yml, the
sponsor issue template, and sponsors.js), LICENSE-NOTICE.md (pairs with
LICENSE), README_CN.md (README translation, 29 relative links).
Also ignore the local memxt agent-memory DB (memxt.db*) so it stops sitting
loose in the working tree.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After transcription the user can paste a translation produced elsewhere
(ChatGPT, DeepL, a human translator) and have it map onto the segments
that already exist — no re-transcription, no timing loss.
Three input shapes are auto-detected: a timestamped .srt/.vtt (cues matched
to segments by time overlap, greedy one-to-one so one long cue can't be
copied onto several rows), numbered lines (`1.` / `2)` / `[3]`, mapped by
number and falling back to order when a model renumbers mid-answer), and
plain lines (positional, blank lines treated as separators rather than
empty translations). Nothing is applied until the preview dialog has shown
every row as before→after with unmatched rows flagged.
Applying goes through `pasteTranslations` in useSegmentEditing, which
mirrors `segmentEditField`'s duties across rows in ONE undo step: write
`text` and `translations[dubLangCode]` in lock-step and clear the stale
machine-translation badges. It never writes `text_original` (the translate
source `handleTranslateAll` reads — overwriting it would poison every later
re-translate) and never touches a language other than the active one.
Changing `text` alone marks those rows stale via the existing per-language
fingerprints, so no new flag is needed.
The new `POST /dub/parse-subtitle-text` is a stateless wrapper over the
existing `services.srt_parser.parse_srt`, so the lenient cue parsing stays
single-sourced instead of being reimplemented in JavaScript.
Also fixes a ReDoS in that parser, reachable today via /dub/import-srt:
`_TIMING_RE` used `^\s*` under re.MULTILINE, so at every line start the
engine consumed all remaining blank lines before failing on the first
digit — quadratic. 20k blank lines already took 1.7s and a 2 MB blank-line
file never returned, pinning the request thread. Horizontal-whitespace-only
classes make the scan linear.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Owner-sanctioned reversal: the publishable write-only PostHog client key is
committed as the in-repo default in backend/core/analytics.py and
frontend/src/utils/analytics.ts (env / baked release token still wins), so
source builds show the same first-run consent ask as installers — skip = off,
nothing is ever sent without an explicit yes. Adds an install_channel property
(installer / docker / source) to lifecycle events, stamped by the desktop shell
via OMNIVOICE_INSTALL_CHANNEL and by the Docker image's existing
OMNIVOICE_SERVER_MODE marker. Guard tests now pin the two-canonical-files
allowlist + same-token invariant, and the uninstall-ping info file works on the
default token.
Fixes#1193
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Only the TTS model (~2.4 GB) is required on first run; ASR models are
per-platform curated picks (curated_on in models.yaml) installed on demand.
Every transcription surface returns a typed asr_model_missing error with a
one-click download CTA instead of silently pulling multi-GB Whisper weights.
Settings -> Models is a grouped, platform-aware catalog. New guided
permissions UX (wizard System Check + Settings -> Permissions + mic
pre-flight) with native mic-state checks and OS settings deep-links. New
parakeet-mlx engine brings Parakeet TDT v3 to Apple Silicon (language-gated
capture preference so multilingual dictation never regresses). Docs:
expressive-speech page, Flush/Unload + CPU-fallback triage, clone-length FAQ.
Hardening: preflight fails open for custom model pins, ROCm curation no
longer inherits NVIDIA picks, Windows mic probe reads the NonPackaged
consent key, CaptureWidget setup race fixed, offline-cache CI simulation
fixes so empty-cache runners stay green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two small README/packaging fixes.
1. Add a self-updating total-downloads badge next to the stars badge.
shields.io re-queries the GitHub releases API on every page view, so the
number (currently ~137k across 563 release assets) stays current without
ever editing the README again.
2. Fix license detection. GitHub's API reported this repo as
"Other" / NOASSERTION, which breaks the license UI and the corporate
license scanners that gate adoption — the exact users the commercial
license exception is for.
The cause was packaging, not content: LICENSE carried a 53-line
plain-language notice prepended above the AGPL text, which pushes the
file below licensee's similarity threshold. The AGPL-3.0 body was
already byte-identical to the canonical text at gnu.org.
LICENSE is now the verbatim canonical AGPL-3.0 text and nothing else.
The notice — including the commercial-license offer, the scope section,
and the Apache-2.0 carve-out for the bundled omnivoice/ model — moves
verbatim to LICENSE-NOTICE.md, linked from LICENSE and the README.
No terms changed.
Co-authored-by: user <user@users-MacBook-Air.local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extends notebooks/OmniVoice_Studio_Colab.ipynb (setup cells 1-7 unchanged —
owner-verified on a real T4) with a Part 2 feature tour: one self-contained,
idempotent cell per feature, inline playback, honest runtime notes, loud
failures pointing at the backend log. Covers multilingual TTS (/generate),
zero-shot cloning (ref_audio multipart, incl. a commented own-voice upload
variant), voice design (/design/describe -> instruct), voice profiles
(save/list/reuse by id), TTS->ASR round trip (/transcribe), AI-watermark
detection with a generated-vs-plain-tone contrast (/watermark/*), the
OpenAI-compatible /v1 audio API via the official openai client, a two-voice
story (/longform/render SSE), a chaptered m4b audiobook (/audiobook SSE),
an optional miniature EN->ES video dub (upload -> prep poll -> transcribe ->
translate -> generate -> mux), and vocal-isolation stems off the dub job.
Every endpoint verified against the router sources and the 254-route
inventory. README/README_CN section text and the CHANGELOG entry now
describe the expanded scope.
Docs-sync for the first-run analytics consent work:
- README + README_CN FAQ ('Does OmniVoice collect any data?'): the app now
ASKS on first run; still nothing without an explicit yes; lists the new
event kinds (install/update/crash/error-type/uninstall ping) and that
source builds never even ask.
- CLAUDE.md: the Local-first constraint bullet and the 'no third-party
telemetry endpoints' note now reflect the owner-sanctioned reality
(opt-in PostHog EU product analytics behind the first-run prompt,
2026-07-16); auto BUG REPORTING stays GitHub-prefill only.
- backend/api/routers/system.py: the /stats/usage comment claimed PostHog
was rejected outright (PR #1110) — reworded: this endpoint stays local
regardless of consent; the consent-gated path lives in core/analytics.py.
- CHANGELOG [Unreleased]: user-facing entry for the consent prompt + new
lifecycle events.
notebooks/OmniVoice_Studio_Colab.ipynb boots the full app (web UI included)
on a free Colab T4: frontend built in-notebook with bun (releases ship no
standalone web bundle), backend installed via 'uv pip install --system .'
(the Docker image's path — keeps Colab's preinstalled CUDA torch), cuDNN 8
compat via scripts/setup.py, UI exposed through Colab's built-in kernel port
proxy (cloudflared alternative documented in-cell). Optional HF-token cell
reads Colab Secrets; smoke-test cell hits /health and plays a real /generate
WAV inline. All cells idempotent and fail loudly with actionable messages.
README.md + README_CN.md: the Colab section now carries the Open-in-Colab
badge for the in-repo notebook. CHANGELOG: [Unreleased] Added entry.
The 'more local open-source' section grows from a bare two-row table
into proper cards: verified logo assets from each repo, a one-line
pitch plus grounded detail (from the repos' own metadata), star badges,
and links. The Sponsor/Donate section gets a one-line cross-promo —
'more apps from the creator' — since a star on a sibling project is
support too.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cut the repetition, keep the charm: the local-first pitch was made three
times before the first screenshot (quote + cell table + CTA block — now
just the quote), Discord had four CTAs (now two), and the Intel-Mac
caveat appeared three times (now once per context, linked). Screenshots
trimmed 8 -> 6. Engine name lists that repeated the validated matrices
inline (Why table, FAQ) now point at the matrices instead. Fixed the FAQ
claiming '10 TTS engines' (it's 14). Added a download button to the hero
so a new user's path from first glance to installed is one click.
Docs-drift + install-docs validators and the CJK guard all pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Docker image was CUDA-only, so AMD GPUs (e.g. RX 7900 XTX under
Podman) silently ran on CPU. Every preview and release now also ships a
ROCm variant built from the same Dockerfile:
- deploy/Dockerfile: parameterize the runtime base with a BASE_IMAGE
build-arg (default unchanged: pytorch/pytorch 2.8.0 CUDA). Add
PIP/UV_BREAK_SYSTEM_PACKAGES for the ROCm base's PEP-668-marked
Ubuntu 24.04 Python (no-op on the conda CUDA base), and a build-time
GPU_FLAVOR guard asserting the dependency install did not clobber the
base image's GPU torch/torchaudio — a future dep bump that forces a
torch reinstall now fails the build instead of shipping a CPU-only
"ROCm" image.
- .github/workflows/docker.yml: new build-and-push-rocm job (separate
job for runner disk — the ROCm base is ~25 GB unpacked, so it frees
the preinstalled toolchains first). Tags mirror the CUDA semantics
with a -rocm suffix (:rocm rolling preview, :stable-rocm, :X.Y.Z-rocm,
:X.Y-rocm, :sha-xxxx-rocm) on both GHCR and Docker Hub, same secret
gating. flavor latest=false so release tags can't clobber :latest.
No cache-to: the ROCm layers would blow the 10 GB GHA cache budget.
- deploy/docker-compose.yml: new opt-in 'rocm' profile passing the GPU
through via /dev/kfd + /dev/dri, with HSA_OVERRIDE_GFX_VERSION=11.0.0
documented (user-set, not baked in — backend auto-sets it for known
consumer GFX IDs).
- Docs-sync: docker.md (ROCm quick start incl. Podman/Quadlet, tag
table, troubleshooting), dockerhub-overview.md, README AMD note,
linux.md ROCm section cross-link, CHANGELOG [Unreleased].
Base image: rocm/pytorch:rocm7.2.4_ubuntu24.04_py3.12_pytorch_release_2.8.0
— torch 2.8.0 exactly matches the CUDA image (identical resolution, so
uv keeps it), py3.12 satisfies requires-python >=3.11 (the ubuntu22.04
variants are py3.10 and do not).
Closes#1165
Co-authored-by: mergetest <nizam4103@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* perf(dub): single-use per-segment refs no longer evict the prompts a dub reuses; add docs/performance.md
The scan-resistance fix:
A dub cuts a distinct reference clip per segment (Wave 3.2 / #486 — each line
clones its own source delivery) and falls back to the per-speaker clone for
segments under 3 s. Both paths flow through the voice-clone prompt cache — an
LRU of 8. Streaming hundreds of one-shot per-segment clips through that LRU
evicts the per-speaker and locked-profile prompts that every fallback segment
reuses, so the speaker ref was re-encoded (~0.4 s each, measured with
scripts/bench_pipeline.py) again and again across the render.
Note what this deliberately does NOT do: the bench's "166 misses vs 2 speakers"
framing suggested keying refs per speaker — but per-segment refs are the
intentional prosody-matching feature, and the re-transcription behind them is
the #1004 correctness fix. Their encode cost is the price of the feature, not
waste. The waste was only the eviction side-effect, and that's what this
removes: _get_clone_prompt(store=False) still reads the cache (a hit is free)
but never inserts, and the dub loop marks exactly the segment-scoped refs
(auto-seg: bindings and auto: bindings resolved to a segment clip) as
single-use. Per-speaker, locked-profile, and preview refs cache as before.
cache_ref is popped in generate_with_cached_ref before the model call — the
model's generate() has an explicit signature and would TypeError — and unknown
engines ignore it (**kw adapters).
The doc:
docs/performance.md is the first performance documentation in the repo — none
of the ~15 perf env vars appeared anywhere in docs/, the Performance panel's
only control is Windows-only, and slowness reports (#1032) arrived as mysteries
instead of settings checks. Covers the three classic causes of "it got slow",
where generation/dub time goes, every knob with defaults and warnings (raising
OMNIVOICE_GPU_WORKERS on a small GPU is the #567 crash, not a speedup), platform
notes, and how to run the bench so reports carry numbers. Linked from README's
install section.
Tests: store=False semantics (encodes, never inserts, still reads), the flood
scenario end to end (a speaker prompt stays warm through 3x the cache cap of
one-shots), and the pop contract (cache_ref never reaches the model). Full
suite: 2974 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs,dub: review round — qualify the per-file cache claim; note the OOM-retry tradeoff
- CodeRabbit: docs/performance.md's "the reference encode is cached per file"
now carves out the dub's per-line clips (single-use by design — nothing for
a cache to save).
- Greptile P2 (OOM retry re-encodes a single-use ref): acknowledged in a code
comment as deliberate — caching the retry's ref would reintroduce the
eviction this flag prevents, to optimize a path that only runs after an OOM
already cost seconds.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* docs(performance): probe-based torch.compile wording; honest accelerator + cache claims (review)
Greptile's repeated OOM-retry finding is deliberately skipped: retaining the
prompt across the retry would require passing prompt objects through the
adapter protocol (backend.generate takes paths), to save 0.4s on a path that
only runs after an OOM already cost seconds — the tradeoff is documented at
the call site.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: mergetest <nizam4103@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(analytics): wire posthog-js — consent-gated, autocapture OFF
The owner supplied the standard snippet:
posthog.init(TOKEN, { api_host, defaults: '2026-05-30' })
Shipping that verbatim would have broken the guarantee we just made, twice:
1. It initialises AT MODULE LOAD — it starts tracking every user before they
have consented to anything. The README now says "OmniVoice sends nothing out
of the box"; this would have made that false on the very next release.
Analytics is therefore started ONLY after the user opts in (Settings →
Privacy), and the stored consent is what restores it at launch.
2. posthog-js AUTOCAPTURES by default, and `defaults: '2026-05-30'` turns that
on. Autocapture sends the text content of the DOM elements a user interacts
with. In THIS app the DOM holds the script they are about to synthesise,
their voice names and their file names — exactly the content we promise never
leaves the machine. It is explicitly disabled, along with session recording
(which records the screen) and pageview capture.
utils/analytics.ts: hardenedConfig() — autocapture false, disable_session_recording
true, capture_pageview/pageleave false, mask_all_text + mask_all_element_attributes
as defence in depth, and opt_out_capturing_by_default so init alone can never
capture. Events pass sanitizeProps(), mirroring the backend allowlist: a key not
on it is DROPPED and long strings refused, so a future caller cannot leak content
by adding a field. Backend down / no consent / no destination → stays off.
The token is taken from VITE_POSTHOG_KEY at BUILD time and is never committed —
a token-shaped literal trips the secret scanner and is a bad habit regardless.
release.yml injects it from a repo secret; the backend already reads
POSTHOG_PROJECT_TOKEN the same way. No token => no destination => the Privacy
toggle isn't offered and nothing can be sent, which is the right default for a
source build. A test fails if a phc_ literal is ever committed to that file.
posthog-js added to frontend/package.json; root bun.lock regenerated and
`bun install --frozen-lockfile` verified (the Docker gate).
11 tests: autocapture/session-recording/pageview off, starts opted-out, allowlist
drops text+paths+names, long strings refused, consent honoured in all three
failure directions, and no token literal in source. Frontend suite 1229 passed.
* test(analytics): guard the committed-token rule in the suite, not just in the scanner
The frontend typecheck failed on the guard I added: it reached for `node:fs`,
which has no type definitions in the frontend tsconfig (and would have been
cwd-dependent at runtime anyway). Wrong layer.
Source-scanning guards in this repo are Python tests (test_no_hardcoded_cjk,
test_no_literal_borders), so this one moves there — and gets strictly stronger
in the process: it scans every tracked file rather than analytics.ts alone, and
matches a PostHog key by SHAPE (phc_[A-Za-z0-9]{20,}), so a *different* key
can't slip through where the old test only knew about the one gitleaks caught.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: mergetest <nizam4103@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The rejected PR #1110 had a genuinely careful PII-free event design, but shipped
three things a local-first app can't: exception autocapture ON (raw tracebacks —
home paths, and in this codebase HF tokens out of exception messages — bypassing
core.failure.sanitize() entirely), no user consent or disclosure, and 3,069 lines
of PostHog wizard scaffolding. This is the same capability with those fixed.
core/analytics.py, three rules, each enforced and tested rather than promised:
1. OFF unless the user says yes. TWO gates must both be true: a build-provided
POSTHOG_PROJECT_TOKEN *and* the user's analytics_enabled pref, default False.
A default install transmits nothing, so "nothing leaves your machine" stays
literally true for everyone who doesn't opt in. A broken prefs file fails
CLOSED. OMNIVOICE_ANALYTICS_DISABLED=1 is a hard kill switch above both.
Withdrawing consent tears the client down immediately — no restart.
2. NO exception autocapture. Explicitly disabled; a test asserts the constructor
arg, because the SDK's default is the leak.
3. Metadata ONLY, by allowlist. Every property passes sanitize_properties(),
which DROPS any key not on _ALLOWED_PROPS and refuses long strings — so no
future caller can leak a take's text, a path, or a voice name by adding a
field. text_length is the LENGTH; the text itself has no way through.
The person id is a random per-install UUID — not hardware, hostname, or username.
UI: Settings → Privacy → "Help improve OmniVoice" states in the panel exactly
what is sent, exactly what never is, and that it can be turned off — rather than
burying it in a policy. No destination in the build (any source build) → the
toggle isn't shown, because an inert switch would be a lie.
Docs: README FAQ answers "does OmniVoice collect any data about me?" honestly.
Also fixed a bug I'd introduced in my own wiring: the generation event referenced
variables not in scope, and the call site's bare `except: pass` swallowed the
NameError — so the event would have silently never fired. The call site now logs.
12 tests (default-off / opt-in without token still can't transmit / both gates /
kill switch / consent withdrawal / prefs failure fails closed / allowlist drops
text+paths+names / long strings refused / autocapture OFF / never raises /
random install id). Backend 2936 passed; frontend 1211 passed.
Refs #1110
Co-authored-by: mergetest <nizam4103@gmail.com>
A Linux AppImage user asked which folders to delete to remove OmniVoice and
whether an uninstaller exists (#1089) — they had to guess. They shouldn't have
to: the app is fully local, so uninstalling IS just deleting the folders it
wrote, and we never documented them.
- scripts/uninstall.sh (macOS/Linux) + scripts/uninstall.ps1 (Windows): find
every OmniVoice folder — app data, the multi-GB managed Python env, config,
logs — plus, listed SEPARATELY because it is a shared cache, the Hugging Face
model cache. Print each with its size as a DRY RUN and stop; delete only on
--yes (--models / -Models to include the shared cache). They honor the same
env overrides the app reads (OMNIVOICE_DATA_DIR, OMNIVOICE_CACHE_DIR,
HF_HOME, HF_HUB_CACHE), and never touch the app binary or anything outside
the paths they list.
- docs/install/uninstall.md: the complete per-platform path table (what each
folder holds and how big it is), the shared-HF-cache caveat, custom/portable
locations, per-platform steps to remove the app itself, and what to keep if
you plan to reinstall.
- Linked from the README FAQ, SUPPORT.md, and install troubleshooting.
Paths mirror backend/core/config.py + frontend/src-tauri/src/setup.rs.
Verified on macOS: dry-run lists the real dirs; sandboxed HOME runs confirm
--yes removes app folders while KEEPING the shared cache, --models removes it,
and the env overrides retarget correctly.
Closes#1089
Co-authored-by: mergetest <nizam4103@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The openai-compat-asr backend (#877) shipped with settings routes but no
discoverable activation path: the config panel hid in Settings → Models,
its hint text claimed "there's no in-app engine picker for ASR yet"
(stale — the matrix has one), and there was no way to check a server
actually answers before pointing a dub/dictation run at it.
Configure → test → activate now live on one screen, Settings → Engines:
- The ASR family tab mounts the config panel (URL / model / optional
API key) below the engine matrix; saving refetches the matrix via a
new reloadToken prop so the engine row flips unavailable → available
and its "Use" button appears without a manual refresh.
- New "Test connection" button + loopback-gated
POST /api/settings/asr-openai-compat/test: saves first (same
stale-config contract as /llm-providers/{id}/test), then probes
GET {base_url}/models — no audio leaves the machine. The structured
verdict maps to localized, actionable messages: latency + whether the
configured model is listed on success; classified auth_failed /
http_error / timeout / unreachable / ok_no_models failures. detail is
core.scrub-ed; the key is never logged or echoed.
- Engine reads persisted config fresh per transcribe (regression test) —
config changes need no backend restart. Never default-active: ASR
auto-detect only picks local engines.
- i18n for every new string (en.json); no hardcoded CJK; identical
behavior on macOS/Windows/Linux (pure HTTP + React).
- Docs-sync: docs/engines/openai-compatible-asr.md rewritten around the
one-screen flow with LM Studio / llama.cpp / Groq / OpenAI examples
and the privacy note; README engine table cell updated.
Verified end-to-end against a fake OpenAI-compatible server: UI drive
(configure → test → row flip → Use) plus a real transcription through
the backend's /v1/audio/transcriptions immediately after a config
change, no restart.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
RTVC (CorentinJ's 50k-star SV2TTS repo) is archived; its users need a
maintained home. New docs/migration/real-time-voice-cloning.md maps
every RTVC concept to its OmniVoice equivalent (encoder+utterance →
reference clip, toolbox → app, vocoder choice → Settings → Engines,
demo_cli.py → REST API/CLI/MCP), is honest about what RTVC did that we
don't (research toolbox, three-stage training, MIT license, smaller
footprint), and walks the first clone with verified UI labels only.
Wired into docs/features.yaml's existence-checked docs list and linked
from the README Quickstart.
README tagline now states the local-first promise verbatim at the very
top: "No accounts. No API keys. No cloud." — everything else on the
front page is unchanged.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@shakib30 built and tested a working Colab notebook for the project
and offered it upstream. Linking it from the README (community-
maintained, credited) makes the no-local-GPU path discoverable without
taking on notebook maintenance in-repo.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Two skills in the standard skills/<name>/SKILL.md layout (vercel-labs/
skills CLI; listed on skills.sh via install telemetry):
- omnivoice — teaches any agent (Claude Code, Cursor, Codex, …) to
speak and transcribe through the user's LOCAL install via the
OpenAI-compatible API at localhost:3900: health preflight, TTS with
cloned-voice-profile discovery via /v1/audio/voices, STT with
srt/vtt subtitle formats, and the local-first rule (never silently
fall back to a cloud API).
- oss-maintainer — the maintainer methodology this repo is actually
run with, distilled from real sessions: absorbed-or-declined queue
discipline, check-the-PR-queue-before-implementing, root-cause →
fix-the-class → regression-test, structural merge gates with
flaky-vs-real judgment, the release protocol, and
thank-contributors-specifically.
Every endpoint/flag in the omnivoice skill verified against
backend/api/routers/openai_compat.py and the README's API section.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Asked directly on Discord ('how it compares to something like 11 labs
in quality?'). The old answer ('yes, comparable for most use cases')
oversold — the honest version names where ElevenLabs still wins
(out-of-the-box English polish/consistency) and where OmniVoice is
genuinely competitive (cloning from clean references, 646 languages,
structural advantages), plus the dubbing-specific truth another
same-day report surfaced: a dub is a chain, and incoherent output
usually traces to transcription quality on the user's source audio —
with the check-the-original-text-first debugging step that actually
helps.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Settings → Engines now stacks one pinned Engine Compatibility Matrix per
family (TTS, ASR, LLM) instead of a single TTS-titled table with the other
families tucked behind a low-discoverability tab. The backend select/prefs
path (family="asr" → prefs.asr_backend, env > prefs > auto-detect) already
worked but was unexercised and undocumented — it's now locked by API and
resolution-order tests, and README + the openai-compat-asr doc stop
promising a picker that didn't exist / denying one that now does.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
The AppImage bundles whatever libwebkit2gtk-4.1-dev the build runner's
apt repos resolve at build time (see the "Linux system deps" step) —
AppRun's LD_LIBRARY_PATH then makes that bundled copy take priority
over the host's system WebKitGTK at runtime. ubuntu-22.04's version
was stale relative to what current distros (Ubuntu 24.04+, Fedora 44)
ship, which is why a from-source build (linking straight against the
host's healthy system library) worked fine on the exact machine where
the shipped AppImage white-screened — the released binary was running
an older, buggier WebKitGTK under the hood regardless of the host.
Bumped the Linux release matrix entry to ubuntu-24.04, and ci.yml's
Tauri shell-check job to match (its own comment already says "Mirror
release.yml" — now it actually does, so a green PR check accurately
predicts the release build will also succeed).
Raises the AppImage's glibc floor from 2.35 to 2.39 (Ubuntu 24.04+) —
README's system-requirements table corrected from the now-false
"Ubuntu 20.04+" claim. No reports of anyone on a pre-2022 distro.
This does not fix the AppRun launcher's separate, related bug (its
WebKitGTK-version auto-detection reads the *system's* pkg-config
version, not the version actually bundled and running) — that would
need a reliable way to read the bundled .so's version from within the
AppImage, which isn't straightforward (WebKitGTK's soname doesn't map
1:1 to its release version) and isn't verifiable without a real Linux
build environment to test against. Left as a known, separate gap.
Cannot be verified from here on a real Ubuntu 26.04 machine — shipped
on the strength of the root-cause diagnosis, pending the reporter's
confirmation.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
First slice of the community's two-track proposal for #877: a generic
OpenAI-compatible ASR backend that works TODAY, without waiting on
transformers to ship a direct Qwen3-ASR integration (tracked separately,
still blocked upstream). Points OmniVoice's transcription at any server
exposing POST /v1/audio/transcriptions — a self-hosted Qwen3-ASR/
FunASR/SenseVoice server, or OpenAI's own API.
- New OpenAICompatASRBackend (backend/services/asr_backend.py): a pure
network client, no local model, no install. Prefers
response_format=verbose_json for real per-segment timestamps,
degrades to plain text (matching MoonshineASRBackend's shape) when a
minimal server rejects that format. Never leaks a raw SDK/httpx
exception to the caller (#977 convention) — wraps network/auth
failures in a clean, actionable RuntimeError naming the server.
- Settings persist via the same encrypted-secret convention as
services/llm_providers.py (settings_store.set_secret for the API key
— Fernet-encrypted, never a .env row, never echoed back; get_text/
set_text for base_url/model). New GET/PUT /api/settings/
asr-openai-compat, loopback-gated like every other settings route.
- Frontend: a small settings panel (Settings → Models) mirroring
HFMirrorPanel's exact structure. No ASR engine picker exists yet for
ANY ASR backend (only TTS has one) — activating this engine still
needs OMNIVOICE_ASR_BACKEND=openai-compat-asr; documented plainly
rather than pretending otherwise.
- README's ASR Engines table (9 → 10 engines) and docs/features.yaml's
drift-checker inventory updated; the '9 engines, all fully local'
claim corrected since this one genuinely isn't.
- docs/engines/openai-compatible-asr.md: setup steps + an explicit
privacy note (unlike every other ASR engine, audio leaves the
machine to whatever server is configured).
Regression tests: tests/test_asr_openai_compat_877.py (12 tests) —
is_available() gating, verbose_json + plain-text response adaptation,
network-failure error hygiene, SDK retry disabling, and the settings
endpoints' persist/mask/clear-vs-unchanged semantics.
Fixed two real full-suite-only failures found during verification (not
brushed aside): the API route inventory snapshot needed regenerating
for the two new routes, and this file's own tests had a module-
staleness bug — a collection-time settings_store import went stale
relative to a test-time-fresh fixture when another test elsewhere in
the ~2400-test suite reimports the module — fixed by making
settings_store itself a fixture resolved at test-run time, same
lesson already applied to tests/test_mm2_lifecycle.py earlier this
session.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
README's Quickstart badges linked a 'Download Debian .deb' button
straight to the releases page — but .deb bundling was deliberately
dropped from release.yml (tauri-cli bug, 'Failed to create control
scripts') and no release has ever shipped one. A community member
investigating #961 confirmed this by checking the actual release
assets. Users clicking that badge got a broken promise, not a package.
Removed the badge; docs/install/linux.md's '## Install (.deb)' section
now honestly states it's unavailable pending a tauri-cli fix, points to
the AppImage as the supported path, and keeps the historical pre-v0.3
.deb upgrade note (ffprobe conflict) since that's still relevant to
existing installs.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(engines): dub and batch TTS honor the active-engine selection — with a real capability gate, not a silent OmniVoice fallback
Dub generation and batch TTS hardcoded services.model_manager.get_model()
(OmniVoice) regardless of the engine picked in Settings → Engines. A user
selecting VoxCPM2 (or any other engine) still got OmniVoice output with no
error — the silent fallback IS the bug class, not just the one report.
Root-caused and fixed for the whole class:
- New `TTSBackend.supports_cloning` capability flag (default True) marks
engines that can only offer fixed preset voices — kittentts,
supertonic3, sherpa-onnx set it False. MLXAudioBackend exposes it as an
instance property (Kokoro doesn't clone, CSM does) since the adapter
multiplexes multiple models with different capabilities.
- `cloning_capable_engine_ids()` and a shared `resolve_generation_backend()`
helper in services/tts_backend.py centralize engine resolution
(id → is_available() → routing gate → optional cloning gate), mirroring
generation.py's /generate resolution instead of inventing a third
parallel mechanism. Both routers now standardize on the existing
get_active_tts_backend() cache (unload-on-switch already handled).
- dub_generate.py's two TTS-generate call sites (main run + OOM retry) and
the /dub/preview-segment route resolve once, up front, with
require_cloning=True — dub's ref_audio is populated for essentially
every real job, so an engine that can't clone fails the whole job with
one actionable message instead of mis-cloning per segment.
- batch.py resolves once per job, require_cloning only when voice_id is
pinned — an unpinned batch job runs fine on any engine.
- Applied the three pre-existing TODO(#312) comments: mastering now skips
via `applies_own_mastering` for both pipelines, matching generation.py.
Regression tests cover the capability-id list, the fail-fast gate (proving
no OmniVoice fallback), the success path on a selected non-OmniVoice
engine, batch's pinned-vs-unpinned voice_id behavior, and the mastering
skip for both pipelines. Three existing dub tests that mocked get_model()
directly were updated to mock the new resolver instead.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(engines): exclude model-dependent adapters from cloning_capable_engine_ids()
getattr(cls, "supports_cloning", True) at the CLASS level returns a
property descriptor object (always truthy) when the flag is an instance
@property, not a plain attribute — MLXAudioBackend uses exactly this
pattern because its cloning capability depends on which of its 7+ curated
models is loaded (only CSM clones; Kokoro etc. don't). Without this fix,
the dub/batch capability-gate error message would always recommend
'switch to mlx-audio' even when the user's configured MLX model can't
clone, sending them in a circle back to the same error.
isinstance(value, bool) distinguishes a resolved boolean from a
descriptor object, so mlx-audio is excluded from the suggestion list
until its actual per-instance capability can be checked (already handled
correctly by resolve_generation_backend()'s per-call instance check).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(changelog): engine-aware dub/batch entry (#987)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
The table described aspirational channels (#showcase/#help/#feature-requests/#dev)
that don't exist on the server; it now matches reality (#announcements,
#releases+#changelog, #issues and #ideas forums, #discuss-ideas, #general).
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Swap the static social-preview banner for the live v0.3.9 Launchpad shot
and drop the now-duplicate Launchpad row from the gallery (shown once).
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- Emoji section headers with explicit <a id> anchors. Emoji breaks
GitHub's auto-generated heading slugs, so every in-page nav target keeps
a stable explicit anchor (verified all href="#..." resolve).
- Refresh the screenshot gallery. The prior set was from April, predating
the launchpad / settings / dictation UI overhaul, so it misrepresented
the app. Captured fresh at retina from the live v0.3.9 UI and led the
gallery with the new Launchpad home: launchpad, studio, voice design,
voice gallery, dubbing, engine-compatibility matrix, model store,
embedded API reference (Scalar), and the in-app changelog reader.
- Fix the stale engine count (11 -> 14 TTS engines) in the comparison
table, FAQ, and roadmap to match the engine table + backend registry.
- Use <kbd> keycaps for the dictation shortcut (Opal detail).
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Add a sponsorship home (SPONSORS.md) with Backer/Bronze/Silver/Gold tiers —
described as placements/benefits, with $ amounts left as `<!-- OWNER: set
amounts -->` placeholders (no invented prices). Primary "become a sponsor"
path is a prefilled GitHub issue form (.github/ISSUE_TEMPLATE/sponsor.yml:
name/org, logo URL, tier, contact), with Ko-fi/PayPal as direct paths and an
OWNER placeholder for a public contact email.
README gains a Sponsors subsection (logo-slot placeholder + SPONSORS.md link),
a Sponsors nav entry, and a note about GitHub's native Sponsor button.
FUNDING.yml adds the SPONSORS.md link alongside the existing ko_fi/PayPal.
Keeps the honest "agent bills" framing; sponsorship is a thank-you, not a
paywall — OmniVoice stays fully free and AGPL-3.0. Docs-only; no fabricated
sponsors, prices, or testimonials.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
torch >=2.3 ships no macOS x86_64 wheels (transformers 5.x needs torch >=2.6),
so `uv sync` can never resolve on an Intel Mac — per the platform-parity rule
the honest option is declaring the platform unsupported, not letting first
launch die in a raw resolver error:
- bootstrap.rs: pre-check on macOS x86_64 before any venv create / uv sync
(first-run AND repair paths) fails fast with an actionable message
(remote-backend escape hatch + docs link); healthy pre-torch-bump venvs are
deliberately untouched. Unit test pins the message's load-bearing phrases.
- BootstrapSplash: routes the failure to a dedicated localized hint
(bootstrap.hint_intel_mac, all 21 locales) and suppresses the useless
Retry-oriented hints for it.
- README + docs/install/macos.md (+ troubleshooting #9): every Intel-Mac
support claim now says UI-installs-but-backend-cannot-run, including the
from-source path (also broken); remote backend documented as the only use.
- release.yml: #889 note on the macos-15-intel leg — artifact is UI-only;
keep-or-drop is an owner call, deliberately not changed here.
- docs/install/windows.md: new "Portable install (Windows)" section promised
in #766 — custom MSI wizard folder / msiexec INSTALLDIR=..., what lives in
OmniVoiceStudio-Data next to the exe, and the Program-Files-greyed-out why.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(engines): Confucius4-TTS scaffold (opt-in, needs hardware validation) (#590)
Plumbing for netease-youdao's Confucius4-TTS — LLM-based 14-language
cross-lingual zero-shot voice cloning, Apache-2.0 — mirroring the opt-in
subprocess-venv pattern of dots.tts / MOSS-TTS-v1.5:
- engines/confucius4/__init__.py: Confucius4Backend(SubprocessBackend), CUDA-only
(gpu_compat=("cuda",)), language passthrough, ref_audio→prompt_wav. is_available
reports a clear reason and stays unavailable without a clone.
- bootstrap.py: dedicated Python 3.10 venv resolution (user clone-level venv →
package venv → uv bootstrap), import-probed on `confuciustts`.
- main.py: sidecar speaking the same length-prefixed JSON-over-stdio protocol as
the other engines, calling ConfuciusTTS(config_path, device).generate(text,
lang, prompt_wav).
- Registered lazily in _LAZY_REGISTRY; docs/engines/confucius4-tts.md.
Gated behind OMNIVOICE_CONFUCIUS4_TTS_DIR — inert on every default install, never
imports the upstream package unless opted in. The sidecar's synthesis API is
derived from the upstream README and is NOT yet validated on a CUDA box; the
module, docs, and CHANGELOG all flag this. 4 tests pin registration +
inert-by-default. No version bump.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(#590): register Confucius4 in install-hints + docs inventory (CI gates)
Registering the engine tripped two completeness gates: every backend needs an
install_hint (test_issue_fixes) and every registry engine must appear in the
tts_engines docs inventory + README (check-docs-drift). Add the install_hint,
the docs/features.yaml entry, and the README engine-table row (with the scaffold
caveat). Docs-drift clean; gates pass. No version bump.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(confucius4): finalize — validate API vs upstream, add 22 sidecar unit tests, document external deps (Amphion/w2v-bert/weights)
The synthesis API (ConfuciusTTS(config_path, device) → generate(text, lang,
prompt_wav) → tensor, model.sample_rate) is confirmed against the
netease-youdao/Confucius4-TTS repo. Added runnable unit tests for the sidecar's
pure logic (language norm, tensor→PCM mono/stereo/clip, config resolution, wire
framing, synthesize dispatch with the model mocked) — 22 cases, all green.
Docs now list the external deps (Amphion/MaskGCT codec, facebook/w2v-bert-2.0,
~2-4GB HF checkpoint) and CUDA 12.6. Softened the scaffold warnings to reflect
API-validated + unit-tested status; a one-time CUDA GPU run is still needed to
confirm live inference + true sample rate.
---------
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(dictation): live local dictation via sherpa-onnx + Voice settings panel
Add a sherpa-onnx ASR engine alongside the existing Whisper/NeMo dictation
path, powering a genuinely live experience: as you speak, words type straight
into the focused field (streaming partials via a new simulate_type command,
self-correcting with backspaces) and commit per pause.
Backend:
- SherpaDictationBackend + sherpa_dictation registry of the 7 models (Parakeet
TDT v3/v2, streaming Zipformer EN/ZH/bilingual, Paraformer bilingual, Whisper
Tiny) from csukuangfj/* int8 HF repos; CPU provider for cross-platform parity.
- /dictation/models + /dictation/prefs router; get_capture_asr_backend() honors
the selected dictation model. get_active_asr_backend() (dub transcription) and
the legacy WebM/Opus capture path are untouched.
- True streaming over /ws/transcribe (OnlineRecognizer: live partials +
per-endpoint finals); offline models surface partials via short re-decode.
Frontend:
- New "Voice" settings panel (enable, Toggle/Hold mode, model picker with
offline/streaming/recommended badges + per-model download/delete).
- Live word-by-word typing via simulate_type (enigo) with prefix-diff delta and
backspace correction; paste fallback retained, no double-insertion.
Deps: sherpa-onnx>=1.13.3 (+ sherpa-onnx-core); uv.lock regenerated, Docker
frozen-install verified. API route-inventory snapshot updated. 40+ new tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(dictation): register sherpa-onnx-asr engine in README + features inventory
Fixes the docs-drift CI guard: the new sherpa-onnx-asr ASR engine existed in
the registry but not in docs/features.yaml or README. Adds the live-dictation
engine row to the ASR Engines table, bumps the engine counts (8→9), and adds
the inventory entry.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(changelog): fold live-dictation into the [0.3.8] section
main is 0.3.8 (untagged), so the dictation feature belongs in that release, not
a separate [Unreleased] block. Merge the two Added lists under one [0.3.8],
refresh the headline to lead with live dictation, and correct the capture
description to reflect live word-by-word typing (not paste-on-pause).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GitHub Sponsors isn't available for this account, so route donations to Ko-fi /
PayPal instead, add a standalone Contact page, and trim the commercial-license
page to the essentials.
- Donate: drop GitHub Sponsors. Pick an amount ($10 / $20 / $50) then choose
Ko-fi or PayPal; PayPal.me carries the amount into checkout. Updated
.github/FUNDING.yml (ko_fi + custom PayPal) and the README badges to match.
- Contact page (new `mode: 'contact'`, ContactPage.jsx): Discord, email, GitHub
issues, and website (palash.dev) as clean one-tap rows; reachable from a new
footer button. Routed in App.jsx, sidebar hidden like the other full pages.
- Commercial License: cut the 6-tile benefit grid + 3-item FAQ down to the
three deciding factors (IP ownership, no per-minute cost, direct support) and
one clear "request a quote" email CTA.
- All new copy goes through i18n (en.json: donate.choose_method*,
enterprise.hero_simple/contact_lead, contact.*, logs.contact*).
Build (vite) + vitest (561 passed) green; en.json validated.
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#551: both CTranslate2 ASR backends request compute_type="float16" on CUDA with
NO fallback. On GPUs without efficient fp16 (older Maxwell/Pascal, GTX 16xx) or a
CTranslate2/cuDNN binary mismatch, WhisperModel/whisperx.load_model raise a
ValueError at construction — which escaped the existing OOM-only `except
RuntimeError`, so every chunk failed and the user got "Transcription produced no
segments". Add a per-device compute_type fallback chain (cuda: float16 →
int8_float16 → int8; cpu: int8 → float32) to both backends + the ASR sidecar,
alongside (not replacing) the existing OOM→CPU path, with an ASR_COMPUTE_TYPE
override for exotic hardware (documented in README).
Also in the same ASR-robustness pass:
- #549: PyTorchWhisperBackend._ensure_pipe wraps the transformers pipeline load
and re-raises an actionable error (reinstall transformers / use faster-whisper)
instead of a bare "Could not import module 'AutoFeatureExtractor'".
- #516: the /dub/transcribe SSE generator is wrapped so it can NEVER close
without a terminal event — any unanticipated exception now yields a structured
`error` (with build_failure's hint) + `done`, turning "stream dropped, likely
ASR failed" into the real cause + Retry.
- failure.py: COMPUTE_TYPE_UNSUPPORTED + TRANSFORMERS_IMPORT classes so the
no-segments toast is actionable.
Tests (fail-before/pass-after): float16-unsupported → int8 for both WhisperX +
FasterWhisper; a generic non-OOM RuntimeError still raises; classify() maps the
two new classes; the SSE stream always terminates with error→done. 7 + 1 passed,
17 in the failure suite (no regression).
Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>