Conversation
… selected - tauri.conf.json: beforeDevCommand/beforeBuildCommand run with cwd already inside ui/, so `pnpm --dir ui ...` was resolving to ui/ui and failing; drop the --dir ui. bundle.targets was hardcoded to macOS-only ["app","dmg"], which silently breaks a Windows build — switch to "all" so each OS bundles what's valid for it (NSIS/MSI on Windows, app/dmg on macOS). - win.rs / hotkey.rs: the local llama.cpp cleanup worker (Qwen 4B) was spawned unconditionally at startup regardless of Cleanup Engine mode, wasting RAM/CPU whenever a cloud provider was actually selected. Now it only starts when mode is Local, and responds live to mode changes. - pnpm-workspace.yaml: allow esbuild's postinstall (needed for vite) instead of silently skipping it. - README/BUILD-STATUS: document the verified NSIS installer build and the LLVM 17.x/18.x pin (bindgen 0.69 silently mis-generates structs on 19+).
…edback bugs Cloud ASR: dictation no longer requires a local Whisper model. A new Speech-to-Text setting (Local/Cloud) reuses the OpenAI key field and speaks the same multipart /audio/transcriptions wire format OpenAI and Groq share, so Groq's fast Whisper endpoint (or any compatible host) works out of the box. Windows shell fixes: - Single-instance guard so relaunching the app surfaces the running instance instead of spawning a duplicate process/taskbar entry (Windows/Linux only; macOS's Dock already does this). - Closing the Hub window now hides it instead of destroying it, so the tray's "Open WhimprFlow" item has something left to show. - Removed the leftover "Demo: recording"/"Demo: idle" tray items. - The dictation pipeline now flashes the pill through transcribing/done/error states instead of jumping straight back to idle — release builds have no console, so this was the only way a failure was ever visible to the user. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Release builds run with windows_subsystem = "windows" (no console attached), so every eprintln! diagnostic in the dictation pipeline was going nowhere a user could see it -- confirmed a user got literally no output running the packaged exe from cmd.exe. Mirror every diagnostic into %APPDATA%\WhimprFlow\debug.log (truncated fresh each run) instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…roke Every keystroke in a settings text field fired its own unawaited Tauri set_settings call (each doing a keyring lookup + rebuilding the HTTP provider). Concurrent calls have no ordering guarantee, so a fast typist could have an earlier, shorter value win the final disk write over the value actually left in the field -- confirmed by a user's asr_base_url persisting as "" while the field showed the typed Groq URL, which sent their cloud ASR requests (with a Groq key) to real OpenAI instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR focuses on making the Windows build truly functional end-to-end while also improving cross-platform reliability around settings/key storage and adding an OpenAI-compatible cloud speech-to-text option.
Changes:
- Add Cloud speech-to-text support (OpenAI-compatible
/audio/transcriptions) plus UI/settings wiring for ASR mode, base URL, and model. - Fix Windows dictation UX/pipeline issues (waveform emission, visible error states, debug logging) and prevent duplicate instances via single-instance plugin.
- Improve operational reliability: keyring backend features enabled, API key save errors surfaced, settings saves debounced, and build/bundle config corrected for Windows.
Reviewed changes
Copilot reviewed 16 out of 17 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/src/hub/SettingsPane.tsx | Adds ASR mode controls and improves API key save UX with error handling. |
| ui/src/hub/App.tsx | Debounces settings persistence to avoid concurrent/overlapping writes. |
| ui/src/hub/api.ts | Adds ASR types/defaults and stops swallowing API key save errors. |
| ui/pnpm-workspace.yaml | Allows esbuild to run its build/postinstall steps in pnpm workspace context. |
| src-tauri/tauri.conf.json | Fixes pnpm command execution context and enables bundling for all targets. |
| src-tauri/src/win.rs | Refactors Windows ASR to support local+cloud engines, adds waveform emission + user-visible error states + debug log. |
| src-tauri/src/lib.rs | Adds single-instance behavior (Win/Linux), hub window hide-on-close, and tray menu cleanup. |
| src-tauri/src/hotkey.rs | Gates local LLM worker startup to Cleanup Engine mode rather than always spawning. |
| src-tauri/Cargo.toml | Enables keyring OS backends and adds single-instance plugin for Win/Linux targets. |
| README.md | Updates Windows status/details and adds LLVM version guidance for builds. |
| docs/BUILD-STATUS.md | Updates build status notes reflecting recent Windows and worker-gating improvements. |
| crates/whimpr-core/src/settings.rs | Adds persisted ASR mode/base URL/model to core settings. |
| crates/whimpr-core/src/lib.rs | Re-exports AsrMode from settings. |
| crates/whimpr-core/src/asr/mod.rs | Adds Cloud ASR engine ID. |
| crates/whimpr-cleanup/src/lib.rs | Implements Cloud ASR engine via multipart upload to OpenAI-compatible transcription APIs. |
| crates/whimpr-cleanup/Cargo.toml | Adds dependencies needed for Cloud ASR (multipart + WAV encoding). |
| Cargo.lock | Locks new/updated dependencies (single-instance plugin, hound, multipart, etc.). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+103
to
+105
| <div style={{ fontSize: 12, color: "#e5484d", marginTop: 6 }}> | ||
| Couldn't save — the OS credential store may be unavailable. Check the app's console output. | ||
| </div> |
| if !text.is_empty() { | ||
| match asr.transcribe(&pcm) { | ||
| Ok(t) => { | ||
| log(format!("TRANSCRIPT: \"{}\"", t.text)); |
Comment on lines
+453
to
+465
| whimpr_core::AsrMode::Local => { | ||
| std::thread::spawn(|| match whimpr_asr::WhisperEngine::load(&whisper_model_path()) { | ||
| Ok(engine) => { | ||
| let engine: Arc<dyn AsrEngine> = Arc::new(engine); | ||
| if let Some(slot) = ASR.get() { | ||
| *slot.lock().unwrap() = Some(engine); | ||
| } | ||
| log("ASR ready (local)"); | ||
| } | ||
| Err(e) => log(format!("ASR load failed: {e}")), | ||
| }); | ||
| } | ||
| } |
Comment on lines
+196
to
+199
| let client = reqwest::blocking::Client::builder() | ||
| .timeout(Duration::from_secs(30)) | ||
| .build() | ||
| .expect("failed to build HTTP client"); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Windows was up and "building" per the README, but most of it was actually broken end-to-end: keys silently wouldn't save, the recording pill never animated, dictation could fail with zero feedback, and relaunching the app spawned duplicate copies. This PR fixes the root causes of all of those and adds a cloud speech-to-text path so Windows users don't need to hand-download a local Whisper model.
What's fixed
API keys wouldn't save (all platforms, not just Windows)
keyring = "3"ships with no storage backend compiled in by default (v3 changed this from v2's OS-default behavior) —Entry::set_passwordwas silently no-op'ing since the very first commit. Addedfeatures = ["apple-native", "windows-native"]. Also made the frontend surface save failures instead of always showing "Saved ✓" regardless of outcome.The pill never animated on Windows
win.rs's push-to-talk handler passed a no-op closure to the audio capture callback, so thewhimpr://audio/waveformevent the pill listens for was never emitted — onlyhotkey.rs(macOS) wired this up. Fixed, and while in there also wired the pill through propertranscribing→done/errorstates, which it never used on Windows.Dictation could fail with literally no feedback
Release builds run with
windows_subsystem = "windows"(no console), so every failure path'seprintln!was going nowhere a user could see. Now: the pill flashes an error state on any failure, and full diagnostics mirror to%APPDATA%\WhimprFlow\debug.log.Relaunching the app opened a duplicate copy
No single-instance guard existed, so double-clicking the exe/shortcut again spawned a second independent process (duplicate taskbar entry). Added
tauri-plugin-single-instance(Windows/Linux only — macOS's Dock already handles this). Also: closing the Hub window (X button) was destroying it, so the tray's "Open WhimprFlow" item had nothing left to show — it now hides instead. Removed the leftover "Demo: recording"/"Demo: idle" tray items.Settings could silently drop a field you just typed
Every keystroke in a settings text field fired its own unawaited save-to-disk call (each doing a keyring lookup + rebuilding an HTTP client). Concurrent calls have no ordering guarantee, so a fast typist could have an earlier/emptier value win the final write — caught this live: an ASR base URL persisted as
""even though the field showed the typed value, silently routing "Groq" requests to real OpenAI with a Groq key (401). Saves are now debounced (400ms after typing stops) instead of firing per keystroke.Local LLM cleanup loaded unconditionally at startup
The llama.cpp worker was spawned regardless of Cleanup Engine mode, burning RAM/CPU even when a cloud provider was selected. Now only loads when mode is
Local, and responds live to mode changes.Build/bundle config
beforeDevCommand/beforeBuildCommandrun with cwd already insideui/, sopnpm --dir ui ...was resolving toui/uiand failing on Windows.bundle.targetswas hardcoded to macOS-only["app","dmg"], silently breaking a Windows build — switched to"all".New: Cloud speech-to-text
A new Local/Cloud toggle in Settings → Speech-to-Text. Cloud speaks the same multipart
/audio/transcriptionswire format OpenAI and Groq share (CloudAsrinwhimpr-cleanup, mirrors the existingOpenAiProviderbase-url pattern), and reuses the OpenAI API key field rather than requiring a separate credential. This means dictation works on Windows without downloading a local Whisper model at all — point the base URL at Groq's free-tier Whisper endpoint and go.Testing
cargo test -p whimpr-core -p whimpr-ipc -p whimpr-audio— 35 passingtsc --noEmitcleanhotkey.rs,paste.rs) untouched except the local-LLM-mode gating, which is shared logic