Commit Graph
12 Commits
Author SHA1 Message Date
debpalash 037a5689de fix(worker): address legacy transport review findings 2026-08-11 23:49:46 +00:00
velixio bda169c900 feat(workers): pin work to the chosen GPU, and say when its model is missing
Three phases that only make sense together: a job that names a worker,
a worker that reports honestly what it can actually run, and the small
defects that made both lie.

**Pinning** (Phase 1). `pinned_worker_id` is now honoured in both places
that choose a worker — `eligible_workers` and `select_worker` build
independent lists, so applying it to one silently leaked work onto
whichever machine was least busy. The pin persists across a restart via
an additive column, deliberately not alembic (justified in the code, per
the precedent already in db.py): quitting mid-render used to drop it
without a word. `max_attempts=1` was rejected as the mechanism — it makes
the FIRST failure terminal, including the penalty-free ones a stale
advisory view produces routinely.

Cancel now actually reaches the worker. `WorkerServicer.cancel` had zero
callers, so cancelling released the slot while the GPU thread kept
running, and a late result could resurrect the task as COMPLETED —
`commit_result` assigned that state directly, bypassing the transition
table where CANCELLED is terminal by construction.

**Honest capabilities** (Phase 4). A worker now probes whether weights
are actually present, and a job stops BEFORE dispatch with a typed 409
naming the model and the machine, instead of failing mid-task. The probe
fails OPEN: `is_cached`/`cache_is_complete` cannot see a user-managed
clone outside the HF layout, so only a positive "absent" refuses.
Refusing an engine that works today would break the compatibility
promise. `pool.supports` deliberately still ignores `downloaded` — had it
not, the scheduler would drop the worker and answer with a terminal
NO_CAPABLE_WORKER, which tells the user to check their install when the
truth is one download away. The frontend no longer offers "Report this
bug" for that state; it offers the download.

Catalog tags resolve against the TARGET's OS/arch/backend, not this
machine's. From a Mac control plane, a CUDA worker's model list was
showing the mlx-community repos it cannot run and hiding the ones it
needs.

**And the quiet ones** (Phase 0 leftovers): a model's human label rides
its own proto field so renaming it cannot orphan breaker history; an
empty model_id no longer forks the capacity slot key into two slots for
one model; the idle sweep cannot evict an engine out from under a live
LOCAL render.

Verified on real hardware, which is the only verification that has ever
caught anything here: 2025 characters, default settings, routed to an
RTX 4090 over the wire — 100% GPU utilisation on the remote box, 119.6 s
of 24 kHz audio returned in 16.6 s, 5.7 MB delivered out of band through
the artifact path rather than the control stream.

