SPB Git

spb/chat-spboucher Public

Private universal chat interface over the OpenRouter ecosystem — 400+ models, branching, streaming, usage tracking. Next.js 16 + SQLite, PWA, deployed on m4m64a at chat.spboucher.ai

TypeScript 78.8% CSS 15.1% JavaScript 4.9% Shell 1.2%
4.8 KB · 90 lines markdown
Rendered Raw Blame History
1# chat.spboucher.ai23![Status](https://img.shields.io/badge/status-live-3FB6A8)4![Models](https://img.shields.io/badge/models-409%2B-3FB6A8)5![Next.js](https://img.shields.io/badge/Next.js-16-0E1116)6![TypeScript](https://img.shields.io/badge/TypeScript-5-3178C6)7![SQLite](https://img.shields.io/badge/SQLite-WAL-E0A458)8![Gateway](https://img.shields.io/badge/gateway-OpenRouter-8B5CF6)9![PWA](https://img.shields.io/badge/PWA-installable-3FB6A8)10![Node](https://img.shields.io/badge/deploy-m4m64a%20%C2%B7%20ngrok-151A22)11![License](https://img.shields.io/badge/license-private-2A3240)1213> **Author:** Simon-Pierre Boucher14> **Contact:** [contact@spboucher.ai](mailto:contact@spboucher.ai)15> **Production:** [https://chat.spboucher.ai](https://chat.spboucher.ai)1617A private, universal chat interface over the entire **OpenRouter** ecosystem — one login, one server-side API key, 400+ models behind a single catalog, full conversation persistence with branching and per-message model attribution. Designed mobile-first as an installable PWA: a personal ChatGPT/Claude-class product, not an API demo.1819---2021## Highlights2223- **One gateway, hundreds of models** — the catalog is synced dynamically from OpenRouter into SQLite (never hand-maintained); removed models are marked `Unavailable`, historical conversations stay intact forever.24- **Real streaming** — normalized SSE event protocol (`generation.start`, `content.delta`, `reasoning.delta`, `usage`, …) with 15 s keep-alive pings that survive ngrok and mobile radio sleep. Stop truly aborts the upstream OpenRouter stream.25- **Branching & regeneration** — regenerating never destroys an answer; siblings live side by side with ‹ 1/2 › navigation, so the same prompt can be compared across models.26- **Generation state machine**`queued → starting → streaming → completed | cancelled | failed`, persisted per generation; a dropped mobile connection resyncs from the server.27- **Usage & cost tracking** — prompt/completion/reasoning/cached tokens and reported cost per generation, with a Today / 7d / 30d / All-time dashboard broken down by model.28- **Instrument Panel design** — ink/graphite surfaces, one teal accent for "alive", one amber accent for "cost", mono type for model IDs and meters, and the signature **Model Rail** cartridge attached to the composer (provider glyph · mono model ID · live context meter · price-per-1M chip).29- **Security posture for a public tunnel** — argon2id single-user auth, DB-backed sessions, aggressive login rate limiting, `Secure`/`HttpOnly`/`SameSite` cookies, CSP + security headers, zero unauthenticated API surface.3031## Architecture3233```34Browser (phone or desktop, PWA)35   │  POST /api/chat  (SSE)3637Next.js 16 backend — node m4m64a (Apple Silicon, macOS)38   │  src/lib/openrouter/  (single client module, normalized errors/streaming)3940OpenRouter API ──► Anthropic · OpenAI · Google · Meta · Qwen · DeepSeek · Mistral · …41```4243- **Runtime:** single Node.js process (API + UI), port 3000, exposed via ngrok at `chat.spboucher.ai`44- **Database:** SQLite (better-sqlite3, WAL) — conversations, message tree, generations, usage, model catalog cache, sessions45- **Supervision:** three launchd agents (`app`, `ngrok`, nightly `backup` at 03:30 with 14-day retention)4647## Local development4849```bash50cp .env.example .env                          # fill in OPENROUTER_API_KEY51npm install52npm run set-password <username> [password]    # generates a strong one if omitted53npm run dev54```5556## Deployment (m4m64a)5758```bash59rsync -az --delete --exclude node_modules --exclude .next --exclude data --exclude .env \60  ~/Desktop/chat-spboucher/ M4M64a:apps/chat.spboucher.ai/61ssh M4M64a '~/apps/chat.spboucher.ai/ops/deploy.sh'62```6364| launchd agent | Role |65|---|---|66| `ai.spboucher.chat.app` | `npm run start` on port 3000, KeepAlive |67| `ai.spboucher.chat.ngrok` | `ngrok http --url=chat.spboucher.ai 3000`, KeepAlive |68| `ai.spboucher.chat.backup` | nightly SQLite backup 03:30, 14-day retention |6970## API surface7172All routes require a session cookie except `POST /api/auth/login`.7374| Endpoint | Purpose |75|---|---|76| `POST /api/chat` | send a message or regenerate (`{regenerateOf}`) — returns a normalized SSE stream |77| `POST /api/generations/:id/cancel` | abort the upstream OpenRouter generation |78| `GET /api/models` · `POST /api/models/sync` | cached catalog · manual refresh |79| `POST /api/models/prefs` | favorites / pinned models |80| `GET/PATCH/DELETE /api/conversations[/:id]` | conversation CRUD, branch leaf selection |81| `GET /api/usage?period=today\|7d\|30d\|all` | usage & cost dashboard data |8283## License8485Private software — © Simon-Pierre Boucher. All rights reserved.8687---8889**Simon-Pierre Boucher** · [contact@spboucher.ai](mailto:contact@spboucher.ai) · [chat.spboucher.ai](https://chat.spboucher.ai)90