LLeMbas — waybread for the long road of thought

A self-hosted web UI for your language models, written in Python.
Talks to anything that speaks the OpenAI API. Themed after Middle-earth.

Python 3.11+ License GPL-3.0 No Node required

--- *Lembas* is the Elvish waybread — one bite sustains a traveller for a day's march. The capitals hide what it runs on: **LLeM**bas. ## Why this exists Most self-hosted LLM front-ends are large JavaScript applications with a Python API bolted underneath. LLeMbas is the other way round: **server-rendered Python**, with htmx and a little Alpine for interactivity. There is no `package.json`, no bundler, no build step, and nothing is fetched from a CDN at runtime. Clone it, `pip install -e .`, run it. ## Features **Working now** - **Chats** — streaming replies, Markdown with server-side syntax highlighting, copy and regenerate, automatic chat titles. Chats are created when you send the first message, so an abandoned one never clutters the sidebar - **System prompts** — instance-wide, per-model and per-chat, with the most specific winning outright - **Reasoning display** — thinking from reasoning models streams into its own collapsible block, labelled with how long it took, and is never replayed as context - **Attachments** — drag, paste or pick images, PDFs and text files. Images are downscaled and sent to vision models; PDF and text content is extracted and put in the prompt - **Folders** — arbitrarily nested, delete a folder without losing the chats inside it - **OpenAI connections** — point at OpenAI, LM Studio, vLLM, llama.cpp, llama-swap, Ollama or OpenRouter; models are discovered and cached - **Model settings** — ordering, pinned models, an instance default and a per-user default, custom names and descriptions, uploaded model images - **Users, groups & permissions** — per-group grants that union rather than override, and model access restricted to chosen groups - **Accounts** — first account becomes the administrator, argon2 password hashing, revocable server-side sessions, self-service password change, admin-managed accounts - **Admin settings** — open or close registration from the UI, stored in the database and effective immediately - **Two themes** — *Moria* (dark) and *Shire* (light), switchable per user **Planned** Built-in tools with admin settings · custom tools and MCP servers · agentic execution (local and over SSH) · image generation · OCR for scanned PDFs. ## Quick start ```bash git clone https://git.houmeres.sk/Houmeres/LLeMbas.git cd LLeMbas python -m venv .venv && . .venv/bin/activate pip install -e ".[dev]" cp .env.example .env lembas secret-key # paste the result into LEMBAS_SECRET_KEY lembas serve # http://127.0.0.1:8080 ``` Open the address and create the first account — it becomes the administrator. Then go to **Admin → Connections** and add an endpoint. For a local runner that is usually `http://localhost:1234/v1` with no API key. Press **Test & refresh** and its models appear in the chat model picker. > The vendored browser libraries (htmx, Alpine) are committed, so no network > access is needed to run. To re-fetch or bump them: > `python scripts/fetch_vendor.py --update`. ## Configuration All variables are prefixed `LEMBAS_` and can live in `.env`. See [`.env.example`](.env.example) for the annotated list. | Variable | Default | Purpose | |---|---|---| | `LEMBAS_SECRET_KEY` | *generated* | Signs sessions and encrypts stored API keys. **Set this.** A generated key changes every restart, signing everyone out and making stored API keys unreadable. | | `LEMBAS_DATA_DIR` | `./data` | SQLite database and uploads. | | `LEMBAS_HOST` / `LEMBAS_PORT` | `127.0.0.1` / `8080` | Bind address. | | `LEMBAS_ALLOW_SIGNUP` | `true` | Whether new users may register themselves — the *initial* value only. Once set under **Admin → General** the stored setting wins. The first account is always an admin regardless. | | `LEMBAS_DEFAULT_THEME` | `moria` | `moria` (dark) or `shire` (light). | | `LEMBAS_SESSION_TTL` | `2592000` | Session lifetime in seconds. | | `LEMBAS_REQUEST_TIMEOUT` | `300` | Seconds to wait on an upstream model. | ### Commands ```bash lembas serve # run the server lembas info # where data lives, what is configured lembas secret-key # generate a value for LEMBAS_SECRET_KEY lembas create-admin # create or promote an administrator ``` ## How it fits together ``` Browser ──form POST──▶ FastAPI ──▶ SQLite ▲ │ │ └──httpx──▶ any OpenAI-compatible endpoint └──── server-sent events ◀───────────────┘ (streamed reply) ``` Sending a message stores the turn and returns two HTML fragments: the user's bubble and an empty assistant bubble carrying an `sse-connect`. That opens a server-sent event stream which appends tokens as they arrive, then replaces the whole bubble with the finished, Markdown-rendered version. Rendering and highlighting happen in Python, so the streamed and final views cannot disagree. ``` src/lembas/ api/ routes: auth, chats, folders, admin, pages db/models/ SQLAlchemy schema security/ password hashing, sessions services/ llm client, chat orchestration, markdown, crypto, sse web/ Jinja templates and static assets assets/ SVG artwork masters scripts/ artwork generator, vendored-JS fetcher deploy/ systemd unit and nginx vhost for a real install ``` ## Development ```bash pytest # test suite ruff check . # lint python scripts/build_artwork.py # regenerate the SVG artwork python scripts/fetch_vendor.py # verify vendored JS against the lockfile ``` There is no Alembic. The schema is SQLite-only and synchronised at startup: missing tables and missing columns are added automatically, so adding a field to a model needs nothing but a restart. Renames, drops and retypes are still manual — see `CLAUDE.md`. ## Artwork The logo, favicon and banner are original vector work, generated by [`scripts/build_artwork.py`](scripts/build_artwork.py) so the mallorn leaf stays identical across every size it appears at. The wordmark is [Source Serif 4](https://github.com/adobe-fonts/source-serif) (SIL OFL 1.1) converted to outlines — a README banner cannot load a webfont, and `` would render in whatever serif the reader happens to have. ## Licence [GPL-3.0](LICENSE). ## A note on the theme This is an independent hobby project, themed as an affectionate nod to J.R.R. Tolkien's world. It is **not affiliated with, endorsed by, or connected to** the Tolkien Estate, Middle-earth Enterprises, or any related rights holder. All artwork here is original.