Backend 5259 passed, frontend 1808 passed.
2026-08-11 15:24:11 +05:30
Palash Debnath 5cab8e0149 feat: rename the product to VoiceStudio (previously OmniVoice-Studio)
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.
2026-08-07 01:30:58 +05:30
debpalashandClaude Fable 5 63fd497caf feat: TTS-only first run, platform-curated ASR, guided OS permissions, parakeet-mlx
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>
2026-07-17 15:20:54 +05:30
mergetestandClaude Fable 5 5b74627e78 fix(backend): stop discarding tracebacks in error-level exception logs (#1160 follow-up)
#1160 fixed one traceback-losing logger.error in dub_pipeline.save_job;
this sweeps the remaining class. 19 sites across 10 files where a real,
unexpected failure was summarized as "...: %s" at ERROR level — losing
the stack trace that makes crash reports diagnosable — now use
logger.exception (diarization/ASR crashes, dictation load/final
failures, ffmpeg mixes that silently degrade output, Smart Fit retime
fallbacks, RVC init/inference, models.yaml catalog load, gallery
search/download 500s, dub-history JSON decode, MCP CLI fatal exit).

Deliberately left alone: WARNING/INFO/DEBUG logs, expected classes with
self-sufficient messages (GPU/ASR timeouts, request validation,
cryptography-availability checks), sites that re-raise immediately
(db migration, _ensure_mcp), subprocess returncode checks where stderr
IS the diagnosis, and sites already logging exc_info/format_exc.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 16:10:06 +05:30
e2c4ea93b0 fix(models-settings): surface async install errors, disk-space guard, cancel wiring, honest restart (#908)
Live-audit fixes for the Models settings surface — the P1s were cases where
the feature silently didn't work for the user.

P1-A — Async install errors were invisible. The `install_error` SSE event
carries excellent mirror-aware text (#890 core/failure.py), but the Model
Store auto-purged the errored row ~800ms later (same as a success) and the
first-run WizardLibrary DELETED the row without ever reading `ev.error`. The
SSE→rowState reduction is now a pure, tested reducer (downloadReducer.js /
reduceWizardDownloadEvent); only SUCCESS terminals auto-purge
(isAutoPurgeTerminal), an error persists on the row with inline text + Retry +
Dismiss (Model Store) / a Retry (wizard).

P1-B — No disk-space check on install. `POST /models/install` now compares the
FDL-05 plan's exact `to_download_bytes` (+ MIN_FREE_GB headroom) against
`shutil.disk_usage(cache).free` BEFORE downloading and emits an actionable
install_error naming the sizes (needs X, headroom Y, have Z) instead of failing
mid-download. `/models` also surfaces `disk_free_gb` in the header. MIN_FREE_GB
+ disk_free_bytes are single-sourced in setup/models.py (wizard delegates).

P2-A — Wired the orphaned cancel. `POST /models/install/cancel` (FDL-11) had
zero frontend refs; the in-progress row now shows a Cancel button that calls it
and transitions the row to install_cancelled.

P2-B — Honest restart_required. The HF-mirror PUT returned restart_required:true
unconditionally; it now returns true only when the persisted value actually
changed, with accurate copy (Model Store downloads use the new mirror
immediately — resolved per-call; only transformers model loads need a restart).

P3 — i18n the un-localized panels (HFMirrorPanel, ApiKeysPanel source
labels/help/status, MODEL_ROLE_LABEL) via new en.json keys; other locales fall
back to en.

Tests: new tests/test_install_disk_space.py (reject-when-over-budget incl. the
worker wiring; allow-when-fits; degrade on unknown size/unprobeable volume),
updated tests/test_hf_mirror_settings.py (change-only restart_required), and new
frontend reducer + column-render tests for install_error persistence, Retry,
Dismiss, and Cancel.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 23:40:15 +05:30
1fe68ba11e fix(setup): weight-aware install-state so truncated model cache isn't read as installed (#622) (#626)
A first-run user whose model download was interrupted after the config/
tokenizer files landed but before the weight shard got stranded on the
Models & Engines page: GET /models computed "installed" purely from cache
size on disk, so a size-positive-but-weight-less cache reported installed=true,
the wizard hid the re-download button, and the model manager (Settings → Models)
that could repair it was unreachable behind the wizard gate.

Make install-state weight-aware. The boolean weight-floor scan now lives in
models.py (the lowest module in the setup import graph) as snapshot_has_weights()
+ cache_is_complete(); list_models() and recommendations() downgrade a truncated
cache to installed=false (+ an explicit incomplete=true on /models), so the
existing "install" action re-appears and the user can re-download in-wizard.

Fixes the whole class, not just /models: download.py's install-time validator
now delegates to the same shared scan (one source of the floors, can't drift),
matching the load-time repair in model_manager.py (#581/#606).

config_only repos (pyannote/speaker-diarization-3.1 — a pipeline whose real
weights live in referenced sub-repos and whose own cache is legitimately tiny)
carry a new config_only:true hint in models.yaml and are exempt, so they're not
false-flagged as incomplete.

Tests: tests/test_mm2_lifecycle.py — snapshot_has_weights truncated-vs-complete,
cache_is_complete on a truncated weight repo + config-only exemption, and
list_models downgrading a size-positive truncated cache to installed=false /
incomplete=true. Full backend suite green (1832 passed).

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 22:38:38 +05:30
dc1d36fe5f refactor(models): model-management v2 cleanup (mm2, all tiers) (#428)
One coherent lifecycle surface over the in-process model, diarization, and
subprocess sidecars; fixes the engine-switch VRAM leak; tightens download
robustness. Backend-only, response shapes preserved, no new deps.

Tier 1 — correctness:
- MM2-01: get_active_tts_backend() caches one instance per backend id and
  unload()s the outgoing engine on switch (fixes the VRAM leak behind #278);
  adds reset_active_backend().
- MM2-02: OmniVoiceBackend.unload() releases the shared model_manager singleton
  + free_vram(); SubprocessBackend.unload() -> unload_sidecar(self.id),
  inherited by all sidecar engines. Idempotent + preload-safe.
- MM2-03: /model/loaded ASR row reports the real device + a note explaining the
  disabled unload button.

Tier 2 — single surface:
- MM2-04: new services/model_lifecycle.py owns list_loaded/unload/unload_all/
  free_vram; system.py routers are thin delegations (shapes unchanged).
- MM2-05: idle timeouts (in-process + sidecar) resolve via prefs.resolve
  (env wins, no restart); removed the duplicated _IDLE_TIMEOUT_SECONDS.

Tier 3 — robustness/observability:
- MM2-06: _install_cooldowns swept (1h TTL) + cleared on success — bounded.
- MM2-07: per-extension weight floors (onnx 64KB, tensors 5MB) OR the original
  >=5MB catch — small ONNX no longer false-flagged, #352 still caught.
- MM2-08: indextts GPU sidecar self-reports vram_mb in pong; parent surfaces it
  in list_live_sidecars (0 = CPU/unmeasured).
- MM2-09: is_cached scan_cache_dir->disk fallback logs WARNING w/ exc type
  (#117/#118), was invisible at DEBUG.

Tests: tests/test_mm2_lifecycle.py (15). Full suite: 1379 passed.
Plan/summary: .planning/quick/260613-mm2-clean-model-management-v2/.

Co-authored-by: mergetest <test@local>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 20:45:32 +05:30
Palash DebnathandClaude Opus 4.8 47729057bd chore(lint): remove unused imports + variables (ruff F401/F841) (#210)
Autofixes the genuine lint behind the CodeQL py/unused-import and
py/unused-local-variable note-level alerts — actually removing the dead
code rather than dismissing it. 68 safe fixes via 'ruff check --select
F401,F841 --fix' across 29 backend files (dead stdlib/symbol imports like
io/sys/json/torch/typing.Optional and unused locals). Only ruff's safe
fixes applied — the 9 'unsafe' fixes and the audio_dsp numpy availability
import were left untouched.

Not touched: empty-except (needs per-site judgement, not autofixable);
frontend js/unused-local-variable (eslint no-unused-vars has no autofix);
the loopback-low-risk path/log/stack-trace alerts (real, left visible).

Verified: full tests/ suite unchanged at 601 passed (the 2 test_supertonic3
failures are pre-existing on main, local .venv state, green in CI).

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-01 07:53:06 +05:30
Palash DebnathandClaude Opus 4.8 ea868386e3 fix(windows): HF cache disk-fallback for WinError 448 (plan-01, closes #117 #118) (#137)
* fix(models): disk fallback when scan_cache_dir raises WinError 448 (#128)

plan-01 fix-sequence step 2. On Windows, huggingface_hub's scan_cache_dir()
raises WinError 448 "untrusted mount point"; the three call sites in
setup/models.py swallowed it and reported "not cached", so the app
re-downloaded models it already had — looping 5× and giving up (#117/#118).

- _is_cached_on_disk / _scan_cache_on_disk: walk the canonical HF layout
  <cache>/models--<org>--<name>/snapshots/<rev>/ directly (honours
  HF_HUB_CACHE/HF_HOME, so a relocated models dir works too).
- is_cached / list_models / recommendations now fall back to the disk scan
  when scan_cache_dir() raises. An empty snapshot dir is not counted.

Symlink-disable env + local_dir_use_symlinks=False were already shipped
(main.py, setup/download.py); this closes the remaining failure path.

Tests (TDD, fail-before/pass-after): tests/test_hf_cache_fallback.py (4).
No regression on the non-Windows path (fallback only triggers on raise).

Closes #117, #118. Addresses #128 (#64 configurable-dir is the follow-up).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(models): probe HF /hub subdir + close scandir handle (bot review)

Addresses #137 review:
- CodeRabbit (critical): hf_cache_dir() returns HF_HOME when HF_HUB_CACHE is
  unset, but repos live under $HF_HOME/hub/models--…. Added _hub_cache_roots()
  so the WinError-448 fallback probes both <dir> (HF_HUB_CACHE-set case) and
  <dir>/hub (HF_HOME-only case); previously it could miss the cache and
  re-download. Regression test added (HF_HOME-only layout).
- Greptile: wrap os.scandir() in `with` so the dir handle closes even when
  any() short-circuits (avoids handle leaks on repeated /models polls).
- CodeQL: drop unused `os` import in the test.

5 tests pass, incl. -W error::ResourceWarning.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 09:27:43 +05:30
Palash Debnath 8d11e19494 feat: dictation maturity + batch TTS pipeline + tests (#32)
Global Hotkey:
- Register ⌘+⇧+Space system-wide via tauri-plugin-global-shortcut
- Shows/focuses window and emits tray-dictate event from any app

Auto-Paste:
- enigo crate simulates ⌘V/Ctrl+V after transcription
- Text auto-pastes into whatever app was active before dictation

Streaming ASR:
- WebSocket endpoint /ws/transcribe for live partial transcription
- 2s buffer interval, configurable via OMNIVOICE_STREAM_INTERVAL
- CaptureButton streams audio chunks, shows italic partial text
- Falls back to HTTP POST if WebSocket unavailable

Batch TTS Pipeline:
- Replace stub worker with full pipeline:
  extract → transcribe → translate → generate → mix → export
- Per-job progress tracking (stage, percent, current_lang, segment)
- GoogleTranslator integration via deep_translator
- Download endpoint GET /batch/download/{id}/{lang}
- BatchQueue UI rewritten: progress bars, cancel/delete, downloads
- Type-safe API client (api/batch.ts)

Tests:
- 23 tests for batch endpoints + streaming ASR helpers
- Lightweight fixtures that stub GPU deps

UX (earlier sessions):
- Dual-mode ASR (Turbo MLX + WhisperX Accurate)
- Enhanced download progress (speed, ETA, bytes)
- Status bar black flash fix
- Cold-start model preloading
- Full accessibility audit (ARIA, focus-visible)
- Compact UI layout improvements
- README updated with new features
2026-04-28 18:54:26 +05:30
debpalash fc76e79ff8 feat: setup wizard, donate page, CI fixes, performance optimizations, and style extraction
- Implement donate page and migrate API fetching to react-query hooks
- Add setup wizard for batch job management and voice clip editing
- Refactor setup router into package (wizard, models, download sub-modules)
- Fix 9 CI test failures from setup router refactor
- Fix cross-device link error in prefs.py atomic writes
- Fix event loop mismatch in export test fixtures
- Modernize README with architecture diagram and 13 app screenshots
- Defer per-segment disk writes in dub_generate for ~6s faster dubs
- Extract 45 inline styles from Launchpad, KeyboardCheatsheet, DubSegmentRow
- Add playwright dev dep and screenshot capture script
2026-04-26 16:47:00 +05:30