Real-time transcription app with network drop awareness. See exactly which parts of your speech were delivered or missed due to network issues during calls.
- Native Offline Transcription - Uses Whisper.cpp (whisper-rs) for fast, accurate speech recognition
- Dual Network Monitoring Modes:
- Simulated Mode - For testing without a peer connection
- Real WebRTC Mode - P2P connection with heartbeat monitoring (100ms intervals)
- Auto-Connect CLI Peer - Lightweight peer server for one-click connection
- Real-time RTT Tracking - Monitor network latency with sub-second precision
- Visual Feedback - Color-coded transcript showing delivered (normal) vs missed (red) segments
- Cross-Platform - Windows, macOS, Linux via Tauri
- Fully Offline - No internet required for transcription
- STUN-based NAT Traversal - Connects peers behind firewalls
- Frontend: Solid.js + TypeScript + Vite
- Backend: Rust + Tauri + whisper-rs
- Transcription: Whisper.cpp (native Rust bindings)
- Network Monitoring: WebRTC data channel with heartbeat
dropped-transcript/
├── src/ # Frontend (Solid.js UI)
│ ├── App.tsx # Main UI with mode toggle & WebRTC setup
│ ├── App.css # Styling
│ ├── services/ # Audio capture
│ └── stores/ # State management
├── src-tauri/ # Backend (Rust)
│ ├── src/
│ │ ├── lib.rs # Tauri commands
│ │ ├── whisper.rs # Transcription engine
│ │ ├── network.rs # Network monitor (dual mode)
│ │ └── webrtc_peer.rs # WebRTC P2P connection
│ └── models/ # Whisper model files
└── PRD.md # Product requirements
- Node.js & pnpm
- Rust & Cargo
- Whisper model file
cd src-tauri/models
curl -L -o ggml-tiny-q5_1.bin \
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny-q5_1.binSee MODEL_SETUP.md for more model options.
pnpm installpnpm tauri devpnpm tauri buildThe final app will bundle everything (Rust binary, Whisper model, frontend) into a single installable file.
- Start the app
- Choose Network Monitoring Mode:
- Simulated - For quick testing without peer setup
- Real - For actual WebRTC connection monitoring
- Enter model path (in dev: full path to
src-tauri/models/ggml-tiny-q5_1.bin) - Click "Start Recording"
- Speak into your microphone
- Watch the transcript appear with color coding:
- Normal text = delivered (network OK)
- Red text = missed (network drop)
The app automatically connects to a peer server when "Real WebRTC" mode is selected.
Terminal 1 - Start the CLI Peer:
cd src-tauri
cargo run --bin peer
# Or with custom settings:
cargo run --bin peer -- --port 9000
cargo run --bin peer -- --host 0.0.0.0 --port 8080Terminal 2 - Run the GUI:
pnpm tauri devIn the GUI:
- Select "Real WebRTC" mode
- ✅ Automatically connects to peer server at
http://localhost:8080 - Status shows "🟢 Connected" with RTT
Auto-Reconnect:
- Shut down the peer server → Status shows "🔴 Disconnected"
- Start the peer server again → Automatically reconnects within 3 seconds
No peer setup required. The network status will be simulated - you can use the browser console to call:
await invoke("mark_network_connected", { rtt: 50 });
await invoke("mark_network_disconnected");The CLI Peer Server is a lightweight, barebones WebRTC peer that runs without any GUI, audio capture, or transcription functionality. It serves a single purpose: maintaining a WebRTC connection with heartbeat monitoring so the main GUI app can detect network drops.
- ✅ WebRTC P2P Connection - Full WebRTC peer with data channel
- ✅ Heartbeat Monitoring - 100ms heartbeat intervals with RTT tracking
- ✅ WebSocket Signaling - Real-time bidirectional signaling with auto-reconnect
- ✅ Auto-Connect - GUI can connect with one click
- ✅ ICE Restart Support - Automatic reconnection on network changes
- ✅ Minimal Resources - No audio, no transcription, no GUI (~5-10 MB memory, <1% CPU)
- ✅ Cross-Platform - Works on Windows, macOS, Linux
- ❌ No Audio Capture
- ❌ No Transcription Engine
- ❌ No Tauri/GUI Components
# Default (localhost:8080)
cargo run --bin peer
# Custom port
cargo run --bin peer -- --port 9000
# Custom host (expose to network)
cargo run --bin peer -- --host 0.0.0.0 --port 8080Expected Output:
🚀 Dropped Transcript - WebRTC Peer Server
==========================================
📝 Creating WebRTC offer...
✅ Offer created successfully
🌐 Starting server on http://127.0.0.1:8080
📋 Available endpoints:
WS ws://127.0.0.1:8080/ws - WebSocket signaling
⏳ Waiting for clients to connect...
(Press Ctrl+C to exit)
Bidirectional WebSocket connection for real-time signaling.
Connection URL:
ws://localhost:8080/ws
Messages from Server:
{
"type": "offer",
"data": {
"sdp": "{\"type\":\"offer\",\"sdp\":\"v=0\\r\\no=...\"}",
"offer_id": "uuid-string"
}
}Messages to Server:
{
"type": "answer",
"data": {
"sdp": "{\"type\":\"answer\",\"sdp\":\"v=0\\r\\no=...\"}",
"offer_id": "uuid-string"
}
}Features:
- Auto-reconnect: Server automatically sends new offers when WebSocket reconnects
- ICE Restart: Automatic ICE restart when network changes detected
- Push-based: Server pushes offers immediately when available
USAGE:
peer [OPTIONS]
OPTIONS:
-p, --port <PORT> Port to listen on [default: 8080]
--host <HOST> Host to bind to [default: 127.0.0.1]
-h, --help Print help
-V, --version Print versionRun peer and GUI on the same machine for development/testing:
# Terminal 1
cd src-tauri
cargo run --bin peer
# Terminal 2
pnpm tauri dev
# Select "Real WebRTC" mode - Automatically connects!Run peer on a server, connect GUI from different machine:
Option 1: SSH Port Forwarding (Easiest)
# Server (192.168.1.100)
cd src-tauri
cargo run --bin peer -- --host 0.0.0.0 --port 8080
# Client - Use SSH port forwarding:
ssh -L 8080:localhost:8080 user@192.168.1.100
# Then connect GUI normally to localhost:8080 (no .env needed)Option 2: Direct Connection (Requires .env configuration)
# Server (192.168.1.100)
cd src-tauri
cargo run --bin peer -- --host 0.0.0.0 --port 8080
# Client - Configure peer URL:
# 1. Copy .env.example to .env
cp .env.example .env
# 2. Edit .env and set remote peer URL:
# VITE_PEER_SERVER_URL=http://192.168.1.100:8080
# 3. Run the GUI:
pnpm tauri dev
# OR for production build:
pnpm tauri buildImportant Notes:
- Development builds:
.envfile is read at build time (restartpnpm tauri devafter changing) - Production builds: Set
VITE_PEER_SERVER_URLbefore building - Default: If not set, defaults to
http://localhost:8080 - Security: Add
.envto.gitignore(already done) to prevent committing secrets
Run peer on a headless server for continuous monitoring:
# Build release version
cargo build --bin peer --release
# Run with nohup
nohup ./target/release/peer --host 0.0.0.0 &Use peer server in automated tests:
# Start peer in background
cargo run --bin peer &
PEER_PID=$!
# Run tests
cargo test
# Stop peer
kill $PEER_PID┌─────────────────────────┐
│ CLI Peer Server │
│ │
│ ┌──────────────────┐ │
│ │ WebSocket Server │ │◄──── WS /ws
│ │ (Axum) │ │ (Signaling)
│ │ Port 8080 │ │◄────► Offers/Answers
│ └────────┬─────────┘ │
│ │ │
│ ┌────────▼─────────┐ │
│ │ WebRTC Peer │ │◄────┐
│ │ - Data Channel │ │ │ Heartbeat
│ │ - Heartbeat │ │─────┘ (100ms)
│ │ - RTT Tracking │ │ over WebRTC
│ │ - ICE Restart │ │
│ └──────────────────┘ │
│ │
│ ┌──────────────────┐ │
│ │ Network Monitor │ │
│ │ - Status │ │
│ │ - Timeout (500ms)│ │
│ └──────────────────┘ │
└─────────────────────────┘
Dependencies:
- axum - Web framework with WebSocket support
- tokio - Async runtime
- webrtc - WebRTC implementation
- clap - Command-line argument parsing
- tower-http - CORS middleware
- serde/serde_json - JSON serialization
- futures - Async stream utilities
Module Reuse: The peer server reuses existing modules from the main app:
network.rs- Network monitoring logicwebrtc_peer.rs- WebRTC peer connection logic
Only the Tauri and Whisper modules are excluded.
Connection Flow:
-
Startup:
- Peer creates WebRTC offer
- WebSocket server starts on specified port
- Offer is ready for clients
-
GUI Connection:
- GUI connects to WebSocket
/ws - Server pushes offer via WebSocket
- GUI creates answer using Tauri command
- GUI sends answer back via WebSocket
- Peer sets answer and completes connection
- GUI connects to WebSocket
-
Heartbeat Monitoring:
- Heartbeats sent every 100ms via WebRTC data channel
- RTT calculated from timestamps
- Auto-disconnect after 500ms timeout
-
Reconnection:
- WebSocket reconnects automatically on network recovery
- Server detects reconnection and triggers ICE restart
- New offer pushed to client via WebSocket
- Connection re-established automatically
CORS Configuration: The peer server allows connections from:
http://localhost:1420(Vite dev server)tauri://localhost(Tauri production)http://tauri.localhost(Tauri dev)- All origins for flexibility
Note: WebSocket connections use a simpler CORS model than HTTP requests.
Error: Address already in use
Solution: Port 8080 is already taken. Use a different port:
cargo run --bin peer -- --port 9000Error: WebSocket connection failed or Failed to connect to peer server
Solutions:
- Ensure peer server is running
- Check the WebSocket URL in console (should be
ws://localhost:8080/ws) - Verify firewall isn't blocking WebSocket connections
- Check if the port is accessible:
# On macOS/Linux lsof -i :8080
Problem: Connection shows as disconnected immediately after connecting.
Solutions:
- Check firewall settings
- Verify STUN server is reachable
- Check network stability
- Try increasing timeout in
network.rs:137
Problem: RTT shows >200ms consistently
Solutions:
- Check network quality
- Ensure both peer and GUI are on same network
- Try different STUN server (edit
webrtc_peer.rs)
Error: Panic or crash when GUI connects
Solution: Run with debug logging:
RUST_LOG=debug cargo run --bin peerResource Usage:
- Memory: ~5-10 MB
- CPU: <1% (idle), ~2-5% (active heartbeat)
- Network: ~100 bytes/second (heartbeat only)
Scalability:
- Single Connection: Optimized for one GUI connection
- Multiple Connections: Not currently supported
Network Exposure:
- Default: Binds to
127.0.0.1(localhost only) - Public: Use
--host 0.0.0.0to expose to network - Risk: WebRTC offers contain network information
Authentication:
- Current: No authentication
- Recommendation: Use firewall rules or VPN for remote access
Data Privacy:
- Heartbeat Only: Only timestamps exchanged
- No Encryption: Data channel is unencrypted (P2P nature)
- STUN Visibility: Public STUN servers can see your IP
| Feature | CLI Peer | Full GUI App |
|---|---|---|
| WebRTC Connection | ✅ | ✅ |
| Heartbeat Monitoring | ✅ | ✅ |
| Network Status | ✅ | ✅ |
| Audio Capture | ❌ | ✅ |
| Transcription | ❌ | ✅ |
| GUI | ❌ | ✅ |
| Auto-Connect Server | ✅ | ❌ |
| Binary Size | ~10 MB | ~100 MB |
| Memory Usage | ~5-10 MB | ~50-200 MB |
| Startup Time | <1s | ~2-5s |
The app supports environment variables for runtime configuration:
| Variable | Description | Default | Example |
|---|---|---|---|
VITE_PEER_SERVER_URL |
WebRTC peer server URL | http://localhost:8080 |
http://192.168.1.100:8080 |
Setup:
# 1. Copy the example file
cp .env.example .env
# 2. Edit .env with your values
nano .env
# 3. Restart dev server (required for changes to take effect)
pnpm tauri devFor production builds:
# Set environment variable before building
export VITE_PEER_SERVER_URL=http://your-server:8080
pnpm tauri buildEdit tauri.conf.json to bundle different models:
{
"bundle": {
"resources": [
"models/ggml-tiny-q5_1.bin" // Change this
]
}
}Edit src-tauri/src/whisper.rs:
params.set_n_threads(4); // Adjust thread count┌─────────────────────────┐
│ Frontend (Solid.js) │
│ - Audio Capture │
│ - UI Display │
│ - Mode Toggle │
└──────────┬──────────────┘
│ Tauri Commands
│ - transcribe_audio_chunk
│ - set_network_mode
│ - create_webrtc_offer/answer
↓
┌─────────────────────────┐ ┌──────────────────┐
│ Backend (Rust) │◄────►│ Remote Peer │
│ - whisper-rs │ │ (WebRTC) │
│ - NetworkMonitor │ └──────────────────┘
│ • Simulated mode │ ▲
│ • Real WebRTC mode │ │
│ - WebRTCPeer │ STUN Server
│ • Data channel │ (NAT Traversal)
│ • Heartbeat (100ms) │
│ • RTT calculation │
└──────────┬──────────────┘
│ Returns: transcript + network status
↓
┌─────────────────────────┐
│ UI (Color-coded) │
│ - Delivered (normal) │
│ - Missed (red) │
│ - RTT display │
└─────────────────────────┘
npx tsc --noEmitcargo clippy --manifest-path=src-tauri/Cargo.tomlpnpm format # Frontend
cargo fmt --manifest-path=src-tauri/Cargo.toml # BackendMODEL_SETUP.md- Whisper model download instructionsPRD.md- Product requirements and specificationssrc-tauri/models/README.md- Model directory info
MIT