Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
b58ae9e
Refine generation→repair pipeline: resilience, latency, converging re…
Jun 13, 2026
b426c44
Harden server: catch-all 500 + client-disconnect guard
Jun 14, 2026
c50b05b
Faster + more reliable generation: validator, auto-fixer, library, cache
Maxpeng59 Jun 14, 2026
7bdf7cb
Validator catches off-screen draws (data-vs-pixel coordinate mixups)
Maxpeng59 Jun 14, 2026
6cd20c7
Integrate 50-scene verified STEM library + smarter retrieval
Maxpeng59 Jun 14, 2026
4116ad6
Validator catches motion that drifts off-screen and never loops
Maxpeng59 Jun 15, 2026
f27a2fe
Merge main into refine-generation-repair-pipeline (resolve squash-mer…
Jun 15, 2026
883202f
Catch drifting-subject motion + steer the model away from it up front
Maxpeng59 Jun 15, 2026
191e34f
Pedagogy: teach the mechanism, don't solve the student's problem
Jun 15, 2026
64e8118
Run VisualLM as an app: one-click launcher + native-window option
Jun 15, 2026
8af5deb
Fix Pylance type error: narrow node path before subprocess.run
Jun 15, 2026
82df16d
Build VisualLM as a real macOS app (double-click .app + custom icon)
Jun 16, 2026
ef63a25
App foundation: in-process entry point + frozen-aware asset paths
Jun 16, 2026
ad559a3
Self-contained app: PyInstaller bundle (dist/VisualLM.app)
Jun 16, 2026
c5e5692
Test coverage for the app launcher (launch.py + app_main)
Jun 16, 2026
c01b413
Fix auto-fixer corrupting scenes that declare their own PI/TAU/math-f…
Jun 16, 2026
0a34690
Fix sanitize_code corrupting t/H/ctx used as call or array values
Jun 16, 2026
46434de
Lock sanitize_code unwrap behavior with edge-case tests
Jun 16, 2026
7bfb540
Interactive curriculum demos: classify topic, fill in values, learn t…
Jun 18, 2026
ef19592
Tutor: step-by-step problem solver + robust LaTeX cleanup
Maxpeng59 Jun 18, 2026
7b5b9ef
Add 200 interactive Algebra 1 / Algebra 2 / Precalculus demos
Maxpeng59 Jun 23, 2026
ee11e08
Tutor: make the step-by-step solver beginner-proof
Maxpeng59 Jun 24, 2026
ff53afb
Correctness audit: fix 11 demos with wrong math
Maxpeng59 Jun 24, 2026
7d069c1
Fix Docker image: ship the curriculum demos (and future physics) too
Maxpeng59 Jun 24, 2026
3f1e695
Performance: smoother animations + lighter web rendering
Maxpeng59 Jun 24, 2026
5a08c4b
Add 120 interactive physics demos (mechanics → modern)
Maxpeng59 Jun 24, 2026
8c6e3d3
Docs: custom-domain (VisualLM.com) deploy steps + no-key note
Maxpeng59 Jun 24, 2026
136f9da
Physics correctness audit: fix 9 demos with wrong physics
Maxpeng59 Jun 24, 2026
ee654fd
Complete physics coverage: add standing waves in pipes/strings (121/121)
Maxpeng59 Jun 24, 2026
ebed0cf
Add chemistry: 3D molecule structures + equation balancing
Jun 25, 2026
fe20b50
Apply correctness-audit fixes to curriculum libraries
Jun 25, 2026
35830c9
render.yaml: deploy the parameterized-demos branch (full feature set)
Jun 25, 2026
bcbad71
Add browser edition (static, client-side) + GitHub Pages
Jun 25, 2026
8aad3a1
Add 87 Geometry + front-half Calculus demos
Jun 25, 2026
2cccf87
Add step-by-step worked-solution slides (triangle solver)
Jun 26, 2026
aa352fa
Browser edition: lazy-load demos (fix upfront-load lag)
Jun 26, 2026
e414a1c
Add 182 AP Physics + AP Chemistry demos
Jun 26, 2026
3a2a882
Route classic equations correctly (E=mc^2, F=ma, PV=nRT, ...)
Jun 26, 2026
7fc40a9
Address evaluation: equation solver, routing, mobile, tutor regex, badge
Jun 26, 2026
1aed966
Declutter the browser-edition help card
Jun 27, 2026
6c9fa14
Code-only AI, canvas light theme, pro value controls, desktop installer
Jun 29, 2026
566cf70
Resizable AI Tutor + app-shell layout; allow file:// previews
Jun 30, 2026
0b79492
Study Packs (DLC) + resizable columns + working Quick Questions
Jul 1, 2026
ad4f433
Package DLCs as .zip folders (import + export)
Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 6 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
"runtimeExecutable": "python3",
"runtimeArgs": ["main.py"],
"port": 4173
},
{
"name": "visuallm-web",
"runtimeExecutable": "python3",
"runtimeArgs": ["-m", "http.server", "4179", "--directory", "web"],
"port": 4179
}
]
}
20 changes: 20 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Copy this file to ".env" (same folder) and fill in your key(s).
# The launcher (launch.py / VisualLM.command / desktop.py) loads it automatically.
# Format is one KEY=VALUE per line. Anything already set in your shell wins.

