School Bud-E is an AI learning companion: a chat app with voice in and voice out, image and song generation, document analysis, a curriculum search and a long-term memory that remembers what a learner is working on.
This edition talks to OpenRouter directly. Paste an OpenRouter key and every part of the app — the chat model, the vision model, speech recognition, speech output, image generation and music — runs on models you pick yourself from OpenRouter's live catalogue, with the privacy properties visible while you choose: EU hosting, zero data retention, price per million tokens.
It runs on Windows, macOS, Linux, Android and iOS from one codebase.
You need Flutter 3.7 or newer
(flutter --version) and a key from
openrouter.ai/keys.
git clone https://github.com/LAION-AI/bud-e-flutter-openrouter.git
cd bud-e-flutter-openrouter
flutter pub get
flutter run -d windows # or: macos, linux, chrome, or a device idThen open Settings (gear icon) and paste your key into Universal API key.
A key starting with sk-or-v1- switches the whole app to OpenRouter; the status
line under the field confirms it with "OpenRouter — direkt, ohne Middleware".
Press Save. That is the entire setup.
flutter build apk --release
# build/app/outputs/flutter-apk/app-release.apkOr grab the APK from the releases page and install it (you will have to allow installation from unknown sources). The app asks for the microphone permission on first use; everything else works without permissions.
Settings → OpenRouter-Modelle → Modelle auswählen opens the picker. It has one tab per job the app needs a model for:
| Tab | Job | What it is used for |
|---|---|---|
| Chat | llm |
the conversation |
| Vision | vlm |
reading images, PDFs and documents |
| ASR | asr |
transcribing what you say |
| TTS | tts |
reading answers out loud |
| Bild | image |
generating and editing pictures |
| Musik | music |
writing songs |
Inside a tab you can:
- Filter by Alle / ZDR / EU / EU+ZDR / Gratis, with the count of matching models on each chip. "ZDR" means the provider offers zero data retention; "EU" means the model has at least one endpoint that runs inside the EU.
- Search by any part of a model's name or id. Prefix matches come first, so
typing
muputs Muse Spark at the top rather than something with "mu" in the middle of its description. - Sort by recommendation, price (up or down), name or context length.
- Pick Automatisch (empfohlen) to let the app decide. It uses a recommended model and falls back to an alternative when a provider has an outage, which is what you want unless you have a reason to pin one.
Every card shows the model id, the badges (EU, ZDR, free, and whether a TTS model really speaks rather than composing), the price per million tokens for input and output, and the context length.
The list is never hardcoded. It is fetched from OpenRouter, cached on disk, refreshed every 30 minutes while you use the app and every six hours in a long session — so a model released next month appears without a new build. The header says how old the list is, and the refresh button forces an update. If OpenRouter is unreachable the last known list is used and labelled as stale.
Picking a model with those badges is not cosmetic. Each request is attempted in this order, and the first one that succeeds is used:
- strict — zero data retention and the region pinned to an EU endpoint
(
provider.only: ["google-vertex/eu"]and friends) - zdr — zero data retention, any region
- plain — whatever OpenRouter picks
Model first, strictness second: swapping to the alternative model keeps EU and ZDR, whereas relaxing the policy gives them up. So a degraded EU endpoint costs a retry, not your privacy setting. If you pinned a model yourself, that model is tried at every strictness level before the app reaches for a different one — a choice you made is not silently swapped at the first hiccup.
OpenRouter's own EU host (eu.openrouter.ai) is used when the account has
regional routing; ordinary keys get a 403 there, which the app notices once and
then remembers, falling back to region pinning on the main host.
TTS voices are vendor-specific, so voice, audio format and speaking style live in one editable settings field:
voice=Eve;format=mp3;style=calm, friendly, unhurried
Anything that is not a key=value directive is taken as the style, so you can
just write how you want it to sound. Grok takes the style as an instructions
field; Gemini wants it written in front of the text and only emits raw PCM,
which the app wraps in a WAV header before playing. You do not have to know any
of that — it is handled per model.
- Wake words — "Hey Buddy" starts recording, "Go Buddy" sends it, "Stop Buddy" cancels or stops the reply. Runs fully on-device with ONNX models (~80 ms per inference). Off by default, and it switches itself back off if it fails to start, so a broken audio stack can never lock you out of the settings screen.
- Three-tier memory — semantic (facts), episodic (past conversations) and working memory, with a nightly consolidation pass and a BM25 index for search.
- Curriculum search — the German Bildungspläne (Hamburg) ship with the app, indexed for retrieval, so answers can cite the actual curriculum.
- Document generation — PPTX, DOCX, PDF and HTML, written by a background sub-agent that can also generate the images it puts on the slides.
- Document analysis — drop in a PDF or a photo and ask about it.
- Sub-agents — long jobs run in the background with their own tool loop and report progress in the chat.
Two of the app's tools need the BUD-E middleware and are not available on an OpenRouter key, because OpenRouter has no equivalent service: code execution and HTML→PDF conversion. The app says so plainly instead of failing quietly.
If you have a BUD-E universal key (sbe-…#…), paste that instead. The app then
routes everything through the middleware exactly as before, and the OpenRouter
section disappears from the settings. Both paths are supported side by side —
the key decides, nothing else.
| Document | What is in it |
|---|---|
| docs/OPENROUTER.md | how the OpenRouter integration works, endpoint by endpoint |
| docs/ARCHITECTURE.md | app structure, services, providers |
| docs/CONTEXT_AND_MEMORY.md | the memory system |
| docs/SKILLS_AND_TOOLS.md | the tools the assistant can call |
| docs/DOCUMENT_GENERATION.md | PPTX/DOCX/PDF generation |
| docs/CURRICULUM_SEARCH.md | curriculum retrieval |
| docs/tests.md | manual and automated test notes |
| README.de.md | diese Anleitung auf Deutsch |
Everything is local, in the app's support directory:
- Windows:
%APPDATA%\ai.laion\school_bud_e_flutter\SchoolBudE\ - macOS:
~/Library/Application Support/SchoolBudE/ - Linux:
~/.local/share/SchoolBudE/ - Android/iOS: the app's private storage
That folder holds settings.json (including your API key), the conversations,
the memory files, the cached model catalogue and the workspace with generated
files. No key is compiled into the app — the source ships with an empty
default, and nothing is sent anywhere except to the API your key points at.
flutter pub get
flutter analyze
flutter test
flutter run -d windowsA small debug HTTP server listens on http://localhost:8790 in desktop debug
builds (/status, /messages, /send?text=…, /screenshot,
/debug_log?last=100, /navigate?to=openrouter). It is not started on mobile.
