PC Voice Assistant
A local, privacy-focused voice assistant for Windows that runs entirely on your machine. Speak naturally to control your PC — open apps, set reminders, search the web, and more.
Features
- Wake Word Detection: Say "Alexa" or "Hey Computer" to activate (ML-based openWakeWord with energy fallback)
- Speech Recognition: Offline Vosk ASR engine — no internet required
- Natural Language Understanding: Rule-based intent matching with optional Ollama LLM integration
- Text-to-Speech: pyttsx3 (offline) or edge-tts (online, higher quality)
- Smart Reminders: Natural time expressions ("in 2 hours", "next Monday at 3pm")
- Fuzzy App Matching: "open chrome" matches "Google Chrome" automatically
- System Integration: Open apps, control volume, reminders, screenshots, and more
- System Tray: Runs in background with tray icon and context menu
- Rotating Logs: All activity logged to
logs/assistant.log - 133 Test Suite: Comprehensive unit tests for all modules
1. Project Overview
The PC Voice Assistant is a Python CLI application that provides hands-free voice control for Windows PCs. It listens for a wake word, records your voice, transcribes it to text, understands your intent, executes the action, and speaks a response.
Pipeline Flow:
Wake Word → Record Audio (VAD) → Transcribe → Parse Intent → Execute Action → Speak Response → Wait for Wake Word
The assistant is:
- Local/Offline: Core functionality works without internet
- Windows-Focused: Uses Windows-specific commands (PowerShell, Windows APIs)
- Privacy-First: All processing happens on your machine
- Extensible: Easy to add new intents and commands
2. Requirements
Python Version
- Python 3.10 or higher required (union type hints like
Tuple[...] | None)
Operating System
- Windows (primary) — fully supported
- Linux/Mac: Wake word and TTS modules may work, but system commands (volume, shutdown, sleep) require Windows
Hardware
- Microphone required for voice input
- Any standard USB or built-in microphone works
- Audio output device required for TTS
External Tools (Optional)
| Tool | Purpose | Install |
|---|---|---|
| Ollama | LLM-powered responses | ollama pull llama3.2 |
| Vosk Model | Offline speech recognition | Download from alphacephei.com |
| edge-tts | Online TTS (better quality) | pip install edge-tts |
| plyer | Desktop notifications | pip install plyer |
3. Installation
Step 1: Clone the Repository
git clone <repository-url>
cd pc-assistant
Step 2: Install Python Dependencies
pip install -r requirements.txt
Step 3: Download and Install Vosk Model
- Download from: https://alphacephei.com/vosk/models
- Recommended model:
vosk-model-small-en-us-0.15(~45MB) orvosk-model-en-us-0.22(~1.8GB) - Extract into the project root directory
- The folder name should match
asr.model_pathin config.json (default:models/vosk-model-small-en-us-0.15)
pc-assistant/
├── models/
│ └── vosk-model-small-en-us-0.15/
│ ├── am/
│ ├── conf/
│ ├── graph/
│ └── ivector/
├── main.py
├── config.json
└── ...
Step 4: (Optional) Install and Start Ollama
For LLM-powered natural language understanding:
- Install Ollama: https://ollama.ai
- Pull a model:
ollama pull llama3.2
- Start Ollama server:
ollama serve - Set
nlu.use_ollamatotruein config.json
Step 5: Run the Assistant
python main.py
Press Ctrl+C to exit gracefully.
4. Configuration (config.json)
All settings are in config.json. Never edit while the assistant is running.
wake_word Section
| Key | Type | Default | Description |
|---|---|---|---|
| enabled | bool | true | Enable wake word detection |
| engine | string | "openwakeword" | Detection engine: "openwakeword" or "energy" |
| input_device | int | 2 | Audio input device index (null = default) |
| confidence_threshold | float | 0.5 | Wake word sensitivity (0.0-1.0, higher = stricter) |
| inference_interval | int | 10 | Frames between inferences (openWakeWord only) |
| trigger_cooldown_seconds | float | 2.0 | Seconds between wake word triggers |
| custom_keywords | list | ["hey assistant", "computer"] | Custom wake words |
| sample_rate | int | 16000 | Audio sample rate (reserved) |
| energy_fallback.threshold | float | 0.02 | Energy threshold for fallback detection |
| energy_fallback.required_chunks | int | 3 | Consecutive chunks above threshold to trigger |
asr Section
| Key | Type | Default | Description |
|---|---|---|---|
| engine | string | "vosk" | Speech recognition engine |
| model_path | string | "./models/vosk-model-small-en-us-0.15" | Path to Vosk model folder |
| sample_rate | int | 16000 | Audio sample rate (reserved) |
| record_seconds | int | 5 | Fallback recording duration in seconds |
| input_device | int | 2 | Audio input device index |
| vad_enabled | bool | true | Enable voice activity detection |
| vad_silence_duration_ms | int | 800 | Milliseconds of silence to stop recording |
| vad_min_record_seconds | float | 1.0 | Minimum recording duration |
| vad_max_record_seconds | float | 10.0 | Maximum recording duration |
| vad_energy_threshold | float | 0.01 | Energy threshold for speech detection |
| vad.enabled | bool | true | Legacy VAD setting |
| vad.silence_threshold | float | 0.005 | Legacy silence threshold |
| vad.silence_duration | float | 1.2 | Legacy silence duration |
| vad.max_record_duration | float | 10.0 | Legacy max duration |
| audio_enhancement.enabled | bool | true | Apply audio enhancements |
| audio_enhancement.noise_reduction | bool | true | Apply high-pass filter |
| audio_enhancement.normalization | bool | true | Normalize audio levels |
| audio_enhancement.volume_boost | float | 1.5 | Volume multiplier |
nlu Section
| Key | Type | Default | Description |
|---|---|---|---|
| use_ollama | bool | false | Enable LLM fallback for unknown intents |
| use_keyword_commands | bool | true | Reserved for keyword-only mode |
| ollama_model | string | "llama3.2" | Ollama model name |
| ollama_base_url | string | "http://localhost:11434" | Ollama API endpoint |
| max_history | int | 10 | Conversation history length |
| fallback_to_llm | bool | true | Reserved (fallback always enabled) |
| log_intents | bool | true | Reserved for intent logging |
| fuzzy_match_threshold | float | 0.6 | Word overlap threshold for fuzzy app matching |
| unknown_intent_response | string | "Sorry, I didn't..." | Response for unrecognized input |
| intent_patterns | dict | {...} | Reserved (patterns hardcoded in handlers) |
tts Section
| Key | Type | Default | Description |
|---|---|---|---|
| engine | string | "pyttsx3" | TTS engine: "pyttsx3" or "edge-tts" |
| edge_voice | string | "en-US-JennyNeural" | edge-tts voice name |
| rate | float | 1.0 | Speech rate multiplier |
| volume | float | 1.0 | Reserved for volume control |
| confirm_reminders | bool | true | Reserved for confirmation UI |
| confirm_actions | bool | true | Reserved for confirmation UI |
assistant Section
| Key | Type | Default | Description |
|---|---|---|---|
| continuous_listening | bool | false | Keep listening after each command |
| audio_feedback | bool | true | Play sounds on wake word detection |
| confirm_destructive | bool | true | Confirm before shutdown/restart |
actions Section
| Key | Type | Default | Description |
|---|---|---|---|
| screenshot_folder | string | "screenshots" | Directory to save screenshots |
| file_search_roots | list | ["C:\Users"] | Root directories for file search |
| file_search_max_results | int | 5 | Maximum file search results |
| file_search_timeout_seconds | int | 30 | Timeout for file search in seconds |
| weather_api_key | string | "" | OpenWeatherMap API key |
| weather_default_city | string | "New York" | Default city for weather queries |
| weather_units | string | "metric" | Weather units: "metric" or "imperial" |
| allow_sleep | bool | true | Allow putting PC to sleep |
| web_search_url | string | "https://google.com/search?q=" | Search engine URL |
| apps | dict | {...} | App name → command mappings |
| websites | dict | {...} | Site name → URL mappings |
reminders Section
| Key | Type | Default | Description |
|---|---|---|---|
| enabled | bool | true | Enable reminder functionality |
| storage_path | string | "reminders.json" | Reminder persistence file |
| check_interval_seconds | int | 30 | Seconds between reminder checks |
| notification_timeout | int | 10 | Desktop notification duration |
| tts_notification | bool | true | Speak reminder via TTS |
ui Section
| Key | Type | Default | Description |
|---|---|---|---|
| enabled | bool | false | Enable PyQt6 GUI |
| enable_tray | bool | false | Enable system tray icon |
| show_window_on_start | bool | false | Show window on startup |
| minimize_to_tray | bool | false | Minimize to tray on close |
| start_minimized | bool | false | Start minimized |
| confirm_exit | bool | true | Confirm before exiting |
tray Section
| Key | Type | Default | Description |
|---|---|---|---|
| enabled | bool | false | Enable system tray |
| show_icon | bool | false | Show tray icon |
| start_listening_on_start | bool | false | Start listening on launch |
| tooltip | string | "PC Voice Assistant" | Tray icon tooltip |
| icon_path | string | null | Custom tray icon path |
logging Section
| Key | Type | Default | Description |
|---|---|---|---|
| level | string | "INFO" | Log level: DEBUG, INFO, WARNING, ERROR |
| file | string | "logs/assistant.log" | Log file path |
| console | bool | true | Also log to console |
| max_bytes | int | 5242880 | Max log file size (5MB) |
| backup_count | int | 5 | Number of backup logs to keep |
| show_logs_on_tray | bool | true | Log viewer in tray menu |
Reserved/Unused Config Keys
These keys exist in config.json but are not currently read by Python code. They are preserved for future use:
| Section | Key | Planned Use |
|---|---|---|
| asr | whisper_model | Future Whisper ASR integration |
| asr | sample_rate | Reserved for sample rate configuration |
| nlu | use_keyword_commands | Keyword-only mode flag |
| nlu | fallback_to_llm | LLM fallback control |
| nlu | log_intents | Intent usage logging |
| nlu | intent_patterns | Configurable intent patterns |
| tts | volume | TTS volume control |
| tts | confirm_reminders | Reminder confirmation UI |
| tts | confirm_actions | Action confirmation UI |
| wake_word | custom_model_paths | Custom wake word models |
| wake_word | pronunciation_hints | Pronunciation tuning |
| wake_word | sample_rate | Sample rate configuration |
5. Voice Commands Reference
Greetings
| Command | Example |
|---|---|
| hello | "Hello" |
| hey | "Hey there" |
| good morning | "Good morning" |
Time & Date
| Command | Example |
|---|---|
| what time is it | "What time is it?" |
| what's the date | "What's today's date?" |
| uptime | "How long has the PC been running?" |
Volume Control
| Command | Example |
|---|---|
| volume up | "Volume up" |
| volume down | "Turn down the volume" |
| mute | "Mute" |
| unmute | "Unmute" |
Applications & Websites
| Command | Example | Notes |
|---|---|---|
| open notepad | "Open Notepad" | Exact match |
| open calculator | "Launch Calculator" | Exact match |
| open chrome | "Open Chrome" | Fuzzy matched to "google chrome" |
| open vs code | "Open VS Code" | Fuzzy matched to "visual studio code" |
| go to github | "Go to GitHub" | Opens URL from config |
Web Search
| Command | Example |
|---|---|
| search for | "Search for Python tutorials" |
| look up | "Look up the weather" |
| find | "Find recipes for pasta" |
Reminders
| Command | Example |
|---|---|
| in X minutes | "Remind me in 10 minutes" |
| at X time | "Remind me at 5 pm" |
| tomorrow at | "Remind me tomorrow at 9am" |
| in X hours | "Remind me in 2 hours" |
| in X hours and Y minutes | "Remind me in 1 hour and 30 minutes" |
| in X seconds | "Remind me in 90 seconds" |
| next weekday at | "Remind me next Monday at 3pm" |
| at noon | "Remind me at noon" |
| at midnight | "Remind me at midnight" |
Actions
| Command | Example |
|---|---|
| screenshot | "Take a screenshot" |
| search | "Search for [query]" |
| empty trash | "Empty the recycle bin" |
| processes | "Show running processes" |
Math
| Command | Example |
|---|---|
| plus/add | "What is 5 plus 3?" |
| minus/subtract | "Calculate 10 minus 4" |
| times/multiply | "What is 3 times 7?" |
System Control
| Command | Example |
|---|---|
| shutdown | "Shutdown" |
| restart | "Restart" |
| sleep | "Sleep" |
Conversation
| Command | Example |
|---|---|
| thank you | "Thank you" |
| goodbye | "Goodbye" |
| help | "Help" |
| who are you | "Who are you?" |
6. Architecture Overview
Pipeline Flow
┌─────────────────────────────────────────────────────────────────┐
│ MAIN LOOP │
├─────────────────────────────────────────────────────────────────┤
│ 1. WAKE WORD DETECTION (wake_word.py) │
│ - Listen for wake word ("alexa", "hey computer") │
│ - Uses openWakeWord ML model or energy threshold │
│ - Blocking wait with VAD to detect speech start │
├─────────────────────────────────────────────────────────────────┤
│ 2. RECORD AUDIO (asr.py) │
│ - Record until silence detected (VAD) │
│ - Configurable min/max duration │
│ - Audio enhancement: noise reduction, normalization │
├─────────────────────────────────────────────────────────────────┤
│ 3. TRANSCRIBE (asr.py) │
│ - Vosk offline ASR → text │
│ - Fallback to keyword detection if model unavailable │
├─────────────────────────────────────────────────────────────────┤
│ 4. PARSE INTENT (nlu.py) │
│ - Keyword-based pattern matching │
│ - Optional Ollama LLM for unknown intents │
│ - Intent handlers: apps, reminders, volume, etc. │
├─────────────────────────────────────────────────────────────────┤
│ 5. EXECUTE ACTION │
│ - Open apps via subprocess │
│ - Set reminders (persisted to JSON) │
│ - Control volume via PowerShell │
│ - Screenshot via mss │
├─────────────────────────────────────────────────────────────────┤
│ 6. SPEAK RESPONSE (tts.py) │
│ - pyttsx3 (offline Windows SAPI) │
│ - edge-tts (online, higher quality) │
│ - Falls back gracefully if primary fails │
└─────────────────────────────────────────────────────────────────┘
Module Responsibilities
| Module | Purpose |
|---|---|
main.py |
Entry point, main loop coordination, signal handling |
wake_word.py |
Wake word detection (openWakeWord/energy fallback) |
asr.py |
Audio recording (VAD), Vosk transcription, audio enhancement |
nlu.py |
Intent parsing, action handlers, reminder scheduler |
tts.py |
Text-to-speech (pyttsx3/edge-tts) |
gui.py |
PyQt6 GUI window |
tray.py |
System tray management |
config_loader.py |
Configuration file loading (singleton) |
config_validator.py |
Configuration validation |
logging_config.py |
Logging setup with file rotation |
audio_feedback.py |
Wake word beep sounds |
performance_metrics.py |
Latency tracking |
config.json |
All configuration values |
7. Troubleshooting
Wake Word Never Triggers
Symptoms: Assistant doesn't respond to wake word.
Solutions:
- Check microphone is working in Windows Sound settings
- Verify
wake_word.input_devicein config.json is correct (or null for default) - Check
wake_word.engineis set to "openwakeword" or "energy" - For openWakeWord: ensure it's installed (
pip install openwakeword) - For energy fallback: lower
wake_word.energy_fallback.threshold(e.g., 0.005) - Lower
wake_word.confidence_threshold(e.g., 0.3) - Speak clearly and closer to the microphone
ASR Model Not Found Error
Symptoms: "Vosk model not found" error on startup.
Solutions:
- Download model from https://alphacephei.com/vosk/models
- Extract to project root, ensure folder structure:
models/vosk-model-small-en-us-0.15/{am,conf,graph,ivector}/ - Verify
asr.model_pathin config.json matches folder name - Check the path doesn't contain special characters
TTS Makes No Sound
Symptoms: Assistant speaks but you hear nothing.
Solutions:
- Check
tts.enginein config.json- pyttsx3: Uses Windows SAPI, check system volume
- edge-tts: Requires internet connection
- Run
python -c "import tts; print(tts.get_engine_info())"to see active engine - Install pyttsx3 if missing:
pip install pyttsx3 - For edge-tts:
pip install edge-tts - Check Windows audio device is not muted
Ollama Intents Not Working
Symptoms: Unknown commands return fallback instead of LLM response.
Solutions:
- Confirm Ollama is running:
ollama serve - Check model is installed:
ollama list - Pull model if missing:
ollama pull llama3.2
- Verify
nlu.use_ollamaistruein config.json - Check
nlu.ollama_base_urlishttp://localhost:11434 - Test manually:
curl http://localhost:11434/api/tags
Reminder Notification Not Showing
Symptoms: Reminder fires but no desktop notification.
Solutions:
- Install plyer for desktop notifications:
pip install plyer
- Check Windows notification settings allow notifications from Python
- Without plyer, reminders still work via TTS voice notification
- Check
reminders.enabledistruein config.json
App Open Command Not Working
Symptoms: "I couldn't find an app" response.
Solutions:
- Check the app exists in
actions.appsin config.json - Add the app with full path if needed:
"notepad": "C:\\Windows\\notepad.exe"
- Fuzzy matching handles partial names:
- "open chrome" matches "google chrome"
- "open vs code" matches "visual studio code"
- For URLs, add to
actions.websites:"youtube": "https://youtube.com"
8. Running Tests
python -m pytest tests/ -v
Expected Result: 133 tests, all passing.
Test Coverage
| Test File | Tests | Coverage |
|---|---|---|
| test_asr.py | 16 | ASR configuration, VAD, transcription |
| test_config.py | 7 | Config loading, validation |
| test_main.py | 7 | Main loop error recovery, pause/resume state |
| test_nlu.py | 77 | Intent handlers, reminders, fuzzy matching, clipboard, file search, weather |
| test_tray.py | 3 | System tray lifecycle |
| test_tts.py | 13 | TTS engines, edge cases |
| test_wake_word.py | 10 | Wake word detection, energy fallback |
9. Adding New Intents
Step 1: Add App/Command to Config (if applicable)
For new apps or websites, add to config.json:
"actions": {
"apps": {
"myapp": "C:\\path\\to\\myapp.exe"
}
}
Step 2: Create Intent Handler Class
In nlu.py, add a new handler class:
class NewIntentHandler(IntentHandler):
def __init__(self):
super().__init__("newintent", ["trigger word", "other trigger"])
def handle(self, text: str) -> Optional[str]:
# Your logic here
return "Response message"
Step 3: Register the Handler
Add to INTENT_HANDLERS list in nlu.py:
INTENT_HANDLERS = [
...
NewIntentHandler(),
]
Step 4: Add Tests
In tests/test_nlu.py:
def test_new_intent_handler(self):
handler = nlu.NewIntentHandler()
assert handler.match("trigger word")
result = handler.handle("trigger word")
assert "Response" in result
Step 5: Update README
Add your command to the Voice Commands Reference section.
Step 6: Run Tests
python -m pytest tests/ -v
10. Changelog
Version 1.1.0 — Current Development
New Features
- VAD Silence Detection: Recording now stops automatically when you stop speaking, instead of fixed duration. Configurable min/max duration and silence threshold.
- Fuzzy App Matching: "open chrome" automatically matches "google chrome" in config. Uses word overlap algorithm with configurable threshold.
- Extended Reminder Parsing: Natural time expressions now supported:
- "in 2 hours"
- "in 1 hour and 30 minutes"
- "tomorrow at 9am"
- "next monday at 3pm"
- "in 90 seconds"
- Unknown Intent Fallback: Customizable response when no intent matches. Configurable via
nlu.unknown_intent_response.
Reliability Improvements
- Config Caching: Config is now loaded once per module, not on every function call. Significant performance improvement.
- Atomic Reminder Writes: Uses temp file + os.replace() to prevent corruption if process is killed mid-write.
- Wake Word Race Condition Fix: Added
_model_readyEvent to properly synchronize background model loading. - TTS MP3/WAV Fix: edge-tts generates MP3 but was being played as WAV. Now uses msedge.exe for correct playback.
- Path Quoting Fix: PowerShell commands now properly escape paths with spaces.
Security Improvements
- subprocess shell=True Removal: All subprocess calls now use list-based arguments, eliminating shell injection risk.
Testing
- 133 Test Suite: Comprehensive edge case coverage added:
- ASR: mic failure, missing model, empty transcription, partial results
- TTS: missing files, long strings, engine fallback
- Wake word: timeout fallback, model loading errors
- Main loop: error recovery from all stages
- Reminders: concurrent writes, persistence, past time handling
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pc_voice_assistant-1.1.0.tar.gz.
File metadata
- Download URL: pc_voice_assistant-1.1.0.tar.gz
- Upload date:
- Size: 20.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2a46debf2d905fd5efe89ac52f731ab7516b10088b425784c4e44779211da0aa
|
|
| MD5 |
d5ffecc7ca7d3bcd524d3041f87c413a
|
|
| BLAKE2b-256 |
0ba561d40142534a55ad12566bc53509c7dd2b533d4b04f8dba314dded86e8c5
|
Provenance
The following attestation bundles were made for pc_voice_assistant-1.1.0.tar.gz:
Publisher:
publish.yml on epicnellson/pc-assistant
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pc_voice_assistant-1.1.0.tar.gz -
Subject digest:
2a46debf2d905fd5efe89ac52f731ab7516b10088b425784c4e44779211da0aa - Sigstore transparency entry: 2508090584
- Sigstore integration time:
-
Permalink:
epicnellson/pc-assistant@e973a6379dd2f3dada55cbfd24e843737350af7c -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/epicnellson
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e973a6379dd2f3dada55cbfd24e843737350af7c -
Trigger Event:
release
-
Statement type:
File details
Details for the file pc_voice_assistant-1.1.0-py3-none-any.whl.
File metadata
- Download URL: pc_voice_assistant-1.1.0-py3-none-any.whl
- Upload date:
- Size: 9.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
115879be44d1a33adcb5843f987df82791c4d5bc2e9b52f5fdb2d28d04ba0ec5
|
|
| MD5 |
b5c649ca498b535630f69f5c896b39dd
|
|
| BLAKE2b-256 |
bed6c66bae5600a67838a11bbc8850df7d2996ee9e2e7e6ee495b857174c19bf
|
Provenance
The following attestation bundles were made for pc_voice_assistant-1.1.0-py3-none-any.whl:
Publisher:
publish.yml on epicnellson/pc-assistant
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pc_voice_assistant-1.1.0-py3-none-any.whl -
Subject digest:
115879be44d1a33adcb5843f987df82791c4d5bc2e9b52f5fdb2d28d04ba0ec5 - Sigstore transparency entry: 2508090641
- Sigstore integration time:
-
Permalink:
epicnellson/pc-assistant@e973a6379dd2f3dada55cbfd24e843737350af7c -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/epicnellson
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e973a6379dd2f3dada55cbfd24e843737350af7c -
Trigger Event:
release
-
Statement type: