Commit Graph
22 Commits
Author SHA1 Message Date
Chad Piha c4ae6071e8 fix(scripts): let download scripts exit after following a redirect (#2410)
* fix(scripts): let download scripts exit after following a redirect

downloadFile followed redirects without reading the redirect body. On
Node 24 the global agent keeps sockets alive and only frees (and unrefs)
a socket once its response ends, so the unread 302 from huggingface.co
pinned the socket and kept `download-whisper-vad-model.js` alive for
minutes, stalling the `predev:main` chain before Electron started.

Drain redirect bodies in downloadFile and fetchJson, drain fetchJson's
non-200 bodies, and destroy a timed-out fetchJson request instead of only
rejecting.

* fix(scripts): keep a followed redirect from cancelling the live download

downloadFile's error and timeout handlers acted on whichever request was
current, so a reset on a redirect whose body was still draining destroyed
the request that replaced it and forced a retry. Each handler now only acts
on its own request; a stale one that times out is just destroyed.

Also adds a test for fetchJson's timeout closing the stalled request.
2026-09-30 00:16:43 +02:00
Gabriel Stein 94aeecb75a fix(notes): give note formatting a ten-minute deadline and stop retrying timeouts (#2204)
## Problem

Generate AI Summary with a bring-your-own-key provider sends the whole transcript to a model that may reason for a minute or more before it writes. Every BYOK request shared the 30-second deadline meant for dictation cleanup, and an expired deadline was treated like a dropped connection and retried three more times. A 2,170-word transcript through GPT-5.6 Terra spun for about 1.5 minutes, was billed for four requests, and ended with "Request timed out after 30s" and no note. 1.10.1 exposed this: raising the reasoning-model output cap to 25k tokens let Terra think long enough to cross 30s.

Enterprise reasoning (Bedrock, Azure, Vertex) had the same bug behind a different constant: the main-process handler applied its own 60-second default because the renderer never sent a deadline.

## Fix

- `getLlmRequestTimeoutSeconds` takes the request's inference scope. `noteFormatting` gets 600s (the OpenAI SDK's own default); dictation cleanup keeps 30s; streaming keeps its 60s idle deadline.
- `llmRequestTimeoutError` carries `LLM_REQUEST_TIMEOUT_CODE`, and `createApiRetryStrategy` refuses to retry it. The same request under the same deadline expires again, and every attempt is billed.
- OpenAI, Gemini, Tinfoil and the generic chat-completions path (Groq, LAN, custom endpoints) pass their scope and raise the coded error. Tinfoil maps the SDK's `APIConnectionTimeoutError` onto it so it is not re-sent either.
- The enterprise provider sends `timeoutMs` from the same helper. The handler already honoured that field. Note: this moves enterprise dictation cleanup from 60s to 30s, matching every other provider.
- Note prompt wrappers and `NOTE_OUTPUT_MAX_TOKENS` move from the action store to `builtinActions.js` so the live canary sends exactly what the app sends.

## Tests

- Every inference scope must be classified as interactive (≤ 60s) or background (≥ 300s); a new scope with no verdict fails.
- OpenAI and Gemini note requests survive the 30s tick, fail once at 600s, and issue exactly one POST. Cleanup still times out at 30s.
- A note request through the real `ReasoningService` dispatch outlives the dictation deadline and is sent once.
- Tinfoil receives the long deadline, maps the SDK timeout to the coded error, and does not re-send.
- Enterprise forwards 600s for notes and 30s for cleanup to the main process.
- `withRetry` makes exactly one attempt for a coded timeout.
- The note store's requests carry the `noteFormatting` scope.

## Canary

`llm-canary` now runs one meeting-sized Detailed Notes request per keyed provider (gpt-5.6-terra, gemini-3.5-flash) through the app's real provider code under the note-formatting deadline, reporting elapsed time, tokens, and a slow warning past half the budget. This is the only layer that can catch a provider or model change crossing the deadline.
2026-09-14 19:03:09 -07:00
Chad Piha 4d6077ded8 fix(parakeet): ship ONNX Runtime under a private DLL name on Windows (#2074)
* build(win32): add a PE import-name reader and patcher

Read and rewrite module names in a PE image's import and delay-load
directories. Names are patched in place, so a replacement can never be
longer than the original. Needed to point the bundled sherpa-onnx
binaries at a privately named ONNX Runtime (#2054).

* fix(parakeet): ship ONNX Runtime as ow-onnxrt.dll on Windows (#2054)

Windows 11 ships an older onnxruntime.dll (1.17) in System32. The bundled
sherpa-onnx 1.13.4 binaries are built against ORT 1.27 and bind the
runtime by bare module name, and on some machines the loader resolves
that name to the System32 copy even though ours sits beside the exe. The
exe then fails with "requested API version [27] is not available" before
it can listen, which the app reports as "parakeet-ws process died during
startup".

Rename the extracted runtime to ow-onnxrt.dll at download time and
rewrite the import tables of every sherpa image to match, so no
name-based loader rule (PreferSystem32Images, KnownDlls, an already
loaded module) can substitute the system copy. Record the rename in the
install marker so checkouts extracted before this change re-download.
Upstream's archive lists each DLL twice (bin/ and lib/), so the library
list is deduplicated.

Remove once microsoft/onnxruntime#27893 (version-suffixed DLL names)
lands and sherpa-onnx adopts it.

* build(win32): fail packaging if onnxruntime.dll would ship

The Windows package must contain ow-onnxrt.dll and no onnxruntime.dll.
A stray upstream DLL or a missing private one means the rename step in
download-sherpa-onnx.js was skipped (stale cache, older marker) and
Parakeet would die at startup for users (#2054).

* fix(parakeet): Invalidate install markers before automatic repairs

Clear the completion marker before every reinstall so a failed DLL patch
cannot leave a partially patched bundle marked complete. Add regression
coverage for patch failure and successful automatic retry.
2026-09-09 00:42:40 +02:00
fad475c6f4 fix(whisper): extract MSVC runtime DLLs beside win32 whisper-server (#1687)
* fix(whisper): extract MSVC runtime DLLs beside win32 whisper-server

The win32 CPU whisper-server.exe links the dynamic MSVC runtime
(msvcp140/vcruntime140/vcruntime140_1/vcomp140) but shipped as a bare exe:
download-whisper-cpp.js copied only the binary out of the release zip, so
on machines without the VC++ 2015-2022 redistributable the Windows loader
killed every spawn with 0xC0000135 (STATUS_DLL_NOT_FOUND) before main(),
with empty stderr — local transcription dead on arrival (CUS-113).

- pin whisper.cpp 0.0.10, the first release whose win32 zips bundle the DLLs
- give the win32 config the same libPattern DLL-companion extraction the
  llama/sherpa download scripts already use, consolidating their duplicated
  findLibrariesInDir/matchesPattern helpers into lib/download-utils.js
- prep whisperVulkanManager with libPattern so the 0.0.10 vulkan zip's CRT
  DLLs are extracted too (its "statically linked" comment was wrong re CRT)

electron-builder's extraResources filter already passes *.dll and
cleanupFiles already spares libraries, so no packaging-config change needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(downloads): validate cached binaries and traversal errors

* fix(whisper): Preserve migration and propagate download failures

Keep legacy Windows Vulkan packs migratable until the DLL-bearing release and digest update lands. Make all-platform downloads exit nonzero after any platform failure and remove the unused numeric library-search options shim.

* fix(gpu): Require Windows whisper runtime libraries

Pin CUDA and Vulkan packs to verified 0.0.10 assets and reject incomplete caches or staged installs. Preserve the existing pack when validation fails.

* fix(deps): Upgrade Tiptap past attribute injection advisory

Align the full Tiptap package family at 3.30.4, the first patched release for GHSA-cp6q-959q-f8rh. Restore the npm audit gate without unrelated dependency upgrades.

* fix(deps): Patch xmldom serialization advisory

Upgrade the existing xmldom override to 0.8.15, the first patched 0.8.x release for GHSA-6gmq-8vp8-gcm6, so the npm audit gate remains clean.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Chadpiha <chadpiha23@gmail.com>
2026-09-02 18:46:44 +02:00
Gabriel SteinandChadpiha 5d69b75a2e feat(calendar): Apple Calendar (EventKit) integration for macOS (#1237)
* feat(calendar): Apple Calendar (EventKit) integration for macOS

Add a second calendar provider that reads the local EventKit store —
covering iCloud, Google, Exchange, and Outlook accounts already in
Calendar.app — with one native permission prompt and push-based updates
instead of OAuth + polling.

- New macos-calendar-listener Swift helper (mic-listener pattern):
  emits permission + calendars/events snapshots as line-delimited JSON,
  re-emits on EKEventStoreChanged, stdin "sync", and a 5-min window roll;
  --request triggers the TCC prompt attributed to OpenWhispr
- Build script patches dev Electron's Info.plist with calendar usage
  strings; packaged builds get them via electron-builder extendInfo,
  plus the calendars entitlement for hardened runtime
- calendar_events gains a provider column; new apple_calendars table;
  Google deletes are provider-scoped; shared read queries suppress
  Apple copies of Google-mirrored meetings (datetime-normalized, Google
  rows never collapsed) so reminders fire once per meeting
- Meeting reminder scheduling extracted from GoogleCalendarManager into
  a provider-agnostic CalendarReminderScheduler shared by both managers
- AppleCalendarManager owns the helper lifecycle; meeting join links are
  extracted from url/location/notes into hangout_link at sync time so
  Join buttons and note linking work unchanged
- Integrations UI gets an Apple Calendar row (macOS only) with connect,
  denied → System Settings flow, source list, and disconnect; i18n keys
  added to all 10 locales

Downstream consumers (Upcoming Meetings, overlay reminders, agent
calendar tool, note linking) read the shared table and work for both
providers with no changes.

* refactor(calendar): Harden EventKit integration and extract shared helpers

* fix(calendar): Show the calendar permission prompt in dev builds

* chore(calendar): Clean up after dev TCC fix

* fix: tentative meeting responses and stale state fix

* fix: dev script

* fix(tests): broken test after rebase

---------

Co-authored-by: Chadpiha <chadpiha23@gmail.com>
2026-08-03 10:42:31 -07:00
e4e5d19ddb feat(parakeet): add NVIDIA Nemotron streaming ASR models (EN + multilingual 3.5) (#1131)
* feat: add Nemotron ASR model

Add Nemotron Speech Streaming EN 0.6B through sherpa-onnx online websocket support.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix(parakeet): bump sherpa-onnx to 1.13.4 for Nemotron streaming support

The Nemotron model artifact in the registry
(sherpa-onnx-nemotron-speech-streaming-en-0.6b-560ms-int8-2026-04-25) was
re-exported alongside runtime updates in sherpa-onnx v1.13.0; the previously
pinned v1.12.23 runtime produces degraded transcripts on Nemotron streaming
models (verified empirically: 1.12.x mis-decodes words that 1.13.4 gets
right on the model's own test wavs).

Also correct the Nemotron registry size to the extracted model size (632MB,
matching the convention used by the other Parakeet entries — this value
feeds the pre-download disk-space check), and document the new model and
the offline/online runtime split in CLAUDE.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdfxroZFwgsVyKMhum1UAo

* feat(parakeet): add multilingual Nemotron 3.5 ASR Streaming 0.6B model

Adds nemotron-3.5-asr-streaming-0.6b to the local model registry alongside
the English Nemotron model. Cache-aware streaming FastConformer with
per-stream automatic language detection; 15 transcription-ready languages.
Uses the same online websocket runtime added for the English model —
no code changes needed, only registry + i18n + docs.

Verified against sherpa-onnx 1.13.4: the published int8 export
(sherpa-onnx-nemotron-3.5-asr-streaming-0.6b-560ms-int8-2026-06-11) loads
with the exact encoder/decoder/joiner/tokens flags OpenWhispr passes and
decodes English, German, and Japanese test wavs with language auto-detection.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdfxroZFwgsVyKMhum1UAo

* feat(parakeet): stream Nemotron live preview over a persistent connection

The Live Transcription Preview previously transcribed buffered 1.5s chunks
independently, which discards a cache-aware streaming model's context at
every chunk boundary. When the selected NVIDIA model has runtime "online"
(Nemotron), the preview now opens one persistent websocket stream to the
online sherpa-onnx server for the whole recording: worklet PCM is converted
to float32 and forwarded as it is captured, partial results replace the
preview text live, and stopping the preview flushes the stream ("Done" /
"Done!" handshake) for the final text. Audio captured while the streaming
server is warming up is buffered and fed into the stream once it opens.

Offline models keep the existing chunked path, and the online path falls
back to it if the stream cannot start.

Covered by test/helpers/parakeetOnlineStream.test.js using a mock websocket
server that speaks the sherpa-onnx online protocol (partials, endpointed
segments, finish handshake, abort).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdfxroZFwgsVyKMhum1UAo

* refactor(parakeet): code-quality pass on Nemotron integration

Applies the findings from a four-angle review (reuse, simplification,
efficiency, altitude) of the Nemotron PR diff:

- Single registry access point: new parakeetModelInfo.js owns
  getModelRuntime and REQUIRED_MODEL_FILES; parakeetWsServer no longer
  reads the model registry, and the getModelRuntime pass-throughs on both
  manager layers are gone. ipcHandlers now asks a capability question
  (supportsOnlineStreaming) instead of comparing runtime strings.
- Runtime-only binary API: getWsBinaryPath/isAvailable take a runtime
  (the model-name-or-runtime union sniffing is gone), callers resolve
  model to runtime once, hasAnyWsBinary replaces the hand-rolled
  offline-OR-online checks, and per-runtime path memoization is restored.
- One online-protocol implementation: _transcribeOnline is now a thin
  wrapper over createOnlineStream instead of a ~55-line duplicate of it.
- Incremental result accumulation: createOnlineAccumulator parses each
  websocket message once (the previous per-message re-parse of the whole
  history was quadratic over a recording) and live preview updates are
  emitted only when the text actually changes. parseOnlineMessages keeps
  its API as a wrapper over the accumulator.
- Wire format stays in the ws layer: the stream handle exposes sendPcm16
  (int16 capture format, converted internally) so ipcHandlers no longer
  performs float32 conversion; abort now settles a pending finish().
- Preview session staleness is tracked by one generation counter instead
  of three heuristic condition sets.
- pcm16ToFloat32 is the canonical converter (returns Float32Array);
  meetingEchoLeakDetector and liveSpeakerIdentifier drop their local
  copies.
- Sidecar reaper matches parakeet against its two exact ws binary
  prefixes instead of the whole "sherpa-onnx-" vendor prefix, which also
  overlapped the diarization entry.
- Download script reuses the shared cleanupFiles (extended to accept
  multiple keep prefixes) and copyBinary drops its redundant label and
  basename handling.

Verified on Node 24 with full dependencies: 278 tests, 270 pass, 0 fail
(8 skipped), tsc --noEmit clean, eslint clean, prettier clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AdfxroZFwgsVyKMhum1UAo

* fix(parakeet): repair sherpa-onnx 1.13.4 binary downloads

- Windows release assets are now suffixed (-MD-Release); old name 404s
- ad-hoc re-sign macOS binaries/dylibs: upstream ships libonnxruntime
  with an invalid arm64 signature that dyld SIGKILLs
- rm before copy so a stale symlink can't redirect writes into an old inode
- re-point the unversioned lib symlink and prune stale versioned libs
- run download:sherpa-onnx in predev/prestart (marker-gated, fast no-op)

* refactor(parakeet): condense comments

---------

Co-authored-by: Joel Tony <github@jaytau.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-07-14 14:15:06 -07:00
Alcahest Malus Dienheim 43b4c16880 fix(linux): fall back to --no-sandbox when user namespaces are restricted (#1042) 2026-07-06 09:32:24 -07:00
Alcahest 3f4aad6549 chore: bump bundled llama.cpp to b9763 (#995) 2026-06-24 19:37:28 -07:00
Gabriel Stein 70a0d677b4 feat(onboarding): use-case intent capture, meeting step, and finish step (#928)
* feat(onboarding): use-case intent capture, meeting step, and finish step

Reworks the onboarding wizard around an id-keyed dynamic step machine:

- New "Why are you here?" step: multi-select use-case cards (OptionCard)
  plus an optional note, persisted to settings and synced fire-and-forget
  to /api/onboarding-intent for signed-in users
- Permission copy rewritten to explain outcomes; system audio badge flips
  to Recommended when the user came for meeting notes
- New conditional meeting step introducing auto-detection and the existing
  meeting hotkey (shown when system audio is granted or meetings selected)
- New finish step: OpenWhispr Cloud summary with Open Settings deep-link
  (Settings > Transcription) or Skip; local/BYOK variant for skip-auth
  users; registry-gated Corti partner screen for medical intent
- All new strings translated across the 10 locale files

* refactor(lint): fix react-refresh and hook-deps warnings, run formatter

- Move pending-invitation token helpers out of AcceptInvitationModal into
  src/utils/pendingInvitationToken.ts so the component file only exports
  components (react-refresh/only-export-components)
- Memoize the invitations fallback in ShareNoteDialog so its callback
  dependencies are stable (react-hooks/exhaustive-deps)
- Prettier reflow in clipboard.js

* fix(onboarding): add back-to-sign-in escape from email verification

Without it, users who never receive the verification email are stuck on
the polling screen with no way back to choose another email or sign-in
method.

* style(onboarding): match back-to-sign-in button to auth screen idiom

Align the verification back button with AuthenticationStep's continue-
without-account styling; prettier array reflow in zh locale files.

* refactor(auth): use Button ghost variant for subtle text actions

Replace bespoke raw buttons (back-to-sign-in, continue-without-account)
with the design-system Button, gaining focus-visible rings and proper
disabled styling.

* feat(onboarding): broaden medical use case to healthcare, clarify multi-select

- Rename use-case id medical -> healthcare; card now reads "Medical,
  clinical & therapy transcription / Dictate patient notes, session notes
  and reports" so therapy and psychology users self-identify
- Add "Select all that apply" hint and switch OptionCard's selection
  indicator from radio circle to checkbox square so multi-select is obvious
- Update Corti finish title to match; all 10 locales updated

* fix(onboarding): allow proceeding with Corti credentials in setup step

The Corti provider tab (#929) now appears in the onboarding transcription
picker, but canProceed only recognized openai/groq/mistral/custom — a user
selecting Corti was blocked on Next unless an OpenAI key happened to be
set. Gate on client id + secret, matching shouldUseStreaming's readiness
rule.

* feat(onboarding): preselect Corti provider when opening settings from finish step

The Corti finish screen's Set up in Settings CTA now sets
cloudTranscriptionProvider to corti so the settings picker opens on the
Corti tab with its credential fields. Safe for signed-in users — the
provider only takes effect in BYOK mode.

* feat(onboarding): inline Corti setup on the finish step

Replace the settings detour with credentials entered in place: honest
copy (clinical-grade, HIPAA compliant), a visible $50 free credit badge
next to the create-account link, Client ID/Secret fields reusing
ApiKeyInput, and a US/EU data-region select (defaults prefilled from the
store). Use Corti activates the provider in BYOK mode and completes
onboarding; Not now falls through to the default finish.

* refactor(onboarding): remove quit button from title bar

The power/quit button only ever rendered in onboarding (TitleBar's sole
consumer) and duplicates OS-level quit (Cmd+Q, dock, window controls).
Remove it end to end: TitleBar quit UI + confirm dialog, app-quit IPC
handler, appQuit preload bridge and type, and the titleBar i18n block in
all 10 locales.

* feat(onboarding): link Corti homepage with UTM and add setup steps

Point the create-account link at corti.ai with the partnership UTM params
instead of the console, and add a three-step guide (sign up, create API
credentials in the console, paste below) since the homepage no longer
lands users on signup.

* feat(onboarding): refine use-case step copy and move progress into macOS title bar

- Subtext constrained to readable width and rewritten to say plainly that
  the user's picks set up the app and steer what we build next
- Select-all hint restyled to the existing uppercase section-label idiom
- Note placeholder reduced to "Optional" — no canned examples
- TitleBar gains a centered no-drag slot; on macOS the step progress
  renders there (reclaiming the progress strip's height), Windows/Linux
  keep the strip alongside their window controls

* feat(onboarding): add upload-audio use case and skip button for optional steps

- New "Uploading audio files" use-case option (id: upload) with FileAudio icon
- Skip button beside Next on optional steps (use case, voice agent, meeting)
- Add common.skip and the upload option copy across all 10 locales

* feat(transcription): add Corti provider icon

Register the Corti logo in PROVIDER_ICONS so the Corti tab and provider
chips show a real icon; monochrome (currentColor) like xai.

* refactor(meetings): consolidate meeting detection into the notification toggle

The meeting-detection notification was the only consumer of detection, so a
detector running with notifications off did nothing but burn CPU. Drive the
audio detector from the notification toggle (notificationsEnabled &&
notifyMeetingDetection) and remove the redundant standalone Audio Detection
setting, its UI section, and the now-dead calendar.detection translations.

* fix(settings): don't grab the mic when opening settings

MicrophoneSettings called getUserMedia on mount to read device labels,
which activates a mic input session and interrupts other audio (pauses
music on macOS) every time Settings opens. Enumerate devices first and
only fall back to getUserMedia when labels are missing (permission not
yet granted).

* feat: add UTM tracking to Corti 'get a key' link

Point the Corti transcription provider's 'Get a key' link at the
corti.ai home page with referral UTM params, matching the canonical
CORTI_SIGNUP_URL used in onboarding instead of console.corti.app.

* chore: apply prettier formatting to pre-existing files

Format 21 docs, build scripts, and test files that were not
prettier-compliant on main. Pure formatting, no logic changes.
Kept separate from the onboarding feature work.

* chore(release): 1.7.3

Bump version to 1.7.3 and document the onboarding upgrade, Corti
provider polish, simplified meeting-detection setting, and the
Settings microphone fix in the changelog.

* docs(changelog): capture full 1.7.3 scope

Expand the 1.7.3 entry to cover everything merged since v1.7.2, not
just the onboarding branch: Corti BYOK and xAI transcription providers,
Snippets, Voice Agent hotkey, dictionary redesign, notification
controls, Linux PipeWire/paste fixes, new AI models, and the broader
fix batch.

* fix(onboarding): keep step progress labels on one line

The stepper renders inside TitleBar's absolutely-positioned `left-1/2`
center slot, whose shrink-to-fit width is capped at the space from
center to edge. That squeezed the row and wrapped the longest label
("Voice Agent") onto two lines. Adding `whitespace-nowrap` lets the
wrapper resolve to the content's full width and center it cleanly,
fixing the layout across all platforms (shared StepProgress component).

* fix(auth): recover stale-bearer cloud requests via session cookie

The main process authenticates cloud requests with a single saved bearer token that only refreshes on sign-in. After an email/password login (which, unlike OAuth, never force-refreshes it) that token can go stale, so main-process requests 401 while the renderer stays signed in via its session cookie. onboarding-intent was the visible casualty: it fires right after the use-case step and failed silently, leaving intent unsaved.

Retry cloud-api-request once with the window's session cookie when a bearer request returns 401. Cookie-only on retry, since a tagging-along bearer overwrites the cookie server-side.

* fix(onboarding): hide voice agent step for continue-without-account users

Continue-without-account users have no LLM configured, so the dictation agent can't run — skip the voice agent onboarding step for them and show it only to signed-in account users.

* fix(i18n): add Hyprland hotkey locale strings

* fix(clipboard): prevent stale clipboard restores during paste

* fix(dictation-agent): reach cloud agent from voice hotkey without a selected model

The voice agent hotkey resolved to "skip" for signed-in OpenWhispr cloud
users because agentReachable required a non-empty dictationAgentModel, which
cloud mode never sets — pasting the raw transcript instead of running the agent.

Mirror the cleanup cloud fallback: the dictation agent is now reachable in
cloud (openwhispr) and self-hosted (lan) modes without an explicit model,
matching ReasoningService.processText's empty-model allowance, and the agent
route forces provider "openwhispr" in cloud mode so it no longer depends on
the cleanup scope. Also pass voiceAgentRequested on the streaming-STT path so
the hotkey works there too.

Reachability logic is extracted into a pure, tested helper
(resolveDictationAgentReachability) alongside resolveDictationRouteKind.

* feat(onboarding): preview the real meeting notification in the meeting step

Replace the abstract "meeting detected" explainer with a faithful preview of
the actual notification, rendered from a shared MeetingNotificationCard that
MeetingNotificationOverlay now also consumes so the two never drift. Add the
onboarding.meeting.notification.{title,body,cta} keys across all 10 locales.

* fix(note-formatting): use cloud provider when no model is selected

Note formatting passed no provider to processText, so cloud users fell back to
getModelProvider(""), which resolves from the cleanup scope — throwing
"No reasoning model selected" when cleanup uses a BYOK/local provider. Pin the
provider to openwhispr in cloud mode for both enhancement and title generation.

* feat(onboarding): temporarily hide the meeting step for all users

* fix(dev): diagnose missing Electron binary

* feat(transcription): dedicated Audio Upload speech-to-text settings

Give audio upload its own transcription context, mirroring how Note Recording was split from Dictation. Adds an Audio Upload tab under Settings -> Speech-to-Text with independent upload* settings; the upload page is now settings-only (inline picker removed). A one-time migration copies existing users' dictation transcription preference into the new context; new users default to OpenWhispr Cloud.

- store: 9 upload* keys, selectResolvedUploadTranscription (falls back to base dictation values), migrateUploadTranscription (runs after provider migration)
- settings: new UploadTranscriptionPanel (openwhispr/providers/local)
- upload page: read-only consumer of resolved upload settings; no-provider CTA routes to the Audio Upload tab
- i18n: tabs.upload across all 10 locales; remove orphaned upload setup strings
- fix cross-window boolean sync for meeting/upload useLocalWhisper (BOOLEAN_SETTINGS)
- extract shared useStartOnboarding hook; drop dead uploadSetupComplete write

* fix(history): inline discarded toggle and stop list flash on refresh

Move the "Show Discarded" toggle inline to the left of "Clear All" in the
first day header so the pair reveals together on hover; keep it reachable
above the empty state. Gate the loading spinner on the initial load only
(no data yet) so toggling discarded keeps the current list mounted and
swaps in new data instead of unmounting to a spinner and flashing.

* feat(upload): cancel button during audio-file transcription

Add a Cancel transcription action beneath the progress bar on the upload page (shown for every transcription). Cancelling returns to the upload screen and discards the result so nothing is saved; a run-id token guards the async path so a cancelled or superseded run never persists a note.

* docs(changelog): document late-breaking 1.7.3 changes

* fix(onboarding): apply Corti to all transcription scopes on finish

Choosing Corti during onboarding only set the dictation cloud provider, so note recording and audio upload kept their previous provider and every Settings tab still showed OpenWhispr Cloud — each tab displays the InferenceMode field, which was never updated.

setCloudTranscriptionForAllScopes now applies the chosen provider to dictation, note recording, and audio upload, and sets each scope's InferenceMode via a shared deriveTranscriptionMode helper (also reused by the provider-settings migration).

* refactor(corti): rework streaming close handshake with ack tracking

Replace the single closeResolve callback with a pendingAck/waitForAck mechanism that resolves on the matching server ack (flushed/ended) or socket close, times out cleanly, and closes the socket with code 1000.

* feat(onboarding): rename activation step to Dictation and add voice agent test area

- Rename the activation onboarding step to "Dictation" in the progress nav and "Dictation Setup" in the step header
- Rename the voice agent step header to "Voice Agent Setup"
- Add a test area under the voice agent hotkey with example spoken commands and a textarea to try it
- Add and translate the new strings across all 10 locales

* fix(meeting): send true 24kHz sample rate to cloud streaming providers

Meeting capture runs at 24kHz end-to-end, but Corti/Deepgram/AssemblyAI were
told 16kHz, so they read the PCM ~1.5x too slow and degraded accuracy. Thread
the real rate through the meeting connect path and teach cortiStreaming to honor
options.sampleRate (matching Deepgram/AssemblyAI). OpenAI already declares 24kHz;
dictation (16kHz) and the local downsample path are untouched.

* fix(dictation-agent): default to OpenWhispr Cloud for signed-in users

The dictation agent scope defaulted to "providers" (BYOK) with no model,
unlike the cleanup and chat-agent scopes which default to OpenWhispr Cloud.
As a result, a signed-in user who never explicitly configured the dictation
agent had it unreachable, so the voice agent hotkey silently typed the raw
transcript instead of running the command.

Default dictationAgentMode and dictationAgentCloudMode to "openwhispr" so the
voice agent works out of the box on Cloud. Users who explicitly chose BYOK,
local, or self-hosted are unaffected (persisted values are read first).

* perf(corti): pre-warm dictation socket to cut startup latency and first-word loss

Corti dictation paid the full token-mint + socket-open + CONFIG_ACCEPTED cost at
hotkey time (~1s), and audio captured before the socket existed was dropped. Give
CortiStreaming a real warmup() that opens the socket and finishes the config
handshake ahead of recording, keeps it alive with WS pings, and lets connect()
promote the warm socket for an instant start — mirroring Deepgram/AssemblyAI.
Falls back to a cold connect when no warm socket exists, so behavior is never
worse than before. Meeting mode (fresh instance, no warmup) is unaffected.
2026-06-23 21:30:41 -07:00
Gabriel Stein dac4a1ba82 fix(windows): pin llama.cpp to b8857 to keep whisper-server loadable
whisper-server.exe (built from OpenWhispr/whisper.cpp 0.0.6, frozen since
2026-01-23) dynamically links against the ggml-*.dll set that
download-llama-server.js ships into resources/bin. Both download scripts
fetch latest, which was fine until a llama.cpp release between b8861 and
b9020 bumped ggml's ABI — whisper-server.exe then crashes on
load_backend, leaving local Whisper users on 1.7.0 unable to transcribe.

Pin LLAMA_CPP_VERSION to b8857 (last build verified working in 1.6.10).
Add a `tag` option to fetchLatestRelease for exact-tag lookup via
/releases/tags/{tag} — the existing tagPrefix path only paginates the 50
most recent releases, which silently fails for old pins like b8857
(~165 releases back).

Bump version to 1.7.1.
2026-05-04 22:18:18 -07:00
Gabriel Stein 94ac45bca3 fix(meeting-aec): link winmm on windows
timeGetTime (used by rtc_base/system_time.cc under WEBRTC_WIN) lives
in winmm.lib. Without /DEFAULTLIB:winmm.lib lld-link errors with
'undefined symbol __declspec(dllimport) timeGetTime'.
2026-04-14 18:16:57 -07:00
Gabriel Stein 8de0662b7d fix(meeting-aec): cross-platform build for linux, windows, and macOS x64
- Extend Bazel select parser to recognize @platforms//cpu:x86_64 so SSE2
  and AVX2 WebRTC targets get compiled into x86_64 builds.
- Compile AVX2 sources with per-file copts (-mavx2 -mfma / /arch:AVX2)
  via set_source_files_properties and a dedicated direct-build bucket.
- Link CoreFoundation on darwin so logging.cc's CFSTR references resolve
  on macOS x64 (implicit on arm64).
- Add _GNU_SOURCE on Linux so pffft.c sees M_PI/M_SQRT2 from glibc.
- Stub rtc_base/win32.h in compat/: webrtc-apm omits it but time_utils.cc
  includes it under WEBRTC_WIN; the referenced Windows types already come
  from <minwinbase.h>.
2026-04-14 16:58:17 -07:00
Gabriel Stein 0646df5c84 fix(meeting-aec): single-pass escape in CMake quoter
Escape both quotes and backslashes in one regex pass to silence CodeQL
incomplete-escape warning. No behavior change for the paths we actually
pass through.
2026-04-14 16:20:41 -07:00
Gabriel Stein 068280ba86 feat(meeting): add WebRTC AEC helper and text-level dedupe
- Adds native WebRTC APM sidecar (meeting-aec-helper) built from
  webrtc-apm + abseil-cpp, wired into the mic pipeline via
  MeetingAecManager with graceful fallback when unavailable.
- Adds transcript-level duplicate suppression (transcriptsOverlap,
  transcriptsLooselyOverlap, buildMergedCandidates) and retract
  segment events to drop mic duplicates after system audio confirms.
- Tunes MeetingEchoLeakDetector thresholds with a system-VAD gate
  and per-state bleed profiles.
- Packaging: electron-builder extraResources, afterPack verify+chmod,
  cache keys on both CI workflows, prebuild:compile:meeting-aec.
2026-04-14 13:36:44 -07:00
Gabriel Stein a893037d5b feat(agent): enable local model tool calling + RAG context injection
Route local models through AI SDK chat completions with tool support
for models >= 4B params. Inject RAG note context via Qdrant semantic
search into system prompt for all models. Bundle MiniLM embedding model
in production builds. Add --jinja flag for llama.cpp tool templates.
Fix redirect handling for 303/307/308 in download utils.
2026-03-25 15:53:06 -07:00
Gabriel Stein 8eb7556338 fix(ci): resolve Intel Mac binary architecture mismatch (#196)
The prebuild:mac npm hook runs download scripts and compile scripts
without target architecture context. On arm64 CI runners building for
x64, this caused:

- Download scripts to resolve to arm64, then cleanup deleted the x64
  binaries (sherpa-onnx, whisper-server, llama-server) and overwrote
  x64 dylibs with arm64 versions
- macos-fast-paste to compile as arm64-only

Fixes:
- parseArgs() now checks TARGET_ARCH/TARGET_PLATFORM env vars (already
  set in CI) before falling back to process.arch/process.platform
- build-macos-fast-paste.js now supports cross-compilation via
  TARGET_ARCH env var and --arch flag, matching the globe-listener
  pattern
- CI cache keys updated to include fast-paste build files
- .gitignore updated for arch-specific hash files
2026-02-23 20:07:40 -08:00
Gabriel Stein c810d2b205 fix(ci): prevent prebuild hook from deleting x64 binaries on arm64 CI runners
The prebuild:mac npm hook runs download scripts with --current but without
--platform/--arch flags. On arm64 CI runners, this resolved to arm64 and
the cleanup logic deleted the x64 binaries that were explicitly downloaded
moments earlier — causing Intel Mac builds to ship without sherpa-onnx,
whisper-server, and llama-server binaries.

parseArgs() now checks TARGET_ARCH/TARGET_PLATFORM env vars (already set
in CI workflows) before falling back to process.arch/process.platform.
2026-02-23 19:50:36 -08:00
Gabriel Stein 92deca76ac fix: await async zip extraction in download scripts
extractZip is async (uses unzipper on Windows) but was called without
await in 4 download scripts, causing the binary search to run before
extraction finished — resulting in "not found in archive" errors.
2026-02-13 10:32:31 -08:00
Marcusandsolwer-se 68c037b455 fix: use unzipper instead of tar for Windows key listener extraction (#273)
Windows' built-in tar command fails with path resolution errors
when extracting the windows-key-listener binary:
  tar: Cannot connect to c: resolve failed

This change switches to the unzipper npm package (already in
dependencies) which properly handles Windows paths and provides
more reliable extraction.

Benefits:
- Eliminates error messages during npm run dev
- Enables push-to-talk for fresh Windows installations
- More reliable cross-platform extraction

Tested on Windows 11 with successful extraction.

Co-authored-by: solwer-se <solwer-se@users.noreply.gitlab.com>
2026-02-13 08:02:44 -08:00
Gabriel Stein 6e9583f240 fix: use tar instead of PowerShell Expand-Archive for zip extraction on Windows 2026-01-27 15:32:14 -08:00
e3969d0538 feat(windows): add Push-to-Talk support with compound hotkeys (#153)
* feat(windows): add Push-to-Talk support with compound hotkeys

Adds full Push-to-Talk functionality on Windows with support for compound
hotkeys like Ctrl+F11, Alt+F8, etc.

## Windows Push-to-Talk

- Add native Windows keyboard hook (windows-key-listener.c) using
  Low-Level Keyboard Hook (WH_KEYBOARD_LL) for reliable KEY_DOWN/KEY_UP
  detection
- Support compound hotkeys with modifiers (Ctrl, Alt, Shift) by tracking
  modifier key states with GetAsyncKeyState()
- Add WindowsKeyManager.js to manage the native binary lifecycle
- Emit KEY_UP when modifier keys are released while main key is held

## Hotkey Capture Fix

- Stop Windows key listener when entering hotkey capture mode so it
  doesn't interfere with recording new hotkeys
- Temporarily unregister globalShortcut during capture to prevent
  key event consumption
- Restart listener when exiting capture mode if in push mode

## ELECTRON_RUN_AS_NODE Fix

- Add run-electron.js wrapper script to unset ELECTRON_RUN_AS_NODE
  environment variable (inherited from Claude Code and similar tools)
- Update dev:main script to use the wrapper

## Additional Improvements

- Add auto-start at login feature with UI in Settings
- Add debug logging for audio recording and whisper-server
- Fix whisper-server PATH to find companion DLLs on Windows
- Fix hotkeyManager to check isRegistered before returning success
- Change dev server port from 5174 to 5080

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

* feat: add Custom Dictionary and fix Windows production build

- Add Custom Dictionary feature for improving transcription accuracy
  - New "Custom Dictionary" section in Settings sidebar
  - Words are passed as context hints to Whisper via initialPrompt
  - Supports adding phrases like "The word is Synty" for difficult words
- Fix windows-key-listener.exe not bundled in production builds
  - Add windows-key-listener* to electron-builder extraResources filter

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

* fix(windows): add prebuilt binary support and improve Push-to-Talk code quality

* refactor: download-nircmd.js to use shared utils

* fix: code quality fixes for download scripts and dev server port alignment

* fix: add GITHUB_TOKEN auth to avoid API rate limits in CI

* chore: updates docs

* fix: hotkey capture mode re-registration and platform improvements

* feat: add F13-F24 extended function key support for Windows, GNOME Wayland, and hotkey capture

* feat: persist custom dictionary to SQLite with localStorage sync and inject into reasoning system prompts

* fix: update llama.cpp download script for new release asset naming and tar.gz format

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

* fix: add gh_token to downlaod scripts to avoid rate limits

---------

Co-authored-by: Craig von Chamier <craig@cvcwebsolutions.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-26 21:14:37 -08:00
Gabriel SteinandClaude 286fab53a8 Unify prompt system into single intelligent prompt (#136)
* refactor: unify system prompts with world-class dictation prompt

- Create centralized prompts.ts (renderer) and prompts.js (main process)
  with a single, comprehensive system prompt
- The unified prompt handles both cleanup and instruction modes,
  letting the LLM intelligently detect which mode to use based on context
- Remove code-based agent detection (text.includes(agentName)) in favor
  of LLM-based contextual understanding
- Update all providers (OpenAI, Anthropic, Gemini, Groq, Local) to use
  the new unified prompt
- Update PromptStudio UI to show single unified prompt with clear
  explanation of how cleanup vs instruction mode works
- Support custom prompt customization via localStorage

The new prompt clearly establishes:
- Primary function is ALWAYS cleanup (filler words, grammar, punctuation)
- Instruction mode is triggered when user directly addresses the agent
- LLM distinguishes between "Hey Agent, do X" vs "I told Agent about X"
- Strict output rules to prevent explanations/commentary

* feat: add smart formatting to unified dictation prompt

Add intelligent formatting capabilities that automatically apply
appropriate structure based on content context:

- Bullet points for lists (shopping, to-do, enumerated items)
- Numbered lists when order/sequence matters
- Paragraph breaks between distinct topics
- Email formatting (greeting, body, closing)
- Social media post structure (hook, body, CTA)

Includes guidance to NOT over-format simple dictations.

* refactor: rename AI Prompts to Prompt Studio in settings

- Rename sidebar item from "AI Prompts" to "Prompt Studio"
- Update section header and description to reflect unified prompt approach
- Clarify that there's a single system prompt for both cleanup and instructions

* refactor: remove cli fallbacks and add llama-server

* fix: add llama-server to build config, extract shared download utils, consolidate prompts to single JSON source, improve health check reliability with failure threshold

* refactor: reuse components between settings and onboarding

* chore: cleanups

* chore: ui cleanups

* feat: AI models to AI text cleanup label change

* chore: UI cleanups

* refactor: improve persisting of api keys

* chore: cleanup dead code

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-22 18:32:56 -08:00