# πŸŽ™οΈ SayIt **Just say it, and write well** Self-hosted speech-to-text server with real-time ASR and AI cleanup. [Quick Start](#quick-start) Β· [Architecture](#architecture) Β· [Configuration](#configuration) Β· [Deployment](#deployment) Β· [License](#license)
--- ## What is SayIt? SayIt is a self-hosted speech-to-text service that combines real-time ASR (Qwen3-ASR) with AI cleanup. It provides: - **Browser Demo** β€” Record and transcribe directly in the browser, no install needed - **Windows Desktop App** β€” Push-to-talk with automatic paste into any application - **Admin Dashboard** β€” Monitor sessions, performance metrics, GPU/CPU usage - **REST & WebSocket API** β€” Integrate speech-to-text into your own applications Everything runs on a single server with one GPU. ## Features - 🎯 **Real-time ASR** β€” Qwen3-ASR-1.7B with vLLM acceleration, <2s latency - ✨ **AI Cleanup** β€” Converts spoken language to written text (Azure OpenAI / Groq / Ollama) - πŸ”₯ **Hotword Boosting** β€” Custom vocabulary for domain-specific terms - πŸ“Š **Admin Dashboard** β€” Session analytics, performance percentiles, system monitoring - 🐳 **Docker Ready** β€” `docker compose up` with GPU support, model baked into image - πŸ”’ **Security** β€” Rate limiting, WebSocket connection limits, admin path isolation - πŸ“¦ **SQLite / PostgreSQL** β€” SQLite for single-node, PostgreSQL for cluster deployment ## Architecture ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ALB (HTTPS 443) β”‚ β”‚ ACM TLS termination β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ FastAPI Backend (:8000) β”‚ β”‚ β”‚ β”‚ / Landing page + demo β”‚ β”‚ /api/* REST APIs β”‚ β”‚ /ws/transcribe WebSocket streaming β”‚ β”‚ /admin/* Dashboard (blocked β”‚ β”‚ on public, SSH only) β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚Qwen3 β”‚ β”‚ LLM β”‚ β”‚Telemetry β”‚ β”‚ β”‚ β”‚ASR+vLLMβ”‚ β”‚proxyβ”‚ β”‚ SQLite/ β”‚ β”‚ β”‚ β”‚ (GPU) β”‚ β”‚ β”‚ β”‚ PG β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` ## Quick Start ### Prerequisites - NVIDIA GPU with β‰₯16GB VRAM (e.g., A10G, L4, RTX 4090) - Docker with [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/install-guide.html) - An LLM API key (Azure OpenAI, OpenAI, Groq, or local Ollama) ### 1. Clone and configure ```bash git clone https://github.com/crosswk/SayIt.git cd SayIt cp .env.example .env cp config.example.yaml config.yaml ``` Edit `.env` β€” fill in your API keys: ```bash SAYIT_OPENAI_API_KEY=sk-... # or SAYIT_AZURE_API_KEY SAYIT_ADMIN_PASSWORD=your-password ``` Edit `config.yaml` β€” adjust LLM provider if needed (default: Azure OpenAI). ### 2. Start ```bash docker compose up -d --build ``` First build takes ~15 minutes (downloads model + builds image). Subsequent starts take ~60 seconds (model loading). ### 3. Access | URL | Description | |-----|-------------| | `https://your-server/` | Landing page + browser demo | | `https://your-server/healthz` | Health check | | `http://localhost:8000/admin` | Admin dashboard (via SSH tunnel) | Admin access: `ssh -L 8000:localhost:8000 your-server` then open `http://localhost:8000/admin` ## Project Layout ``` SayIt/ β”œβ”€β”€ backend/ # FastAPI backend β”‚ β”œβ”€β”€ app/ β”‚ β”‚ β”œβ”€β”€ main.py # Routes, WebSocket handler β”‚ β”‚ β”œβ”€β”€ config.py # Nested config dataclasses β”‚ β”‚ β”œβ”€β”€ asr.py # Qwen3-ASR engine + VAD β”‚ β”‚ β”œβ”€β”€ llm.py # AI cleanup (multi-provider) β”‚ β”‚ β”œβ”€β”€ admin.py # Admin API endpoints β”‚ β”‚ β”œβ”€β”€ telemetry.py # Usage tracking + analytics β”‚ β”‚ β”œβ”€β”€ db.py # SQLite / PostgreSQL abstraction β”‚ β”‚ └── ratelimit.py # Token-bucket rate limiter β”‚ β”œβ”€β”€ tests/ β”‚ β”œβ”€β”€ Dockerfile β”‚ └── requirements.txt β”œβ”€β”€ gateway/ # HTTPS reverse proxy (Node.js) β”œβ”€β”€ web/ # Landing page, browser demo, admin UI β”œβ”€β”€ prompts/ # System prompt + hotword files β”œβ”€β”€ config.example.yaml # Configuration template β”œβ”€β”€ .env.example # Secrets template β”œβ”€β”€ docker-compose.yml └── LICENSE # AGPL-3.0 ``` ## Configuration Configuration is split into two files: | File | Contains | Committed to git? | |------|----------|-------------------| | `config.yaml` | All settings (ASR, LLM, ports, logging, etc.) | Yes (template) | | `.env` | Secrets only (API keys, passwords) | **No** | All environment variables use the `SAYIT_` prefix. See [config.example.yaml](./config.example.yaml) for full documentation. ### Key settings ```yaml asr: engine: "qwen3" # or "firered" model: "Qwen/Qwen3-ASR-1.7B" device: "cuda:0" llm: enabled: true provider: "openai" # azure / openai / groq / ollama web_demo: enabled: true max_duration_sec: 600 # Max recording per session max_concurrency_per_ip: 3 admin: enabled: true ``` ### Database Default: SQLite (zero config). For multi-node deployment: ```yaml telemetry: db_backend: "postgresql" db_path: "postgresql://user:pass@host:5432/sayit" ``` Or via environment variable: `SAYIT_DB_URL=postgresql://...` ## Deployment ### Single server (recommended for most users) ```bash docker compose up -d --build ``` ### With ALB (AWS) 1. Request ACM certificate for your domain 2. Create ALB with HTTPS listener + ACM certificate 3. Target group β†’ EC2:8000, health check `/healthz` 4. Block `/admin/*` at ALB level (fixed 404 response) 5. Access admin via SSH tunnel See [docs/deployment.md](./docs/deployment.md) for details. ## Development ```bash # Create virtualenv python3 -m venv .venv source .venv/bin/activate pip install -r backend/requirements.txt # Run backend directly cd backend uvicorn app.main:app --host 0.0.0.0 --port 8000 # Run tests python -m pytest backend/tests/ -v ``` ## API ### WebSocket `/ws/transcribe` ```json // β†’ Client sends {"cmd": "start", "client_meta": {"user_id": "..."}, "app_context": {"process_name": "..."}} // β†’ Client sends PCM audio frames (16kHz, 16-bit, mono) {"cmd": "stop", "usage_meta": {"ptt_hold_ms": 1500}} // ← Server responds {"type": "asr", "text": "δ½ ε₯½δΈ–η•Œ", "asr_ms": 450} {"type": "final", "asr_text": "δ½ ε₯½δΈ–η•Œ", "llm_text": "δ½ ε₯½οΌŒδΈ–η•Œγ€‚", "asr_ms": 450, "llm_ms": 320} {"type": "done"} ``` ### REST | Method | Path | Description | |--------|------|-------------| | GET | `/healthz` | Health check | | GET | `/api/public/config` | Public site configuration | | GET | `/api/hotwords` | Current hotword list | | PUT | `/api/hotwords` | Update hotwords | | GET | `/admin/api/overview` | Dashboard metrics (auth required) | | GET | `/admin/api/sessions` | Session list (auth required) | See [docs/api.md](./docs/api.md) for full API reference. ## License This project is licensed under the [GNU Affero General Public License v3.0](./LICENSE). You are free to self-host and modify SayIt. If you distribute a modified version or run it as a network service, you must make your source code available under the same license. For commercial licensing inquiries, please contact the maintainers.