A Chrome extension that reads a thread on X (Twitter) and returns a bullet-point summary powered by an LLM.
threadit/
├── extension-frontend/ Vite + React Chrome extension (Manifest V3)
└── backend-api/ FastAPI server — caching, database, Groq API
- Open a thread on
x.comand click the extension icon. - The popup sends a message to the content script, which reads the visible tweets from the DOM.
- The popup POSTs the scraped tweets to the local FastAPI server.
- The server checks an in-memory cache, then PostgreSQL. On a miss, it calls Groq's
llama-3.1-8b-instantand caches the result in both. - The summary is returned and displayed in the popup.
cd backend-api
# Start a local Postgres instance
docker compose up -d
python -m venv venv
venv\Scripts\activate # macOS/Linux: source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# Add your GROQ_API_KEY to .env
uvicorn main:app --reload --port 8000Verify: http://localhost:8000/health should return {"status": "ok"}.
cd extension-frontend
npm install
npm run buildThen in Chrome:
- Go to
chrome://extensions - Enable Developer mode
- Click Load unpacked → select
extension-frontend/dist
Summaries are persisted to PostgreSQL via SQLAlchemy's async ORM (db.py).
Schema:
threads
id, content_hash (unique), tweet_count, created_at, last_accessed_at, access_count
│
│ 1-to-many
▼
summaries
id, thread_id (FK → threads.id), summary_text, model, created_at
request_log
id, content_hash, cache_hit, created_at
request_log stores content_hash directly instead of a foreign key — it's a write-heavy audit table used only for aggregate analytics, so skipping the join keeps writes cheap.
Endpoints:
GET /history?limit=10— recent summaries, newest firstGET /stats— cache hit rate and usage countsGET /cache/memory— current in-memory cache size
Inspect directly:
docker exec -it threadit-db psql -U postgres -d threadit
\dt
SELECT * FROM summaries ORDER BY created_at DESC LIMIT 5;Two layers:
| Layer | Implementation | Persistent |
|---|---|---|
| L1 | In-memory TTL dict (SHA-256 keyed) | No — cleared on restart |
| L2 | PostgreSQL | Yes |
TTL defaults to 1 hour, configurable via CACHE_TTL_SECONDS in .env.
content.js targets article[data-testid="tweet"] and div[data-testid="tweetText"]. These attributes are used by X's own test suite and are more stable than the obfuscated class names. If scraping breaks, open DevTools on a tweet page and verify the data-testid values haven't changed.