├── .cargo └── config.toml ├── src-tauri ├── rustfmt.toml ├── src │ ├── helpers │ │ ├── mod.rs │ │ └── clamshell.rs │ ├── audio_toolkit │ │ ├── constants.rs │ │ ├── mod.rs │ │ ├── audio │ │ │ ├── mod.rs │ │ │ ├── utils.rs │ │ │ ├── device.rs │ │ │ └── resampler.rs │ │ ├── utils.rs │ │ └── vad │ │ │ ├── mod.rs │ │ │ ├── silero.rs │ │ │ └── smoothed.rs │ ├── managers │ │ └── mod.rs │ ├── main.rs │ ├── llm_client.rs │ ├── commands │ │ ├── transcription.rs │ │ ├── history.rs │ │ └── mod.rs │ ├── utils.rs │ └── signal_handle.rs ├── build.rs ├── icons │ ├── 32x32.png │ ├── 64x64.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ ├── logo.png │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── StoreLogo.png │ ├── Square30x30Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square310x310Logo.png │ ├── ios │ │ ├── AppIcon-512@2x.png │ │ ├── AppIcon-20x20@1x.png │ │ ├── AppIcon-20x20@2x.png │ │ ├── AppIcon-20x20@3x.png │ │ ├── AppIcon-29x29@1x.png │ │ ├── AppIcon-29x29@2x.png │ │ ├── AppIcon-29x29@3x.png │ │ ├── AppIcon-40x40@1x.png │ │ ├── AppIcon-40x40@2x.png │ │ ├── AppIcon-40x40@3x.png │ │ ├── AppIcon-60x60@2x.png │ │ ├── AppIcon-60x60@3x.png │ │ ├── AppIcon-76x76@1x.png │ │ ├── AppIcon-76x76@2x.png │ │ ├── AppIcon-20x20@2x-1.png │ │ ├── AppIcon-29x29@2x-1.png │ │ ├── AppIcon-40x40@2x-1.png │ │ └── AppIcon-83.5x83.5@2x.png │ └── android │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png │ │ └── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_launcher_foreground.png ├── resources │ ├── handy.png │ ├── pop_start.wav │ ├── pop_stop.wav │ ├── recording.png │ ├── tray_idle.png │ ├── marimba_start.wav │ ├── marimba_stop.wav │ ├── transcribing.png │ ├── tray_idle_dark.png │ ├── tray_recording.png │ ├── tray_recording_dark.png │ ├── tray_transcribing.png │ ├── models │ │ └── silero_vad_v4.onnx │ ├── tray_transcribing_dark.png │ └── default_settings.json ├── .gitignore ├── Info.plist ├── capabilities │ ├── desktop.json │ └── default.json ├── Entitlements.plist ├── gen │ └── apple │ │ └── PrivacyInfo.xcprivacy ├── tauri.conf.json └── Cargo.toml ├── .prettierrc ├── src ├── vite-env.d.ts ├── components │ ├── footer │ │ ├── index.ts │ │ └── Footer.tsx │ ├── onboarding │ │ ├── index.ts │ │ ├── ModelCard.tsx │ │ └── Onboarding.tsx │ ├── update-checker │ │ └── index.ts │ ├── settings │ │ ├── PostProcessingSettingsApi │ │ │ ├── types.ts │ │ │ ├── index.tsx │ │ │ ├── ProviderSelect.tsx │ │ │ ├── ApiKeyField.tsx │ │ │ ├── BaseUrlField.tsx │ │ │ └── ModelSelect.tsx │ │ ├── PostProcessingSettingsPrompts.tsx │ │ ├── debug │ │ │ ├── index.ts │ │ │ ├── WordCorrectionThreshold.tsx │ │ │ ├── DebugPaths.tsx │ │ │ ├── LogLevelSelector.tsx │ │ │ ├── DebugSettings.tsx │ │ │ └── LogDirectory.tsx │ │ ├── VolumeSlider.tsx │ │ ├── PushToTalk.tsx │ │ ├── StartHidden.tsx │ │ ├── AutostartToggle.tsx │ │ ├── PostProcessingToggle.tsx │ │ ├── UpdateChecksToggle.tsx │ │ ├── MuteWhileRecording.tsx │ │ ├── AlwaysOnMicrophone.tsx │ │ ├── AppendTrailingSpace.tsx │ │ ├── AudioFeedback.tsx │ │ ├── advanced │ │ │ └── AdvancedSettings.tsx │ │ ├── general │ │ │ └── GeneralSettings.tsx │ │ ├── ShowOverlay.tsx │ │ ├── HistoryLimit.tsx │ │ ├── ClipboardHandling.tsx │ │ ├── index.ts │ │ ├── RecordingRetentionPeriod.tsx │ │ ├── SoundPicker.tsx │ │ ├── PasteMethod.tsx │ │ ├── TranslateToEnglish.tsx │ │ ├── MicrophoneSelector.tsx │ │ ├── ModelUnloadTimeout.tsx │ │ ├── AppDataDirectory.tsx │ │ ├── OutputDeviceSelector.tsx │ │ ├── about │ │ │ └── AboutSettings.tsx │ │ ├── ClamshellMicrophoneSelector.tsx │ │ └── CustomWords.tsx │ ├── shared │ │ ├── index.ts │ │ └── ProgressBar.tsx │ ├── icons │ │ ├── index.ts │ │ ├── ResetIcon.tsx │ │ ├── CancelIcon.tsx │ │ └── MicrophoneIcon.tsx │ ├── model-selector │ │ ├── index.ts │ │ ├── DownloadProgressDisplay.tsx │ │ └── ModelStatusButton.tsx │ ├── ui │ │ ├── index.ts │ │ ├── Badge.tsx │ │ ├── SettingsGroup.tsx │ │ ├── Textarea.tsx │ │ ├── ResetButton.tsx │ │ ├── Input.tsx │ │ ├── Button.tsx │ │ ├── ToggleSwitch.tsx │ │ ├── Slider.tsx │ │ ├── TextDisplay.tsx │ │ └── Dropdown.tsx │ ├── AccessibilityPermissions.tsx │ └── Sidebar.tsx ├── main.tsx ├── overlay │ ├── main.tsx │ ├── index.html │ ├── RecordingOverlay.css │ └── RecordingOverlay.tsx ├── lib │ ├── utils │ │ └── format.ts │ └── constants │ │ └── languages.ts ├── App.css ├── hooks │ └── useSettings.ts └── App.tsx ├── .github ├── FUNDING.yml ├── workflows │ ├── prettier.yml │ ├── build-test.yml │ └── release.yml └── ISSUE_TEMPLATE │ ├── config.yml │ ├── bug_report.md │ └── feature_request.md ├── sponsor-images ├── epicenter.png └── wordcab.png ├── .vscode └── extensions.json ├── tsconfig.node.json ├── .prettierignore ├── index.html ├── .gitignore ├── tailwind.config.js ├── tsconfig.json ├── LICENSE ├── vite.config.ts ├── BUILD.md ├── package.json ├── CRUSH.md └── AGENTS.md /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | -------------------------------------------------------------------------------- /src-tauri/rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf" 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/src/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod clamshell; 2 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src/components/footer/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Footer"; 2 | -------------------------------------------------------------------------------- /src/components/onboarding/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./Onboarding"; 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: cjpais 2 | custom: https://handy.computer/donate 3 | -------------------------------------------------------------------------------- /src-tauri/src/audio_toolkit/constants.rs: -------------------------------------------------------------------------------- 1 | pub const WHISPER_SAMPLE_RATE: u32 = 16000; 2 | -------------------------------------------------------------------------------- /src/components/update-checker/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./UpdateChecker"; 2 | -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/64x64.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/logo.png -------------------------------------------------------------------------------- /sponsor-images/epicenter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/sponsor-images/epicenter.png -------------------------------------------------------------------------------- /sponsor-images/wordcab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/sponsor-images/wordcab.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/resources/handy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/resources/handy.png -------------------------------------------------------------------------------- /src-tauri/resources/pop_start.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/resources/pop_start.wav -------------------------------------------------------------------------------- /src-tauri/resources/pop_stop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/resources/pop_stop.wav -------------------------------------------------------------------------------- /src-tauri/resources/recording.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/resources/recording.png -------------------------------------------------------------------------------- /src-tauri/resources/tray_idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/resources/tray_idle.png -------------------------------------------------------------------------------- /src-tauri/src/managers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod audio; 2 | pub mod history; 3 | pub mod model; 4 | pub mod transcription; 5 | -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-512@2x.png -------------------------------------------------------------------------------- /src-tauri/resources/marimba_start.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/resources/marimba_start.wav -------------------------------------------------------------------------------- /src-tauri/resources/marimba_stop.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/resources/marimba_stop.wav -------------------------------------------------------------------------------- /src-tauri/resources/transcribing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/resources/transcribing.png -------------------------------------------------------------------------------- /src-tauri/resources/tray_idle_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/resources/tray_idle_dark.png -------------------------------------------------------------------------------- /src-tauri/resources/tray_recording.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/resources/tray_recording.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-20x20@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-20x20@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-20x20@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-29x29@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-29x29@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-29x29@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-40x40@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-40x40@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-40x40@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-60x60@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-60x60@3x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-76x76@1x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-76x76@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-20x20@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-20x20@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-29x29@2x-1.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-40x40@2x-1.png -------------------------------------------------------------------------------- /src-tauri/resources/tray_recording_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/resources/tray_recording_dark.png -------------------------------------------------------------------------------- /src-tauri/resources/tray_transcribing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/resources/tray_transcribing.png -------------------------------------------------------------------------------- /src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png -------------------------------------------------------------------------------- /src-tauri/resources/models/silero_vad_v4.onnx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/resources/models/silero_vad_v4.onnx -------------------------------------------------------------------------------- /src/components/settings/PostProcessingSettingsApi/types.ts: -------------------------------------------------------------------------------- 1 | export type ModelOption = { 2 | value: string; 3 | label: string; 4 | }; 5 | -------------------------------------------------------------------------------- /src-tauri/resources/tray_transcribing_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/resources/tray_transcribing_dark.png -------------------------------------------------------------------------------- /src/components/shared/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ProgressBar } from "./ProgressBar"; 2 | export type { ProgressData } from "./ProgressBar"; 3 | -------------------------------------------------------------------------------- /src/components/settings/PostProcessingSettingsApi/index.tsx: -------------------------------------------------------------------------------- 1 | export { PostProcessingSettingsApi } from "../post-processing/PostProcessingSettings"; 2 | -------------------------------------------------------------------------------- /src/components/settings/PostProcessingSettingsPrompts.tsx: -------------------------------------------------------------------------------- 1 | export { PostProcessingSettingsPrompts } from "./post-processing/PostProcessingSettings"; 2 | -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "tauri-apps.tauri-vscode", 4 | "rust-lang.rust-analyzer", 5 | "esbenp.prettier-vscode" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shanselman/Handy/main/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Generated by Tauri 6 | # will have schema files for capabilities auto-completion 7 | /gen/schemas 8 | -------------------------------------------------------------------------------- /src/components/settings/debug/index.ts: -------------------------------------------------------------------------------- 1 | export { WordCorrectionThreshold } from "./WordCorrectionThreshold"; 2 | export { LogDirectory } from "./LogDirectory"; 3 | export { LogLevelSelector } from "./LogLevelSelector"; 4 | -------------------------------------------------------------------------------- /src/components/icons/index.ts: -------------------------------------------------------------------------------- 1 | export { default as MicrophoneIcon } from "./MicrophoneIcon"; 2 | export { default as TranscriptionIcon } from "./TranscriptionIcon"; 3 | export { default as CancelIcon } from "./CancelIcon"; 4 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | // Prevents additional console window on Windows in release, DO NOT REMOVE!! 2 | #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] 3 | 4 | fn main() { 5 | handy_app_lib::run() 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | 5 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 6 | 7 | 8 | , 9 | ); 10 | -------------------------------------------------------------------------------- /src/components/model-selector/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from "./ModelSelector"; 2 | export { default as ModelStatusButton } from "./ModelStatusButton"; 3 | export { default as ModelDropdown } from "./ModelDropdown"; 4 | export { default as DownloadProgressDisplay } from "./DownloadProgressDisplay"; 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | bun.lock 4 | package-lock.json 5 | 6 | # Build outputs 7 | dist 8 | target 9 | *.bundle.* 10 | 11 | # Tauri 12 | src-tauri/target 13 | src-tauri/gen 14 | 15 | # Generated files 16 | src/bindings.ts 17 | 18 | # Misc 19 | .DS_Store 20 | *.log 21 | -------------------------------------------------------------------------------- /src/overlay/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import RecordingOverlay from "./RecordingOverlay"; 4 | 5 | ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( 6 | 7 | 8 | , 9 | ); 10 | -------------------------------------------------------------------------------- /src-tauri/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSMicrophoneUsageDescription 6 | Request microphone access to transcribe audio locally 7 | 8 | -------------------------------------------------------------------------------- /src-tauri/capabilities/desktop.json: -------------------------------------------------------------------------------- 1 | { 2 | "identifier": "desktop-capability", 3 | "platforms": ["macOS", "windows", "linux"], 4 | "windows": ["main"], 5 | "permissions": [ 6 | "autostart:default", 7 | "global-shortcut:default", 8 | "autostart:default", 9 | "autostart:default", 10 | "updater:default" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/components/ui/index.ts: -------------------------------------------------------------------------------- 1 | export { Dropdown } from "./Dropdown"; 2 | export { Slider } from "./Slider"; 3 | export { ToggleSwitch } from "./ToggleSwitch"; 4 | export { SettingContainer } from "./SettingContainer"; 5 | export { SettingsGroup } from "./SettingsGroup"; 6 | export { TextDisplay } from "./TextDisplay"; 7 | export { Textarea } from "./Textarea"; 8 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | handy 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src-tauri/Entitlements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.device.microphone 6 | 7 | com.apple.security.device.audio-input 8 | 9 | 10 | -------------------------------------------------------------------------------- /src-tauri/src/audio_toolkit/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod audio; 2 | pub mod constants; 3 | pub mod text; 4 | pub mod utils; 5 | pub mod vad; 6 | 7 | pub use audio::{ 8 | list_input_devices, list_output_devices, save_wav_file, AudioRecorder, CpalDeviceInfo, 9 | }; 10 | pub use text::apply_custom_words; 11 | pub use utils::get_cpal_host; 12 | pub use vad::{SileroVad, VoiceActivityDetector}; 13 | -------------------------------------------------------------------------------- /src-tauri/src/audio_toolkit/audio/mod.rs: -------------------------------------------------------------------------------- 1 | // Re-export all audio components 2 | mod device; 3 | mod recorder; 4 | mod resampler; 5 | mod utils; 6 | mod visualizer; 7 | 8 | pub use device::{list_input_devices, list_output_devices, CpalDeviceInfo}; 9 | pub use recorder::AudioRecorder; 10 | pub use resampler::FrameResampler; 11 | pub use utils::save_wav_file; 12 | pub use visualizer::AudioVisualiser; 13 | -------------------------------------------------------------------------------- /src-tauri/resources/default_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "bindings": { 3 | "transcribe": { 4 | "id": "transcribe", 5 | "name": "Transcribe Keyboard Shortcut", 6 | "description": "Converts your speech into text.", 7 | "default_binding": "Platform-specific: ctrl+space (Windows/Linux), alt+space (macOS)" 8 | } 9 | }, 10 | "push_to_talk": true, 11 | "selected_language": "auto" 12 | } 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | *.local.* 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | 27 | /target/ 28 | recording_* 29 | .crush/ 30 | -------------------------------------------------------------------------------- /.github/workflows/prettier.yml: -------------------------------------------------------------------------------- 1 | name: "prettier" 2 | on: [pull_request] 3 | 4 | jobs: 5 | prettier: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v4 9 | 10 | - uses: oven-sh/setup-bun@v1 11 | with: 12 | bun-version: latest 13 | 14 | - name: Install dependencies 15 | run: bun install --frozen-lockfile 16 | 17 | - name: Run prettier 18 | run: bun run format:check 19 | -------------------------------------------------------------------------------- /src-tauri/src/audio_toolkit/utils.rs: -------------------------------------------------------------------------------- 1 | /// Returns the appropriate CPAL host for the current platform. 2 | /// On Linux, uses ALSA host. On other platforms, uses the default host. 3 | pub fn get_cpal_host() -> cpal::Host { 4 | #[cfg(target_os = "linux")] 5 | { 6 | cpal::host_from_id(cpal::HostId::Alsa).unwrap_or_else(|_| cpal::default_host()) 7 | } 8 | #[cfg(not(target_os = "linux"))] 9 | { 10 | cpal::default_host() 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: { 6 | colors: { 7 | text: "var(--color-text)", 8 | background: "var(--color-background)", 9 | "logo-primary": "var(--color-logo-primary)", 10 | "logo-stroke": "var(--color-logo-stroke)", 11 | "text-stroke": "var(--color-text-stroke)", 12 | }, 13 | }, 14 | }, 15 | plugins: [], 16 | }; 17 | -------------------------------------------------------------------------------- /src-tauri/gen/apple/PrivacyInfo.xcprivacy: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSPrivacyAccessedAPITypes 6 | 7 | 8 | NSPrivacyAccessedAPIType 9 | NSPrivacyAccessedAPICategoryFileTimestamp 10 | NSPrivacyAccessedAPITypeReasons 11 | 12 | C617.1 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/components/ui/Badge.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface BadgeProps { 4 | children: React.ReactNode; 5 | variant?: "primary"; 6 | className?: string; 7 | } 8 | 9 | const Badge: React.FC = ({ 10 | children, 11 | variant = "primary", 12 | className = "", 13 | }) => { 14 | const variantClasses = { 15 | primary: "bg-logo-primary", 16 | }; 17 | 18 | return ( 19 | 22 | {children} 23 | 24 | ); 25 | }; 26 | 27 | export default Badge; 28 | -------------------------------------------------------------------------------- /src/overlay/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Recording Overlay 6 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src-tauri/capabilities/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../gen/schemas/desktop-schema.json", 3 | "identifier": "default", 4 | "description": "Capabilities for the app", 5 | "windows": ["main", "recording_overlay"], 6 | "permissions": [ 7 | "core:default", 8 | "opener:default", 9 | "store:default", 10 | "updater:default", 11 | "process:default", 12 | "global-shortcut:allow-is-registered", 13 | "global-shortcut:allow-register", 14 | "global-shortcut:allow-unregister", 15 | "global-shortcut:allow-unregister-all", 16 | "macos-permissions:default", 17 | "fs:read-files", 18 | "fs:allow-resource-read-recursive" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/lib/utils/format.ts: -------------------------------------------------------------------------------- 1 | export const formatModelSize = (sizeMb: number | null | undefined): string => { 2 | if (!sizeMb || !Number.isFinite(sizeMb) || sizeMb <= 0) { 3 | return "Unknown size"; 4 | } 5 | 6 | if (sizeMb >= 1024) { 7 | const sizeGb = sizeMb / 1024; 8 | const formatter = new Intl.NumberFormat(undefined, { 9 | minimumFractionDigits: sizeGb >= 10 ? 0 : 1, 10 | maximumFractionDigits: sizeGb >= 10 ? 0 : 1, 11 | }); 12 | return `${formatter.format(sizeGb)} GB`; 13 | } 14 | 15 | const formatter = new Intl.NumberFormat(undefined, { 16 | minimumFractionDigits: sizeMb >= 100 ? 0 : 1, 17 | maximumFractionDigits: sizeMb >= 100 ? 0 : 1, 18 | }); 19 | 20 | return `${formatter.format(sizeMb)} MB`; 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/settings/PostProcessingSettingsApi/ProviderSelect.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Dropdown, type DropdownOption } from "../../ui/Dropdown"; 3 | 4 | interface ProviderSelectProps { 5 | options: DropdownOption[]; 6 | value: string; 7 | onChange: (value: string) => void; 8 | disabled?: boolean; 9 | } 10 | 11 | export const ProviderSelect: React.FC = React.memo( 12 | ({ options, value, onChange, disabled }) => { 13 | return ( 14 | 21 | ); 22 | }, 23 | ); 24 | 25 | ProviderSelect.displayName = "ProviderSelect"; 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: ✏️ Post-processing / Editing Transcripts 4 | url: https://github.com/cjpais/Handy/discussions/168 5 | about: Looking to edit, format, or post-process transcripts? Join this discussion 6 | - name: ⌨️ Keyboard Shortcuts / Hotkeys 7 | url: https://github.com/cjpais/Handy/discussions/211 8 | about: Want different keyboard shortcuts or hotkey configurations? Join this discussion 9 | - name: 💡 Feature Request or Idea 10 | url: https://github.com/cjpais/Handy/discussions 11 | about: Please post feature requests and ideas in our Discussions tab 12 | - name: 💬 General Discussion 13 | url: https://github.com/cjpais/Handy/discussions 14 | about: Ask questions and discuss Handy with the community 15 | -------------------------------------------------------------------------------- /src-tauri/src/audio_toolkit/audio/utils.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use hound::{WavSpec, WavWriter}; 3 | use log::debug; 4 | use std::path::Path; 5 | 6 | /// Save audio samples as a WAV file 7 | pub async fn save_wav_file>(file_path: P, samples: &[f32]) -> Result<()> { 8 | let spec = WavSpec { 9 | channels: 1, 10 | sample_rate: 16000, 11 | bits_per_sample: 16, 12 | sample_format: hound::SampleFormat::Int, 13 | }; 14 | 15 | let mut writer = WavWriter::create(file_path.as_ref(), spec)?; 16 | 17 | // Convert f32 samples to i16 for WAV 18 | for sample in samples { 19 | let sample_i16 = (sample * i16::MAX as f32) as i16; 20 | writer.write_sample(sample_i16)?; 21 | } 22 | 23 | writer.finalize()?; 24 | debug!("Saved WAV file: {:?}", file_path.as_ref()); 25 | Ok(()) 26 | } 27 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "types": ["node"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Path Aliases */ 19 | "baseUrl": ".", 20 | "paths": { 21 | "@/bindings": ["./src/bindings.ts"] 22 | }, 23 | 24 | /* Linting */ 25 | "strict": true, 26 | "noUnusedLocals": false, 27 | "noUnusedParameters": false, 28 | "noFallthroughCasesInSwitch": true 29 | }, 30 | "include": ["src"], 31 | "references": [{ "path": "./tsconfig.node.json" }] 32 | } 33 | -------------------------------------------------------------------------------- /src/components/settings/VolumeSlider.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Slider } from "../ui/Slider"; 3 | import { useSettings } from "../../hooks/useSettings"; 4 | 5 | export const VolumeSlider: React.FC<{ disabled?: boolean }> = ({ 6 | disabled = false, 7 | }) => { 8 | const { getSetting, updateSetting } = useSettings(); 9 | const audioFeedbackVolume = getSetting("audio_feedback_volume") ?? 0.5; 10 | 11 | return ( 12 | 15 | updateSetting("audio_feedback_volume", value) 16 | } 17 | min={0} 18 | max={1} 19 | step={0.1} 20 | label="Volume" 21 | description="Adjust the volume of audio feedback sounds" 22 | descriptionMode="tooltip" 23 | grouped 24 | formatValue={(value) => `${Math.round(value * 100)}%`} 25 | disabled={disabled} 26 | /> 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /src/components/ui/SettingsGroup.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface SettingsGroupProps { 4 | title?: string; 5 | description?: string; 6 | children: React.ReactNode; 7 | } 8 | 9 | export const SettingsGroup: React.FC = ({ 10 | title, 11 | description, 12 | children, 13 | }) => { 14 | return ( 15 |
16 | {title && ( 17 |
18 |

19 | {title} 20 |

21 | {description && ( 22 |

{description}

23 | )} 24 |
25 | )} 26 |
27 |
{children}
28 |
29 |
30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/components/ui/Textarea.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | interface TextareaProps 4 | extends React.TextareaHTMLAttributes { 5 | variant?: "default" | "compact"; 6 | } 7 | 8 | export const Textarea: React.FC = ({ 9 | className = "", 10 | variant = "default", 11 | ...props 12 | }) => { 13 | const baseClasses = 14 | "px-2 py-1 text-sm font-semibold bg-mid-gray/10 border border-mid-gray/80 rounded text-left transition-[background-color,border-color] duration-150 hover:bg-logo-primary/10 hover:border-logo-primary focus:outline-none focus:bg-logo-primary/10 focus:border-logo-primary resize-y"; 15 | 16 | const variantClasses = { 17 | default: "px-3 py-2 min-h-[100px]", 18 | compact: "px-2 py-1 min-h-[80px]", 19 | }; 20 | 21 | return ( 22 |