You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CLAUDE.md
+65-10Lines changed: 65 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,6 +6,8 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
6
6
7
7
VoiceFlow is a cross-platform voice-to-text paste utility built with Pyloid (Python desktop framework using PySide6/Qt WebEngine) and React. Users hold a hotkey to record audio, release to transcribe using faster-whisper, and the text is automatically pasted at the cursor. Supports Windows, Linux (Wayland/X11), and macOS.
8
8
9
+
As of v1.6.0, VoiceFlow also ships a **Meeting Mode** — long-form recording with mic + system-loopback capture, async transcription, and LLM-generated summaries. This is a separate feature surface from the push-to-talk paste flow, kept self-contained under `services/recording/` so it can evolve (or be extracted) without touching PTT code.
10
+
9
11
## Commands
10
12
11
13
```bash
@@ -26,28 +28,44 @@ pnpm run build:installer # Windows (.exe via Inno Setup)
26
28
pnpm run build:installer:linux # Linux (.tar.gz + .AppImage)
27
29
pnpm run build:installer:macos # macOS (.dmg)
28
30
29
-
# Run Python tests
30
-
cd VoiceFlow && uv run -p .venv pytest src-pyloid/tests/
31
+
# Run Python tests (fast suite — excludes test_transcription, which downloads a model)
32
+
pnpm run test
33
+
pnpm run test:all # includes test_transcription
31
34
32
35
# Run single test file
33
36
uv run -p .venv pytest src-pyloid/tests/test_transcription.py -v
34
37
38
+
# Everything CI runs (lint + typecheck + tests + version consistency)
39
+
pnpm run check
40
+
41
+
# Version bump — updates all five version files (package.json, voiceflow.iss,
42
+
# pyproject.toml, constants.ts, uv.lock); never edit them by hand
43
+
pnpm run version:bump X.Y.Z
44
+
pnpm run version:check
45
+
35
46
# Run frontend only (for UI development)
36
47
pnpm run vite
37
48
38
-
# Lint frontend
49
+
# Lint / typecheck frontend
39
50
pnpm run lint
51
+
pnpm run typecheck
40
52
```
41
53
54
+
CI (`.github/workflows/ci.yml`) runs lint, typecheck, version check, and the fast
55
+
test suite on every PR and push to main. The release workflow
56
+
(`.github/workflows/release.yml`) additionally refuses tags that don't match
57
+
package.json and verifies the Linux artifact's audio-library layout
Python backend using Pyloid framework with PySide6:
47
65
48
66
-**main.py** - Application entry point. Creates Pyloid app, tray icon, main dashboard window, and recording popup window. Sets up UI callbacks connecting backend events to popup state changes.
49
-
-**server.py** - RPC server using `PyloidRPC`. Exposes methods (`get_settings`, `update_settings`, `get_history`, etc.) that frontend calls via `pyloid-js` RPC.
50
-
-**app_controller.py** - Singleton controller orchestrating all services. Handles hotkey activate/deactivate flow: start recording -> stop recording -> transcribe -> paste at cursor -> save to history.
67
+
-**server.py** - RPC server using `PyloidRPC`. Exposes PTT methods (`get_settings`, `update_settings`, `get_history`, etc.) plus the full Meeting Mode surface (`meetings.list_audio_sources`, `meetings.start`, `meetings.pause`, `meetings.resume`, `meetings.stop`, `meetings.transcribe`, `meetings.summarize`, `meetings.get_llm_config`, `meetings.test_llm_connection`, etc.) that frontend calls via `pyloid-js` RPC.
68
+
-**app_controller.py** - Singleton controller orchestrating all services. Handles hotkey activate/deactivate flow: start recording -> stop recording -> transcribe -> paste at cursor -> save to history. Also constructs and owns the `MeetingsController` (exposed as `controller.meetings`) and runs an unfinished-recording recovery sweep on startup.
51
69
52
70
**Services (src-pyloid/services/):**
53
71
-`audio.py` - Microphone recording using sounddevice, streams amplitude for visualizer
@@ -56,21 +74,37 @@ Python backend using Pyloid framework with PySide6:
56
74
-`clipboard.py` - Clipboard operations and paste-at-cursor using pyautogui
57
75
-`settings.py` - Settings management with defaults
58
76
-`database.py` - SQLite database for settings and history (stored at ~/.VoiceFlow/VoiceFlow.db)
59
-
-`logger.py` - Domain-based logging with hybrid format `[timestamp] [LEVEL] [domain] message | {json}`. Supports domains: model, audio, hotkey, settings, database, clipboard, window. Configured with 100MB log rotation.
60
-
-`model_manager.py` - Whisper model download/cache management using huggingface_hub. Provides download progress tracking (percent, speed, ETA), cancellation via CancelToken, daemon thread execution, and `clear_cache()` to delete only VoiceFlow's faster-whisper models.
77
+
-`logger.py` - Domain-based logging with hybrid format `[timestamp] [LEVEL] [domain] message | {json}`. Supports domains: model, audio, hotkey, settings, database, clipboard, window, plus Meeting-Mode domains (recording, transcribe, summary, llm). Configured with 100MB log rotation.
78
+
-`model_catalog.py` - Single source of truth for the Whisper model catalog (names, sizes, HF repo IDs). Imported by `model_manager`, `transcription`, and `settings`; the frontend gets repo IDs over RPC (`get_model_info` → `repoId`) rather than keeping its own copy. Never re-declare the model list elsewhere.
79
+
-`model_manager.py` - Whisper model download/cache management using huggingface_hub. Owns the single-flight background download session (`start_download` / `cancel_download` / `get_download_status` — the RPC surface), download progress tracking (percent, speed, ETA), cancellation via CancelToken, and `delete_model()` / `clear_cache()`. Model *loading* lives in `TranscriptionService` (the only load path, since it resolves the user's device preference).
Self-contained per `docs/adr/0003-meeting-mode-isolation.md`; do not call these from the PTT path and vice versa.
83
+
-`controller.py` - `MeetingsController` — the feature's facade. All RPC handlers go through this object. Emits `recording-state`, `recording-transcribe-progress`, and `recording-summarize-progress` events to the frontend via the emitter installed by `main.py`.
84
+
-`recorder.py` - Long-form recorder with pause/resume, segmented WAV writing, and clock tracking. Sources are fixed at `start()` and cannot change mid-recording.
85
+
-`audio_source.py` - Enumerates available mic + loopback devices for the UI device picker.
86
+
-`loopback_linux.py` / `loopback_pulse.py` / `loopback_windows.py` - Platform-specific system-audio capture (PulseAudio/PipeWire on Linux, WASAPI loopback on Windows).
87
+
-`clock.py` - Monotonic recording clock that survives pause/resume.
88
+
-`llm.py` - LLM client with preset + custom-endpoint support; used by summary/title generation.
89
+
-`summary.py` / `title.py` - LLM-driven summary and auto-title generation for finished recordings.
90
+
-`secrets.py` - API-key storage for LLM providers (kept out of the main settings table).
91
+
-`export.py` - Exports a recording's transcript/summary to text formats.
92
+
-`recovery.py` - On startup, sweeps recordings left in `recording` / `paused` state from a previous (crashed) session and rolls them forward.
93
+
-`audio_scheme.py` / `audio_scheme_handler.py` - Custom Qt `audio://` URL scheme so the WebEngine `<audio>` element can stream recording WAVs from disk without a server.
61
94
62
95
### Frontend (src/)
63
96
64
97
React 18 + TypeScript + Vite frontend:
65
98
66
99
-**App.tsx** - Hash-based routing between `/popup`, `/onboarding`, and `/dashboard`. Checks model cache on startup and shows recovery modal if model is missing.
67
-
-**lib/api.ts** - RPC wrapper using `pyloid-js` to call Python backend methods. Includes model management APIs (`getModelInfo`, `startModelDownload`, `cancelModelDownload`).
-**lib/api.ts** - RPC wrapper using `pyloid-js` to call Python backend methods. Includes model management APIs (`getModelInfo`, `startModelDownload`, `cancelModelDownload`) plus the full `recordings*` / meetings RPC surface.
101
+
-**lib/types.ts** - TypeScript interfaces for Settings, HistoryEntry, Stats, Options, ModelInfo, DownloadProgress, plus meeting types: `Recording`, `RecordingSegment`, `RecorderState`, and LLM config types.
@@ -124,6 +158,27 @@ For transparent popup windows on Windows:
124
158
6. On completion, model is cached in huggingface cache directory
125
159
7. Turbo model uses `mobiuslabsgmbh/faster-whisper-large-v3-turbo` (same as faster-whisper internal mapping)
126
160
161
+
### Meeting Mode (long-form recording)
162
+
163
+
Separate from the PTT paste flow. Entrypoint: `controller.meetings` (`MeetingsController`). See `docs/adr/0001-stereo-channel-layout-for-recordings.md` for the on-disk audio layout decision.
164
+
165
+
1. UI calls `meetings.list_audio_sources()` to populate the device picker (mic + loopback).
166
+
2.`meetings.start(mic_device_id, loopback_device_id)` opens up to two simultaneous capture streams. Sources are fixed at start — they cannot be added/removed mid-recording.
167
+
3. Audio is written to a WAV file under `~/.VoiceFlow/recordings/`:
168
+
- Two active sources → **stereo 16 kHz PCM16**, mic on **L**, loopback on **R** (kept separate on purpose; enables future speaker diarization with no ML — see ADR 0001).
169
+
- One active source → mono 16 kHz PCM16.
170
+
4.`meetings.pause()` / `meetings.resume()` use a monotonic `Clock` to track real recording time; segments are stitched into one logical recording.
171
+
5.`meetings.stop()` finalizes the WAV and persists metadata. Recording rows live in the same SQLite DB but in their own table.
172
+
6. Transcription is **async and on-demand**: `meetings.transcribe(id)` runs faster-whisper in a daemon thread and emits `recording-transcribe-progress` events. Long jobs do not block the RPC channel (see fix `dc04d29`).
173
+
7. After transcription, `meetings.summarize(id, prompt)` calls the configured LLM provider (preset or custom endpoint) to produce an AI summary, and `title.py` auto-generates a title. LLM config and API keys live in `services/recording/llm.py` + `secrets.py`, not in the main `settings` table.
174
+
8. Audio playback in the detail page uses a custom Qt `audio://` URL scheme (`audio_scheme.py`) so the WebEngine can stream the WAV directly without an HTTP server.
175
+
9. On startup, `recovery.py` rolls forward any recordings left in `recording` / `paused` state from a crashed previous session.
176
+
177
+
**Platform quirks**:
178
+
- Linux loopback uses PulseAudio/PipeWire monitor sources (`loopback_pulse.py` / `loopback_linux.py`).
179
+
- Windows loopback uses WASAPI. Must open the loopback stream at the device's native channel count (`max_output_channels`) — opening at a forced channel count fails on many devices (fixes `96b0b73`, `13d45be`).
180
+
- Pyloid validates window IDs on every RPC roundtrip from a background thread; long-running meeting RPCs work around this (see `135fdd7`).
0 commit comments