Shipping Voice-to-Text Without the Cloud

How I am building LocalWispr: a Windows voice-to-text tray app that records from a hotkey, transcribes locally, and types into whatever app is focused.

LocalWisprWhisperElectronLocal-first

LocalWispr started from a tiny annoyance: I wanted to speak into my computer and have the words appear wherever my cursor already was. Not inside a web dashboard. Not inside another note-taking app. Just press a hotkey, talk, and keep working.

The catch was privacy. Voice input is personal, messy, and often full of unfinished thoughts. I did not want the core product to depend on sending raw audio to a remote API, so the first real constraint became simple: transcription should happen on the machine.

What LocalWispr does

  • Listens from a global hotkey so recording can start from any app.
  • Captures audio locally, trims silence with voice activity detection, and passes it to the transcription engine.
  • Uses whisper.cpp for local speech recognition instead of a cloud transcription API.
  • Injects the final text into the focused app so the user can keep their current workflow.
  • Lives in the tray with quick controls instead of demanding a full-screen app experience.

The hard part was not only transcription

The obvious hard part is speech recognition, but the product lives or dies in the edges around it. A local transcription model can be good enough, but the app still feels broken if recording starts late, if silence creates awkward junk, or if the final text lands in the wrong window.

Hotkeys and focus

Global shortcuts need to feel instant without fighting the user’s editor, browser, terminal, or IDE. The app has to start recording from the background and then return text to the place the user was already typing.

Audio cleanup

Raw microphone input is noisy. Voice activity detection is important because it makes the experience feel deliberate: the user speaks, pauses, and gets the text they meant to send, not a transcript padded with dead air.

Text injection

Typing into the focused window sounds simple until you try it across real applications. Cursor, VS Code, browsers, terminals, and chat boxes all behave slightly differently. This is the part that makes LocalWispr feel like a tool rather than a demo.

local-transcription.sh
./main -m models/ggml-base.en.bin \
  -f capture.wav \
  -otxt

Why local-first matters here

Voice-to-text is one of those features where privacy cannot be a footer note. People dictate half-formed ideas, private messages, client context, and sometimes code or credentials by accident. The safest default is to avoid uploading audio in the first place.

Local-first also changes reliability. Once the model is available on the machine, the app can keep working when the network is bad, when an API is down, or when the user simply wants a tool that does not meter every sentence.

The best version of this product should feel boring in the right way: press a key, speak, text appears.

Current stack

  • Electron and React for the desktop shell and UI.
  • TypeScript for the app surface and control flow.
  • Python/FastAPI for local backend pieces where useful.
  • whisper.cpp for on-device transcription.
  • Silero-style VAD flow for cleaner capture boundaries.

What I want to improve next

  • Better first-run setup for model downloads.
  • More reliable text injection across stubborn Windows apps.
  • A cleaner command/history surface for repeated dictation.
  • Packaging polish so installation feels premium, not experimental.

LocalWispr is still building, but the shape is clear: speech input that respects the user’s machine, privacy, and focus.