# Best-quality animations (recommended). Get a key at https://console.anthropic.com
ANTHROPIC_API_KEY=sk-ant-...

# Optional alternatives / fallbacks (used only if Anthropic isn't set):
# OPENAI_API_KEY=sk-...
# GEMINI_API_KEY=...

# No cloud key? VisualLM still runs entirely on its built-in pure-code library —
# 3D chemistry, balanced reactions, the step-by-step solver, and the interactive
# demos all work offline. A key only adds AI generation for free-form prompts.

# Optional: pin the port (default 4173).
# VISUALLM_PORT=4173

# Optional: require a shared access code to generate (for a published instance).
# VISUALLM_ACCESS_CODE=letmein
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,16 @@ __pycache__/
.env
.venv/
venv/

# macOS app build artifacts (run ./make_app.sh to regenerate)
VisualLM.app/
VisualLM.iconset/
VisualLM.png
VisualLM.icns
build/
dist/
*.spec.bak

# Runtime user/AI DLC packs (not source)
data/dlc_custom/
web/data/dlc_custom/
7 changes: 6 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY main.py scene_library.py scene_library_generated.json validate_scene.js \
# Server, the libraries (STEM scenes + curriculum demos + chemistry), the
# generated data (*_generated.json — scene, demo, physics, chemistry), the
# validator, and the UI. The glob keeps new generated data files shipping
# automatically.
COPY main.py scene_library.py demo_library.py chemistry.py validate_scene.js \
*_generated.json \
index.html app.js sandbox-worker.js styles.css ./

# Cloud platforms inject PORT; main.py binds 0.0.0.0 automatically when set.
Expand Down
110 changes: 92 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,36 @@ or 3D explanation**. Instead of picking from a handful of fixed demos, the AI
*writes the animation itself* — generating JavaScript that runs in a locked-down
sandbox in your browser — so it can visualize essentially any topic.

## Two editions

VisualLM ships in two flavors of the same project:

| | **Browser edition** (`web/`) | **Desktop / server edition** (repo root) |
|---|---|---|
| Runs | 100% in the browser — **no server, no install, no API key** | Local Python server (or packaged desktop app) |
| Host | **GitHub Pages** (static) — instant, free, shareable link | Your machine, or any server host (Render, etc.) |
| Chemistry: 3D molecules + equation balancing | ✅ | ✅ |
| 333 interactive curriculum demos + 50 curated scenes | ✅ | ✅ |
| Free-form AI generation (type *any* idea) | ❌ (needs a model + key) | ✅ (Claude ▸ ChatGPT ▸ Gemini) |
| AI tutor chat | ❌ | ✅ |

The browser edition is a faithful client-side port: the chemistry engine and the
demo/scene matcher are reimplemented in JavaScript (`web/js/`) with **verified
parity** against the Python — same molecules, same exact-integer balancing, same
scene routing. It's the fastest way to share VisualLM (just a link); the desktop
edition adds the open-ended AI generation that needs server-side keys.

- **Browser edition (live):** https://maxpeng59.github.io/VisualLM/
- **Desktop app:** `python3 launch.py` (browser app-window) or `python3 desktop.py`
(native window), or build a standalone app with `./build_app.sh`.
- **Server deploy (full app):** see [Deploy](#deploy) below (Render blueprint).

## How it works

```
prompt ──▶ /api/visualize ──▶ AI writes scene code ──▶ sandbox renders it
│ │
(Claude ▸ ChatGPT ▸ Gemini ▸ Ollama)
(Claude ▸ ChatGPT ▸ Gemini)
error? └─▶ /api/repair ──┐
fixed code ◀───────────────┘
Expand Down Expand Up @@ -97,25 +121,50 @@ Generation tries providers in this order — the first one with a key wins:
| **Claude** | `ANTHROPIC_API_KEY` (+ `pip install anthropic`) | Primary generator — best quality |
| **ChatGPT** | `OPENAI_API_KEY` | Cloud fallback generator |
| **Gemini** | `GEMINI_API_KEY` | Cloud fallback generator |
| **Ollama** | run `ollama serve` locally | Offline fallback + default tutor |

All cloud providers are called over plain REST — no extra SDKs required.
All cloud providers are called over plain REST — no extra SDKs required. **The AI
relies only on code — there is no local model app.** With **no key at all**,
VisualLM still runs entirely on its built-in pure-code library: 3D chemistry,
balanced reactions, the step-by-step solver, and every interactive demo work
offline. A key only adds free-form "type any idea" generation.

## Run locally
## Run it (as an app)

```bash
# 1. (Recommended) enable at least one cloud generator
pip install -r requirements.txt # only needed for Claude
export ANTHROPIC_API_KEY=sk-ant-... # and/or OPENAI_API_KEY / GEMINI_API_KEY
The easy way — after a one-time setup, **no terminal needed**:

# 2. (Optional) local fallback + tutor: run Ollama with a model
ollama pull qwen2.5:7b
1. **Install dependencies (once).** Run **`./install.sh`** (macOS/Linux) or
**`install.bat`** (Windows). It creates a `.venv` and downloads everything —
the Claude client and the native-window backend (pywebview). Then, optionally,
add a key to `.env`: `ANTHROPIC_API_KEY=sk-ant-...`. **No key? Skip it** —
chemistry, reactions, the solver, and the demos all run offline; a key only
adds free-form AI generation.
2. **Launch it.**
- **macOS:** double-click **`VisualLM.command`** in Finder (first time: right-click → Open to clear the macOS warning).
- **Any OS:** `python3 launch.py`

# 3. Start the app
python3 main.py
```
The launcher loads `.env`, starts the server on a free port, waits until it's
healthy, and opens VisualLM in its own app-style window. Keep that window open;
Ctrl+C (or closing it) stops everything.

**Prefer a true native window** (no browser chrome at all)? `pip install pywebview`
then `python3 desktop.py`.

Then open <http://127.0.0.1:4173>.
**Want a real, self-contained app** (bundles Python — no terminal, no repo needed
to run)? `./build_app.sh` produces **`dist/VisualLM.app`** via PyInstaller; move it
to /Applications and double-click. It runs the server in-process (`app_main.py`)
and serves bundled assets. For Claude inside the bundle, `pip install anthropic`
before building; for a native window, `pip install pywebview` before building.
The packaged app reads a `.env` placed next to `VisualLM.app` or in `~/.visuallm/`.

### Plain manual start

Equivalent to what the launcher does, if you'd rather drive it yourself:

```bash
./install.sh # one-time: creates .venv + installs deps
export ANTHROPIC_API_KEY=sk-ant-... # optional — and/or OPENAI_API_KEY / GEMINI_API_KEY
python3 main.py # then open http://127.0.0.1:4173
```

## Deploy to the web

Expand All @@ -134,6 +183,23 @@ binds `0.0.0.0` automatically when it's set.
strangers can't burn your API credits. Per-IP rate limiting is on by
default (`VISUALLM_RATE_LIMIT`, 10/min via render.yaml).

It works with **no API key** — all curriculum demos and STEM library scenes are
served from the bundled libraries (no model calls), so the site is fully useful
out of the box. A key only enables free-form "type any idea" generation.

### Custom domain (e.g. www.VisualLM.com)

The app is origin-agnostic (all requests are relative paths), so a custom domain
needs only DNS — no code changes:

1. Own the domain (buy `VisualLM.com` from any registrar if you don't).
2. In your Render service → **Settings → Custom Domains** → add `www.visuallm.com`
(and `visuallm.com`). Render shows the exact DNS records to create.
3. At your registrar's DNS panel, add what Render gives you — typically:
- `www` → **CNAME** → `your-app.onrender.com`
- root `@` → Render's **A record** (or an ALIAS/ANAME → `your-app.onrender.com`)
4. Wait for DNS to propagate (minutes–hours); Render auto-provisions free HTTPS.

### Any other Docker host (Fly.io, Railway, Cloud Run, a VPS…)

```bash
Expand All @@ -153,9 +219,12 @@ HTTP round-trips against every endpoint (with stubbed generators).

## Files

- `main.py` — web server, the four AI bridges (Claude/OpenAI/Gemini/Ollama),
the validate→auto-fix→repair pipeline, library retrieval + cache, rate
limiting, and the system prompt that defines the rendering contract.
- `main.py` — web server, the three cloud AI bridges (Claude/OpenAI/Gemini —
no local model app), the validate→auto-fix→repair pipeline, library retrieval
+ cache, rate limiting, and the system prompt that defines the rendering
contract.
- `install.sh` / `install.bat` — one-shot dependency installers (create a
`.venv`, download the Claude client + the pywebview native-window backend).
- `sandbox-worker.js` — the sandboxed Web Worker that runs generated code on
an OffscreenCanvas, the `H` helper library, and the software 3D pipeline.
- `validate_scene.js` — headless server-side scene validator (hardened Node
Expand All @@ -165,6 +234,12 @@ HTTP round-trips against every endpoint (with stubbed generators).
- `app.js` — orchestration: sandbox runner, generate→run→repair loop, orbit
controls, playback, tutor chat, resources, status.
- `index.html` / `styles.css` — app structure and visual design.
- `launch.py` / `VisualLM.command` / `desktop.py` — run VisualLM as an app:
the one-click launcher (loads `.env`, starts the server on a free port, opens
an app-style window), the macOS double-click wrapper, and the optional
native-window version (pywebview). `.env.example` is the key template.
- `stem-viz-plugin/` — the scene-generation capability packaged as a portable
Claude skill + plugin (drop into any AI); see its own README.
- `Dockerfile` / `render.yaml` — production deployment (Docker image bundles
Node for the validator).
- `tests/` — stdlib-only test suite (covers the validator, auto-fixer,
Expand All @@ -188,7 +263,6 @@ When `VISUALLM_ACCESS_CODE` is set, all POST/DELETE endpoints require the
- `ANTHROPIC_API_KEY` / `ANTHROPIC_MODEL` (default `claude-opus-4-8`)
- `OPENAI_API_KEY` / `OPENAI_MODEL` (default `gpt-4o`) / `OPENAI_BASE_URL`
- `GEMINI_API_KEY` / `GEMINI_MODEL` (default `gemini-2.0-flash`) / `GEMINI_BASE_URL`
- `OLLAMA_URL` (default `http://127.0.0.1:11434`) / `OLLAMA_MODEL`
- `VISUALLM_ACCESS_CODE` — require a shared code for generation (recommended
on public deployments).
- `VISUALLM_RATE_LIMIT` — requests/min per IP (default 20; 0 disables).
Expand Down
133 changes: 133 additions & 0 deletions USER_EVALUATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# VisualLM — User Evaluation Form

> A hands-on evaluation done by using the app as a student would, then filling out
> this form. Each finding is grounded in something actually typed/clicked, with a
> concrete fix and a code reference where one applies.

---

## 1. Session details

| Field | Value |
|---|---|
| Tester role | Student (first-time-ish user) |
| Edition tested | Browser edition (client-side, no server/key) |
| URL / build | `http://localhost:4179` (web/, `parameterized-demos` branch) |
| Viewports | Desktop (1280×800) + Mobile (375×812) |
| Date | 2026-06-26 |
| Console errors seen | None |

## 2. What I actually tested

`2H2 + O2 -> 2H2O` (balance) · `x^2 - 5x + 6 = 0` (quadratic) ·
`triangle with a=5, b=7, C=40 degrees` (solver) · `benzene` (3D molecule) ·
"Surprise me" · `how does a transformer neural network work` (out-of-scope) ·
"Solve it step by step" button · mobile layout.

## 3. Overall impression

**★★★☆☆ — Strong, correct, instant content; weak bridge from a user's words to it.**

The deterministic engine (triangle solver, molecules, balancing, slider demos) is
genuinely good and fast. The weak link is routing: a plain typed equation or a
casual question frequently lands on a confidently-wrong demo, and one headline
feature ("step-by-step solver") promises more than the browser edition delivers.

## 4. What works well (keep it)

- [x] **3D molecules** (`benzene`) — ball-and-stick, CPK colors, double bonds as
parallel sticks, drag-to-orbit / scroll-zoom. Excellent.
- [x] **Triangle solver** — auto-advancing "Slide 2 of 4", Law of Cosines worked
through *your* numbers, triangle drawn to scale. Best feature in the app.
- [x] **Chemistry balancing** — the atom-balance tally (H 4=4 ✓, O 2=2) makes
conservation visible.
- [x] **Instant render + live sliders**, clean dark/light theming, zero console
errors, and a populated demo on landing.

## 5. Findings by severity

### P0 — Topic routing confidently serves the wrong demo

The matcher is keyword-only, with no sense of equation *structure* or common-word
stopwords. The 2.0 "serve confidently" threshold lets false positives through with
**no "closest match" caveat**.

| Typed | Got | Cause |
|---|---|---|
| `x^2 - 5x + 6 = 0` | **0/0 limit by factoring** (Calculus, score 3) | "quadratic" never spelled out; correct demos (Quadratic formula, Completing the square) sit at 2.5 and lose |
| `how does a transformer neural network work` | **Work done by a force** (score 2) | everyday word "work" |
| `explain how vaccines work` | **Work done by a force** (score 2) | same |
| `power dynamics in a relationship` | **Electrical power P=V·I** (score 2) | "power" |

Verified via the scorer: adding the literal word "quadratic" lifts the right demo
from 2.5 → 4.5. The content exists; the engine just can't read a typed equation's shape.

**Fixes (in order of payoff):**
1. **Structural recognition of typed equations** — you already do this for triangles
in `web/js/solver.js`. Extend it: degree-2 polynomial `= 0` → quadratic;
`ax+b=0` → linear; a single unknown with `=` → equation-solve. Route those
*before* keyword scoring.
2. **Stopword list** in `web/js/matching.js` so "work", "power", "field",
"function", "how", "of", "real" don't carry full topic weight.
3. **Calibrate confidence + a "did you mean…?" affordance.** On a marginal/tied top
score, offer the runner-up as a chip instead of silently committing. Today the
honest "free-form AI is in the desktop app" help card almost never fires, because
nearly any English sentence keyword-matches *something*.

### P0 — "Step-by-step solver" over-promises in the browser edition

Header advertises "…**step-by-step solver**…" and the most prominent tutor CTA is
**"Solve it step by step."** But in the browser edition:

- The local solver (`web/js/solver.js`) only handles **triangles**.
- The button just **fills the chat box** (no submit, no scroll), and that box is
below the fold — so to the user *nothing happens*. (`web/app.js:1018`)
- If they then click "Ask", `/api/chat` **always** returns "the AI tutor runs in
the desktop app." (`web/js/browser-backend.js:67`)

So the headline CTA is a dead end for anything but triangles.

**Fix:** relabel honestly ("Triangle solver" / "Worked solutions"), or have the
button run the local solver for supported inputs and **disable/relabel** itself
when no solver applies.

### P1 — Mobile buries the primary action

At 375px the stack is Explanation → Canvas → Prompt. Measured positions: canvas at
**864px**, prompt box + Visualize at **~1580–1800px** — two-plus screens below the
fold, behind a wall of text about the default scene. A first-time mobile user can't
find where to type.

**Fix:** on mobile, reorder to **Prompt → Canvas → Explanation**.

### P1 — Two tutor markdown bugs mangle the text

- **"left"/"right" deleted from prose.** `web/app.js:93`
`s.replace(/\b(left|right)\b/g, "")` is meant to strip LaTeX `\left`/`\right` but
nukes the English words. "Reactants **(left)** … products **(right)**" rendered as
"Reactants **()** … **()**". Fix: require the backslash — `/\\(left|right)\b/g`.
- **Multiplication `*` rendered as italics.** `web/app.js:109`'s emphasis regex
matches `* m *`, so `gamma * m * c^2` renders with "m" italicized and the asterisks
eaten. Per CommonMark, `* x *` (spaces inside) isn't emphasis — tighten the regex
to not match space-flanked `*`.

### P2 — Polish

- **Overlapping badges** on the 3D-molecule canvas: the green status pill collides
with the "drag to orbit · scroll to zoom · double-click to reset" hint at bottom-left.
- **Desktop dead space**: the asymmetric grid leaves a tall empty area below the
short right column; aligning panel heights (or moving the tutor up) tightens it.

## 6. Recommended next steps

| Priority | Item | Effort | Risk |
|---|---|---|---|
| 1 | Structural equation recognition + stopwords (P0 routing) | Medium | Low–med |
| 2 | Honest labeling / wiring of the "step-by-step solver" (P0) | Small | Low |
| 3 | Mobile reorder Prompt → Canvas → Explanation (P1) | Small | Low |
| 4 | Two tutor regex fixes (P1) | Tiny | Low |
| 5 | Badge overlap + desktop dead space (P2) | Tiny | Low |

**Biggest lever:** P0 #1. The single highest-impact change is teaching the router
to read a typed equation's structure, because that's the input a student is most
likely to paste.
Loading