Beamer+ is a local, browser-based presentation tool for interactive lectures. It renders PDF slide decks (e.g. LaTeX Beamer output) and layers live features on top: pen and shape annotations, embedded video / audio / 3D models, interactive widgets (calculators, plotters, Python REPLs, maps, …), and real-time audience surveys with AI-powered response summarization.
Everything runs from a single Flask server — on your machine for solo use, or
hosted by your university for multiple concurrent lectures. Each presenter
creates a session and works at /s/{code}/; audience survey QR codes
point to URLs under that same session prefix.
Requires Python 3.10+.
pip install -r requirements.txt
python launch.py # production launcher: HTTPS with a self-signed certOpen the https://... URL the launcher prints, then Start a new session
on the welcome screen (or go directly to /s/{code}/ if you already have a
code). Browsers warn about the
self-signed certificate — click Advanced → Proceed. HTTPS is needed so
camera/microphone widgets work on LAN addresses.
For development there is a lighter entry point:
python app.py # ad-hoc HTTPS cert (needs `cryptography`)
python app.py --http # plain HTTP (camera widget disabled on LAN)To try the built-in demo presentation, click the tour button in the bottom-left corner of the app.
Use the upload button (top-right) to load either:
- A plain PDF — every page becomes a slide.
- A presentation ZIP — a PDF plus saved configuration, media, widgets and AI models (see below). The in-app editor can add media/widgets to slides and save the result back out as a ZIP.
presentation.zip
├── slides.pdf # required — the slide deck
├── config/
│ ├── slide-order.json # slide sequence: pdf pages, blank slides, split views, hidden flags
│ ├── s<N>.json # per-slide config: embedded videos/audio/models/widgets + layout
│ ├── annotations.json # saved pen strokes per slide
│ └── widget-states.json # widget state snapshots so widgets resume where they left off
├── assets/ video/ models/ … # media referenced by the configs
└── ai/
└── <model>.py # optional survey-summarization models (see below)
Survey widgets generate a QR code that audience members scan to answer from their phones (open text, word cloud, or multiple choice). Responses stream back live over Socket.IO. Open-text responses can be clustered into thematic summaries by an AI model.
Two models ship built in:
claude— uses the Anthropic API (ANTHROPIC_API_KEYenv var, or paste a key in the widget settings; requirespip install anthropic)gpt4o— uses the OpenAI API (OPENAI_API_KEY; requirespip install openai)
Drop a Python file in ai/ (server-wide) or inside a presentation ZIP under
ai/. It must expose:
def summarize(responses: list[str], n: int, api_key: str | None = None):
"""Return a list of exactly n (summary_text, num_respondents) tuples."""Widgets are standalone HTML files in widgets/, rendered in iframes on
slides. Built-ins include a browser, calculator, camera, circuit sandbox,
function plotter, Jupyter-notebook runner, map, MCQ/survey/word-cloud panels,
PDF textbook viewer, Python REPL, shell, timer, and YouTube player.
To add your own, drop an .html file in widgets/. A <script id="widget-schema" type="application/json"> block in the file describes the
editable fields the editor shows for it, and widgets can sync state across
instances by emitting widget_state over Socket.IO.
Beamer+ assumes a cooperative local network (a lecture hall, not the open internet):
- Loading a presentation ZIP executes any
ai/*.pyinside it — only open ZIPs you trust. - Socket.IO accepts connections from any origin so widget iframes and audience devices can connect.
- The TLS certificate is self-signed (
cert.pem/key.pem, regenerated bylaunch.py; delete them to force regeneration).
Do not expose the server to the public internet.
beamer-plus/
├── app.py # dev entry point (`python app.py`)
├── launch.py # production launcher: HTTPS cert + startup info
├── server/ # backend (Flask) — one module per concern
│ ├── core.py # Flask app + Socket.IO instances
│ ├── paths.py # BASE_PATH and derived folders
│ ├── state.py # in-memory shared state (surveys, presentation)
│ ├── ai_models.py # survey-summarization model loading
│ ├── pages.py # HTML pages + PWA files
│ ├── presentation.py # upload, ZIP assets, demo packaging
│ ├── widgets.py # widget serving, embeddability probe
│ ├── surveys.py # survey REST API
│ └── sockets.py # Socket.IO handlers
├── ai/ # built-in survey-summarization models (plugin dir)
├── templates/ # audience-facing pages + presenter shell
├── static/
│ ├── css/ # tokens → base → components/ → features/
│ ├── js/
│ │ ├── main.js # orchestrator: state, navigation, rendering
│ │ ├── core/ # event bus, canvas, modal, widget iframes
│ │ ├── slides/ # navigator, thumbnails, structure, media, spotlight
│ │ ├── annotations/ # toolbar, pen slots, shape tools
│ │ ├── app/ # settings, help, tour, uploader
│ │ └── editor/ # edit mode (overlays, properties, picker, save)
│ └── vendor/ # vendored third-party libraries (no CDN)
├── widgets/ # built-in widget HTML files (one file per widget)
├── demo/ # bundled demo presentation (used by the tour)
├── tests/ # pytest suite for the Flask API
├── manifest.json # PWA manifest (served from /manifest.json)
└── service-worker.js # PWA service worker (served from /)
pip install -r requirements-dev.txt
pytestThird-party frontend libraries are vendored in static/vendor/ so the app
has no CDN dependency and works offline (some widgets still pull large
optional libraries, e.g. Pyodide, from CDNs). The app is an installable PWA;
bump the cache version in service-worker.js when changing static assets.
MIT — see LICENSE.