format everything (#323)
This commit is contained in:
parent
a80c37097b
commit
70ec82285a
45 changed files with 528 additions and 387 deletions
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
12
.github/ISSUE_TEMPLATE/bug_report.md
vendored
|
|
@ -1,9 +1,9 @@
|
||||||
---
|
---
|
||||||
name: Bug Report
|
name: Bug Report
|
||||||
about: Create a report to help us improve Handy
|
about: Create a report to help us improve Handy
|
||||||
title: '[BUG] '
|
title: "[BUG] "
|
||||||
labels: ['bug']
|
labels: ["bug"]
|
||||||
assignees: ''
|
assignees: ""
|
||||||
---
|
---
|
||||||
|
|
||||||
## Before You Submit
|
## 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.
|
**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
|
## Bug Description
|
||||||
|
|
||||||
A clear and concise description of what the bug is.
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
## System Information
|
## System Information
|
||||||
|
|
||||||
**App Version:**
|
**App Version:**
|
||||||
|
|
||||||
<!-- You can find this in the app settings or about section -->
|
<!-- You can find this in the app settings or about section -->
|
||||||
|
|
||||||
**Operating System:**
|
**Operating System:**
|
||||||
|
|
||||||
<!-- e.g., macOS 14.1, Windows 11, Ubuntu 22.04 -->
|
<!-- e.g., macOS 14.1, Windows 11, Ubuntu 22.04 -->
|
||||||
|
|
||||||
**CPU:**
|
**CPU:**
|
||||||
|
|
||||||
<!-- e.g., Apple M2, Intel i7-12700K, AMD Ryzen 7 5800X -->
|
<!-- e.g., Apple M2, Intel i7-12700K, AMD Ryzen 7 5800X -->
|
||||||
|
|
||||||
**GPU:**
|
**GPU:**
|
||||||
|
|
||||||
<!-- e.g., Apple M2 GPU, NVIDIA RTX 4080, AMD RX 6800 XT, Intel UHD Graphics -->
|
<!-- e.g., Apple M2 GPU, NVIDIA RTX 4080, AMD RX 6800 XT, Intel UHD Graphics -->
|
||||||
|
|
|
||||||
7
.github/ISSUE_TEMPLATE/feature_request.md
vendored
7
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
|
@ -1,18 +1,19 @@
|
||||||
---
|
---
|
||||||
name: Feature Request
|
name: Feature Request
|
||||||
about: Suggest an idea or new feature for Handy
|
about: Suggest an idea or new feature for Handy
|
||||||
title: ''
|
title: ""
|
||||||
labels: []
|
labels: []
|
||||||
assignees: ''
|
assignees: ""
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🎯 Feature Requests Go in Discussions!
|
## 🎯 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.**
|
**Please post feature requests in our [Discussions tab](https://github.com/cjpais/Handy/discussions) instead of opening an issue.**
|
||||||
|
|
||||||
This helps us:
|
This helps us:
|
||||||
|
|
||||||
- Keep issues focused on bugs and actionable tasks
|
- Keep issues focused on bugs and actionable tasks
|
||||||
- Have more open-ended conversations about features
|
- Have more open-ended conversations about features
|
||||||
- Gather community feedback and input
|
- Gather community feedback and input
|
||||||
|
|
|
||||||
20
.prettierignore
Normal file
20
.prettierignore
Normal file
|
|
@ -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
|
||||||
3
.prettierrc
Normal file
3
.prettierrc
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"endOfLine": "lf"
|
||||||
|
}
|
||||||
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
|
|
@ -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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
AGENTS.md
14
AGENTS.md
|
|
@ -5,10 +5,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||||
## Development Commands
|
## Development Commands
|
||||||
|
|
||||||
**Prerequisites:**
|
**Prerequisites:**
|
||||||
|
|
||||||
- [Rust](https://rustup.rs/) (latest stable)
|
- [Rust](https://rustup.rs/) (latest stable)
|
||||||
- [Bun](https://bun.sh/) package manager
|
- [Bun](https://bun.sh/) package manager
|
||||||
|
|
||||||
**Core Development:**
|
**Core Development:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
bun install
|
bun install
|
||||||
|
|
@ -28,6 +30,7 @@ bun run preview # Preview built frontend
|
||||||
```
|
```
|
||||||
|
|
||||||
**Model Setup (Required for Development):**
|
**Model Setup (Required for Development):**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create models directory
|
# Create models directory
|
||||||
mkdir -p src-tauri/resources/models
|
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
|
### Core Components
|
||||||
|
|
||||||
**Backend (Rust - src-tauri/src/):**
|
**Backend (Rust - src-tauri/src/):**
|
||||||
|
|
||||||
- `lib.rs` - Main application entry point with Tauri setup, tray menu, and managers
|
- `lib.rs` - Main application entry point with Tauri setup, tray menu, and managers
|
||||||
- `managers/` - Core business logic managers:
|
- `managers/` - Core business logic managers:
|
||||||
- `audio.rs` - Audio recording and device management
|
- `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
|
- `transcription.rs` - Speech-to-text processing pipeline
|
||||||
- `audio_toolkit/` - Low-level audio processing:
|
- `audio_toolkit/` - Low-level audio processing:
|
||||||
- `audio/` - Device enumeration, recording, resampling
|
- `audio/` - Device enumeration, recording, resampling
|
||||||
- `vad/` - Voice Activity Detection using Silero VAD
|
- `vad/` - Voice Activity Detection using Silero VAD
|
||||||
- `commands/` - Tauri command handlers for frontend communication
|
- `commands/` - Tauri command handlers for frontend communication
|
||||||
- `shortcut.rs` - Global keyboard shortcut handling
|
- `shortcut.rs` - Global keyboard shortcut handling
|
||||||
- `settings.rs` - Application settings management
|
- `settings.rs` - Application settings management
|
||||||
|
|
||||||
**Frontend (React/TypeScript - src/):**
|
**Frontend (React/TypeScript - src/):**
|
||||||
|
|
||||||
- `App.tsx` - Main application component with onboarding flow
|
- `App.tsx` - Main application component with onboarding flow
|
||||||
- `components/settings/` - Settings UI components
|
- `components/settings/` - Settings UI components
|
||||||
- `components/model-selector/` - Model management interface
|
- `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
|
### Technology Stack
|
||||||
|
|
||||||
**Core Libraries:**
|
**Core Libraries:**
|
||||||
|
|
||||||
- `whisper-rs` - Local Whisper inference with GPU acceleration
|
- `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
|
- `vad-rs` - Voice Activity Detection
|
||||||
- `rdev` - Global keyboard shortcuts
|
- `rdev` - Global keyboard shortcuts
|
||||||
- `rubato` - Audio resampling
|
- `rubato` - Audio resampling
|
||||||
- `rodio` - Audio playback for feedback sounds
|
- `rodio` - Audio playback for feedback sounds
|
||||||
|
|
||||||
**Platform-Specific Features:**
|
**Platform-Specific Features:**
|
||||||
|
|
||||||
- macOS: Metal acceleration for Whisper, accessibility permissions
|
- macOS: Metal acceleration for Whisper, accessibility permissions
|
||||||
- Windows: Vulkan acceleration, code signing
|
- Windows: Vulkan acceleration, code signing
|
||||||
- Linux: OpenBLAS + Vulkan acceleration
|
- 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 System
|
||||||
|
|
||||||
Settings are stored using Tauri's store plugin with reactive updates:
|
Settings are stored using Tauri's store plugin with reactive updates:
|
||||||
|
|
||||||
- Keyboard shortcuts (configurable, supports push-to-talk)
|
- Keyboard shortcuts (configurable, supports push-to-talk)
|
||||||
- Audio devices (microphone/output selection)
|
- Audio devices (microphone/output selection)
|
||||||
- Model preferences (Small/Medium/Turbo/Large Whisper variants)
|
- Model preferences (Small/Medium/Turbo/Large Whisper variants)
|
||||||
|
|
|
||||||
10
BUILD.md
10
BUILD.md
|
|
@ -5,6 +5,7 @@ This guide covers how to set up the development environment and build Handy from
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
### All Platforms
|
### All Platforms
|
||||||
|
|
||||||
- [Rust](https://rustup.rs/) (latest stable)
|
- [Rust](https://rustup.rs/) (latest stable)
|
||||||
- [Bun](https://bun.sh/) package manager
|
- [Bun](https://bun.sh/) package manager
|
||||||
- [Tauri Prerequisites](https://tauri.app/start/prerequisites/)
|
- [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
|
### Platform-Specific Requirements
|
||||||
|
|
||||||
#### macOS
|
#### macOS
|
||||||
|
|
||||||
- Xcode Command Line Tools
|
- Xcode Command Line Tools
|
||||||
- Install with: `xcode-select --install`
|
- Install with: `xcode-select --install`
|
||||||
|
|
||||||
#### Windows
|
#### Windows
|
||||||
|
|
||||||
- Microsoft C++ Build Tools
|
- Microsoft C++ Build Tools
|
||||||
- Visual Studio 2019/2022 with C++ development tools
|
- Visual Studio 2019/2022 with C++ development tools
|
||||||
- Or Visual Studio Build Tools 2019/2022
|
- Or Visual Studio Build Tools 2019/2022
|
||||||
|
|
||||||
#### Linux
|
#### Linux
|
||||||
|
|
||||||
- Build essentials
|
- Build essentials
|
||||||
- ALSA development libraries
|
- ALSA development libraries
|
||||||
- Install with:
|
- Install with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Ubuntu/Debian
|
# Ubuntu/Debian
|
||||||
sudo apt update
|
sudo apt update
|
||||||
|
|
@ -42,15 +47,18 @@ This guide covers how to set up the development environment and build Handy from
|
||||||
## Setup Instructions
|
## Setup Instructions
|
||||||
|
|
||||||
### 1. Clone the Repository
|
### 1. Clone the Repository
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone git@github.com:cjpais/Handy.git
|
git clone git@github.com:cjpais/Handy.git
|
||||||
cd Handy
|
cd Handy
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Install Dependencies
|
### 2. Install Dependencies
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun install
|
bun install
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Download Required Models
|
### 3. Download Required Models
|
||||||
|
|
||||||
Handy requires a VAD (Voice Activity Detection) model to function
|
Handy requires a VAD (Voice Activity Detection) model to function
|
||||||
|
|
|
||||||
21
CHANGELOG.md
21
CHANGELOG.md
|
|
@ -3,38 +3,45 @@
|
||||||
## [0.3.0] - 2025-07-11
|
## [0.3.0] - 2025-07-11
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **Translate to English** setting: Added automatic translation of speech to English
|
- **Translate to English** setting: Added automatic translation of speech to English
|
||||||
- Settings refactored into React hooks for better state management
|
- Settings refactored into React hooks for better state management
|
||||||
- Audio device switching capability
|
- Audio device switching capability
|
||||||
- Hysteresis to VAD (Voice Activity Detection) for more stable recording
|
- Hysteresis to VAD (Voice Activity Detection) for more stable recording
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Major audio backend refactor for improved performance and reliability
|
- Major audio backend refactor for improved performance and reliability
|
||||||
- Moved audio toolkit into src-tauri directory for better permissions handling
|
- Moved audio toolkit into src-tauri directory for better permissions handling
|
||||||
- Model files no longer need to be downloaded separately for releases
|
- Model files no longer need to be downloaded separately for releases
|
||||||
- Updated settings components and transcription logic
|
- Updated settings components and transcription logic
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Audio toolkit permissions issues
|
- Audio toolkit permissions issues
|
||||||
- Various stability improvements
|
- Various stability improvements
|
||||||
|
|
||||||
## [0.2.3] - 2025-07-03
|
## [0.2.3] - 2025-07-03
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Keycode bug that was causing input issues
|
- Keycode bug that was causing input issues
|
||||||
- Whisper model optimization: switched to unquantized Whisper Turbo, updated Whisper Medium quantization to 4_1
|
- Whisper model optimization: switched to unquantized Whisper Turbo, updated Whisper Medium quantization to 4_1
|
||||||
|
|
||||||
## [0.2.2] - 2025-07-02
|
## [0.2.2] - 2025-07-02
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Removed 50ms delay feature flag for Windows (now applies to all platforms for consistency)
|
- Removed 50ms delay feature flag for Windows (now applies to all platforms for consistency)
|
||||||
|
|
||||||
## [0.2.1] - 2025-07-01
|
## [0.2.1] - 2025-07-01
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Ctrl+Space key binding for Windows platform
|
- Ctrl+Space key binding for Windows platform
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Windows crash issue
|
- Windows crash issue
|
||||||
- Model loading on startup when available
|
- Model loading on startup when available
|
||||||
- Windows paste functionality bug
|
- Windows paste functionality bug
|
||||||
|
|
@ -42,54 +49,64 @@
|
||||||
## [0.2.0] - 2025-06-30
|
## [0.2.0] - 2025-06-30
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **Microphone activation on demand**: More efficient resource usage
|
- **Microphone activation on demand**: More efficient resource usage
|
||||||
- Less permissive VAD settings for better accuracy
|
- Less permissive VAD settings for better accuracy
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improved microphone management and activation system
|
- Improved microphone management and activation system
|
||||||
|
|
||||||
## [0.1.6] - 2025-06-30
|
## [0.1.6] - 2025-06-30
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **Multiple models support**: Users can now select from different transcription models
|
- **Multiple models support**: Users can now select from different transcription models
|
||||||
- Model selection onboarding flow
|
- Model selection onboarding flow
|
||||||
- Cleanup and refactoring of model management
|
- Cleanup and refactoring of model management
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Enhanced user experience with model selection interface
|
- Enhanced user experience with model selection interface
|
||||||
- Better language and UI tweaks
|
- Better language and UI tweaks
|
||||||
|
|
||||||
## [0.1.5] - 2025-06-27
|
## [0.1.5] - 2025-06-27
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **Different start and stop recording sounds**: Enhanced audio feedback
|
- **Different start and stop recording sounds**: Enhanced audio feedback
|
||||||
- Recording sound samples for better user experience
|
- Recording sound samples for better user experience
|
||||||
|
|
||||||
## [0.1.4] - 2025-06-27
|
## [0.1.4] - 2025-06-27
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Build issues
|
- Build issues
|
||||||
- Auto-update functionality improvements
|
- Auto-update functionality improvements
|
||||||
|
|
||||||
## [0.1.3] - 2025-06-26
|
## [0.1.3] - 2025-06-26
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Paste functionality using enigo library for better cross-platform compatibility
|
- Paste functionality using enigo library for better cross-platform compatibility
|
||||||
|
|
||||||
## [0.1.2] - 2025-06-26
|
## [0.1.2] - 2025-06-26
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **Auto-update functionality**: Application can now automatically update itself
|
- **Auto-update functionality**: Application can now automatically update itself
|
||||||
- Footer displaying current version
|
- Footer displaying current version
|
||||||
- Improved menu system
|
- Improved menu system
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Better user interface for version management
|
- Better user interface for version management
|
||||||
- Enhanced update workflow
|
- Enhanced update workflow
|
||||||
|
|
||||||
## [0.1.1] - 2025-06-25
|
## [0.1.1] - 2025-06-25
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **Comprehensive build system**: Support for Windows, macOS, and Linux
|
- **Comprehensive build system**: Support for Windows, macOS, and Linux
|
||||||
- Windows code signing for trusted installation
|
- Windows code signing for trusted installation
|
||||||
- Ubuntu/Linux build support with Vulkan
|
- Ubuntu/Linux build support with Vulkan
|
||||||
|
|
@ -97,15 +114,18 @@
|
||||||
- GitHub Actions CI/CD workflow
|
- GitHub Actions CI/CD workflow
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improved build process and release workflow
|
- Improved build process and release workflow
|
||||||
- Better cross-platform compatibility
|
- Better cross-platform compatibility
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Various build-related issues across platforms
|
- Various build-related issues across platforms
|
||||||
|
|
||||||
## [0.1.0] - 2025-05-16
|
## [0.1.0] - 2025-05-16
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **Initial release** of Handy
|
- **Initial release** of Handy
|
||||||
- Basic speech-to-text transcription functionality
|
- Basic speech-to-text transcription functionality
|
||||||
- Voice Activity Detection (VAD) for automatic recording
|
- Voice Activity Detection (VAD) for automatic recording
|
||||||
|
|
@ -128,6 +148,7 @@
|
||||||
- **MIT License** for open-source distribution
|
- **MIT License** for open-source distribution
|
||||||
|
|
||||||
### Technical Implementation
|
### Technical Implementation
|
||||||
|
|
||||||
- Built with Tauri (Rust backend) and React (TypeScript frontend)
|
- Built with Tauri (Rust backend) and React (TypeScript frontend)
|
||||||
- Audio processing with cpal and whisper-rs
|
- Audio processing with cpal and whisper-rs
|
||||||
- Real-time transcription with performance optimizations
|
- Real-time transcription with performance optimizations
|
||||||
|
|
|
||||||
|
|
@ -26,22 +26,26 @@ Before you begin, ensure you have the following installed:
|
||||||
1. **Fork the repository** on GitHub
|
1. **Fork the repository** on GitHub
|
||||||
|
|
||||||
2. **Clone your fork**:
|
2. **Clone your fork**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone git@github.com:YOUR_USERNAME/Handy.git
|
git clone git@github.com:YOUR_USERNAME/Handy.git
|
||||||
cd Handy
|
cd Handy
|
||||||
```
|
```
|
||||||
|
|
||||||
3. **Add upstream remote**:
|
3. **Add upstream remote**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git remote add upstream git@github.com:cjpais/Handy.git
|
git remote add upstream git@github.com:cjpais/Handy.git
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Install dependencies**:
|
4. **Install dependencies**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun install
|
bun install
|
||||||
```
|
```
|
||||||
|
|
||||||
5. **Download required models**:
|
5. **Download required models**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p src-tauri/resources/models
|
mkdir -p src-tauri/resources/models
|
||||||
curl -o src-tauri/resources/models/silero_vad_v4.onnx https://blob.handy.computer/silero_vad_v4.onnx
|
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:
|
Handy follows a clean architecture pattern:
|
||||||
|
|
||||||
**Backend (Rust - `src-tauri/src/`):**
|
**Backend (Rust - `src-tauri/src/`):**
|
||||||
|
|
||||||
- `lib.rs` - Main application entry point with Tauri setup
|
- `lib.rs` - Main application entry point with Tauri setup
|
||||||
- `managers/` - Core business logic (audio, model, transcription)
|
- `managers/` - Core business logic (audio, model, transcription)
|
||||||
- `audio_toolkit/` - Low-level audio processing (recording, VAD)
|
- `audio_toolkit/` - Low-level audio processing (recording, VAD)
|
||||||
|
|
@ -69,6 +74,7 @@ Handy follows a clean architecture pattern:
|
||||||
- `settings.rs` - Application settings management
|
- `settings.rs` - Application settings management
|
||||||
|
|
||||||
**Frontend (React/TypeScript - `src/`):**
|
**Frontend (React/TypeScript - `src/`):**
|
||||||
|
|
||||||
- `App.tsx` - Main application component
|
- `App.tsx` - Main application component
|
||||||
- `components/` - React UI components
|
- `components/` - React UI components
|
||||||
- `hooks/` - Reusable React hooks
|
- `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:
|
When creating a bug report, please include:
|
||||||
|
|
||||||
**System Information:**
|
**System Information:**
|
||||||
|
|
||||||
- App version (found in settings or about section)
|
- App version (found in settings or about section)
|
||||||
- Operating System (e.g., macOS 14.1, Windows 11, Ubuntu 22.04)
|
- Operating System (e.g., macOS 14.1, Windows 11, Ubuntu 22.04)
|
||||||
- CPU (e.g., Apple M2, Intel i7-12700K, AMD Ryzen 7 5800X)
|
- CPU (e.g., Apple M2, Intel i7-12700K, AMD Ryzen 7 5800X)
|
||||||
- GPU (e.g., Apple M2 GPU, NVIDIA RTX 4080, Intel UHD Graphics)
|
- GPU (e.g., Apple M2 GPU, NVIDIA RTX 4080, Intel UHD Graphics)
|
||||||
|
|
||||||
**Bug Details:**
|
**Bug Details:**
|
||||||
|
|
||||||
- Clear description of the bug
|
- Clear description of the bug
|
||||||
- Steps to reproduce
|
- Steps to reproduce
|
||||||
- Expected behavior
|
- Expected behavior
|
||||||
|
|
@ -132,6 +140,7 @@ We use GitHub Discussions for feature requests rather than issues. This keeps is
|
||||||
### Development Workflow
|
### Development Workflow
|
||||||
|
|
||||||
1. **Create a feature branch**:
|
1. **Create a feature branch**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git checkout -b feature/your-feature-name
|
git checkout -b feature/your-feature-name
|
||||||
# or
|
# 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
|
- Use debug mode to verify audio/transcription behavior
|
||||||
|
|
||||||
4. **Commit your changes**:
|
4. **Commit your changes**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add .
|
git add .
|
||||||
git commit -m "feat: add your feature description"
|
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
|
- `chore:` for maintenance tasks
|
||||||
|
|
||||||
5. **Keep your fork updated**:
|
5. **Keep your fork updated**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git fetch upstream
|
git fetch upstream
|
||||||
git rebase upstream/main
|
git rebase upstream/main
|
||||||
```
|
```
|
||||||
|
|
||||||
6. **Push to your fork**:
|
6. **Push to your fork**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git push origin feature/your-feature-name
|
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
|
### Code Style Guidelines
|
||||||
|
|
||||||
**Rust:**
|
**Rust:**
|
||||||
|
|
||||||
- Follow standard Rust formatting (`cargo fmt`)
|
- Follow standard Rust formatting (`cargo fmt`)
|
||||||
- Run `cargo clippy` and address warnings
|
- Run `cargo clippy` and address warnings
|
||||||
- Use descriptive variable and function names
|
- 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)
|
- Handle errors explicitly (avoid unwrap in production code)
|
||||||
|
|
||||||
**TypeScript/React:**
|
**TypeScript/React:**
|
||||||
|
|
||||||
- Use TypeScript strictly, avoid `any` types
|
- Use TypeScript strictly, avoid `any` types
|
||||||
- Follow React hooks best practices
|
- Follow React hooks best practices
|
||||||
- Use functional components
|
- 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
|
- Use Tailwind CSS for styling
|
||||||
|
|
||||||
**General:**
|
**General:**
|
||||||
|
|
||||||
- Write self-documenting code
|
- Write self-documenting code
|
||||||
- Add comments for non-obvious logic
|
- Add comments for non-obvious logic
|
||||||
- Keep functions small and single-purpose
|
- 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
|
### Testing Your Changes
|
||||||
|
|
||||||
**Manual Testing:**
|
**Manual Testing:**
|
||||||
|
|
||||||
- Run the app in development mode: `bun run tauri dev`
|
- Run the app in development mode: `bun run tauri dev`
|
||||||
- Test your changes with debug mode enabled
|
- Test your changes with debug mode enabled
|
||||||
- Verify on multiple platforms if possible
|
- 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
|
- Try various transcription scenarios
|
||||||
|
|
||||||
**Building for Production:**
|
**Building for Production:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run tauri build
|
bun run tauri build
|
||||||
```
|
```
|
||||||
|
|
@ -247,6 +264,7 @@ Documentation improvements are highly valued! You can contribute by:
|
||||||
## 🎯 Good First Issues
|
## 🎯 Good First Issues
|
||||||
|
|
||||||
Look for issues labeled `good first issue` or `help wanted` if you're new to the project. These are typically:
|
Look for issues labeled `good first issue` or `help wanted` if you're new to the project. These are typically:
|
||||||
|
|
||||||
- Well-defined and scoped
|
- Well-defined and scoped
|
||||||
- Good for learning the codebase
|
- Good for learning the codebase
|
||||||
- Mentor support available
|
- Mentor support available
|
||||||
|
|
|
||||||
12
CRUSH.md
12
CRUSH.md
|
|
@ -1,6 +1,7 @@
|
||||||
# Development Commands
|
# Development Commands
|
||||||
|
|
||||||
**Environment Setup:**
|
**Environment Setup:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun install # Install dependencies
|
bun install # Install dependencies
|
||||||
mkdir -p src-tauri/resources/models
|
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:**
|
**Development:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bun run tauri dev # Full app development
|
bun run tauri dev # Full app development
|
||||||
CMAKE_POLICY_VERSION_MINIMUM=3.5 bun run tauri dev # macOS with cmake fix
|
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:**
|
**Type Check & Build:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
bunx tsc --noEmit # Type checking
|
bunx tsc --noEmit # Type checking
|
||||||
bun run build # Build and validate
|
bun run build # Build and validate
|
||||||
```
|
```
|
||||||
|
|
||||||
# Code Style Guidelines
|
# Code Style Guidelines
|
||||||
|
|
||||||
**Rust (Backend):**
|
**Rust (Backend):**
|
||||||
|
|
||||||
- Use `anyhow::Error` for error handling with descriptive messages
|
- Use `anyhow::Error` for error handling with descriptive messages
|
||||||
- Prefer `Arc<Mutex<T>>` for shared state in managers
|
- Prefer `Arc<Mutex<T>>` for shared state in managers
|
||||||
- Log with appropriate levels: `debug!`, `info!`, `eprintln!` for errors
|
- 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: `/* ─────────── */`
|
- Separate logical sections with comment blocks: `/* ─────────── */`
|
||||||
|
|
||||||
**TypeScript/React (Frontend):**
|
**TypeScript/React (Frontend):**
|
||||||
|
|
||||||
- Functional components with TypeScript interfaces
|
- Functional components with TypeScript interfaces
|
||||||
- Zod schemas for type validation and inference
|
- Zod schemas for type validation and inference
|
||||||
- `useCallback` hooks for stable function references
|
- `useCallback` hooks for stable function references
|
||||||
|
|
@ -42,16 +47,19 @@ bun run build # Build and validate
|
||||||
- PascalCase for components, camelCase for variables/functions
|
- PascalCase for components, camelCase for variables/functions
|
||||||
|
|
||||||
**Imports:**
|
**Imports:**
|
||||||
|
|
||||||
- Group imports: external libs, internal modules, relative imports
|
- Group imports: external libs, internal modules, relative imports
|
||||||
- Use type imports for TypeScript: `import type { Settings }`
|
- Use type imports for TypeScript: `import type { Settings }`
|
||||||
- Named imports preferred over default exports
|
- Named imports preferred over default exports
|
||||||
|
|
||||||
**Error Handling:**
|
**Error Handling:**
|
||||||
|
|
||||||
- Frontend: Try/catch with user feedback, rollback optimistic updates
|
- Frontend: Try/catch with user feedback, rollback optimistic updates
|
||||||
- Backend: `?` operator with anyhow context messages
|
- Backend: `?` operator with anyhow context messages
|
||||||
- Log errors appropriately for debugging level
|
- Log errors appropriately for debugging level
|
||||||
|
|
||||||
**Component Patterns:**
|
**Component Patterns:**
|
||||||
|
|
||||||
- Container component pattern for layout
|
- Container component pattern for layout
|
||||||
- Composition over inheritance
|
- Composition over inheritance
|
||||||
- Prop drilling minimized with context where appropriate
|
- Prop drilling minimized with context where appropriate
|
||||||
|
|
|
||||||
21
README.md
21
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
|
4. **Get** your transcribed text pasted directly into whatever app you're using
|
||||||
|
|
||||||
The process is entirely local:
|
The process is entirely local:
|
||||||
|
|
||||||
- Silence is filtered using VAD (Voice Activity Detection) with Silero
|
- Silence is filtered using VAD (Voice Activity Detection) with Silero
|
||||||
- Transcription uses your choice of models:
|
- Transcription uses your choice of models:
|
||||||
- **Whisper models** (Small/Medium/Turbo/Large) with GPU acceleration when available
|
- **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
|
### Debug Mode
|
||||||
|
|
||||||
Handy includes an advanced debug mode for development and troubleshooting. Access it by pressing:
|
Handy includes an advanced debug mode for development and troubleshooting. Access it by pressing:
|
||||||
|
|
||||||
- **macOS**: `Cmd+Shift+D`
|
- **macOS**: `Cmd+Shift+D`
|
||||||
- **Windows/Linux**: `Ctrl+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)
|
### Major Issues (Help Wanted)
|
||||||
|
|
||||||
**Whisper Model Crashes:**
|
**Whisper Model Crashes:**
|
||||||
|
|
||||||
- Whisper models crash on certain system configurations (Windows and Linux)
|
- Whisper models crash on certain system configurations (Windows and Linux)
|
||||||
- Does not affect all systems - issue is configuration-dependent
|
- 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!
|
- If you experience crashes and are a developer, please help to fix and provide debug logs!
|
||||||
|
|
||||||
**Wayland Support (Linux):**
|
**Wayland Support (Linux):**
|
||||||
|
|
||||||
- Limited or no support for Wayland display server
|
- Limited or no support for Wayland display server
|
||||||
|
|
||||||
### Platform Support
|
### Platform Support
|
||||||
|
|
||||||
- **macOS (both Intel and Apple Silicon)**
|
- **macOS (both Intel and Apple Silicon)**
|
||||||
- **x64 Windows**
|
- **x64 Windows**
|
||||||
- **x64 Linux**
|
- **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.
|
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:**
|
**For Whisper Models:**
|
||||||
|
|
||||||
- **macOS**: M series Mac, Intel Mac
|
- **macOS**: M series Mac, Intel Mac
|
||||||
- **Windows**: Intel, AMD, or NVIDIA GPU
|
- **Windows**: Intel, AMD, or NVIDIA GPU
|
||||||
- **Linux**: 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:**
|
**For Parakeet V3 Model:**
|
||||||
|
|
||||||
- **CPU-only operation** - runs on a wide variety of hardware
|
- **CPU-only operation** - runs on a wide variety of hardware
|
||||||
- **Minimum**: Intel Skylake (6th gen) or equivalent AMD processors
|
- **Minimum**: Intel Skylake (6th gen) or equivalent AMD processors
|
||||||
- **Performance**: ~5x real-time speed on mid-range hardware (tested on i5)
|
- **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
|
### In Progress
|
||||||
|
|
||||||
**Debug Logging:**
|
**Debug Logging:**
|
||||||
|
|
||||||
- Adding debug logging to a file to help diagnose issues
|
- Adding debug logging to a file to help diagnose issues
|
||||||
|
|
||||||
**macOS Keyboard Improvements:**
|
**macOS Keyboard Improvements:**
|
||||||
|
|
||||||
- Support for Globe key as transcription trigger
|
- Support for Globe key as transcription trigger
|
||||||
- A rewrite of global shortcut handling for MacOS, and potentially other OS's too.
|
- A rewrite of global shortcut handling for MacOS, and potentially other OS's too.
|
||||||
|
|
||||||
**Opt-in Analytics:**
|
**Opt-in Analytics:**
|
||||||
|
|
||||||
- Collect anonymous usage data to help improve Handy
|
- Collect anonymous usage data to help improve Handy
|
||||||
- Privacy-first approach with clear opt-in
|
- Privacy-first approach with clear opt-in
|
||||||
|
|
||||||
**Settings Refactoring:**
|
**Settings Refactoring:**
|
||||||
|
|
||||||
- Cleanup and refactor settings system which is becoming bloated and messy
|
- Cleanup and refactor settings system which is becoming bloated and messy
|
||||||
- Implement better abstractions for settings management
|
- Implement better abstractions for settings management
|
||||||
|
|
||||||
**Tauri Commands Cleanup:**
|
**Tauri Commands Cleanup:**
|
||||||
|
|
||||||
- Abstract and organize Tauri command patterns
|
- Abstract and organize Tauri command patterns
|
||||||
- Investigate tauri-specta for improved type safety and organization
|
- Investigate tauri-specta for improved type safety and organization
|
||||||
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Manual Model Installation (For Proxy Users or Network Restrictions)
|
### 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
|
- **Windows/Linux**: `Ctrl+Shift+D` to open debug menu
|
||||||
|
|
||||||
The typical paths are:
|
The typical paths are:
|
||||||
|
|
||||||
- **macOS**: `~/Library/Application Support/com.pais.handy/`
|
- **macOS**: `~/Library/Application Support/com.pais.handy/`
|
||||||
- **Windows**: `C:\Users\{username}\AppData\Roaming\com.pais.handy\`
|
- **Windows**: `C:\Users\{username}\AppData\Roaming\com.pais.handy\`
|
||||||
- **Linux**: `~/.config/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
|
Download the models you want from below
|
||||||
|
|
||||||
**Whisper Models (single .bin files):**
|
**Whisper Models (single .bin files):**
|
||||||
|
|
||||||
- Small (487 MB): `https://blob.handy.computer/ggml-small.bin`
|
- Small (487 MB): `https://blob.handy.computer/ggml-small.bin`
|
||||||
- Medium (492 MB): `https://blob.handy.computer/whisper-medium-q4_1.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`
|
- 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`
|
- Large (1100 MB): `https://blob.handy.computer/ggml-large-v3-q5_0.bin`
|
||||||
|
|
||||||
**Parakeet Models (compressed archives):**
|
**Parakeet Models (compressed archives):**
|
||||||
|
|
||||||
- V2 (473 MB): `https://blob.handy.computer/parakeet-v2-int8.tar.gz`
|
- V2 (473 MB): `https://blob.handy.computer/parakeet-v2-int8.tar.gz`
|
||||||
- V3 (478 MB): `https://blob.handy.computer/parakeet-v3-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:**
|
**Important Notes:**
|
||||||
|
|
||||||
- For Parakeet models, the extracted directory name **must** match exactly as shown above
|
- 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
|
- 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
|
- 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."_
|
||||||
|
|
|
||||||
3
bun.lock
3
bun.lock
|
|
@ -33,6 +33,7 @@
|
||||||
"@types/react-dom": "^18.3.7",
|
"@types/react-dom": "^18.3.7",
|
||||||
"@types/react-select": "^5.0.1",
|
"@types/react-select": "^5.0.1",
|
||||||
"@vitejs/plugin-react": "^4.7.0",
|
"@vitejs/plugin-react": "^4.7.0",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
"typescript": "~5.6.3",
|
"typescript": "~5.6.3",
|
||||||
"vite": "^6.4.1",
|
"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=="],
|
"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=="],
|
"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=="],
|
"react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="],
|
||||||
|
|
|
||||||
18
index.html
18
index.html
|
|
@ -1,13 +1,13 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>handy</title>
|
<title>handy</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,11 @@
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "tsc && vite build",
|
"build": "tsc && vite build",
|
||||||
"preview": "vite preview",
|
"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": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.16",
|
"@tailwindcss/vite": "^4.1.16",
|
||||||
|
|
@ -39,6 +43,7 @@
|
||||||
"@types/react-dom": "^18.3.7",
|
"@types/react-dom": "^18.3.7",
|
||||||
"@types/react-select": "^5.0.1",
|
"@types/react-select": "^5.0.1",
|
||||||
"@vitejs/plugin-react": "^4.7.0",
|
"@vitejs/plugin-react": "^4.7.0",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
"typescript": "~5.6.3",
|
"typescript": "~5.6.3",
|
||||||
"vite": "^6.4.1"
|
"vite": "^6.4.1"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,7 @@
|
||||||
{
|
{
|
||||||
"identifier": "desktop-capability",
|
"identifier": "desktop-capability",
|
||||||
"platforms": [
|
"platforms": ["macOS", "windows", "linux"],
|
||||||
"macOS",
|
"windows": ["main"],
|
||||||
"windows",
|
|
||||||
"linux"
|
|
||||||
],
|
|
||||||
"windows": [
|
|
||||||
"main"
|
|
||||||
],
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"autostart:default",
|
"autostart:default",
|
||||||
"global-shortcut:default",
|
"global-shortcut:default",
|
||||||
|
|
@ -15,4 +9,4 @@
|
||||||
"autostart:default",
|
"autostart:default",
|
||||||
"updater:default"
|
"updater:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
src-tauri/rustfmt.toml
Normal file
1
src-tauri/rustfmt.toml
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
edition = "2021"
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
use async_openai::{config::OpenAIConfig, Client};
|
|
||||||
use crate::settings::PostProcessProvider;
|
use crate::settings::PostProcessProvider;
|
||||||
|
use async_openai::{config::OpenAIConfig, Client};
|
||||||
|
|
||||||
/// Create an OpenAI-compatible client configured for the given provider
|
/// Create an OpenAI-compatible client configured for the given provider
|
||||||
pub fn create_client(provider: &PostProcessProvider, api_key: String) -> Result<Client<OpenAIConfig>, String> {
|
pub fn create_client(
|
||||||
|
provider: &PostProcessProvider,
|
||||||
|
api_key: String,
|
||||||
|
) -> Result<Client<OpenAIConfig>, String> {
|
||||||
let base_url = provider.base_url.trim_end_matches('/');
|
let base_url = provider.base_url.trim_end_matches('/');
|
||||||
let config = OpenAIConfig::new()
|
let config = OpenAIConfig::new()
|
||||||
.with_api_base(base_url)
|
.with_api_base(base_url)
|
||||||
|
|
|
||||||
10
src/App.css
10
src/App.css
|
|
@ -3,8 +3,8 @@
|
||||||
@theme {
|
@theme {
|
||||||
--color-text: #0f0f0f;
|
--color-text: #0f0f0f;
|
||||||
--color-background: #fbfbfb;
|
--color-background: #fbfbfb;
|
||||||
--color-background-ui: #DA5893;
|
--color-background-ui: #da5893;
|
||||||
--color-logo-primary: #FAA2CA;
|
--color-logo-primary: #faa2ca;
|
||||||
--color-logo-stroke: #382731;
|
--color-logo-stroke: #382731;
|
||||||
--color-text-stroke: #f6f6f6;
|
--color-text-stroke: #f6f6f6;
|
||||||
--color-mid-gray: #808080;
|
--color-mid-gray: #808080;
|
||||||
|
|
@ -49,8 +49,8 @@
|
||||||
/* Colors - Dark Theme */
|
/* Colors - Dark Theme */
|
||||||
--color-text: #fbfbfb;
|
--color-text: #fbfbfb;
|
||||||
--color-background: #2c2b29;
|
--color-background: #2c2b29;
|
||||||
--color-logo-primary: #F28CBB;
|
--color-logo-primary: #f28cbb;
|
||||||
--color-logo-stroke: #FAD1ED;
|
--color-logo-stroke: #fad1ed;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,4 +68,4 @@
|
||||||
fill: var(--color-logo-stroke);
|
fill: var(--color-logo-stroke);
|
||||||
stroke: var(--color-logo-stroke);
|
stroke: var(--color-logo-stroke);
|
||||||
stroke-width: 1;
|
stroke-width: 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
export { default } from './Footer';
|
export { default } from "./Footer";
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
export { default as MicrophoneIcon } from './MicrophoneIcon';
|
export { default as MicrophoneIcon } from "./MicrophoneIcon";
|
||||||
export { default as TranscriptionIcon } from './TranscriptionIcon';
|
export { default as TranscriptionIcon } from "./TranscriptionIcon";
|
||||||
export { default as CancelIcon } from './CancelIcon';
|
export { default as CancelIcon } from "./CancelIcon";
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,9 @@ const DownloadProgressDisplay: React.FC<DownloadProgressDisplayProps> = ({
|
||||||
return null;
|
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);
|
const stats = downloadStats.get(progress.model_id);
|
||||||
return {
|
return {
|
||||||
id: progress.model_id,
|
id: progress.model_id,
|
||||||
|
|
|
||||||
|
|
@ -184,22 +184,22 @@ const ModelDropdown: React.FC<ModelDropdownProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-logo-primary tabular-nums">
|
<div className="text-xs text-logo-primary tabular-nums">
|
||||||
{isDownloading && progress ? (
|
{isDownloading && progress
|
||||||
`${Math.max(0, Math.min(100, Math.round(progress.percentage)))}%`
|
? `${Math.max(0, Math.min(100, Math.round(progress.percentage)))}%`
|
||||||
) : (
|
: "Download"}
|
||||||
"Download"
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isDownloading && progress && (
|
{isDownloading && progress && (
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
progress={[{
|
progress={[
|
||||||
id: model.id,
|
{
|
||||||
percentage: progress.percentage,
|
id: model.id,
|
||||||
label: model.name
|
percentage: progress.percentage,
|
||||||
}]}
|
label: model.name,
|
||||||
|
},
|
||||||
|
]}
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -199,19 +199,19 @@ const ModelSelector: React.FC<ModelSelectorProps> = ({ onError }) => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const extractionFailedUnlisten = listen<{model_id: string, error: string}>(
|
const extractionFailedUnlisten = listen<{
|
||||||
"model-extraction-failed",
|
model_id: string;
|
||||||
(event) => {
|
error: string;
|
||||||
const modelId = event.payload.model_id;
|
}>("model-extraction-failed", (event) => {
|
||||||
setExtractingModels((prev) => {
|
const modelId = event.payload.model_id;
|
||||||
const next = new Set(prev);
|
setExtractingModels((prev) => {
|
||||||
next.delete(modelId);
|
const next = new Set(prev);
|
||||||
return next;
|
next.delete(modelId);
|
||||||
});
|
return next;
|
||||||
setModelError(`Failed to extract model: ${event.payload.error}`);
|
});
|
||||||
setModelStatus("error");
|
setModelError(`Failed to extract model: ${event.payload.error}`);
|
||||||
},
|
setModelStatus("error");
|
||||||
);
|
});
|
||||||
|
|
||||||
// Click outside to close dropdown
|
// Click outside to close dropdown
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
|
@ -304,8 +304,8 @@ const ModelSelector: React.FC<ModelSelectorProps> = ({ onError }) => {
|
||||||
if (extractingModels.size > 0) {
|
if (extractingModels.size > 0) {
|
||||||
if (extractingModels.size === 1) {
|
if (extractingModels.size === 1) {
|
||||||
const [modelId] = Array.from(extractingModels);
|
const [modelId] = Array.from(extractingModels);
|
||||||
const model = models.find(m => m.id === modelId);
|
const model = models.find((m) => m.id === modelId);
|
||||||
return `Extracting ${model?.name || 'Model'}...`;
|
return `Extracting ${model?.name || "Model"}...`;
|
||||||
} else {
|
} else {
|
||||||
return `Extracting ${extractingModels.size} models...`;
|
return `Extracting ${extractingModels.size} models...`;
|
||||||
}
|
}
|
||||||
|
|
@ -332,7 +332,9 @@ const ModelSelector: React.FC<ModelSelectorProps> = ({ onError }) => {
|
||||||
case "loading":
|
case "loading":
|
||||||
return currentModel ? `Loading ${currentModel.name}...` : "Loading...";
|
return currentModel ? `Loading ${currentModel.name}...` : "Loading...";
|
||||||
case "extracting":
|
case "extracting":
|
||||||
return currentModel ? `Extracting ${currentModel.name}...` : "Extracting...";
|
return currentModel
|
||||||
|
? `Extracting ${currentModel.name}...`
|
||||||
|
: "Extracting...";
|
||||||
case "error":
|
case "error":
|
||||||
return modelError || "Model Error";
|
return modelError || "Model Error";
|
||||||
case "unloaded":
|
case "unloaded":
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
export { default } from './Onboarding';
|
export { default } from "./Onboarding";
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,22 @@ interface AlwaysOnMicrophoneProps {
|
||||||
grouped?: boolean;
|
grouped?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AlwaysOnMicrophone: React.FC<AlwaysOnMicrophoneProps> = React.memo(({
|
export const AlwaysOnMicrophone: React.FC<AlwaysOnMicrophoneProps> = React.memo(
|
||||||
descriptionMode = "tooltip",
|
({ descriptionMode = "tooltip", grouped = false }) => {
|
||||||
grouped = false,
|
const { getSetting, updateSetting, isUpdating } = useSettings();
|
||||||
}) => {
|
|
||||||
const { getSetting, updateSetting, isUpdating } = useSettings();
|
|
||||||
|
|
||||||
const alwaysOnMode = getSetting("always_on_microphone") || false;
|
const alwaysOnMode = getSetting("always_on_microphone") || false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
checked={alwaysOnMode}
|
checked={alwaysOnMode}
|
||||||
onChange={(enabled) => updateSetting("always_on_microphone", enabled)}
|
onChange={(enabled) => updateSetting("always_on_microphone", enabled)}
|
||||||
isUpdating={isUpdating("always_on_microphone")}
|
isUpdating={isUpdating("always_on_microphone")}
|
||||||
label="Always-On Microphone"
|
label="Always-On Microphone"
|
||||||
description="Keep microphone active for low latency recording. This may prevent your computer from sleeping."
|
description="Keep microphone active for low latency recording. This may prevent your computer from sleeping."
|
||||||
descriptionMode={descriptionMode}
|
descriptionMode={descriptionMode}
|
||||||
grouped={grouped}
|
grouped={grouped}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -14,30 +14,28 @@ const clipboardHandlingOptions = [
|
||||||
{ value: "copy_to_clipboard", label: "Copy to Clipboard" },
|
{ value: "copy_to_clipboard", label: "Copy to Clipboard" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ClipboardHandlingSetting: React.FC<ClipboardHandlingProps> = React.memo(({
|
export const ClipboardHandlingSetting: React.FC<ClipboardHandlingProps> =
|
||||||
descriptionMode = "tooltip",
|
React.memo(({ descriptionMode = "tooltip", grouped = false }) => {
|
||||||
grouped = false,
|
const { getSetting, updateSetting, isUpdating } = useSettings();
|
||||||
}) => {
|
|
||||||
const { getSetting, updateSetting, isUpdating } = useSettings();
|
|
||||||
|
|
||||||
const selectedHandling = (getSetting("clipboard_handling") ||
|
const selectedHandling = (getSetting("clipboard_handling") ||
|
||||||
"dont_modify") as ClipboardHandling;
|
"dont_modify") as ClipboardHandling;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingContainer
|
<SettingContainer
|
||||||
title="Clipboard Handling"
|
title="Clipboard Handling"
|
||||||
description="Don't Modify Clipboard preserves your current clipboard contents after transcription. Copy to Clipboard leaves the transcription result in your clipboard after pasting."
|
description="Don't Modify Clipboard preserves your current clipboard contents after transcription. Copy to Clipboard leaves the transcription result in your clipboard after pasting."
|
||||||
descriptionMode={descriptionMode}
|
descriptionMode={descriptionMode}
|
||||||
grouped={grouped}
|
grouped={grouped}
|
||||||
>
|
>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
options={clipboardHandlingOptions}
|
options={clipboardHandlingOptions}
|
||||||
selectedValue={selectedHandling}
|
selectedValue={selectedHandling}
|
||||||
onSelect={(value) =>
|
onSelect={(value) =>
|
||||||
updateSetting("clipboard_handling", value as ClipboardHandling)
|
updateSetting("clipboard_handling", value as ClipboardHandling)
|
||||||
}
|
}
|
||||||
disabled={isUpdating("clipboard_handling")}
|
disabled={isUpdating("clipboard_handling")}
|
||||||
/>
|
/>
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,57 +9,66 @@ interface MicrophoneSelectorProps {
|
||||||
grouped?: boolean;
|
grouped?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MicrophoneSelector: React.FC<MicrophoneSelectorProps> = React.memo(({
|
export const MicrophoneSelector: React.FC<MicrophoneSelectorProps> = React.memo(
|
||||||
descriptionMode = "tooltip",
|
({ descriptionMode = "tooltip", grouped = false }) => {
|
||||||
grouped = false,
|
const {
|
||||||
}) => {
|
getSetting,
|
||||||
const {
|
updateSetting,
|
||||||
getSetting,
|
resetSetting,
|
||||||
updateSetting,
|
isUpdating,
|
||||||
resetSetting,
|
isLoading,
|
||||||
isUpdating,
|
audioDevices,
|
||||||
isLoading,
|
refreshAudioDevices,
|
||||||
audioDevices,
|
} = useSettings();
|
||||||
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) => {
|
const handleMicrophoneSelect = async (deviceName: string) => {
|
||||||
await updateSetting("selected_microphone", deviceName);
|
await updateSetting("selected_microphone", deviceName);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReset = async () => {
|
const handleReset = async () => {
|
||||||
await resetSetting("selected_microphone");
|
await resetSetting("selected_microphone");
|
||||||
};
|
};
|
||||||
|
|
||||||
const microphoneOptions = audioDevices.map(device => ({
|
const microphoneOptions = audioDevices.map((device) => ({
|
||||||
value: device.name,
|
value: device.name,
|
||||||
label: device.name
|
label: device.name,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingContainer
|
<SettingContainer
|
||||||
title="Microphone"
|
title="Microphone"
|
||||||
description="Select your preferred microphone device"
|
description="Select your preferred microphone device"
|
||||||
descriptionMode={descriptionMode}
|
descriptionMode={descriptionMode}
|
||||||
grouped={grouped}
|
grouped={grouped}
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-1">
|
<div className="flex items-center space-x-1">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
options={microphoneOptions}
|
options={microphoneOptions}
|
||||||
selectedValue={selectedMicrophone}
|
selectedValue={selectedMicrophone}
|
||||||
onSelect={handleMicrophoneSelect}
|
onSelect={handleMicrophoneSelect}
|
||||||
placeholder={isLoading || audioDevices.length === 0 ? "Loading..." : "Select microphone..."}
|
placeholder={
|
||||||
disabled={isUpdating("selected_microphone") || isLoading || audioDevices.length === 0}
|
isLoading || audioDevices.length === 0
|
||||||
onRefresh={refreshAudioDevices}
|
? "Loading..."
|
||||||
/>
|
: "Select microphone..."
|
||||||
<ResetButton
|
}
|
||||||
onClick={handleReset}
|
disabled={
|
||||||
disabled={isUpdating("selected_microphone") || isLoading}
|
isUpdating("selected_microphone") ||
|
||||||
/>
|
isLoading ||
|
||||||
</div>
|
audioDevices.length === 0
|
||||||
</SettingContainer>
|
}
|
||||||
);
|
onRefresh={refreshAudioDevices}
|
||||||
});
|
/>
|
||||||
|
<ResetButton
|
||||||
|
onClick={handleReset}
|
||||||
|
disabled={isUpdating("selected_microphone") || isLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</SettingContainer>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ interface PostProcessingToggleProps {
|
||||||
grouped?: boolean;
|
grouped?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PostProcessingToggle: React.FC<PostProcessingToggleProps> = React.memo(
|
export const PostProcessingToggle: React.FC<PostProcessingToggleProps> =
|
||||||
({ descriptionMode = "tooltip", grouped = false }) => {
|
React.memo(({ descriptionMode = "tooltip", grouped = false }) => {
|
||||||
const { getSetting, updateSetting, isUpdating } = useSettings();
|
const { getSetting, updateSetting, isUpdating } = useSettings();
|
||||||
|
|
||||||
const enabled = getSetting("post_process_enabled") || false;
|
const enabled = getSetting("post_process_enabled") || false;
|
||||||
|
|
@ -24,5 +24,4 @@ export const PostProcessingToggle: React.FC<PostProcessingToggleProps> = React.m
|
||||||
grouped={grouped}
|
grouped={grouped}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
});
|
||||||
);
|
|
||||||
|
|
|
||||||
|
|
@ -7,23 +7,22 @@ interface PushToTalkProps {
|
||||||
grouped?: boolean;
|
grouped?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PushToTalk: React.FC<PushToTalkProps> = React.memo(({
|
export const PushToTalk: React.FC<PushToTalkProps> = React.memo(
|
||||||
descriptionMode = "tooltip",
|
({ descriptionMode = "tooltip", grouped = false }) => {
|
||||||
grouped = false,
|
const { getSetting, updateSetting, isUpdating } = useSettings();
|
||||||
}) => {
|
|
||||||
const { getSetting, updateSetting, isUpdating } = useSettings();
|
|
||||||
|
|
||||||
const pttEnabled = getSetting("push_to_talk") || false;
|
const pttEnabled = getSetting("push_to_talk") || false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
checked={pttEnabled}
|
checked={pttEnabled}
|
||||||
onChange={(enabled) => updateSetting("push_to_talk", enabled)}
|
onChange={(enabled) => updateSetting("push_to_talk", enabled)}
|
||||||
isUpdating={isUpdating("push_to_talk")}
|
isUpdating={isUpdating("push_to_talk")}
|
||||||
label="Push To Talk"
|
label="Push To Talk"
|
||||||
description="Hold to record, release to stop"
|
description="Hold to record, release to stop"
|
||||||
descriptionMode={descriptionMode}
|
descriptionMode={descriptionMode}
|
||||||
grouped={grouped}
|
grouped={grouped}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -15,30 +15,29 @@ const overlayOptions = [
|
||||||
{ value: "top", label: "Top" },
|
{ value: "top", label: "Top" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ShowOverlay: React.FC<ShowOverlayProps> = React.memo(({
|
export const ShowOverlay: React.FC<ShowOverlayProps> = React.memo(
|
||||||
descriptionMode = "tooltip",
|
({ descriptionMode = "tooltip", grouped = false }) => {
|
||||||
grouped = false,
|
const { getSetting, updateSetting, isUpdating } = useSettings();
|
||||||
}) => {
|
|
||||||
const { getSetting, updateSetting, isUpdating } = useSettings();
|
|
||||||
|
|
||||||
const selectedPosition = (getSetting("overlay_position") ||
|
const selectedPosition = (getSetting("overlay_position") ||
|
||||||
"bottom") as OverlayPosition;
|
"bottom") as OverlayPosition;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingContainer
|
<SettingContainer
|
||||||
title="Overlay Position"
|
title="Overlay Position"
|
||||||
description="Display visual feedback overlay during recording and transcription"
|
description="Display visual feedback overlay during recording and transcription"
|
||||||
descriptionMode={descriptionMode}
|
descriptionMode={descriptionMode}
|
||||||
grouped={grouped}
|
grouped={grouped}
|
||||||
>
|
>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
options={overlayOptions}
|
options={overlayOptions}
|
||||||
selectedValue={selectedPosition}
|
selectedValue={selectedPosition}
|
||||||
onSelect={(value) =>
|
onSelect={(value) =>
|
||||||
updateSetting("overlay_position", value as OverlayPosition)
|
updateSetting("overlay_position", value as OverlayPosition)
|
||||||
}
|
}
|
||||||
disabled={isUpdating("overlay_position")}
|
disabled={isUpdating("overlay_position")}
|
||||||
/>
|
/>
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -7,24 +7,23 @@ interface StartHiddenProps {
|
||||||
grouped?: boolean;
|
grouped?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const StartHidden: React.FC<StartHiddenProps> = React.memo(({
|
export const StartHidden: React.FC<StartHiddenProps> = React.memo(
|
||||||
descriptionMode = "tooltip",
|
({ descriptionMode = "tooltip", grouped = false }) => {
|
||||||
grouped = false,
|
const { getSetting, updateSetting, isUpdating } = useSettings();
|
||||||
}) => {
|
|
||||||
const { getSetting, updateSetting, isUpdating } = useSettings();
|
|
||||||
|
|
||||||
const startHidden = getSetting("start_hidden") ?? false;
|
const startHidden = getSetting("start_hidden") ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToggleSwitch
|
<ToggleSwitch
|
||||||
checked={startHidden}
|
checked={startHidden}
|
||||||
onChange={(enabled) => updateSetting("start_hidden", enabled)}
|
onChange={(enabled) => updateSetting("start_hidden", enabled)}
|
||||||
isUpdating={isUpdating("start_hidden")}
|
isUpdating={isUpdating("start_hidden")}
|
||||||
label="Start Hidden"
|
label="Start Hidden"
|
||||||
description="Launch to system tray without opening the window."
|
description="Launch to system tray without opening the window."
|
||||||
descriptionMode={descriptionMode}
|
descriptionMode={descriptionMode}
|
||||||
grouped={grouped}
|
grouped={grouped}
|
||||||
tooltipPosition="bottom"
|
tooltipPosition="bottom"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,9 @@ export const DebugPaths: React.FC<DebugPathsProps> = ({
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span className="font-medium">Settings:</span>{" "}
|
<span className="font-medium">Settings:</span>{" "}
|
||||||
<span className="font-mono text-xs">%APPDATA%/handy/settings_store.json</span>
|
<span className="font-mono text-xs">
|
||||||
|
%APPDATA%/handy/settings_store.json
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SettingContainer>
|
</SettingContainer>
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
export { default as ProgressBar } from './ProgressBar';
|
export { default as ProgressBar } from "./ProgressBar";
|
||||||
export type { ProgressData } from './ProgressBar';
|
export type { ProgressData } from "./ProgressBar";
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,7 @@ interface ResetButtonProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ResetButton: React.FC<ResetButtonProps> = React.memo(
|
export const ResetButton: React.FC<ResetButtonProps> = React.memo(
|
||||||
({
|
({ onClick, disabled = false, className = "", ariaLabel, children }) => (
|
||||||
onClick,
|
|
||||||
disabled = false,
|
|
||||||
className = "",
|
|
||||||
ariaLabel,
|
|
||||||
children,
|
|
||||||
}) => (
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
|
|
|
||||||
|
|
@ -39,10 +39,14 @@ type NonCreatableProps = {
|
||||||
|
|
||||||
export type SelectProps = BaseProps & (CreatableProps | NonCreatableProps);
|
export type SelectProps = BaseProps & (CreatableProps | NonCreatableProps);
|
||||||
|
|
||||||
const baseBackground = "color-mix(in srgb, var(--color-mid-gray) 10%, transparent)";
|
const baseBackground =
|
||||||
const hoverBackground = "color-mix(in srgb, var(--color-logo-primary) 12%, transparent)";
|
"color-mix(in srgb, var(--color-mid-gray) 10%, transparent)";
|
||||||
const focusBackground = "color-mix(in srgb, var(--color-logo-primary) 20%, transparent)";
|
const hoverBackground =
|
||||||
const neutralBorder = "color-mix(in srgb, var(--color-mid-gray) 80%, transparent)";
|
"color-mix(in srgb, var(--color-logo-primary) 12%, transparent)";
|
||||||
|
const focusBackground =
|
||||||
|
"color-mix(in srgb, var(--color-logo-primary) 20%, transparent)";
|
||||||
|
const neutralBorder =
|
||||||
|
"color-mix(in srgb, var(--color-mid-gray) 80%, transparent)";
|
||||||
|
|
||||||
const selectStyles: StylesConfig<SelectOption, false> = {
|
const selectStyles: StylesConfig<SelectOption, false> = {
|
||||||
control: (base, state) => ({
|
control: (base, state) => ({
|
||||||
|
|
@ -94,7 +98,8 @@ const selectStyles: StylesConfig<SelectOption, false> = {
|
||||||
zIndex: 30,
|
zIndex: 30,
|
||||||
backgroundColor: "var(--color-background)",
|
backgroundColor: "var(--color-background)",
|
||||||
color: "var(--color-text)",
|
color: "var(--color-text)",
|
||||||
border: "1px solid color-mix(in srgb, var(--color-mid-gray) 30%, transparent)",
|
border:
|
||||||
|
"1px solid color-mix(in srgb, var(--color-mid-gray) 30%, transparent)",
|
||||||
boxShadow: "0 10px 30px rgba(15, 15, 15, 0.2)",
|
boxShadow: "0 10px 30px rgba(15, 15, 15, 0.2)",
|
||||||
}),
|
}),
|
||||||
option: (base, state) => ({
|
option: (base, state) => ({
|
||||||
|
|
|
||||||
|
|
@ -164,11 +164,15 @@ export const SettingContainer: React.FC<SettingContainerProps> = ({
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{showTooltip && (
|
{showTooltip && (
|
||||||
<div className={`absolute ${tooltipPosition === "top" ? "bottom-full" : "top-[150%]"} left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 bg-background border border-mid-gray/80 rounded-lg shadow-lg z-50 max-w-xs min-w-[200px] whitespace-normal animate-in fade-in-0 zoom-in-95 duration-200`}>
|
<div
|
||||||
|
className={`absolute ${tooltipPosition === "top" ? "bottom-full" : "top-[150%]"} left-1/2 transform -translate-x-1/2 mb-2 px-3 py-2 bg-background border border-mid-gray/80 rounded-lg shadow-lg z-50 max-w-xs min-w-[200px] whitespace-normal animate-in fade-in-0 zoom-in-95 duration-200`}
|
||||||
|
>
|
||||||
<p className="text-sm text-center leading-relaxed">
|
<p className="text-sm text-center leading-relaxed">
|
||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
<div className={`absolute ${tooltipPosition === "top" ? "top-full" : "bottom-full rotate-180"} left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-[6px] border-r-[6px] border-t-[6px] border-l-transparent border-r-transparent border-t-mid-gray/80`}></div>
|
<div
|
||||||
|
className={`absolute ${tooltipPosition === "top" ? "top-full" : "bottom-full rotate-180"} left-1/2 transform -translate-x-1/2 w-0 h-0 border-l-[6px] border-r-[6px] border-t-[6px] border-l-transparent border-r-transparent border-t-mid-gray/80`}
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
export { default } from './UpdateChecker';
|
export { default } from "./UpdateChecker";
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,7 @@ export const useModels = () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const extractionCompletedUnlisten = listen<string>(
|
const extractionCompletedUnlisten = listen<string>(
|
||||||
"model-extraction-completed",
|
"model-extraction-completed",
|
||||||
(event) => {
|
(event) => {
|
||||||
const modelId = event.payload;
|
const modelId = event.payload;
|
||||||
setExtractingModels((prev) => {
|
setExtractingModels((prev) => {
|
||||||
|
|
@ -193,18 +193,18 @@ export const useModels = () => {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const extractionFailedUnlisten = listen<{model_id: string, error: string}>(
|
const extractionFailedUnlisten = listen<{
|
||||||
"model-extraction-failed",
|
model_id: string;
|
||||||
(event) => {
|
error: string;
|
||||||
const modelId = event.payload.model_id;
|
}>("model-extraction-failed", (event) => {
|
||||||
setExtractingModels((prev) => {
|
const modelId = event.payload.model_id;
|
||||||
const next = new Set(prev);
|
setExtractingModels((prev) => {
|
||||||
next.delete(modelId);
|
const next = new Set(prev);
|
||||||
return next;
|
next.delete(modelId);
|
||||||
});
|
return next;
|
||||||
setError(`Failed to extract model: ${event.payload.error}`);
|
});
|
||||||
},
|
setError(`Failed to extract model: ${event.payload.error}`);
|
||||||
);
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
progressUnlisten.then((fn) => fn());
|
progressUnlisten.then((fn) => fn());
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ interface UseSettingsReturn {
|
||||||
refreshSettings: () => Promise<void>;
|
refreshSettings: () => Promise<void>;
|
||||||
refreshAudioDevices: () => Promise<void>;
|
refreshAudioDevices: () => Promise<void>;
|
||||||
refreshOutputDevices: () => Promise<void>;
|
refreshOutputDevices: () => Promise<void>;
|
||||||
|
|
||||||
// Binding-specific actions
|
// Binding-specific actions
|
||||||
updateBinding: (id: string, binding: string) => Promise<void>;
|
updateBinding: (id: string, binding: string) => Promise<void>;
|
||||||
resetBinding: (id: string) => Promise<void>;
|
resetBinding: (id: string) => Promise<void>;
|
||||||
|
|
@ -31,8 +31,14 @@ interface UseSettingsReturn {
|
||||||
|
|
||||||
// Post-processing helpers
|
// Post-processing helpers
|
||||||
setPostProcessProvider: (providerId: string) => Promise<void>;
|
setPostProcessProvider: (providerId: string) => Promise<void>;
|
||||||
updatePostProcessBaseUrl: (providerId: string, baseUrl: string) => Promise<void>;
|
updatePostProcessBaseUrl: (
|
||||||
updatePostProcessApiKey: (providerId: string, apiKey: string) => Promise<void>;
|
providerId: string,
|
||||||
|
baseUrl: string,
|
||||||
|
) => Promise<void>;
|
||||||
|
updatePostProcessApiKey: (
|
||||||
|
providerId: string,
|
||||||
|
apiKey: string,
|
||||||
|
) => Promise<void>;
|
||||||
updatePostProcessModel: (providerId: string, model: string) => Promise<void>;
|
updatePostProcessModel: (providerId: string, model: string) => Promise<void>;
|
||||||
fetchPostProcessModels: (providerId: string) => Promise<string[]>;
|
fetchPostProcessModels: (providerId: string) => Promise<string[]>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,4 +56,4 @@ export const LANGUAGES: Language[] = [
|
||||||
{ value: "et", label: "Estonian" },
|
{ value: "et", label: "Estonian" },
|
||||||
{ value: "mk", label: "Macedonian" },
|
{ value: "mk", label: "Macedonian" },
|
||||||
{ value: "br", label: "Breton" },
|
{ value: "br", label: "Breton" },
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,10 @@ export type ModelUnloadTimeout = z.infer<typeof ModelUnloadTimeoutSchema>;
|
||||||
export const PasteMethodSchema = z.enum(["ctrl_v", "direct", "shift_insert"]);
|
export const PasteMethodSchema = z.enum(["ctrl_v", "direct", "shift_insert"]);
|
||||||
export type PasteMethod = z.infer<typeof PasteMethodSchema>;
|
export type PasteMethod = z.infer<typeof PasteMethodSchema>;
|
||||||
|
|
||||||
export const ClipboardHandlingSchema = z.enum(["dont_modify", "copy_to_clipboard"]);
|
export const ClipboardHandlingSchema = z.enum([
|
||||||
|
"dont_modify",
|
||||||
|
"copy_to_clipboard",
|
||||||
|
]);
|
||||||
export type ClipboardHandling = z.infer<typeof ClipboardHandlingSchema>;
|
export type ClipboardHandling = z.infer<typeof ClipboardHandlingSchema>;
|
||||||
|
|
||||||
export const LLMPromptSchema = z.object({
|
export const LLMPromptSchema = z.object({
|
||||||
|
|
@ -93,14 +96,8 @@ export const SettingsSchema = z.object({
|
||||||
.array(PostProcessProviderSchema)
|
.array(PostProcessProviderSchema)
|
||||||
.optional()
|
.optional()
|
||||||
.default([]),
|
.default([]),
|
||||||
post_process_api_keys: z
|
post_process_api_keys: z.record(z.string()).optional().default({}),
|
||||||
.record(z.string())
|
post_process_models: z.record(z.string()).optional().default({}),
|
||||||
.optional()
|
|
||||||
.default({}),
|
|
||||||
post_process_models: z
|
|
||||||
.record(z.string())
|
|
||||||
.optional()
|
|
||||||
.default({}),
|
|
||||||
post_process_prompts: z.array(LLMPromptSchema).optional().default([]),
|
post_process_prompts: z.array(LLMPromptSchema).optional().default([]),
|
||||||
post_process_selected_prompt_id: z.string().nullable().optional(),
|
post_process_selected_prompt_id: z.string().nullable().optional(),
|
||||||
mute_while_recording: z.boolean().optional().default(false),
|
mute_while_recording: z.boolean().optional().default(false),
|
||||||
|
|
|
||||||
|
|
@ -1,95 +1,95 @@
|
||||||
.recording-overlay {
|
.recording-overlay {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
width: 172px;
|
width: 172px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 1fr auto;
|
grid-template-columns: auto 1fr auto;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 6px;
|
padding: 6px;
|
||||||
background: #000000cc;
|
background: #000000cc;
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: opacity 300ms ease-out;
|
transition: opacity 300ms ease-out;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-left {
|
.overlay-left {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-middle {
|
.overlay-middle {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.overlay-right {
|
.overlay-right {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bars-container {
|
.bars-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 3px;
|
gap: 3px;
|
||||||
padding-bottom: 0px;
|
padding-bottom: 0px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bar {
|
.bar {
|
||||||
width: 6px;
|
width: 6px;
|
||||||
background: #FFE5EE;
|
background: #ffe5ee;
|
||||||
max-height: 20px;
|
max-height: 20px;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
transition: height 80ms linear;
|
transition: height 80ms linear;
|
||||||
min-height: 4px;
|
min-height: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.recording-overlay.fade-in {
|
.recording-overlay.fade-in {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.transcribing-text {
|
.transcribing-text {
|
||||||
color: white;
|
color: white;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-family:
|
font-family:
|
||||||
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||||
animation: transcribing-pulse 1.5s infinite ease-in-out;
|
animation: transcribing-pulse 1.5s infinite ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes transcribing-pulse {
|
@keyframes transcribing-pulse {
|
||||||
0%,
|
0%,
|
||||||
100% {
|
100% {
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
50% {
|
50% {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-button {
|
.cancel-button {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition:
|
transition:
|
||||||
background-color 150ms ease-out,
|
background-color 150ms ease-out,
|
||||||
transform 100ms ease-out;
|
transform 100ms ease-out;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-button:hover {
|
.cancel-button:hover {
|
||||||
background: #FAA2CA33;
|
background: #faa2ca33;
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cancel-button:active {
|
.cancel-button:active {
|
||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,27 @@
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Recording Overlay</title>
|
<title>Recording Overlay</title>
|
||||||
<style>
|
<style>
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
#root {
|
#root {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script type="module" src="/src/overlay/main.tsx"></script>
|
<script type="module" src="/src/overlay/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,19 @@ interface SettingsStore {
|
||||||
playTestSound: (soundType: "start" | "stop") => Promise<void>;
|
playTestSound: (soundType: "start" | "stop") => Promise<void>;
|
||||||
checkCustomSounds: () => Promise<void>;
|
checkCustomSounds: () => Promise<void>;
|
||||||
setPostProcessProvider: (providerId: string) => Promise<void>;
|
setPostProcessProvider: (providerId: string) => Promise<void>;
|
||||||
updatePostProcessSetting: (settingType: 'base_url' | 'api_key' | 'model', providerId: string, value: string) => Promise<void>;
|
updatePostProcessSetting: (
|
||||||
updatePostProcessBaseUrl: (providerId: string, baseUrl: string) => Promise<void>;
|
settingType: "base_url" | "api_key" | "model",
|
||||||
updatePostProcessApiKey: (providerId: string, apiKey: string) => Promise<void>;
|
providerId: string,
|
||||||
|
value: string,
|
||||||
|
) => Promise<void>;
|
||||||
|
updatePostProcessBaseUrl: (
|
||||||
|
providerId: string,
|
||||||
|
baseUrl: string,
|
||||||
|
) => Promise<void>;
|
||||||
|
updatePostProcessApiKey: (
|
||||||
|
providerId: string,
|
||||||
|
apiKey: string,
|
||||||
|
) => Promise<void>;
|
||||||
updatePostProcessModel: (providerId: string, model: string) => Promise<void>;
|
updatePostProcessModel: (providerId: string, model: string) => Promise<void>;
|
||||||
fetchPostProcessModels: (providerId: string) => Promise<string[]>;
|
fetchPostProcessModels: (providerId: string) => Promise<string[]>;
|
||||||
setPostProcessModelOptions: (providerId: string, models: string[]) => void;
|
setPostProcessModelOptions: (providerId: string, models: string[]) => void;
|
||||||
|
|
@ -151,7 +161,7 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
const { load } = await import("@tauri-apps/plugin-store");
|
const { load } = await import("@tauri-apps/plugin-store");
|
||||||
const store = await load("settings_store.json", {
|
const store = await load("settings_store.json", {
|
||||||
defaults: DEFAULT_SETTINGS,
|
defaults: DEFAULT_SETTINGS,
|
||||||
autoSave: false
|
autoSave: false,
|
||||||
});
|
});
|
||||||
const settings = (await store.get("settings")) as Settings;
|
const settings = (await store.get("settings")) as Settings;
|
||||||
|
|
||||||
|
|
@ -234,7 +244,6 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
checkCustomSounds: async () => {
|
checkCustomSounds: async () => {
|
||||||
try {
|
try {
|
||||||
const sounds = await invoke("check_custom_sounds");
|
const sounds = await invoke("check_custom_sounds");
|
||||||
|
|
@ -386,25 +395,25 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
|
|
||||||
// Generic updater for post-processing provider settings
|
// Generic updater for post-processing provider settings
|
||||||
updatePostProcessSetting: async (
|
updatePostProcessSetting: async (
|
||||||
settingType: 'base_url' | 'api_key' | 'model',
|
settingType: "base_url" | "api_key" | "model",
|
||||||
providerId: string,
|
providerId: string,
|
||||||
value: string
|
value: string,
|
||||||
) => {
|
) => {
|
||||||
const { setUpdating, refreshSettings } = get();
|
const { setUpdating, refreshSettings } = get();
|
||||||
const updateKey = `post_process_${settingType}:${providerId}`;
|
const updateKey = `post_process_${settingType}:${providerId}`;
|
||||||
|
|
||||||
// Map setting types to command names
|
// Map setting types to command names
|
||||||
const commandMap = {
|
const commandMap = {
|
||||||
base_url: 'change_post_process_base_url_setting',
|
base_url: "change_post_process_base_url_setting",
|
||||||
api_key: 'change_post_process_api_key_setting',
|
api_key: "change_post_process_api_key_setting",
|
||||||
model: 'change_post_process_model_setting',
|
model: "change_post_process_model_setting",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Map setting types to param names
|
// Map setting types to param names
|
||||||
const paramMap = {
|
const paramMap = {
|
||||||
base_url: 'baseUrl',
|
base_url: "baseUrl",
|
||||||
api_key: 'apiKey',
|
api_key: "apiKey",
|
||||||
model: 'model',
|
model: "model",
|
||||||
};
|
};
|
||||||
|
|
||||||
setUpdating(updateKey, true);
|
setUpdating(updateKey, true);
|
||||||
|
|
@ -416,14 +425,17 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
});
|
});
|
||||||
await refreshSettings();
|
await refreshSettings();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to update post-process ${settingType.replace('_', ' ')}:`, error);
|
console.error(
|
||||||
|
`Failed to update post-process ${settingType.replace("_", " ")}:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
setUpdating(updateKey, false);
|
setUpdating(updateKey, false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
updatePostProcessBaseUrl: async (providerId, baseUrl) => {
|
updatePostProcessBaseUrl: async (providerId, baseUrl) => {
|
||||||
return get().updatePostProcessSetting('base_url', providerId, baseUrl);
|
return get().updatePostProcessSetting("base_url", providerId, baseUrl);
|
||||||
},
|
},
|
||||||
|
|
||||||
updatePostProcessApiKey: async (providerId, apiKey) => {
|
updatePostProcessApiKey: async (providerId, apiKey) => {
|
||||||
|
|
@ -434,11 +446,11 @@ export const useSettingsStore = create<SettingsStore>()(
|
||||||
[providerId]: [],
|
[providerId]: [],
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
return get().updatePostProcessSetting('api_key', providerId, apiKey);
|
return get().updatePostProcessSetting("api_key", providerId, apiKey);
|
||||||
},
|
},
|
||||||
|
|
||||||
updatePostProcessModel: async (providerId, model) => {
|
updatePostProcessModel: async (providerId, model) => {
|
||||||
return get().updatePostProcessSetting('model', providerId, model);
|
return get().updatePostProcessSetting("model", providerId, model);
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchPostProcessModels: async (providerId) => {
|
fetchPostProcessModels: async (providerId) => {
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,16 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
content: [
|
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||||
"./index.html",
|
|
||||||
"./src/**/*.{js,ts,jsx,tsx}",
|
|
||||||
],
|
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
text: 'var(--color-text)',
|
text: "var(--color-text)",
|
||||||
background: 'var(--color-background)',
|
background: "var(--color-background)",
|
||||||
'logo-primary': 'var(--color-logo-primary)',
|
"logo-primary": "var(--color-logo-primary)",
|
||||||
'logo-stroke': 'var(--color-logo-stroke)',
|
"logo-stroke": "var(--color-logo-stroke)",
|
||||||
'text-stroke': 'var(--color-text-stroke)',
|
"text-stroke": "var(--color-text-stroke)",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue