Skip to content

Add Phrases: trigger → snippet substitution for dictated text#777

Open
mattmoran56 wants to merge 3 commits into
OpenWhispr:mainfrom
mattmoran56:session/feat/snippets
Open

Add Phrases: trigger → snippet substitution for dictated text#777
mattmoran56 wants to merge 3 commits into
OpenWhispr:mainfrom
mattmoran56:session/feat/snippets

Conversation

@mattmoran56
Copy link
Copy Markdown

@mattmoran56 mattmoran56 commented May 15, 2026

Summary

Adds a new Phrases section to the Control Panel sidebar where users define trigger → snippet pairs. After Whisper transcribes their speech, each trigger phrase is replaced with the corresponding snippet while surrounding text is preserved verbatim.

Use cases: dictate "my email" and have it expand to an email address; dictate "message template one best wishes Matt" and have the template expand while "best wishes Matt" stays intact.

Changes

  • New Phrases view (src/components/PhrasesView.tsx) with empty state, inline-editable trigger / snippet rows, auto-growing snippet textarea (CSS field-sizing: content), and add/delete controls
  • Sidebar entry wired through ControlPanelSidebar.tsx and ControlPanel.tsx (new phrases view, Replace icon)
  • Substitution engine (src/utils/phraseSubstitution.js) — case-insensitive, Unicode-aware word boundaries (\p{L}\p{N}), longer triggers win when they overlap, regex metacharacters escaped, blank triggers skipped
  • Single pipeline injection in audioManager.processAudio after reasoning, before paste/save — covers every transcription provider (local Whisper, Parakeet, OpenWhispr cloud, OpenAI). Raw transcript is preserved in DB; only the substituted text reaches the clipboard
  • Persistence via customPhrases in settingsStore (localStorage, mirroring customDictionary); typed CustomPhrase shape in src/types/phrases.ts
  • i18nsidebar.phrases + phrases.* keys translated into all 10 locales (en source + de, es, fr, it, ja, pt, ru, zh-CN, zh-TW). Each non-English phrases block carries a _comment field noting the translations were AI-generated by Claude and have not been reviewed by a native speaker
  • Teststest/helpers/phraseSubstitution.test.js (12 cases) using node:test, matching the repo's existing test convention. Adds an npm test script that runs the full test/**/*.test.js suite (24 tests total alongside the 12 pre-existing helper tests)

Out of scope (follow-up)

  • Streaming dictation bypasses processAudio; phrase substitution only runs on the post-transcription path for v1
  • Snippet templating ({{date}} etc.) is not supported

Testing

  • npm run lint — 0 errors (3 pre-existing warnings unrelated to this change)
  • npm run typecheck — clean
  • npm test — 24/24 pass (12 new + 12 pre-existing). New cases: basic substitution, trailing punctuation preserved, case-insensitive matching, plural word-boundary non-match, longest-trigger-wins, empty-list no-op, leading-position match, multi-line snippets, regex-metachar escaping, blank-trigger handling, multiple occurrences, empty text
  • node scripts/check-i18n.js — locale keys and placeholders consistent across all 10 locales
  • Manual: open Control Panel → new Phrases sidebar entry renders, empty state shows "Add your first phrase" CTA
  • Manual: add a phrase my emailme@example.com, verify it persists across reloads (localStorage)
  • Manual: dictate "send my email please" — clipboard receives "send me@example.com please" (trigger expanded, surrounding text preserved)
  • Manual: dictate "message template one, best wishes Matt" with message template one → multi-line template — trailing ", best wishes Matt" preserved verbatim after the inserted template
  • Manual: dictate with no matching phrase — output unchanged (no-op when triggers don't fire)
  • Manual: dictate "my emails are slow" with trigger my email — does not match (word-boundary respect verified end-to-end)
  • Manual: snippet textarea auto-grows vertically as content is typed
  • Manual: deleting a phrase removes it from subsequent dictations
  • Manual: History view still shows the raw spoken words (rawText preserved in DB), while clipboard/preview show the substituted text

📝 Personal note: this is one of my favourite features on the paid Whispr apps — it'd be awesome to see it land here. Happy to revise direction, scope, or naming based on feedback!

@mattmoran56 mattmoran56 marked this pull request as draft May 15, 2026 10:44
@mattmoran56 mattmoran56 force-pushed the session/feat/snippets branch from 04e55e0 to 4ce275b Compare May 15, 2026 10:50
Add a Phrases sidebar where users define trigger → snippet pairs.
After Whisper transcribes speech, each trigger is replaced with its
snippet while surrounding text is preserved. Matching is case-insensitive
and word-boundary aware; longer triggers win when they overlap.
Cover basic substitution, trailing punctuation preservation, case
insensitivity, word boundaries, longest-match priority, regex
metacharacter escaping, multi-line snippets, blank-trigger handling,
multiple occurrences, and empty inputs. Add an npm test script that
runs the existing node:test suite under test/.
Replace English fallbacks for the new phrases.* keys with translated
copy in de, es, fr, it, ja, pt, ru, zh-CN, zh-TW. Each non-English
phrases block carries a _comment noting the translations were
AI-generated (Claude) and have not been reviewed by a native speaker.
@mattmoran56 mattmoran56 force-pushed the session/feat/snippets branch from 485ad05 to 0219405 Compare May 15, 2026 11:30
@mattmoran56 mattmoran56 marked this pull request as ready for review May 15, 2026 12:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant