Commit graph

364 commits

Author SHA1 Message Date
CJ Pais
557d274db6 release v0.8.0 2026-03-23 08:43:26 +08:00
Marcelo Terreiro Prado
f6e71862ff
fix: show friendly error when no audio input device is available (#1115) 2026-03-23 07:53:55 +08:00
Fero
17277cf6ce
Save recordings before transcription (#1024)
* save recordings before transcription

* impart cj preferences

* cleanup

* re-transcribe anything

* format

* fix tests

* format

* ui cleanup

* format

* cleanup

* bunch of fixes, mainly for vm

* put comments back

* update transcription failure text

* alert -> toast

* spawn_blocking

---------

Co-authored-by: CJ Pais <cj@cjpais.com>
2026-03-22 15:43:02 +08:00
Viren Mohindra
58cda3f3b3
fix: sha256 verification to prevent corrupt partial download loop (#1095)
* fix: add SHA256 verification to prevent corrupt partial download loop

Fixes issue #858 — corrupt .partial files caused an infinite loop where
a failed/cancelled download would resume from EOF, pass extraction, and
loop forever with no way to recover without manual file deletion.

Three-part fix:
- Add sha2 crate and compute_sha256() helper; verify hash post-download
  before extraction/rename for all 15 built-in models. Hash mismatch
  deletes the .partial so the next attempt starts fresh.
- Delete .partial on extraction failure (independent of checksums) so
  corrupt archives can't re-enter the loop via the same path.
- Emit model-download-failed event from the command handler so all
  callers (ModelsSettings, Onboarding) get consistent error feedback
  via a central store listener rather than per-caller return-value checks.

SHA256 hashes hardcoded alongside each model's URL in ModelInfo.
Custom models (sha256: None) skip verification silently.

* chore: remove doc comment from sha256 field

Doc comments on exported specta types propagate into bindings.ts as
inline JSDoc — not useful in generated TS. Removing at the source.

* test: add SHA256 verification unit tests

Extracts inline verify logic into ModelManager::verify_sha256() and
covers all four branches:
- None expected hash → skipped (custom models)
- Matching hash → ok, file kept
- Mismatched hash → error returned, partial file deleted
- Unreadable file → error returned

Ensures corrupt partial downloads are always cleaned up so the next
attempt starts fresh.

* chore: cargo fmt

* fix: clear cancel flag on sha256 failure, spawn_blocking for hash, fallback cleanup

- remove cancel_flags entry on sha256 failure to prevent a stale flag
  from immediately cancelling the next retry attempt (HIGH)
- add fallback state cleanup in modelStore when result.status != ok,
  so spinner clears even if model-download-failed event is missed (HIGH)
- run compute_sha256 in spawn_blocking to avoid stalling the async
  executor while hashing large model files (up to 1.6 GB) (MEDIUM)

* emit some more events for the ui to pick up

* add translations + format

---------

Co-authored-by: CJ Pais <cj@cjpais.com>
2026-03-21 14:19:26 +08:00
CJ Pais
e35f0a714e
improve history performance (#1107)
* paginate history

* fixes

* fix

* format

* remove unecessary key
2026-03-21 12:06:29 +08:00
CJ Pais
a3015026a0
whisper-rs 0.16.0 and ort.rc12 (#1041)
* test build for 0.16.0

* macos 10.15?

* format

* shorter dir for build on windows?

* Update build.yml

* move

* Update build.yml

* long path support

* clang

* try

* try

* move to ort rc12

* use ms prebuilt?

* use lower version for 1.23.1

* download my prebuilt

* Update build.yml

* Update build.yml

* fix?

* fix(nix): use dynamic linking for ort-sys 2.0.0-rc.12

ort-sys rc.12 removed pkg-config support; without ORT_PREFER_DYNAMIC_LINK
it defaults to static linking against ORT_LIB_LOCATION, which fails
because nixpkgs only provides shared libraries (.so).

* fix(nix): override onnxruntime to 1.24.2 for ort-sys rc.12 compatibility

ort 2.0.0-rc.12 enables API v24 by default, but nixpkgs only ships
onnxruntime 1.23.2 (API v23), causing a runtime panic on model load.

Use Microsoft's prebuilt binaries for onnxruntime 1.24.2 via overlay,
patched with autoPatchelfHook for NixOS compatibility.

This overlay should be removed once nixpkgs merges onnxruntime ≥ 1.24:
https://github.com/NixOS/nixpkgs/pull/499389

* updated deps for whisper

* fix(nix): sync devShell with packages for ort-sys rc.12 compatibility

- Extract shared definitions (commonNativeDeps, gstPlugins, commonEnv,
  onnxruntimeOverlay) to avoid duplication between packages and devShells
- Add onnxruntime 1.24.2 overlay to devShell (was only in packages)
- Add BINDGEN_EXTRA_CLANG_ARGS to devShell so bindgen can find stdio.h
  and generate correct vulkan bindings for whisper-rs-sys
- Add ORT_LIB_LOCATION and ORT_PREFER_DYNAMIC_LINK to devShell
- Replace libappindicator with libayatana-appindicator in devShell
- Add onnxruntime and vulkan-loader to devShell LD_LIBRARY_PATH

* fix missing lib in the handy blob dl

* scope lib verification

* dont run bun2nix on windows

---------

Co-authored-by: Evgeny <evgeny.khudoba@yandex.ru>
2026-03-19 15:00:51 +08:00
Viren Mohindra
cb32d35b9e
feat(audio): lazy stream close for bluetooth mic latency (#747)
* feat(audio): opt-in lazy stream close for bluetooth mic latency

keep the microphone stream open for 30s after recording stops to
reduce latency on back-to-back transcriptions. gated behind an
experimental setting (off by default) since it keeps the mic actively
capturing while idle — degrading bluetooth audio quality on macOS.

adds lazy_stream_close setting, tauri command, toggle in experimental
settings section, and i18n strings.

* fix: gate lazy close on setting in cancel_recording path

cancel_recording was unconditionally calling schedule_lazy_close,
keeping the mic open for 30s even when the setting was disabled.
also bump close_generation on AlwaysOn→OnDemand switch to cancel
any stale lazy close timers.

* feat(audio): opt-in lazy stream close for bluetooth mic latency

keep the microphone stream open for 30s after recording stops to
reduce latency on back-to-back transcriptions. gated behind an
experimental setting (off by default) since it keeps the mic actively
capturing while idle — degrading bluetooth audio quality on macOS.

adds lazy_stream_close setting, tauri command, toggle in experimental
settings section, and i18n strings.

* fix: gate lazy close on setting in cancel_recording path

cancel_recording was unconditionally calling schedule_lazy_close,
keeping the mic open for 30s even when the setting was disabled.
also bump close_generation on AlwaysOn→OnDemand switch to cancel
any stale lazy close timers.

* chore: fix cargo fmt on model.rs line from main merge

* fix race

---------

Co-authored-by: CJ Pais <cj@cjpais.com>
2026-03-19 14:12:43 +08:00
Viren Mohindra
095f4ac455
fix: prevent idle watcher from unloading model during recording (#1085)
* fix: prevent idle watcher from unloading model during recording

the idle watcher treats ModelUnloadTimeout::Immediately as a 0s
timeout, causing idle_ms > 0 to always be true. this unloads the
model on the next 10s tick — even while the user is still recording.

the Immediately variant is already handled correctly by
maybe_unload_immediately() which fires after each transcription
completes. the idle watcher should skip it entirely.

fixes regression introduced in d1da935.

* fix race condition

* cleanup

* format

---------

Co-authored-by: CJ Pais <cj@cjpais.com>
2026-03-19 13:45:09 +08:00
Viren Mohindra
0b3322faf3
feat(audio): use device default sample rate and always downsample (#1084)
* feat(audio): use device default sample rate and always downsample

instead of forcing the microphone to open at 16kHz (which can cause
issues with bluetooth codecs, some ALSA drivers, and other devices
that advertise 16kHz support but produce suboptimal audio), use the
device's native/default sample rate and let the existing FrameResampler
downsample to 16kHz for the whisper pipeline.

the resampling infrastructure (rubato FftFixedIn) already exists in
run_consumer() and short-circuits when in_hz == out_hz, so devices
that natively default to 16kHz are unaffected.

* fix: graceful fallback when supported_input_configs fails

some ALSA/PipeWire drivers support default_input_config but have
a broken supported_input_configs implementation. fall back to the
default config instead of propagating the error. also add a warn
log when no config matches the device's default rate.
2026-03-19 08:53:03 +08:00
CJ Pais
d33535cfd3 release v0.7.12 2026-03-19 08:33:28 +08:00
CJ Pais
2eeb21296d
upgrade path from old giga-am to new (#1088) 2026-03-18 23:20:27 +08:00
CJ Pais
5a3e6e33d1
add extra recording buffer (#1089)
* add extra recording buffer

* translations
2026-03-18 21:00:24 +08:00
Viren Mohindra
d1da935479
fix: auto-unload model after idle timeout to reduce memory (#1051)
- Default model_unload_timeout from Never to Min5
- Fix Drop impl: use take() on watcher handle so clones from
  initiate_model_load() don't kill the watcher thread
- Reset last_activity on model load to prevent immediate unload
- Upgrade watcher logging from debug to info level
- Remove duplicate "unloaded" event (unload_model already emits it)

Co-authored-by: CJ Pais <cj@cjpais.com>
2026-03-17 12:51:15 +08:00
CJ Pais
789a4393e8 release v0.7.11 2026-03-17 12:24:32 +08:00
CJ Pais
cafc2b7208
experimental: pick between cpu/gpu acceleration + enable directml on windows (#1058)
* first pass at gpu acceleration

* unload the model when changing

* mock accelerator

* translations and build fix

* format

* clean up helptext

* type cleanup

* format

* lint
2026-03-16 20:41:30 +08:00
Ivan Zhuravlev
f8bbcd791c
Migrate to transcribe-rs-0.3.1 and add Canary support (#1023)
* refactor: migrate transcribe-rs from v0.2.8 to v0.3.0

Breaking API migration:
- engines::* → onnx::* module paths, whisper_cpp::* for Whisper
- TranscriptionEngine trait → SpeechModel trait
- Two-step new()+load_model() → one-step Model::load()
- transcribe_samples(Vec<f32>) → transcribe(&[f32])
- Explicit unload_model() → RAII drop
- Canary engine now from transcribe-rs instead of local crate

Removes canary-engine local crate dependency.
Uses path dependency to local transcribe-rs clone (temporary).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: upgrade transcribe-rs to v0.3.1, add Canary models

Update transcribe-rs dependency from v0.2.8 to v0.3.1 with
restructured features (whisper + onnx). Add Canary 180M Flash
(4 languages, 146MB) and Canary 1B v2 (25 EU languages, 692MB)
with translation support. Add i18n translations for all 17 locales.

Languages verified against HuggingFace model cards:
- Canary 180M Flash: en, de, es, fr
- Canary 1B v2: bg, hr, cs, da, nl, en, et, fi, fr, de, el, hu,
  it, lv, lt, mt, pl, pt, ro, sk, sl, es, sv, ru, uk

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

* make sure when changing models the language changes with it

* format

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: CJ Pais <cj@cjpais.com>
2026-03-16 16:31:44 +08:00
CJ Pais
f45ad97e51
ensure samples don't get dropped (#1043)
* ensure samples don't get dropped

* format
2026-03-16 08:47:00 +08:00
Fero
a6e8fc8651
fix: keep Windows microphone guidance Windows-only (#1032)
* fix windows-only mic permission guidance

* don't hardcode error strings in rust

* format

---------

Co-authored-by: CJ Pais <cj@cjpais.com>
2026-03-15 22:44:48 +08:00
CJ Pais
b833a21f04 update handy keys 2026-03-14 18:54:10 +08:00
CJ Pais
cbdb4983cd
upgrade to handy-keys 0.2.3 (#1039) 2026-03-14 12:39:15 +08:00
CJ Pais
30a940629a
be able to change models from the tray (#1002)
* be able to change models from the tray

* persist at the right times

* fixes

* fix tests
2026-03-14 12:19:06 +08:00
CJ Pais
46fd170d69
attempt to clean up warnings on build (#1037)
* attempt to clean up warnings on build

* format

* fix linux warning
2026-03-14 11:42:42 +08:00
Javier Campanini
85a8ed77b5
feat: pass custom words as Whisper initial_prompt instead of post-correction (#1035)
Whisper supports an `initial_prompt` parameter that biases the model's
vocabulary during transcription. For Whisper models, we now join the
user's custom words list into a comma-separated string and pass it as
the initial_prompt. This guides the model to prefer those spellings
during decoding rather than relying solely on fuzzy post-correction.

For non-Whisper engines (Parakeet, Moonshine, SenseVoice, GigaAM),
the existing apply_custom_words post-correction continues to apply
since those engines don't support prompt-based vocabulary biasing.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 09:37:46 +08:00
Fero
dfd445d422
Add Windows microphone permission onboarding (#991) 2026-03-13 22:06:46 +08:00
CJ Pais
3795bc2417
unwind instead of crash (#1030) 2026-03-13 21:04:50 +08:00
CJ Pais
a58216e076
default to handy keys on windowss (#1029) 2026-03-13 20:37:09 +08:00
Jon Deibel
aebd432390
fix: overlay not showing on non-primary monitors (#969) 2026-03-12 06:51:42 +08:00
CJ Pais
426031a8d7 Release v0.7.10 2026-03-10 13:17:20 +08:00
Fero
785c3317e4
Handle microphone init failure without aborting (#945)
* Handle microphone start failures without aborting

Return recorder init errors instead of panicking/aborting in the worker thread and reset UI state when recording start fails.

Refs #436

* prop errors to ui

* format

* Add missing recording error translation keys

---------

Co-authored-by: CJ Pais <cj@cjpais.com>
2026-03-10 13:07:21 +08:00
CJ Pais
615b3c93bb
feat: language-aware filler word removal (#971)
* Remove filler words that conflict with European languages

Removed three filler words from the transcription filter that are actual
words in European languages:

- 'um' - Portuguese/Spanish indefinite article meaning 'a/an' (masculine)
  Example: Portuguese 'Isto é um teste' (This is a test)
  Removing this was breaking Portuguese transcriptions

- 'ha' - Spanish/Italian/Norwegian/Swedish auxiliary verb meaning 'has/have'
  Example: Spanish 'Él ha comido' (He has eaten)
  This is a very common verb form in Romance and Scandinavian languages

- 'eh' - Italian interjection and Canadian English discourse marker
  While less critical, this can appear in legitimate Italian speech

The remaining filler words are primarily English
vocalized hesitations that don't conflict with common words in other
European languages.

Updated tests to use 'uhm' instead of 'um' where needed.

Fixes #941

* customize list

---------

Co-authored-by: Alexander Yastrebov <yastrebov.alex@gmail.com>
2026-03-06 20:24:29 +08:00
Shehab Tarek
10beb60d63
feat: add portable mode to NSIS installer (#807)
* ci: add temporary test workflow for portable ZIP validation

* ci: fix mock setup in test workflow

* ci: remove temporary test workflow

* feat: add portable mode option to NSIS installer

Custom NSIS template based on tauri-v2.9.1 upstream with 6 targeted
modifications for portable mode support:

- New PortableMode variable and /PORTABLE CLI flag for silent installs
- Install type selection page with Normal/Portable radio buttons
- Conditional default directory (Desktop for portable, LocalAppData for normal)
- Skip registry writes, file associations, deep links, and uninstaller for portable
- Skip Start Menu and desktop shortcuts for portable installs
- Create portable marker file and Data/ directory on portable install

The existing portable.rs runtime detection is unchanged — it looks for the
same marker file this installer creates.

Usage:
  GUI: select "Portable Installation" on the install type page
  Silent: Handy_setup.exe /S /PORTABLE /D=C:\path\to\Handy

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: implement true portable mode via source code path override

Detect a `portable` marker file next to the executable at startup.
When present, redirect all data paths (settings, models, recordings,
database, logs) to a local `Data/` directory instead of %APPDATA%.

This approach works because it intercepts path resolution at the Rust
level, bypassing Tauri/Windows Shell API limitations that made the
previous environment-variable and junction approaches unreliable.

Changes:
- Add src-tauri/src/portable.rs with OnceLock-based detection
- Replace all app.path().app_data_dir() calls with portable helper
- Configure tauri-plugin-log to use Folder target when portable
- Configure tauri-plugin-store with absolute path when portable
- Simplify build.yml portable ZIP (exe + resources + marker file)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add missing Manager trait import in portable.rs

The .path() method on AppHandle requires `tauri::Manager` in scope.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: redirect WebView2 cache to portable Data dir

Move main window creation from tauri.conf.json to setup closure so
we can set data_directory when in portable mode. This prevents
WebView2 from creating %LOCALAPPDATA%\com.pais.handy.

Also set data_directory on the recording overlay window.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: auto-detect portable mode during updates

When the Tauri updater runs the installer with /UPDATE, the install type
page is skipped and /PORTABLE is not passed. Detect the existing portable
marker file in .onInit so updates preserve portable mode automatically.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style: fix prettier formatting in portable.rs and lib.rs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: CJ Pais <cj@cjpais.com>
2026-03-06 12:07:11 +08:00
CJ Pais
a6b5c32cdb move to tauri dialog 2.6 2026-03-02 14:38:02 +07:00
CJ Pais
998449d2ad release v0.7.9 2026-03-01 22:05:33 +07:00
CJ Pais
f403cb12db update transcribe-rs 2026-03-01 22:04:47 +07:00
CJ Pais
eade87a178
upgrade to handy keys 0.2.2 (#926)
* Update translation.json

* upgrade handy keys
2026-03-01 21:12:13 +07:00
pantafive
ff8612297b
feat: add GigaAM v3 for Russian speech recognition (#913)
* feat: add GigaAM v3 model for Russian speech recognition

Add GigaAM v3 e2e_ctc as a new transcription engine using
transcribe-rs 0.2.7 gigaam feature. Russian speech recognition
with punctuation, Latin characters and digit support.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: cargo fmt formatting

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Keep the file name of the model download the same as the file on the
blob website.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: CJ Pais <cj@cjpais.com>
2026-03-01 20:53:48 +07:00
Brandon Chen
17d34a910f
fix: upgrade tauri-plugin-updater to v2.10.0 to fix duplicate registry entries (#873) (#876)
* fix: upgrade tauri-plugin-updater to v2.10.0 to fix duplicate registry entries (#873)

Upgrade tauri and tauri-plugin-updater to get bundle-specific target
resolution, which prevents NSIS-installed users from receiving MSI
updates via OTA and creating duplicate registry entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* move to my branch

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: CJ Pais <cj@cjpais.com>
2026-03-01 20:41:55 +07:00
Brandon Chen
2361b8511c
fix(i18n): tray menu shows Simplified Chinese when Traditional Chinese is selected (#901)
The tray locale lookup truncated "zh-TW" to "zh", always hitting
Simplified Chinese. Now tries full locale first, then language prefix,
then English.

Follow-up fix for #796.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: CJ Pais <cj@cjpais.com>
2026-03-01 10:39:14 +07:00
CJ Pais
1d4d6820f6
[macOS] Fix tray icon disabled + start hidden causing app to become permanently invisible (#903)
* tray fix

* format

* Update lib.rs

* Update lib.rs
2026-02-27 10:55:04 +08:00
CJ Pais
52047ae06e release v0.7.8 2026-02-24 23:05:11 +08:00
CJ Pais
0c8358899f
use blocking for handy keys (#881) 2026-02-23 11:18:47 +08:00
CJ Pais
74951cfca4 release v0.7.7 2026-02-20 13:25:25 +08:00
CJ Pais
4fcc974ab7 Merge branch 'main' of github.com:cjpais/Handy 2026-02-20 13:25:04 +08:00
CJ Pais
f367353752
fix handy-keys not firing when in the ui (#856) 2026-02-20 13:24:47 +08:00
CJ Pais
3e140e53b2 upgrade handy-keys 2026-02-20 13:18:51 +08:00
Pierre Carru
b90077b228
Add "external script" paste method (#638)
* feat(linux): add "external script" paste method

* feat(i18n): external script translation for fr, de, it
2026-02-19 17:07:16 +08:00
Mathieu Dupuis
f8ee7fce7c
feat: add z.ai post-process provider (#849)
Co-authored-by: Mathieu Dupuis <mathieu.dupuis@moov.ai>
2026-02-19 16:54:36 +08:00
CJ Pais
3c0fb95185
drain audio (#838) 2026-02-19 10:32:30 +08:00
CJ Pais
58b95c5c95 release v0.7.6 2026-02-17 13:02:48 +08:00
Anatoliy Guskov
133c50c6d4
feat: add CLI parameters for linux (#792)
* feat: add CLI parameters for Linux (--start-hidden, --no-tray, --toggle-transcription, --debug)

Add command-line interface using clap for better Linux desktop integration:
- --start-hidden: launch without showing the main window
- --no-tray: launch without system tray icon (closing window quits app)
- --toggle-transcription: toggle recording on/off on a running instance
  via tauri_plugin_single_instance
- --debug: enable debug mode with Trace-level logging (runtime-only)

Extract toggle_transcription() from signal_handle.rs into a reusable
function shared between SIGUSR2 handler and CLI single-instance callback.

Update CLAUDE.md and README.md with CLI documentation including setup
instructions for GNOME, KDE Plasma, Sway/i3, and Hyprland.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* add cancel and post processs

* cleanup

* format

* docs

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: CJ Pais <cj@cjpais.com>
2026-02-17 12:48:43 +08:00