Skip to content

slubbles/quick-capture

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

12 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Quick Capture

Standalone voice capture app — record in the browser, live transcript via ElevenLabs Scribe realtime, copy to clipboard, optional DeepSeek “Organize”, SQLite history.

Architecture

Quick Capture architecture

Diagram (SVG) docs/q-capture.svg
Open in Excalidraw Open diagram
Excalidraw source docs/architecture.excalidraw
Public gist (for #url=) gist.github.com/slubbles/eb39eac2dcd9b15c9b052b9bc66076f8

After editing docs/architecture.excalidraw, keep the shareable link working:

# from repo root (requires gh auth with gist scope)
gh gist edit eb39eac2dcd9b15c9b052b9bc66076f8 docs/architecture.excalidraw
# then commit/push the same file so repo + gist stay aligned

How it fits together

  1. The browser loads the Capture UI from this app and captures microphone audio.
  2. The app mints a short-lived, single-use ElevenLabs token (API key never goes to the browser).
  3. The browser streams audio directly to ElevenLabs over a WebSocket; partial/final text streams back into the UI.
  4. On stop: text is copied to the clipboard and saved to SQLite (data/capture.db).
  5. Optional Organize sends the text (not audio) to DeepSeek for cleanup only.
  6. In production, nginx terminates TLS and rate-limits paid routes; the app listens on 127.0.0.1 only.

What “Scribe realtime access” means

Scribe here is ElevenLabs’ speech-to-text product name (not this repo’s name). Realtime means live streaming STT over a WebSocket while you talk (partials appear as you speak), not “upload a finished file and wait.”

Your ElevenLabs account/API key must be allowed to call their realtime Scribe endpoints (plan/feature access on ElevenLabs’ side). Without that, token mint or the browser WebSocket will fail even if the key string is present.

Product model

  • Free & open — no accounts, no profiles
  • Anyone can record — shared SQLite history on the server
  • Captures may be visible to anyone who opens /history
  • Operator funds STT/LLM and caps spend (ElevenLabs console + nginx rate limits)

Do not treat this as private note storage.

Features

  • One-tap record / stop (push-to-talk style)
  • Live partial + committed transcripts
  • Autosave while recording (survives tab close mid-session)
  • Clipboard copy on finish
  • Organize button (optional DeepSeek cleanup)
  • History page (/history)
  • Production defaults: OpenAPI /docs and /redoc off unless ENABLE_DOCS=1
  • Server-side max text length on autosave / save / organize (default 100 000 chars)

Requirements

  • Python 3.11+
  • ElevenLabs API key with Scribe realtime (streaming STT) enabled on the account
  • Optional: DeepSeek API key for Organize

Run locally

cd quick-capture
py -3 -m venv .venv

# Windows
.venv\Scripts\pip install -r requirements.txt
copy .env.example .env
# edit .env — set ELEVENLABS_API_KEY=

.venv\Scripts\uvicorn app.main:app --reload --host 127.0.0.1 --port 8010
# macOS / Linux
.venv/bin/pip install -r requirements.txt
cp .env.example .env
.venv/bin/uvicorn app.main:app --reload --host 127.0.0.1 --port 8010

Optional local docs: ENABLE_DOCS=1 then open /docs.

Open http://127.0.0.1:8010/ (HTTPS or localhost required for mic access in most browsers).

Health check: GET /health{"ok":true,"db":true}

API (browser uses these)

Method Path Purpose
GET / Capture UI
GET /history Saved transcripts (shared)
GET /capture/token Mint ElevenLabs single-use token
POST /capture/start Create pending DB row
POST /capture/autosave { transcript_id, text }
POST /capture/save Finalize { text, transcript_id? }
POST /capture/organize { text, transcript_id? } → cleaned text

Oversized text on autosave/save/organize returns 413 with a clear error.

Data

SQLite file: data/capture.db (gitignored). One shared database for the instance.

Deploy on a VPS (no Docker)

Artifacts live under deploy/. Prefer a clean reimage of a dedicated Contabo-style box. Full checklist: deploy/harden-checklist.md.

Reference production shape (this project’s public instance): https://capture.slubbles.com — DNS A record → VPS, nginx reverse-proxy, Let’s Encrypt, app on 127.0.0.1:8010 only. Never commit real API keys (only .env.example is tracked).

1. System user and code

sudo useradd --system --home /opt/quick-capture --shell /usr/sbin/nologin capture
sudo mkdir -p /opt/quick-capture
sudo chown capture:capture /opt/quick-capture
sudo -u capture git clone <YOUR_REPO_URL> /opt/quick-capture
cd /opt/quick-capture
sudo -u capture python3 -m venv .venv
sudo -u capture .venv/bin/pip install -r requirements.txt
sudo -u capture cp .env.example .env
sudo -u capture nano .env   # set ELEVENLABS_API_KEY=  and optional DEEPSEEK_API_KEY=
sudo chmod 600 .env
sudo chown capture:capture .env
sudo -u capture mkdir -p data

2. systemd (localhost only)

sudo cp deploy/quick-capture.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now quick-capture
sudo systemctl status quick-capture
# Must show 127.0.0.1:8010 — never public 0.0.0.0
ss -lntp | grep 8010

3. nginx + TLS

Rate limits in deploy/nginx-quick-capture.conf (paid paths tight, general traffic looser):

Path Zone Rate Burst
/capture/token capture_token_rl 10 req/min 3
/capture/organize capture_organize_rl 6 req/min 3
everything else qc_general 10 req/s 20

client_max_body_size 256k. No Basic Auth (open tool by design).

Recommended cert flow (HTTP first, then Let’s Encrypt):

# 1) Point DNS A record: capture.example.com -> your VPS IP
# 2) Install packages
sudo apt-get update && sudo apt-get install -y nginx certbot python3-certbot-nginx

# 3) Put limit_req_zone directives in http context (e.g. conf.d), then site:
#    - either copy deploy/nginx-quick-capture.conf and replace CAPTURE_DOMAIN
#    - or start with an HTTP-only server_name for ACME, then:
sudo certbot --nginx -d capture.example.com
sudo nginx -t && sudo systemctl reload nginx

If you paste the full sample file into sites-available, keep limit_req_zone lines once under /etc/nginx/conf.d/ (http context) if nginx complains about duplicate zones.

4. Smoke test

curl -sS https://capture.example.com/health
# Expect: {"ok":true,"db":true}
# /docs and /redoc should 404
# GET / should mention shared history / free & open

5. Deploy loop (after code changes)

cd /opt/quick-capture
sudo -u capture git pull
sudo systemctl restart quick-capture
curl -sS https://capture.example.com/health

Also set a hard spend cap in the ElevenLabs (and DeepSeek) consoles.

Tests

# from repo root, after venv + pip install -r requirements.txt
.venv/bin/pip install -r requirements-dev.txt   # pytest
.venv/bin/pytest -q

Offline suite covers health, privacy copy, docs off, start→autosave→save→history, token-without-key, text size limits, and organize error mapping.

Layout

quick-capture/
  app/
    main.py           # FastAPI routes, text limits, docs off by default
    db.py             # SQLite transcripts
    elevenlabs_stt.py # token mint
    organize.py       # DeepSeek cleanup
  deploy/
    quick-capture.service
    nginx-quick-capture.conf
    harden-checklist.md
  docs/
    q-capture.svg         # architecture diagram (README)
    architecture.excalidraw
  static/capture.js
  templates/capture.html
  templates/history.html
  requirements.txt
  requirements-dev.txt  # pytest
  .env.example

Not included (on purpose)

  • Docker / compose
  • File-upload archive pipeline
  • Multi-user auth / private per-user history

License

Private.

About

Standalone voice quick-capture (ElevenLabs Scribe realtime) extracted from Scribe

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors