Spaces:
Running
Running
SilverElixir commited on
Add files via upload
Browse files- docs/ARCHITECTURE.md +1 -1
- docs/README.md +118 -0
docs/ARCHITECTURE.md
CHANGED
|
@@ -12,7 +12,7 @@ This document goes one level deeper than the main [README](../README.md) into ho
|
|
| 12 |
|
| 13 |
## Automatic model routing
|
| 14 |
|
| 15 |
-
|
| 16 |
|
| 17 |
- **A YouTube or website link** → Gemini only. It's the only provider that can read page content (`url_context`) or analyze a video by URL.
|
| 18 |
- **A video or audio attachment** → Gemini only. OpenRouter's multimodal models only accept images as base64.
|
|
|
|
| 12 |
|
| 13 |
## Automatic model routing
|
| 14 |
|
| 15 |
+
Routing is fully automatic: the router builds a fresh candidate list for every message:
|
| 16 |
|
| 17 |
- **A YouTube or website link** → Gemini only. It's the only provider that can read page content (`url_context`) or analyze a video by URL.
|
| 18 |
- **A video or audio attachment** → Gemini only. OpenRouter's multimodal models only accept images as base64.
|
docs/README.md
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Lumen
|
| 3 |
+
colorFrom: yellow
|
| 4 |
+
colorTo: gray
|
| 5 |
+
sdk: docker
|
| 6 |
+
app_port: 7860
|
| 7 |
+
pinned: false
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
<p align="center">
|
| 11 |
+
<img src="assets/lumen-banner.svg" alt="Lumen: Intelligence. Clarity." width="480">
|
| 12 |
+
</p>
|
| 13 |
+
|
| 14 |
+
A Telegram bot styled after Claude's tone and personality (direct, warm, light on hedging), running on Google Gemini and free OpenRouter models with automatic per-message routing between them. Lumen also generates images, downloads TikTok videos without watermarks, and reads text back as speech. It runs as a webhook service on Hugging Face Spaces (Docker).
|
| 15 |
+
|
| 16 |
+
**Stack:** Python 3.13 · aiogram · FastAPI · google-genai · Docker
|
| 17 |
+
|
| 18 |
+
> The YAML block above is Hugging Face Space metadata, not a mistake: this same `README.md` doubles as the Space card. Everything below it is normal GitHub documentation.
|
| 19 |
+
|
| 20 |
+
## Features
|
| 21 |
+
|
| 22 |
+
- **Automatic model routing.** Every message goes to whichever provider actually has what it needs: web search and link reading go to Gemini, everything else defaults to free OpenRouter models, protecting Gemini's tight daily quota.
|
| 23 |
+
- **Streaming replies** with a self-calibrating typing-speed pacer, so answers type themselves in instead of landing in a few large chunks.
|
| 24 |
+
- **Defenses against prompt injection and identity leaks.** A deterministic input filter plus output scrubbers keep the bot from revealing which model or provider actually answered.
|
| 25 |
+
- **TikTok downloads** without watermarks: video, slideshows (including "live" photo slides), and original sound.
|
| 26 |
+
- **Image generation** through Pollinations.ai. The model is picked from the prompt itself (anime, fantasy, realism, quick sketch, or a general default).
|
| 27 |
+
- **Text-to-speech**, trying Fish Audio first and falling back to Gemini TTS.
|
| 28 |
+
- `/draw` and `/tts` also work as plain phrases at the start of a message ("draw a cat," "read this out loud"), no slash required.
|
| 29 |
+
- **Persistent state.** An optional Upstash Redis backend keeps chat history and quota counters alive across redeploys.
|
| 30 |
+
- **Error tracking** through an optional Sentry integration that scrubs secrets before sending anything.
|
| 31 |
+
|
| 32 |
+
## How it works
|
| 33 |
+
|
| 34 |
+
Lumen is a single FastAPI + aiogram service. Telegram delivers updates to a webhook. Each message gets routed through a chain of candidate models (Gemini and/or OpenRouter), built on the fly from the message's content: attachments, links, and a couple of lightweight heuristics for "does this need current information" and "is this a heavy request." The first provider that answers wins; the other is tried as a fallback if its whole chain fails.
|
| 35 |
+
|
| 36 |
+
The codebase is a modular monolith. `bot.py` is the orchestrator; the rest is split into focused modules: `lumen_router_config.py` (model routing), `lumen_formatting.py` (markdown to Telegram HTML), `lumen_security.py` (injection and leak defenses), `lumen_images.py`, `lumen_tts.py`, `lumen_tiktok.py`, `lumen_telegram_transport.py`, `lumen_state_storage.py`, and `lumen_typing_pace.py`.
|
| 37 |
+
|
| 38 |
+
See **[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)** for a deeper look at routing, streaming, the security layers, and the TikTok downloader.
|
| 39 |
+
|
| 40 |
+
## Deploying on Hugging Face Spaces
|
| 41 |
+
|
| 42 |
+
1. Push this repository to a Space (`sdk: docker` is already set in the frontmatter above).
|
| 43 |
+
2. Set the two required secrets: `BOT_TOKEN` and `GEMINI_API_KEY`.
|
| 44 |
+
3. Hugging Face builds the image from `Dockerfile` and runs `python -u bot.py`.
|
| 45 |
+
4. On startup the bot registers its own webhook. If that fails, check the logs for `[webhook] setWebhook failed` and register it manually:
|
| 46 |
+
```bash
|
| 47 |
+
curl -H "Authorization: Bearer <ADMIN_PANEL_KEY>" https://<space-host>/webhook_url
|
| 48 |
+
```
|
| 49 |
+
then open the `register_link` it returns.
|
| 50 |
+
5. `WEBHOOK_SECRET` and `ADMIN_PANEL_KEY` are derived deterministically from `BOT_TOKEN` (or from `ADMIN_SECRET_SEED`, if set). Fetch them with:
|
| 51 |
+
```bash
|
| 52 |
+
curl -H "Authorization: Bearer <BOT_TOKEN>" https://<space-host>/admin_keys
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
Hugging Face Spaces' outbound IPs are blocked by Telegram's API at the network level, so a small proxy is required. See [Proxy setup](#proxy-setup) below.
|
| 56 |
+
|
| 57 |
+
## Configuration
|
| 58 |
+
|
| 59 |
+
Only `BOT_TOKEN` and `GEMINI_API_KEY` are required. Everything else has a sane default; the full reference (proxy addresses, timeouts, storage, rate limiting, TikTok tuning) lives in **[docs/ENVIRONMENT.md](docs/ENVIRONMENT.md)**.
|
| 60 |
+
|
| 61 |
+
## Proxy setup
|
| 62 |
+
|
| 63 |
+
Hugging Face Spaces' outbound IPs are blocked by Telegram's Bot API entirely, and TikWM (the TikTok API this bot uses) returns `403` to the same IPs. Both are fixed by one small pass-through proxy, deployed separately on Deno Deploy (source: `proxy.ts`):
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
TELEGRAM_API_BASE_URL=https://<proxy-domain>/fetch/api.telegram.org
|
| 67 |
+
TIKWM_API_BASE_URL=https://<proxy-domain>/fetch/www.tikwm.com
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
The proxy only forwards to an explicit host allowlist (`api.telegram.org`, `www.tikwm.com`, `tikwm.com`). See `proxy.ts` for the implementation and `proxy_test.ts` for its tests.
|
| 71 |
+
|
| 72 |
+
## Diagnostics
|
| 73 |
+
|
| 74 |
+
All three endpoints below require `Authorization: Bearer <ADMIN_PANEL_KEY>` (`/admin_keys`, covered in the deploy steps above, uses `BOT_TOKEN` instead):
|
| 75 |
+
|
| 76 |
+
| Endpoint | Purpose |
|
| 77 |
+
|---|---|
|
| 78 |
+
| `GET /diag` | Checks outbound network reachability (Telegram, Gemini, OpenRouter, TikWM, Pollinations, etc.) from inside the container. |
|
| 79 |
+
| `GET /webhook_url` | Shows the computed webhook URL and a ready-to-open registration link. |
|
| 80 |
+
| `GET /export_state` | Dumps all chat histories and quota counters as JSON, for manual backup. |
|
| 81 |
+
|
| 82 |
+
`/logs` (a Telegram command, owner-only) sends the current `bot.log` with secrets redacted.
|
| 83 |
+
|
| 84 |
+
## Commands
|
| 85 |
+
|
| 86 |
+
`/start`, `/reset`, `/draw`, and `/tts` show up in Telegram's command menu:
|
| 87 |
+
|
| 88 |
+
| Command | Access | Purpose |
|
| 89 |
+
|---|---|---|
|
| 90 |
+
| `/start` | anyone | Shows a short intro and the command list. |
|
| 91 |
+
| `/reset` | anyone in DMs; group admins/owner in groups | Clears the chat's conversation history. |
|
| 92 |
+
| `/draw [description]` | anyone | Generates an image (see [Features](#features)). |
|
| 93 |
+
| `/tts [text]` | anyone | Reads text out loud. |
|
| 94 |
+
|
| 95 |
+
Two more owner-only commands stay out of the menu on purpose, since they surface internal details that shouldn't be visible in a group chat:
|
| 96 |
+
|
| 97 |
+
| Command | Access | Purpose |
|
| 98 |
+
|---|---|---|
|
| 99 |
+
| `/stats` | bot owner, DMs only | Active chat count, process uptime, per-model quota usage. |
|
| 100 |
+
| `/logs` | bot owner, DMs only | Sends `bot.log` with secrets redacted. |
|
| 101 |
+
|
| 102 |
+
## Testing
|
| 103 |
+
|
| 104 |
+
```bash
|
| 105 |
+
pip install -r requirements.txt -r requirements-dev.txt
|
| 106 |
+
pytest -v
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
Test files mirror the module split: `test_lumen_formatting.py`, `test_lumen_security.py`, `test_lumen_router_config.py`, and `test_lumen_typing_pace.py` each test their matching module directly, while `test_bot.py` covers everything defined in `bot.py` itself. `conftest.py` stubs `BOT_TOKEN`/`GEMINI_API_KEY`/`BOT_LOG_PATH` so the suite needs no real secrets.
|
| 110 |
+
|
| 111 |
+
CI (`.github/workflows/ci.yml`) runs `pyflakes` + `pytest` + `pip-audit` on every push and pull request; a successful run triggers `sync-to-hf.yml`, which mirrors the commit to the Hugging Face Space.
|
| 112 |
+
|
| 113 |
+
## Known limitations
|
| 114 |
+
|
| 115 |
+
- Chat history and quota counters survive redeploys only if Upstash is configured (see [docs/ENVIRONMENT.md](docs/ENVIRONMENT.md)); otherwise they live on the container's ephemeral disk and reset on every rebuild.
|
| 116 |
+
- YouTube downloading isn't supported, because Hugging Face Spaces' outbound IPs are blocked at the TLS handshake level. Viewing and analyzing a video by link still works.
|
| 117 |
+
- TikTok downloading depends on the unofficial TikWM API, not an official TikTok endpoint, and needs the proxy above to work from Hugging Face Spaces at all.
|
| 118 |
+
- Streaming has only been exercised against mocked clients, not live Gemini/OpenRouter SSE traffic. See the manual smoke-test checklist in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) before touching that code path.
|