diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index fe1f6cf..36b06d7 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,9 +1,9 @@ --- name: Bug Report about: Create a report to help us improve Handy -title: '[BUG] ' -labels: ['bug'] -assignees: '' +title: "[BUG] " +labels: ["bug"] +assignees: "" --- ## Before You Submit @@ -11,17 +11,23 @@ assignees: '' **Please search [existing issues](https://github.com/cjpais/Handy/issues) to avoid duplicates.** Your bug may already be reported! Right now it's just me maintaining this project so many issues can be overwhelming! Help me out by checking first. ## Bug Description + A clear and concise description of what the bug is. ## System Information + **App Version:** + **Operating System:** + **CPU:** + **GPU:** + diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 9a05c67..72597e7 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,18 +1,19 @@ --- name: Feature Request about: Suggest an idea or new feature for Handy -title: '' +title: "" labels: [] -assignees: '' +assignees: "" --- ## 🎯 Feature Requests Go in Discussions! -Thanks for your interest in improving Handy! +Thanks for your interest in improving Handy! **Please post feature requests in our [Discussions tab](https://github.com/cjpais/Handy/discussions) instead of opening an issue.** This helps us: + - Keep issues focused on bugs and actionable tasks - Have more open-ended conversations about features - Gather community feedback and input diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..aa64a9d --- /dev/null +++ b/.prettierignore @@ -0,0 +1,20 @@ +# Dependencies +node_modules +bun.lock +package-lock.json + +# Build outputs +dist +target +*.bundle.* + +# Tauri +src-tauri/target +src-tauri/gen + +# Generated files +src/bindings.ts + +# Misc +.DS_Store +*.log diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..a598fca --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "endOfLine": "lf" +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 24d7cc6..f86c2e2 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,7 @@ { - "recommendations": ["tauri-apps.tauri-vscode", "rust-lang.rust-analyzer"] + "recommendations": [ + "tauri-apps.tauri-vscode", + "rust-lang.rust-analyzer", + "esbenp.prettier-vscode" + ] } diff --git a/AGENTS.md b/AGENTS.md index 2806520..3bb64d0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,10 +5,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Development Commands **Prerequisites:** + - [Rust](https://rustup.rs/) (latest stable) - [Bun](https://bun.sh/) package manager **Core Development:** + ```bash # Install dependencies bun install @@ -28,6 +30,7 @@ bun run preview # Preview built frontend ``` **Model Setup (Required for Development):** + ```bash # Create models directory mkdir -p src-tauri/resources/models @@ -43,19 +46,21 @@ Handy is a cross-platform desktop speech-to-text application built with Tauri (R ### Core Components **Backend (Rust - src-tauri/src/):** + - `lib.rs` - Main application entry point with Tauri setup, tray menu, and managers - `managers/` - Core business logic managers: - `audio.rs` - Audio recording and device management - - `model.rs` - Whisper model downloading and management + - `model.rs` - Whisper model downloading and management - `transcription.rs` - Speech-to-text processing pipeline - `audio_toolkit/` - Low-level audio processing: - - `audio/` - Device enumeration, recording, resampling + - `audio/` - Device enumeration, recording, resampling - `vad/` - Voice Activity Detection using Silero VAD - `commands/` - Tauri command handlers for frontend communication - `shortcut.rs` - Global keyboard shortcut handling - `settings.rs` - Application settings management **Frontend (React/TypeScript - src/):** + - `App.tsx` - Main application component with onboarding flow - `components/settings/` - Settings UI components - `components/model-selector/` - Model management interface @@ -73,14 +78,16 @@ Handy is a cross-platform desktop speech-to-text application built with Tauri (R ### Technology Stack **Core Libraries:** + - `whisper-rs` - Local Whisper inference with GPU acceleration -- `cpal` - Cross-platform audio I/O +- `cpal` - Cross-platform audio I/O - `vad-rs` - Voice Activity Detection - `rdev` - Global keyboard shortcuts - `rubato` - Audio resampling - `rodio` - Audio playback for feedback sounds **Platform-Specific Features:** + - macOS: Metal acceleration for Whisper, accessibility permissions - Windows: Vulkan acceleration, code signing - Linux: OpenBLAS + Vulkan acceleration @@ -96,6 +103,7 @@ Handy is a cross-platform desktop speech-to-text application built with Tauri (R ### Settings System Settings are stored using Tauri's store plugin with reactive updates: + - Keyboard shortcuts (configurable, supports push-to-talk) - Audio devices (microphone/output selection) - Model preferences (Small/Medium/Turbo/Large Whisper variants) diff --git a/BUILD.md b/BUILD.md index cd3fea8..c9c29e6 100644 --- a/BUILD.md +++ b/BUILD.md @@ -5,6 +5,7 @@ This guide covers how to set up the development environment and build Handy from ## Prerequisites ### All Platforms + - [Rust](https://rustup.rs/) (latest stable) - [Bun](https://bun.sh/) package manager - [Tauri Prerequisites](https://tauri.app/start/prerequisites/) @@ -12,18 +13,22 @@ This guide covers how to set up the development environment and build Handy from ### Platform-Specific Requirements #### macOS + - Xcode Command Line Tools - Install with: `xcode-select --install` -#### Windows +#### Windows + - Microsoft C++ Build Tools - Visual Studio 2019/2022 with C++ development tools - Or Visual Studio Build Tools 2019/2022 #### Linux + - Build essentials - ALSA development libraries - Install with: + ```bash # Ubuntu/Debian sudo apt update @@ -42,15 +47,18 @@ This guide covers how to set up the development environment and build Handy from ## Setup Instructions ### 1. Clone the Repository + ```bash git clone git@github.com:cjpais/Handy.git cd Handy ``` ### 2. Install Dependencies + ```bash bun install ``` ### 3. Download Required Models + Handy requires a VAD (Voice Activity Detection) model to function diff --git a/CHANGELOG.md b/CHANGELOG.md index c6c295a..47f11c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,38 +3,45 @@ ## [0.3.0] - 2025-07-11 ### Added + - **Translate to English** setting: Added automatic translation of speech to English - Settings refactored into React hooks for better state management - Audio device switching capability - Hysteresis to VAD (Voice Activity Detection) for more stable recording ### Changed + - Major audio backend refactor for improved performance and reliability - Moved audio toolkit into src-tauri directory for better permissions handling - Model files no longer need to be downloaded separately for releases - Updated settings components and transcription logic ### Fixed + - Audio toolkit permissions issues - Various stability improvements ## [0.2.3] - 2025-07-03 ### Fixed + - Keycode bug that was causing input issues - Whisper model optimization: switched to unquantized Whisper Turbo, updated Whisper Medium quantization to 4_1 ## [0.2.2] - 2025-07-02 ### Fixed + - Removed 50ms delay feature flag for Windows (now applies to all platforms for consistency) ## [0.2.1] - 2025-07-01 ### Added + - Ctrl+Space key binding for Windows platform ### Fixed + - Windows crash issue - Model loading on startup when available - Windows paste functionality bug @@ -42,54 +49,64 @@ ## [0.2.0] - 2025-06-30 ### Added + - **Microphone activation on demand**: More efficient resource usage - Less permissive VAD settings for better accuracy ### Changed + - Improved microphone management and activation system ## [0.1.6] - 2025-06-30 ### Added + - **Multiple models support**: Users can now select from different transcription models - Model selection onboarding flow - Cleanup and refactoring of model management ### Changed + - Enhanced user experience with model selection interface - Better language and UI tweaks ## [0.1.5] - 2025-06-27 ### Added + - **Different start and stop recording sounds**: Enhanced audio feedback - Recording sound samples for better user experience ## [0.1.4] - 2025-06-27 ### Fixed + - Build issues - Auto-update functionality improvements ## [0.1.3] - 2025-06-26 ### Fixed + - Paste functionality using enigo library for better cross-platform compatibility ## [0.1.2] - 2025-06-26 ### Added + - **Auto-update functionality**: Application can now automatically update itself - Footer displaying current version - Improved menu system ### Changed + - Better user interface for version management - Enhanced update workflow ## [0.1.1] - 2025-06-25 ### Added + - **Comprehensive build system**: Support for Windows, macOS, and Linux - Windows code signing for trusted installation - Ubuntu/Linux build support with Vulkan @@ -97,15 +114,18 @@ - GitHub Actions CI/CD workflow ### Changed + - Improved build process and release workflow - Better cross-platform compatibility ### Fixed + - Various build-related issues across platforms ## [0.1.0] - 2025-05-16 ### Added + - **Initial release** of Handy - Basic speech-to-text transcription functionality - Voice Activity Detection (VAD) for automatic recording @@ -128,6 +148,7 @@ - **MIT License** for open-source distribution ### Technical Implementation + - Built with Tauri (Rust backend) and React (TypeScript frontend) - Audio processing with cpal and whisper-rs - Real-time transcription with performance optimizations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8fee6e9..94fd0d8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,22 +26,26 @@ Before you begin, ensure you have the following installed: 1. **Fork the repository** on GitHub 2. **Clone your fork**: + ```bash git clone git@github.com:YOUR_USERNAME/Handy.git cd Handy ``` 3. **Add upstream remote**: + ```bash git remote add upstream git@github.com:cjpais/Handy.git ``` 4. **Install dependencies**: + ```bash bun install ``` 5. **Download required models**: + ```bash mkdir -p src-tauri/resources/models curl -o src-tauri/resources/models/silero_vad_v4.onnx https://blob.handy.computer/silero_vad_v4.onnx @@ -61,6 +65,7 @@ For detailed platform-specific setup instructions, see [BUILD.md](BUILD.md). Handy follows a clean architecture pattern: **Backend (Rust - `src-tauri/src/`):** + - `lib.rs` - Main application entry point with Tauri setup - `managers/` - Core business logic (audio, model, transcription) - `audio_toolkit/` - Low-level audio processing (recording, VAD) @@ -69,6 +74,7 @@ Handy follows a clean architecture pattern: - `settings.rs` - Application settings management **Frontend (React/TypeScript - `src/`):** + - `App.tsx` - Main application component - `components/` - React UI components - `hooks/` - Reusable React hooks @@ -90,12 +96,14 @@ For more details, see the Architecture section in [README.md](README.md) or [AGE When creating a bug report, please include: **System Information:** + - App version (found in settings or about section) - Operating System (e.g., macOS 14.1, Windows 11, Ubuntu 22.04) - CPU (e.g., Apple M2, Intel i7-12700K, AMD Ryzen 7 5800X) - GPU (e.g., Apple M2 GPU, NVIDIA RTX 4080, Intel UHD Graphics) **Bug Details:** + - Clear description of the bug - Steps to reproduce - Expected behavior @@ -132,6 +140,7 @@ We use GitHub Discussions for feature requests rather than issues. This keeps is ### Development Workflow 1. **Create a feature branch**: + ```bash git checkout -b feature/your-feature-name # or @@ -151,6 +160,7 @@ We use GitHub Discussions for feature requests rather than issues. This keeps is - Use debug mode to verify audio/transcription behavior 4. **Commit your changes**: + ```bash git add . git commit -m "feat: add your feature description" @@ -167,12 +177,14 @@ We use GitHub Discussions for feature requests rather than issues. This keeps is - `chore:` for maintenance tasks 5. **Keep your fork updated**: + ```bash git fetch upstream git rebase upstream/main ``` 6. **Push to your fork**: + ```bash git push origin feature/your-feature-name ``` @@ -191,6 +203,7 @@ We use GitHub Discussions for feature requests rather than issues. This keeps is ### Code Style Guidelines **Rust:** + - Follow standard Rust formatting (`cargo fmt`) - Run `cargo clippy` and address warnings - Use descriptive variable and function names @@ -198,6 +211,7 @@ We use GitHub Discussions for feature requests rather than issues. This keeps is - Handle errors explicitly (avoid unwrap in production code) **TypeScript/React:** + - Use TypeScript strictly, avoid `any` types - Follow React hooks best practices - Use functional components @@ -205,6 +219,7 @@ We use GitHub Discussions for feature requests rather than issues. This keeps is - Use Tailwind CSS for styling **General:** + - Write self-documenting code - Add comments for non-obvious logic - Keep functions small and single-purpose @@ -213,6 +228,7 @@ We use GitHub Discussions for feature requests rather than issues. This keeps is ### Testing Your Changes **Manual Testing:** + - Run the app in development mode: `bun run tauri dev` - Test your changes with debug mode enabled - Verify on multiple platforms if possible @@ -220,6 +236,7 @@ We use GitHub Discussions for feature requests rather than issues. This keeps is - Try various transcription scenarios **Building for Production:** + ```bash bun run tauri build ``` @@ -247,6 +264,7 @@ Documentation improvements are highly valued! You can contribute by: ## 🎯 Good First Issues Look for issues labeled `good first issue` or `help wanted` if you're new to the project. These are typically: + - Well-defined and scoped - Good for learning the codebase - Mentor support available diff --git a/CRUSH.md b/CRUSH.md index be1126e..ab03fff 100644 --- a/CRUSH.md +++ b/CRUSH.md @@ -1,6 +1,7 @@ # Development Commands **Environment Setup:** + ```bash bun install # Install dependencies mkdir -p src-tauri/resources/models @@ -8,6 +9,7 @@ curl -o src-tauri/resources/models/silero_vad_v4.onnx https://blob.handy.compute ``` **Development:** + ```bash bun run tauri dev # Full app development CMAKE_POLICY_VERSION_MINIMUM=3.5 bun run tauri dev # macOS with cmake fix @@ -17,14 +19,16 @@ bun run tauri build # Production build ``` **Type Check & Build:** + ```bash -bunx tsc --noEmit # Type checking +bunx tsc --noEmit # Type checking bun run build # Build and validate ``` # Code Style Guidelines **Rust (Backend):** + - Use `anyhow::Error` for error handling with descriptive messages - Prefer `Arc>` for shared state in managers - Log with appropriate levels: `debug!`, `info!`, `eprintln!` for errors @@ -33,6 +37,7 @@ bun run build # Build and validate - Separate logical sections with comment blocks: `/* ─────────── */` **TypeScript/React (Frontend):** + - Functional components with TypeScript interfaces - Zod schemas for type validation and inference - `useCallback` hooks for stable function references @@ -42,16 +47,19 @@ bun run build # Build and validate - PascalCase for components, camelCase for variables/functions **Imports:** + - Group imports: external libs, internal modules, relative imports - Use type imports for TypeScript: `import type { Settings }` - Named imports preferred over default exports **Error Handling:** + - Frontend: Try/catch with user feedback, rollback optimistic updates - Backend: `?` operator with anyhow context messages - Log errors appropriately for debugging level **Component Patterns:** + - Container component pattern for layout - Composition over inheritance -- Prop drilling minimized with context where appropriate \ No newline at end of file +- Prop drilling minimized with context where appropriate diff --git a/README.md b/README.md index 018f604..e33212e 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Handy isn't trying to be the best speech-to-text app—it's trying to be the mos 4. **Get** your transcribed text pasted directly into whatever app you're using The process is entirely local: + - Silence is filtered using VAD (Voice Activity Detection) with Silero - Transcription uses your choice of models: - **Whisper models** (Small/Medium/Turbo/Large) with GPU acceleration when available @@ -62,6 +63,7 @@ Handy is built as a Tauri application combining: ### Debug Mode Handy includes an advanced debug mode for development and troubleshooting. Access it by pressing: + - **macOS**: `Cmd+Shift+D` - **Windows/Linux**: `Ctrl+Shift+D` @@ -72,14 +74,17 @@ This project is actively being developed and has some [known issues](https://git ### Major Issues (Help Wanted) **Whisper Model Crashes:** + - Whisper models crash on certain system configurations (Windows and Linux) - Does not affect all systems - issue is configuration-dependent - If you experience crashes and are a developer, please help to fix and provide debug logs! **Wayland Support (Linux):** + - Limited or no support for Wayland display server ### Platform Support + - **macOS (both Intel and Apple Silicon)** - **x64 Windows** - **x64 Linux** @@ -89,12 +94,14 @@ This project is actively being developed and has some [known issues](https://git The following are recommendations for running Handy on your own machine. If you don't meet the system requirements, the performance of the application may be degraded. We are working on improving the performance across all kinds of computers and hardware. **For Whisper Models:** + - **macOS**: M series Mac, Intel Mac - **Windows**: Intel, AMD, or NVIDIA GPU - **Linux**: Intel, AMD, or NVIDIA GPU - * Ubuntu 22.04, 24.04 + - Ubuntu 22.04, 24.04 **For Parakeet V3 Model:** + - **CPU-only operation** - runs on a wide variety of hardware - **Minimum**: Intel Skylake (6th gen) or equivalent AMD processors - **Performance**: ~5x real-time speed on mid-range hardware (tested on i5) @@ -107,25 +114,29 @@ We're actively working on several features and improvements. Contributions and f ### In Progress **Debug Logging:** + - Adding debug logging to a file to help diagnose issues **macOS Keyboard Improvements:** + - Support for Globe key as transcription trigger - A rewrite of global shortcut handling for MacOS, and potentially other OS's too. **Opt-in Analytics:** + - Collect anonymous usage data to help improve Handy - Privacy-first approach with clear opt-in **Settings Refactoring:** + - Cleanup and refactor settings system which is becoming bloated and messy - Implement better abstractions for settings management **Tauri Commands Cleanup:** + - Abstract and organize Tauri command patterns - Investigate tauri-specta for improved type safety and organization - ## Troubleshooting ### Manual Model Installation (For Proxy Users or Network Restrictions) @@ -141,6 +152,7 @@ If you're behind a proxy, firewall, or in a restricted network environment where - **Windows/Linux**: `Ctrl+Shift+D` to open debug menu The typical paths are: + - **macOS**: `~/Library/Application Support/com.pais.handy/` - **Windows**: `C:\Users\{username}\AppData\Roaming\com.pais.handy\` - **Linux**: `~/.config/com.pais.handy/` @@ -162,12 +174,14 @@ New-Item -ItemType Directory -Force -Path "$env:APPDATA\com.pais.handy\models" Download the models you want from below **Whisper Models (single .bin files):** + - Small (487 MB): `https://blob.handy.computer/ggml-small.bin` - Medium (492 MB): `https://blob.handy.computer/whisper-medium-q4_1.bin` - Turbo (1600 MB): `https://blob.handy.computer/ggml-large-v3-turbo.bin` - Large (1100 MB): `https://blob.handy.computer/ggml-large-v3-q5_0.bin` **Parakeet Models (compressed archives):** + - V2 (473 MB): `https://blob.handy.computer/parakeet-v2-int8.tar.gz` - V3 (478 MB): `https://blob.handy.computer/parakeet-v3-int8.tar.gz` @@ -206,6 +220,7 @@ Final structure should look like: ``` **Important Notes:** + - For Parakeet models, the extracted directory name **must** match exactly as shown above - Do not rename the `.bin` files for Whisper models—use the exact filenames from the download URLs - After placing the files, restart Handy to detect the new models @@ -260,4 +275,4 @@ MIT License - see [LICENSE](LICENSE) file for details. --- -*"Your search for the right speech-to-text tool can end here—not because Handy is perfect, but because you can make it perfect for you."* +_"Your search for the right speech-to-text tool can end here—not because Handy is perfect, but because you can make it perfect for you."_ diff --git a/bun.lock b/bun.lock index 4050238..8beb3b9 100644 --- a/bun.lock +++ b/bun.lock @@ -33,6 +33,7 @@ "@types/react-dom": "^18.3.7", "@types/react-select": "^5.0.1", "@vitejs/plugin-react": "^4.7.0", + "prettier": "^3.6.2", "typescript": "~5.6.3", "vite": "^6.4.1", }, @@ -441,6 +442,8 @@ "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], + "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], "react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], diff --git a/index.html b/index.html index 330a7e4..df00d31 100644 --- a/index.html +++ b/index.html @@ -1,13 +1,13 @@ - - - - handy - + + + + handy + - -
- - + +
+ + diff --git a/package.json b/package.json index 57412fd..bc3b18d 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,11 @@ "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", - "tauri": "tauri" + "tauri": "tauri", + "format": "prettier --write . && cd src-tauri && cargo fmt", + "format:check": "prettier --check . && cd src-tauri && cargo fmt -- --check", + "format:frontend": "prettier --write .", + "format:backend": "cd src-tauri && cargo fmt" }, "dependencies": { "@tailwindcss/vite": "^4.1.16", @@ -39,6 +43,7 @@ "@types/react-dom": "^18.3.7", "@types/react-select": "^5.0.1", "@vitejs/plugin-react": "^4.7.0", + "prettier": "^3.6.2", "typescript": "~5.6.3", "vite": "^6.4.1" } diff --git a/src-tauri/capabilities/desktop.json b/src-tauri/capabilities/desktop.json index 07e57a0..b41479e 100644 --- a/src-tauri/capabilities/desktop.json +++ b/src-tauri/capabilities/desktop.json @@ -1,13 +1,7 @@ { "identifier": "desktop-capability", - "platforms": [ - "macOS", - "windows", - "linux" - ], - "windows": [ - "main" - ], + "platforms": ["macOS", "windows", "linux"], + "windows": ["main"], "permissions": [ "autostart:default", "global-shortcut:default", @@ -15,4 +9,4 @@ "autostart:default", "updater:default" ] -} \ No newline at end of file +} diff --git a/src-tauri/rustfmt.toml b/src-tauri/rustfmt.toml new file mode 100644 index 0000000..3a26366 --- /dev/null +++ b/src-tauri/rustfmt.toml @@ -0,0 +1 @@ +edition = "2021" diff --git a/src-tauri/src/llm_client.rs b/src-tauri/src/llm_client.rs index a6d579d..2177ea6 100644 --- a/src-tauri/src/llm_client.rs +++ b/src-tauri/src/llm_client.rs @@ -1,8 +1,11 @@ -use async_openai::{config::OpenAIConfig, Client}; use crate::settings::PostProcessProvider; +use async_openai::{config::OpenAIConfig, Client}; /// Create an OpenAI-compatible client configured for the given provider -pub fn create_client(provider: &PostProcessProvider, api_key: String) -> Result, String> { +pub fn create_client( + provider: &PostProcessProvider, + api_key: String, +) -> Result, String> { let base_url = provider.base_url.trim_end_matches('/'); let config = OpenAIConfig::new() .with_api_base(base_url) diff --git a/src/App.css b/src/App.css index c0a659d..8a8e66f 100644 --- a/src/App.css +++ b/src/App.css @@ -3,8 +3,8 @@ @theme { --color-text: #0f0f0f; --color-background: #fbfbfb; - --color-background-ui: #DA5893; - --color-logo-primary: #FAA2CA; + --color-background-ui: #da5893; + --color-logo-primary: #faa2ca; --color-logo-stroke: #382731; --color-text-stroke: #f6f6f6; --color-mid-gray: #808080; @@ -49,8 +49,8 @@ /* Colors - Dark Theme */ --color-text: #fbfbfb; --color-background: #2c2b29; - --color-logo-primary: #F28CBB; - --color-logo-stroke: #FAD1ED; + --color-logo-primary: #f28cbb; + --color-logo-stroke: #fad1ed; } } @@ -68,4 +68,4 @@ fill: var(--color-logo-stroke); stroke: var(--color-logo-stroke); stroke-width: 1; -} \ No newline at end of file +} diff --git a/src/components/footer/index.ts b/src/components/footer/index.ts index be92134..3738288 100644 --- a/src/components/footer/index.ts +++ b/src/components/footer/index.ts @@ -1 +1 @@ -export { default } from './Footer'; +export { default } from "./Footer"; diff --git a/src/components/icons/index.ts b/src/components/icons/index.ts index ee5fec9..a233a9d 100644 --- a/src/components/icons/index.ts +++ b/src/components/icons/index.ts @@ -1,3 +1,3 @@ -export { default as MicrophoneIcon } from './MicrophoneIcon'; -export { default as TranscriptionIcon } from './TranscriptionIcon'; -export { default as CancelIcon } from './CancelIcon'; +export { default as MicrophoneIcon } from "./MicrophoneIcon"; +export { default as TranscriptionIcon } from "./TranscriptionIcon"; +export { default as CancelIcon } from "./CancelIcon"; diff --git a/src/components/model-selector/DownloadProgressDisplay.tsx b/src/components/model-selector/DownloadProgressDisplay.tsx index 4a994cb..b550b1f 100644 --- a/src/components/model-selector/DownloadProgressDisplay.tsx +++ b/src/components/model-selector/DownloadProgressDisplay.tsx @@ -30,7 +30,9 @@ const DownloadProgressDisplay: React.FC = ({ return null; } - const progressData: ProgressData[] = Array.from(downloadProgress.values()).map((progress) => { + const progressData: ProgressData[] = Array.from( + downloadProgress.values(), + ).map((progress) => { const stats = downloadStats.get(progress.model_id); return { id: progress.model_id, diff --git a/src/components/model-selector/ModelDropdown.tsx b/src/components/model-selector/ModelDropdown.tsx index d825fd6..54cc633 100644 --- a/src/components/model-selector/ModelDropdown.tsx +++ b/src/components/model-selector/ModelDropdown.tsx @@ -184,22 +184,22 @@ const ModelDropdown: React.FC = ({
- {isDownloading && progress ? ( - `${Math.max(0, Math.min(100, Math.round(progress.percentage)))}%` - ) : ( - "Download" - )} + {isDownloading && progress + ? `${Math.max(0, Math.min(100, Math.round(progress.percentage)))}%` + : "Download"}
{isDownloading && progress && (
diff --git a/src/components/model-selector/ModelSelector.tsx b/src/components/model-selector/ModelSelector.tsx index 472aa98..98b24b4 100644 --- a/src/components/model-selector/ModelSelector.tsx +++ b/src/components/model-selector/ModelSelector.tsx @@ -199,19 +199,19 @@ const ModelSelector: React.FC = ({ onError }) => { }, ); - const extractionFailedUnlisten = listen<{model_id: string, error: string}>( - "model-extraction-failed", - (event) => { - const modelId = event.payload.model_id; - setExtractingModels((prev) => { - const next = new Set(prev); - next.delete(modelId); - return next; - }); - setModelError(`Failed to extract model: ${event.payload.error}`); - setModelStatus("error"); - }, - ); + const extractionFailedUnlisten = listen<{ + model_id: string; + error: string; + }>("model-extraction-failed", (event) => { + const modelId = event.payload.model_id; + setExtractingModels((prev) => { + const next = new Set(prev); + next.delete(modelId); + return next; + }); + setModelError(`Failed to extract model: ${event.payload.error}`); + setModelStatus("error"); + }); // Click outside to close dropdown const handleClickOutside = (event: MouseEvent) => { @@ -304,8 +304,8 @@ const ModelSelector: React.FC = ({ onError }) => { if (extractingModels.size > 0) { if (extractingModels.size === 1) { const [modelId] = Array.from(extractingModels); - const model = models.find(m => m.id === modelId); - return `Extracting ${model?.name || 'Model'}...`; + const model = models.find((m) => m.id === modelId); + return `Extracting ${model?.name || "Model"}...`; } else { return `Extracting ${extractingModels.size} models...`; } @@ -332,7 +332,9 @@ const ModelSelector: React.FC = ({ onError }) => { case "loading": return currentModel ? `Loading ${currentModel.name}...` : "Loading..."; case "extracting": - return currentModel ? `Extracting ${currentModel.name}...` : "Extracting..."; + return currentModel + ? `Extracting ${currentModel.name}...` + : "Extracting..."; case "error": return modelError || "Model Error"; case "unloaded": diff --git a/src/components/onboarding/index.ts b/src/components/onboarding/index.ts index c0ad2b1..adfa58c 100644 --- a/src/components/onboarding/index.ts +++ b/src/components/onboarding/index.ts @@ -1 +1 @@ -export { default } from './Onboarding'; +export { default } from "./Onboarding"; diff --git a/src/components/settings/AlwaysOnMicrophone.tsx b/src/components/settings/AlwaysOnMicrophone.tsx index 48fdae0..035895b 100644 --- a/src/components/settings/AlwaysOnMicrophone.tsx +++ b/src/components/settings/AlwaysOnMicrophone.tsx @@ -7,23 +7,22 @@ interface AlwaysOnMicrophoneProps { grouped?: boolean; } -export const AlwaysOnMicrophone: React.FC = React.memo(({ - descriptionMode = "tooltip", - grouped = false, -}) => { - const { getSetting, updateSetting, isUpdating } = useSettings(); +export const AlwaysOnMicrophone: React.FC = React.memo( + ({ descriptionMode = "tooltip", grouped = false }) => { + const { getSetting, updateSetting, isUpdating } = useSettings(); - const alwaysOnMode = getSetting("always_on_microphone") || false; + const alwaysOnMode = getSetting("always_on_microphone") || false; - return ( - updateSetting("always_on_microphone", enabled)} - isUpdating={isUpdating("always_on_microphone")} - label="Always-On Microphone" - description="Keep microphone active for low latency recording. This may prevent your computer from sleeping." - descriptionMode={descriptionMode} - grouped={grouped} - /> - ); -}); + return ( + updateSetting("always_on_microphone", enabled)} + isUpdating={isUpdating("always_on_microphone")} + label="Always-On Microphone" + description="Keep microphone active for low latency recording. This may prevent your computer from sleeping." + descriptionMode={descriptionMode} + grouped={grouped} + /> + ); + }, +); diff --git a/src/components/settings/ClipboardHandling.tsx b/src/components/settings/ClipboardHandling.tsx index 2f7fbf7..457fb76 100644 --- a/src/components/settings/ClipboardHandling.tsx +++ b/src/components/settings/ClipboardHandling.tsx @@ -14,30 +14,28 @@ const clipboardHandlingOptions = [ { value: "copy_to_clipboard", label: "Copy to Clipboard" }, ]; -export const ClipboardHandlingSetting: React.FC = React.memo(({ - descriptionMode = "tooltip", - grouped = false, -}) => { - const { getSetting, updateSetting, isUpdating } = useSettings(); +export const ClipboardHandlingSetting: React.FC = + React.memo(({ descriptionMode = "tooltip", grouped = false }) => { + const { getSetting, updateSetting, isUpdating } = useSettings(); - const selectedHandling = (getSetting("clipboard_handling") || - "dont_modify") as ClipboardHandling; + const selectedHandling = (getSetting("clipboard_handling") || + "dont_modify") as ClipboardHandling; - return ( - - - updateSetting("clipboard_handling", value as ClipboardHandling) - } - disabled={isUpdating("clipboard_handling")} - /> - - ); -}); + return ( + + + updateSetting("clipboard_handling", value as ClipboardHandling) + } + disabled={isUpdating("clipboard_handling")} + /> + + ); + }); diff --git a/src/components/settings/MicrophoneSelector.tsx b/src/components/settings/MicrophoneSelector.tsx index f37ab38..7626d6c 100644 --- a/src/components/settings/MicrophoneSelector.tsx +++ b/src/components/settings/MicrophoneSelector.tsx @@ -9,57 +9,66 @@ interface MicrophoneSelectorProps { grouped?: boolean; } -export const MicrophoneSelector: React.FC = React.memo(({ - descriptionMode = "tooltip", - grouped = false, -}) => { - const { - getSetting, - updateSetting, - resetSetting, - isUpdating, - isLoading, - audioDevices, - refreshAudioDevices, - } = useSettings(); +export const MicrophoneSelector: React.FC = React.memo( + ({ descriptionMode = "tooltip", grouped = false }) => { + const { + getSetting, + updateSetting, + resetSetting, + isUpdating, + isLoading, + audioDevices, + refreshAudioDevices, + } = useSettings(); - const selectedMicrophone = getSetting("selected_microphone") === "default" ? "Default" : getSetting("selected_microphone") || "Default"; - + const selectedMicrophone = + getSetting("selected_microphone") === "default" + ? "Default" + : getSetting("selected_microphone") || "Default"; - const handleMicrophoneSelect = async (deviceName: string) => { - await updateSetting("selected_microphone", deviceName); - }; + const handleMicrophoneSelect = async (deviceName: string) => { + await updateSetting("selected_microphone", deviceName); + }; - const handleReset = async () => { - await resetSetting("selected_microphone"); - }; + const handleReset = async () => { + await resetSetting("selected_microphone"); + }; - const microphoneOptions = audioDevices.map(device => ({ - value: device.name, - label: device.name - })); + const microphoneOptions = audioDevices.map((device) => ({ + value: device.name, + label: device.name, + })); - return ( - -
- - -
-
- ); -}); + return ( + +
+ + +
+
+ ); + }, +); diff --git a/src/components/settings/PostProcessingToggle.tsx b/src/components/settings/PostProcessingToggle.tsx index b042d80..811654a 100644 --- a/src/components/settings/PostProcessingToggle.tsx +++ b/src/components/settings/PostProcessingToggle.tsx @@ -7,8 +7,8 @@ interface PostProcessingToggleProps { grouped?: boolean; } -export const PostProcessingToggle: React.FC = React.memo( - ({ descriptionMode = "tooltip", grouped = false }) => { +export const PostProcessingToggle: React.FC = + React.memo(({ descriptionMode = "tooltip", grouped = false }) => { const { getSetting, updateSetting, isUpdating } = useSettings(); const enabled = getSetting("post_process_enabled") || false; @@ -24,5 +24,4 @@ export const PostProcessingToggle: React.FC = React.m grouped={grouped} /> ); - }, -); + }); diff --git a/src/components/settings/PushToTalk.tsx b/src/components/settings/PushToTalk.tsx index 01ed019..947eb67 100644 --- a/src/components/settings/PushToTalk.tsx +++ b/src/components/settings/PushToTalk.tsx @@ -7,23 +7,22 @@ interface PushToTalkProps { grouped?: boolean; } -export const PushToTalk: React.FC = React.memo(({ - descriptionMode = "tooltip", - grouped = false, -}) => { - const { getSetting, updateSetting, isUpdating } = useSettings(); +export const PushToTalk: React.FC = React.memo( + ({ descriptionMode = "tooltip", grouped = false }) => { + const { getSetting, updateSetting, isUpdating } = useSettings(); - const pttEnabled = getSetting("push_to_talk") || false; + const pttEnabled = getSetting("push_to_talk") || false; - return ( - updateSetting("push_to_talk", enabled)} - isUpdating={isUpdating("push_to_talk")} - label="Push To Talk" - description="Hold to record, release to stop" - descriptionMode={descriptionMode} - grouped={grouped} - /> - ); -}); + return ( + updateSetting("push_to_talk", enabled)} + isUpdating={isUpdating("push_to_talk")} + label="Push To Talk" + description="Hold to record, release to stop" + descriptionMode={descriptionMode} + grouped={grouped} + /> + ); + }, +); diff --git a/src/components/settings/ShowOverlay.tsx b/src/components/settings/ShowOverlay.tsx index d606e74..e7475b3 100644 --- a/src/components/settings/ShowOverlay.tsx +++ b/src/components/settings/ShowOverlay.tsx @@ -15,30 +15,29 @@ const overlayOptions = [ { value: "top", label: "Top" }, ]; -export const ShowOverlay: React.FC = React.memo(({ - descriptionMode = "tooltip", - grouped = false, -}) => { - const { getSetting, updateSetting, isUpdating } = useSettings(); +export const ShowOverlay: React.FC = React.memo( + ({ descriptionMode = "tooltip", grouped = false }) => { + const { getSetting, updateSetting, isUpdating } = useSettings(); - const selectedPosition = (getSetting("overlay_position") || - "bottom") as OverlayPosition; + const selectedPosition = (getSetting("overlay_position") || + "bottom") as OverlayPosition; - return ( - - - updateSetting("overlay_position", value as OverlayPosition) - } - disabled={isUpdating("overlay_position")} - /> - - ); -}); + return ( + + + updateSetting("overlay_position", value as OverlayPosition) + } + disabled={isUpdating("overlay_position")} + /> + + ); + }, +); diff --git a/src/components/settings/StartHidden.tsx b/src/components/settings/StartHidden.tsx index 87321b7..348e37b 100644 --- a/src/components/settings/StartHidden.tsx +++ b/src/components/settings/StartHidden.tsx @@ -7,24 +7,23 @@ interface StartHiddenProps { grouped?: boolean; } -export const StartHidden: React.FC = React.memo(({ - descriptionMode = "tooltip", - grouped = false, -}) => { - const { getSetting, updateSetting, isUpdating } = useSettings(); +export const StartHidden: React.FC = React.memo( + ({ descriptionMode = "tooltip", grouped = false }) => { + const { getSetting, updateSetting, isUpdating } = useSettings(); - const startHidden = getSetting("start_hidden") ?? false; + const startHidden = getSetting("start_hidden") ?? false; - return ( - updateSetting("start_hidden", enabled)} - isUpdating={isUpdating("start_hidden")} - label="Start Hidden" - description="Launch to system tray without opening the window." - descriptionMode={descriptionMode} - grouped={grouped} - tooltipPosition="bottom" - /> - ); -}); + return ( + updateSetting("start_hidden", enabled)} + isUpdating={isUpdating("start_hidden")} + label="Start Hidden" + description="Launch to system tray without opening the window." + descriptionMode={descriptionMode} + grouped={grouped} + tooltipPosition="bottom" + /> + ); + }, +); diff --git a/src/components/settings/debug/DebugPaths.tsx b/src/components/settings/debug/DebugPaths.tsx index 6fccca2..52c1834 100644 --- a/src/components/settings/debug/DebugPaths.tsx +++ b/src/components/settings/debug/DebugPaths.tsx @@ -28,7 +28,9 @@ export const DebugPaths: React.FC = ({
Settings:{" "} - %APPDATA%/handy/settings_store.json + + %APPDATA%/handy/settings_store.json +
diff --git a/src/components/shared/index.ts b/src/components/shared/index.ts index 126aef7..5fa32b2 100644 --- a/src/components/shared/index.ts +++ b/src/components/shared/index.ts @@ -1,2 +1,2 @@ -export { default as ProgressBar } from './ProgressBar'; -export type { ProgressData } from './ProgressBar'; +export { default as ProgressBar } from "./ProgressBar"; +export type { ProgressData } from "./ProgressBar"; diff --git a/src/components/ui/ResetButton.tsx b/src/components/ui/ResetButton.tsx index 72ac8b5..a6dc049 100644 --- a/src/components/ui/ResetButton.tsx +++ b/src/components/ui/ResetButton.tsx @@ -10,13 +10,7 @@ interface ResetButtonProps { } export const ResetButton: React.FC = React.memo( - ({ - onClick, - disabled = false, - className = "", - ariaLabel, - children, - }) => ( + ({ onClick, disabled = false, className = "", ariaLabel, children }) => (