Upload a photo of a mathematical expression. Get a structured, interactive breakdown — powered by Claude Vision.
Requirements: Docker Desktop + an Anthropic API key
git clone https://github.com/vilhelmhilding/resolvent
cd resolvent
./setup.shThe script asks for your API key, generates a secret, starts the containers, and opens http://localhost:3000 in your browser. That's it.
Each sub-expression is identified, color-coded, individually visualized, and explained in plain language. A built-in AI tutor answers follow-up questions with full awareness of the analysis. Analyses can be saved to a personal dashboard and revisited with full chat context intact.
Complete tech stack rewrite
| v1 | v2 | |
|---|---|---|
| Backend | Flask (sync, 2 routes) | FastAPI (async, 9+ routes) |
| Frontend | Vanilla JS + Jinja2 HTML templates | Next.js 15, TypeScript (strict), React 19 |
| State management | Global JS object in ui.js |
Zustand 5 |
| Rendering | Server-side HTML fragment (html_renderer.py) |
Client-side React components |
| Styling | Single styles.css |
Component CSS in globals.css, typed throughout |
| Canvas renderers | Monolithic functions in ui.js |
Modular TypeScript modules per viz type |
| Package management | requirements.txt |
pyproject.toml (hatchling, optional test extras) |
| Dev server | python app.py (port 5001) |
make dev via pm2 (backend 8000, frontend 3000) |
| AI model | claude-sonnet-4-5 | claude-sonnet-4-6 |
Analysis pipeline
- 4-stage async streaming — results stream to the client as each stage completes. The identified equation appears mid-load, before the full analysis is ready.
- LaTeX input — paste LaTeX directly instead of uploading a photo.
- Step-by-step derivation — collapsible breakdown with numbered LaTeX steps and bilingual notes.
- Insight + animation effect per sub-expression — each part now shows a key non-obvious mathematical insight and a concrete description of what visually changes during animation.
- Prompt caching —
cache_control: ephemeralon all prompts reduces latency and cost. - Viz validation with auto-retry — stage 2a automatically retries if the model returns an all-NaN or flat-constant expression.
- OCR correction — common transcription errors fixed automatically (
\int_0^{-}^{\infty}→\int_{0^-}^{\infty}, etc.). - Robust JSON repair — bare LaTeX backslashes escaped automatically; preamble text and trailing garbage tolerated.
Accounts and dashboard
- User accounts — register/login with bcrypt-hashed passwords and in-memory sessions (auto-expire on server restart).
- Save and restore — full analysis sessions including chat history saved to SQLite, accessible from a personal dashboard.
- Auto-save — chat history saved automatically 1.5 s after the last assistant message.
- Encrypted storage — blobs are gzip-compressed and AES-128 encrypted (Fernet) at rest. 30-day TTL.
Infrastructure
- Docker Compose — one command deploys the full stack (
docker compose up -d) with a persistent SQLite volume, accessible from any device on the local network. - CI/CD — GitHub Actions: full test suite + Docker build smoke test on every push and pull request.
- 90 automated tests — 51 backend (pytest-asyncio) + 39 frontend (vitest).
UI
- Infinity-symbol logo — vectorized SVG logo next to the "Resolvent" wordmark, clickable to navigate home.
- Mobile-first layout — hamburger menu, sticky header, proportional canvas scaling via CSS
aspectRatio, no scroll hijack during 3D drag, iOS input zoom fix. - Smooth auth modal — page content blurs behind the login popup; spring-curve entry animation.
- Progressive loading messages — two sets of animated status messages (pre- and post-identification).
- Upload a photo of any mathematical expression — handwritten or printed — or paste LaTeX directly
- Crop to the relevant region (image mode)
- Receive a full analysis:
- LaTeX rendering of the full expression
- Plain-language summary, intuition, and (when applicable) an example box naming the concrete values chosen for the visualization
- Each sub-expression labeled, color-coded, and explained
- Interactive 2D and 3D canvas visualizations with a play button for real-time animation
- Step-by-step derivation (collapsible)
- An AI chat tutor that knows everything about the current analysis
- Save analyses to your personal dashboard (requires account)
- Export:
- Full analysis as a PDF (via the browser print dialog)
- Chat conversation as a
.txtfile
Handles any area of mathematics: calculus, complex analysis, PDEs, Fourier analysis, probability, statistics, linear algebra, combinatorics, number theory, and more.
Analysis proceeds in four async stages:
| Stage | What it does |
|---|---|
| Stage 1 | Claude Vision identifies the expression, extracts LaTeX, sub-parts, animation variable |
| Stage 2a | Produces summary, intuition, example, main visualization config |
| Stage 2b | Produces per-part names, explanations, insights, and per-part visualization configs |
| Stage 2c | Produces step-by-step derivation |
Stage 2b and 2c run in parallel via asyncio.gather. Intermediate results stream to the frontend via Server-Sent Events so the UI progressively reveals content as each stage completes.
For abstract or general expressions (e.g. a Fourier transform with no concrete input function), the model chooses a pedagogically ideal standard-case scenario — the classic textbook example that best reveals the phenomenon. All sub-part visualizations use the same chosen values so the entire page tells one consistent story. The example box under the intuition text names every chosen value.
Each expression receives the most appropriate visualization, rendered on an HTML5 Canvas. All types have both a 2D and an interactive 3D view:
| Type | Used for | Animates via |
|---|---|---|
function_plot |
Real-valued functions of one or two variables | t (time), k (parameter) |
parametric_curve |
Contours and paths in the plane | static — t is the curve parameter |
unit_circle |
Rotating phasors, complex exponentials | k (discrete steps) |
scalar |
Real discrete sequences and partial sums | k |
spiral_sum |
Complex partial sums (Fourier, power series) | k |
complex_point |
Complex numbers and k-indexed complex trajectories | k |
Users can create an account (username + password) and save full analysis sessions including chat history. Saved analyses are listed in a personal dashboard, and any saved session can be restored exactly — including all chat context.
- Passwords are bcrypt-hashed
- Analysis data is gzip-compressed and AES-128 encrypted (Fernet) before storage
- Sessions are in-memory only — restarting the server logs all users out automatically
- Saved analyses expire after 30 days (lazily purged on dashboard load)
| Layer | Technology |
|---|---|
| Framework | FastAPI 0.115 (async) |
| AI | Anthropic SDK (claude-sonnet-4-6), prompt caching |
| Database | SQLite via aiosqlite |
| Auth | bcrypt passwords, in-memory sessions |
| Encryption | cryptography (Fernet / AES-128-CBC + HMAC-SHA256) |
| Server | Uvicorn |
| Layer | Technology |
|---|---|
| Framework | Next.js 15 (App Router, Turbopack) |
| Language | TypeScript (strict) |
| State | Zustand |
| Math rendering | MathJax 3 |
| Canvas | HTML5 Canvas (custom 2D + 3D renderers) |
The recommended way to run Resolvent — one command, accessible from any device on your local network.
Requirements: Docker, Docker Compose, an Anthropic API key
git clone https://github.com/vilhelmhilding/resolvent
cd resolvent
cp backend/.env.example backend/.envOpen backend/.env and fill in:
ANTHROPIC_API_KEY=sk-ant-...
SECRET_KEY=any-long-random-string
docker compose up -dBoth services build and start. The frontend waits for the backend health check before coming up.
| From | URL |
|---|---|
| Same machine | http://localhost:3000 |
| Any device on the network | http://<host-ip>:3000 |
To find your IP:
# macOS
ipconfig getifaddr en0
# Linux
hostname -I | awk '{print $1}'SQLite data is stored in a named Docker volume (db_data) and survives container restarts.
docker compose down # stop everything
docker compose logs -f # tail logs from both services
docker compose restart # restart without rebuilding
docker compose up -d --build # rebuild images (after code changes)Requirements: Python 3.11+, Node.js 20+, an Anthropic API key, mamba/conda
git clone https://github.com/vilhelmhilding/resolvent
cd resolventcd backend
mamba create -n main python=3.12
mamba activate main
pip install -e .
cp .env.example .env # fill in ANTHROPIC_API_KEY and SECRET_KEYcd frontend
npm installnpm install -g pm2 # once — runs processes in background
make dev # starts both backend and frontend
make stop # stops both
make logs # tail pm2 logs# Backend (pytest)
cd backend && python -m pytest -v
# Frontend (vitest)
cd frontend && npm testCI runs both suites automatically on every push and pull request.
All configuration is via environment variables in backend/.env:
| Variable | Default | Description |
|---|---|---|
ANTHROPIC_API_KEY |
— | Your Anthropic API key |
ANTHROPIC_MODEL |
claude-sonnet-4-6 |
Model for all API calls |
SECRET_KEY |
— | Secret for session signing and database encryption |
DB_PATH |
/data/resolvent.db |
SQLite database path (Docker) |
DEBUG |
false |
Enable Uvicorn reload |
HOST |
0.0.0.0 |
Bind address |
PORT |
8000 |
Bind port |
resolvent/
├── docker-compose.yml
├── Makefile dev / stop / logs / restart
├── ecosystem.config.js pm2 process config
├── backend/
│ ├── main.py FastAPI app, CORS, lifespan, /health
│ ├── config.py Pydantic settings (reads .env)
│ ├── pyproject.toml
│ ├── Dockerfile
│ ├── .env.example
│ ├── api/
│ │ ├── analyze.py SSE streaming — image analysis pipeline
│ │ ├── analyze_latex.py SSE streaming — LaTeX analysis pipeline
│ │ ├── chat.py Non-streaming chat endpoint
│ │ ├── auth.py Register, login, logout, /me
│ │ └── analyses.py Save, list, load, delete analyses
│ ├── core/
│ │ ├── equation_extractor.py 4-stage extraction with prompt + JSON parsing
│ │ ├── anthropic_client.py Async Anthropic SDK wrapper, prompt caching
│ │ ├── database.py SQLite schema, Fernet encryption, compression
│ │ ├── visualization_engine.py Animation variable detection and slider config
│ │ └── color_registry.py Per-part color assignment
│ └── tests/
│ ├── conftest.py
│ ├── test_auth.py
│ ├── test_analyses.py
│ ├── test_database.py
│ ├── test_visualization.py
│ └── test_extractor.py
├── frontend/
│ ├── Dockerfile
│ ├── next.config.ts
│ ├── tsconfig.json
│ ├── package.json
│ ├── vitest.config.ts
│ └── src/
│ ├── app/
│ │ ├── layout.tsx Root layout, MathJax config, viewport
│ │ ├── page.tsx Entry point
│ │ ├── globals.css
│ │ └── api/ Next.js proxy routes to backend
│ ├── components/
│ │ ├── ClientApp.tsx Top-level state machine
│ │ ├── TopBar.tsx Logo, 2D/3D toggle, language toggle, auth controls
│ │ ├── analysis/ AnalysisResult, AnimControls, ChatSection, DerivationSteps, PartCard
│ │ ├── auth/ AuthModal
│ │ ├── dashboard/ Dashboard
│ │ ├── upload/ UploadZone, ImageCropper
│ │ └── viz/ VizCanvas
│ ├── hooks/ useAnimation, useCamera3D, useMathJax
│ ├── lib/
│ │ ├── api.ts SSE streaming client
│ │ ├── expr-compiler.ts JS expression compiler for visualization
│ │ ├── math-ext.ts factorial, gamma, math extensions
│ │ └── renderers/ 2D and 3D canvas renderers per viz type
│ ├── store/ Zustand store
│ └── types/ TypeScript types
└── demos/
├── demo1.gif
└── demo2.gif
MIT © Vilhelm Hilding

