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
|
||||
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:**
|
||||
|
||||
<!-- You can find this in the app 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, AMD RX 6800 XT, Intel UHD Graphics -->
|
||||
|
|
|
|||
5
.github/ISSUE_TEMPLATE/feature_request.md
vendored
5
.github/ISSUE_TEMPLATE/feature_request.md
vendored
|
|
@ -1,9 +1,9 @@
|
|||
---
|
||||
name: Feature Request
|
||||
about: Suggest an idea or new feature for Handy
|
||||
title: ''
|
||||
title: ""
|
||||
labels: []
|
||||
assignees: ''
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
## 🎯 Feature Requests Go in Discussions!
|
||||
|
|
@ -13,6 +13,7 @@ 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
|
||||
|
|
|
|||
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"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,6 +46,7 @@ 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
|
||||
|
|
@ -56,6 +60,7 @@ Handy is a cross-platform desktop speech-to-text application built with Tauri (R
|
|||
- `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,6 +78,7 @@ 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
|
||||
- `vad-rs` - Voice Activity Detection
|
||||
|
|
@ -81,6 +87,7 @@ Handy is a cross-platform desktop speech-to-text application built with Tauri (R
|
|||
- `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)
|
||||
|
|
|
|||
8
BUILD.md
8
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
|
||||
|
||||
- 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
|
||||
|
|
|
|||
21
CHANGELOG.md
21
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
8
CRUSH.md
8
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,6 +19,7 @@ bun run tauri build # Production build
|
|||
```
|
||||
|
||||
**Type Check & Build:**
|
||||
|
||||
```bash
|
||||
bunx tsc --noEmit # Type checking
|
||||
bun run build # Build and validate
|
||||
|
|
@ -25,6 +28,7 @@ bun run build # Build and validate
|
|||
# Code Style Guidelines
|
||||
|
||||
**Rust (Backend):**
|
||||
|
||||
- Use `anyhow::Error` for error handling with descriptive messages
|
||||
- Prefer `Arc<Mutex<T>>` 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
|
||||
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
|
||||
|
||||
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."_
|
||||
|
|
|
|||
3
bun.lock
3
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=="],
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
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 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<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 config = OpenAIConfig::new()
|
||||
.with_api_base(base_url)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export { default } from './Footer';
|
||||
export { default } from "./Footer";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -30,7 +30,9 @@ const DownloadProgressDisplay: React.FC<DownloadProgressDisplayProps> = ({
|
|||
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,
|
||||
|
|
|
|||
|
|
@ -184,22 +184,22 @@ const ModelDropdown: React.FC<ModelDropdownProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
<div className="text-xs text-logo-primary tabular-nums">
|
||||
{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"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isDownloading && progress && (
|
||||
<div className="mt-2">
|
||||
<ProgressBar
|
||||
progress={[{
|
||||
progress={[
|
||||
{
|
||||
id: model.id,
|
||||
percentage: progress.percentage,
|
||||
label: model.name
|
||||
}]}
|
||||
label: model.name,
|
||||
},
|
||||
]}
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -199,9 +199,10 @@ const ModelSelector: React.FC<ModelSelectorProps> = ({ onError }) => {
|
|||
},
|
||||
);
|
||||
|
||||
const extractionFailedUnlisten = listen<{model_id: string, error: string}>(
|
||||
"model-extraction-failed",
|
||||
(event) => {
|
||||
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);
|
||||
|
|
@ -210,8 +211,7 @@ const ModelSelector: React.FC<ModelSelectorProps> = ({ onError }) => {
|
|||
});
|
||||
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<ModelSelectorProps> = ({ 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<ModelSelectorProps> = ({ 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":
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export { default } from './Onboarding';
|
||||
export { default } from "./Onboarding";
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@ interface AlwaysOnMicrophoneProps {
|
|||
grouped?: boolean;
|
||||
}
|
||||
|
||||
export const AlwaysOnMicrophone: React.FC<AlwaysOnMicrophoneProps> = React.memo(({
|
||||
descriptionMode = "tooltip",
|
||||
grouped = false,
|
||||
}) => {
|
||||
export const AlwaysOnMicrophone: React.FC<AlwaysOnMicrophoneProps> = React.memo(
|
||||
({ descriptionMode = "tooltip", grouped = false }) => {
|
||||
const { getSetting, updateSetting, isUpdating } = useSettings();
|
||||
|
||||
const alwaysOnMode = getSetting("always_on_microphone") || false;
|
||||
|
|
@ -26,4 +24,5 @@ export const AlwaysOnMicrophone: React.FC<AlwaysOnMicrophoneProps> = React.memo(
|
|||
grouped={grouped}
|
||||
/>
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,10 +14,8 @@ const clipboardHandlingOptions = [
|
|||
{ value: "copy_to_clipboard", label: "Copy to Clipboard" },
|
||||
];
|
||||
|
||||
export const ClipboardHandlingSetting: React.FC<ClipboardHandlingProps> = React.memo(({
|
||||
descriptionMode = "tooltip",
|
||||
grouped = false,
|
||||
}) => {
|
||||
export const ClipboardHandlingSetting: React.FC<ClipboardHandlingProps> =
|
||||
React.memo(({ descriptionMode = "tooltip", grouped = false }) => {
|
||||
const { getSetting, updateSetting, isUpdating } = useSettings();
|
||||
|
||||
const selectedHandling = (getSetting("clipboard_handling") ||
|
||||
|
|
|
|||
|
|
@ -9,10 +9,8 @@ interface MicrophoneSelectorProps {
|
|||
grouped?: boolean;
|
||||
}
|
||||
|
||||
export const MicrophoneSelector: React.FC<MicrophoneSelectorProps> = React.memo(({
|
||||
descriptionMode = "tooltip",
|
||||
grouped = false,
|
||||
}) => {
|
||||
export const MicrophoneSelector: React.FC<MicrophoneSelectorProps> = React.memo(
|
||||
({ descriptionMode = "tooltip", grouped = false }) => {
|
||||
const {
|
||||
getSetting,
|
||||
updateSetting,
|
||||
|
|
@ -23,8 +21,10 @@ export const MicrophoneSelector: React.FC<MicrophoneSelectorProps> = React.memo(
|
|||
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);
|
||||
|
|
@ -34,9 +34,9 @@ export const MicrophoneSelector: React.FC<MicrophoneSelectorProps> = React.memo(
|
|||
await resetSetting("selected_microphone");
|
||||
};
|
||||
|
||||
const microphoneOptions = audioDevices.map(device => ({
|
||||
const microphoneOptions = audioDevices.map((device) => ({
|
||||
value: device.name,
|
||||
label: device.name
|
||||
label: device.name,
|
||||
}));
|
||||
|
||||
return (
|
||||
|
|
@ -51,8 +51,16 @@ export const MicrophoneSelector: React.FC<MicrophoneSelectorProps> = React.memo(
|
|||
options={microphoneOptions}
|
||||
selectedValue={selectedMicrophone}
|
||||
onSelect={handleMicrophoneSelect}
|
||||
placeholder={isLoading || audioDevices.length === 0 ? "Loading..." : "Select microphone..."}
|
||||
disabled={isUpdating("selected_microphone") || isLoading || audioDevices.length === 0}
|
||||
placeholder={
|
||||
isLoading || audioDevices.length === 0
|
||||
? "Loading..."
|
||||
: "Select microphone..."
|
||||
}
|
||||
disabled={
|
||||
isUpdating("selected_microphone") ||
|
||||
isLoading ||
|
||||
audioDevices.length === 0
|
||||
}
|
||||
onRefresh={refreshAudioDevices}
|
||||
/>
|
||||
<ResetButton
|
||||
|
|
@ -62,4 +70,5 @@ export const MicrophoneSelector: React.FC<MicrophoneSelectorProps> = React.memo(
|
|||
</div>
|
||||
</SettingContainer>
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ interface PostProcessingToggleProps {
|
|||
grouped?: boolean;
|
||||
}
|
||||
|
||||
export const PostProcessingToggle: React.FC<PostProcessingToggleProps> = React.memo(
|
||||
({ descriptionMode = "tooltip", grouped = false }) => {
|
||||
export const PostProcessingToggle: React.FC<PostProcessingToggleProps> =
|
||||
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<PostProcessingToggleProps> = React.m
|
|||
grouped={grouped}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@ interface PushToTalkProps {
|
|||
grouped?: boolean;
|
||||
}
|
||||
|
||||
export const PushToTalk: React.FC<PushToTalkProps> = React.memo(({
|
||||
descriptionMode = "tooltip",
|
||||
grouped = false,
|
||||
}) => {
|
||||
export const PushToTalk: React.FC<PushToTalkProps> = React.memo(
|
||||
({ descriptionMode = "tooltip", grouped = false }) => {
|
||||
const { getSetting, updateSetting, isUpdating } = useSettings();
|
||||
|
||||
const pttEnabled = getSetting("push_to_talk") || false;
|
||||
|
|
@ -26,4 +24,5 @@ export const PushToTalk: React.FC<PushToTalkProps> = React.memo(({
|
|||
grouped={grouped}
|
||||
/>
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -15,10 +15,8 @@ const overlayOptions = [
|
|||
{ value: "top", label: "Top" },
|
||||
];
|
||||
|
||||
export const ShowOverlay: React.FC<ShowOverlayProps> = React.memo(({
|
||||
descriptionMode = "tooltip",
|
||||
grouped = false,
|
||||
}) => {
|
||||
export const ShowOverlay: React.FC<ShowOverlayProps> = React.memo(
|
||||
({ descriptionMode = "tooltip", grouped = false }) => {
|
||||
const { getSetting, updateSetting, isUpdating } = useSettings();
|
||||
|
||||
const selectedPosition = (getSetting("overlay_position") ||
|
||||
|
|
@ -41,4 +39,5 @@ export const ShowOverlay: React.FC<ShowOverlayProps> = React.memo(({
|
|||
/>
|
||||
</SettingContainer>
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,10 +7,8 @@ interface StartHiddenProps {
|
|||
grouped?: boolean;
|
||||
}
|
||||
|
||||
export const StartHidden: React.FC<StartHiddenProps> = React.memo(({
|
||||
descriptionMode = "tooltip",
|
||||
grouped = false,
|
||||
}) => {
|
||||
export const StartHidden: React.FC<StartHiddenProps> = React.memo(
|
||||
({ descriptionMode = "tooltip", grouped = false }) => {
|
||||
const { getSetting, updateSetting, isUpdating } = useSettings();
|
||||
|
||||
const startHidden = getSetting("start_hidden") ?? false;
|
||||
|
|
@ -27,4 +25,5 @@ export const StartHidden: React.FC<StartHiddenProps> = React.memo(({
|
|||
tooltipPosition="bottom"
|
||||
/>
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,9 @@ export const DebugPaths: React.FC<DebugPathsProps> = ({
|
|||
</div>
|
||||
<div>
|
||||
<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>
|
||||
</SettingContainer>
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -10,13 +10,7 @@ interface ResetButtonProps {
|
|||
}
|
||||
|
||||
export const ResetButton: React.FC<ResetButtonProps> = React.memo(
|
||||
({
|
||||
onClick,
|
||||
disabled = false,
|
||||
className = "",
|
||||
ariaLabel,
|
||||
children,
|
||||
}) => (
|
||||
({ onClick, disabled = false, className = "", ariaLabel, children }) => (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={ariaLabel}
|
||||
|
|
|
|||
|
|
@ -39,10 +39,14 @@ type NonCreatableProps = {
|
|||
|
||||
export type SelectProps = BaseProps & (CreatableProps | NonCreatableProps);
|
||||
|
||||
const baseBackground = "color-mix(in srgb, var(--color-mid-gray) 10%, transparent)";
|
||||
const hoverBackground = "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 baseBackground =
|
||||
"color-mix(in srgb, var(--color-mid-gray) 10%, transparent)";
|
||||
const hoverBackground =
|
||||
"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> = {
|
||||
control: (base, state) => ({
|
||||
|
|
@ -94,7 +98,8 @@ const selectStyles: StylesConfig<SelectOption, false> = {
|
|||
zIndex: 30,
|
||||
backgroundColor: "var(--color-background)",
|
||||
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)",
|
||||
}),
|
||||
option: (base, state) => ({
|
||||
|
|
|
|||
|
|
@ -164,11 +164,15 @@ export const SettingContainer: React.FC<SettingContainerProps> = ({
|
|||
/>
|
||||
</svg>
|
||||
{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">
|
||||
{description}
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export { default } from './UpdateChecker';
|
||||
export { default } from "./UpdateChecker";
|
||||
|
|
|
|||
|
|
@ -193,9 +193,10 @@ export const useModels = () => {
|
|||
},
|
||||
);
|
||||
|
||||
const extractionFailedUnlisten = listen<{model_id: string, error: string}>(
|
||||
"model-extraction-failed",
|
||||
(event) => {
|
||||
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);
|
||||
|
|
@ -203,8 +204,7 @@ export const useModels = () => {
|
|||
return next;
|
||||
});
|
||||
setError(`Failed to extract model: ${event.payload.error}`);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return () => {
|
||||
progressUnlisten.then((fn) => fn());
|
||||
|
|
|
|||
|
|
@ -31,8 +31,14 @@ interface UseSettingsReturn {
|
|||
|
||||
// Post-processing helpers
|
||||
setPostProcessProvider: (providerId: string) => Promise<void>;
|
||||
updatePostProcessBaseUrl: (providerId: string, baseUrl: string) => Promise<void>;
|
||||
updatePostProcessApiKey: (providerId: string, apiKey: string) => Promise<void>;
|
||||
updatePostProcessBaseUrl: (
|
||||
providerId: string,
|
||||
baseUrl: string,
|
||||
) => Promise<void>;
|
||||
updatePostProcessApiKey: (
|
||||
providerId: string,
|
||||
apiKey: string,
|
||||
) => Promise<void>;
|
||||
updatePostProcessModel: (providerId: string, model: string) => Promise<void>;
|
||||
fetchPostProcessModels: (providerId: string) => Promise<string[]>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,10 @@ export type ModelUnloadTimeout = z.infer<typeof ModelUnloadTimeoutSchema>;
|
|||
export const PasteMethodSchema = z.enum(["ctrl_v", "direct", "shift_insert"]);
|
||||
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 const LLMPromptSchema = z.object({
|
||||
|
|
@ -93,14 +96,8 @@ export const SettingsSchema = z.object({
|
|||
.array(PostProcessProviderSchema)
|
||||
.optional()
|
||||
.default([]),
|
||||
post_process_api_keys: z
|
||||
.record(z.string())
|
||||
.optional()
|
||||
.default({}),
|
||||
post_process_models: z
|
||||
.record(z.string())
|
||||
.optional()
|
||||
.default({}),
|
||||
post_process_api_keys: z.record(z.string()).optional().default({}),
|
||||
post_process_models: z.record(z.string()).optional().default({}),
|
||||
post_process_prompts: z.array(LLMPromptSchema).optional().default([]),
|
||||
post_process_selected_prompt_id: z.string().nullable().optional(),
|
||||
mute_while_recording: z.boolean().optional().default(false),
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
|
||||
.bar {
|
||||
width: 6px;
|
||||
background: #FFE5EE;
|
||||
background: #ffe5ee;
|
||||
max-height: 20px;
|
||||
border-radius: 2px;
|
||||
transition: height 80ms linear;
|
||||
|
|
@ -86,7 +86,7 @@
|
|||
}
|
||||
|
||||
.cancel-button:hover {
|
||||
background: #FAA2CA33;
|
||||
background: #faa2ca33;
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,9 +29,19 @@ interface SettingsStore {
|
|||
playTestSound: (soundType: "start" | "stop") => Promise<void>;
|
||||
checkCustomSounds: () => Promise<void>;
|
||||
setPostProcessProvider: (providerId: string) => Promise<void>;
|
||||
updatePostProcessSetting: (settingType: 'base_url' | 'api_key' | 'model', providerId: string, value: string) => Promise<void>;
|
||||
updatePostProcessBaseUrl: (providerId: string, baseUrl: string) => Promise<void>;
|
||||
updatePostProcessApiKey: (providerId: string, apiKey: string) => Promise<void>;
|
||||
updatePostProcessSetting: (
|
||||
settingType: "base_url" | "api_key" | "model",
|
||||
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>;
|
||||
fetchPostProcessModels: (providerId: string) => Promise<string[]>;
|
||||
setPostProcessModelOptions: (providerId: string, models: string[]) => void;
|
||||
|
|
@ -151,7 +161,7 @@ export const useSettingsStore = create<SettingsStore>()(
|
|||
const { load } = await import("@tauri-apps/plugin-store");
|
||||
const store = await load("settings_store.json", {
|
||||
defaults: DEFAULT_SETTINGS,
|
||||
autoSave: false
|
||||
autoSave: false,
|
||||
});
|
||||
const settings = (await store.get("settings")) as Settings;
|
||||
|
||||
|
|
@ -234,7 +244,6 @@ export const useSettingsStore = create<SettingsStore>()(
|
|||
}
|
||||
},
|
||||
|
||||
|
||||
checkCustomSounds: async () => {
|
||||
try {
|
||||
const sounds = await invoke("check_custom_sounds");
|
||||
|
|
@ -386,25 +395,25 @@ export const useSettingsStore = create<SettingsStore>()(
|
|||
|
||||
// Generic updater for post-processing provider settings
|
||||
updatePostProcessSetting: async (
|
||||
settingType: 'base_url' | 'api_key' | 'model',
|
||||
settingType: "base_url" | "api_key" | "model",
|
||||
providerId: string,
|
||||
value: string
|
||||
value: string,
|
||||
) => {
|
||||
const { setUpdating, refreshSettings } = get();
|
||||
const updateKey = `post_process_${settingType}:${providerId}`;
|
||||
|
||||
// Map setting types to command names
|
||||
const commandMap = {
|
||||
base_url: 'change_post_process_base_url_setting',
|
||||
api_key: 'change_post_process_api_key_setting',
|
||||
model: 'change_post_process_model_setting',
|
||||
base_url: "change_post_process_base_url_setting",
|
||||
api_key: "change_post_process_api_key_setting",
|
||||
model: "change_post_process_model_setting",
|
||||
};
|
||||
|
||||
// Map setting types to param names
|
||||
const paramMap = {
|
||||
base_url: 'baseUrl',
|
||||
api_key: 'apiKey',
|
||||
model: 'model',
|
||||
base_url: "baseUrl",
|
||||
api_key: "apiKey",
|
||||
model: "model",
|
||||
};
|
||||
|
||||
setUpdating(updateKey, true);
|
||||
|
|
@ -416,14 +425,17 @@ export const useSettingsStore = create<SettingsStore>()(
|
|||
});
|
||||
await refreshSettings();
|
||||
} catch (error) {
|
||||
console.error(`Failed to update post-process ${settingType.replace('_', ' ')}:`, error);
|
||||
console.error(
|
||||
`Failed to update post-process ${settingType.replace("_", " ")}:`,
|
||||
error,
|
||||
);
|
||||
} finally {
|
||||
setUpdating(updateKey, false);
|
||||
}
|
||||
},
|
||||
|
||||
updatePostProcessBaseUrl: async (providerId, baseUrl) => {
|
||||
return get().updatePostProcessSetting('base_url', providerId, baseUrl);
|
||||
return get().updatePostProcessSetting("base_url", providerId, baseUrl);
|
||||
},
|
||||
|
||||
updatePostProcessApiKey: async (providerId, apiKey) => {
|
||||
|
|
@ -434,11 +446,11 @@ export const useSettingsStore = create<SettingsStore>()(
|
|||
[providerId]: [],
|
||||
},
|
||||
}));
|
||||
return get().updatePostProcessSetting('api_key', providerId, apiKey);
|
||||
return get().updatePostProcessSetting("api_key", providerId, apiKey);
|
||||
},
|
||||
|
||||
updatePostProcessModel: async (providerId, model) => {
|
||||
return get().updatePostProcessSetting('model', providerId, model);
|
||||
return get().updatePostProcessSetting("model", providerId, model);
|
||||
},
|
||||
|
||||
fetchPostProcessModels: async (providerId) => {
|
||||
|
|
|
|||
|
|
@ -1,19 +1,16 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./index.html",
|
||||
"./src/**/*.{js,ts,jsx,tsx}",
|
||||
],
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
text: 'var(--color-text)',
|
||||
background: 'var(--color-background)',
|
||||
'logo-primary': 'var(--color-logo-primary)',
|
||||
'logo-stroke': 'var(--color-logo-stroke)',
|
||||
'text-stroke': 'var(--color-text-stroke)',
|
||||
text: "var(--color-text)",
|
||||
background: "var(--color-background)",
|
||||
"logo-primary": "var(--color-logo-primary)",
|
||||
"logo-stroke": "var(--color-logo-stroke)",
|
||||
"text-stroke": "var(--color-text-stroke)",
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue