feat: local web control panel (openadapt panel)#1008
Open
tamiso999 wants to merge 12 commits into
Open
Conversation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A bare `src` ignored any src/ dir repo-wide, which silently dropped the Miso React UI from a commit and would drop the panel frontend. Scope to /src/ (the editable-install location) only. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Scaffold the control panel (blueprint P0): a local FastAPI web UI over the CLI, shipped as the in-repo sibling `openadapt-panel` and wired via a new `openadapt panel` command. - openadapt-panel/: FastAPI app factory, run_panel launcher (loopback only), and a read-only Dashboard/System page. system.py mirrors doctor — it inspects ecosystem state via importlib.metadata/find_spec and imports no sibling, so the app boots headless with zero siblings installed. - cli.py: `openadapt panel` lazy-imports the sibling; uses find_spec to tell "not installed" from "installed but broken" so a phantom symbol surfaces instead of being masked (the OpenAdaptAI#999 bug class). - pyproject.toml: new `panel` extra, added to the `all` bundle. - tests/test_cli_smoke.py: seam contracts — panel calls run_panel with kwargs that exist in its signature, and system_report imports no sibling packages. - CI installs ./openadapt-panel from the local path so those seam tests run rather than skip. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ettings) Turns the P0 shell into a usable panel: FastAPI backend + vanilla-JS SPA with six pages, background jobs, SSE log streaming, and per-session token auth. Backend (openadapt-panel), all sibling imports lazy inside handlers: - routes/captures: list (openadapt_capture.Capture), viewer (create_html), record (CLI subprocess job). - routes/train: start (CLI subprocess), status (training_log.json), stop (STOP_TRAINING sentinel) — same seams as `openadapt train`. - routes/evals: mock + live/checkpoint runs (openadapt_evals) in worker-thread jobs returning structured metrics. - routes/models: browse checkpoints on disk. routes/settings: view/edit a curated config subset, persisted to .env (secrets never echoed back). - jobs.py: JobManager for subprocess + thread jobs; stop via interrupt signal (capture) or sentinel (train). auth.py: loopback token dependency. - deps.ensure_installed: find_spec-based 503 for "not installed" that still lets an installed-but-broken symbol surface as a real error (OpenAdaptAI#999 discipline). Frontend: SPA (index.html/app.js/styles.css) served from the wheel; reads the launch-URL token, streams job logs over SSE. Tests (tests/test_panel_seams.py): behavior tests (auth, routes, settings round-trip, 503-when-absent) run everywhere; seam contracts against sibling signatures skip locally, run in CI. CI installs ./openadapt-panel[dev]. Verified headless: full test suite green, ruff clean, live uvicorn smoke test (auth 401/200, static assets, 503 path). Live capture/train/eval need a machine with the siblings + GPU/display. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Frontend is now a React app under openadapt-panel/frontend/ (six pages +
shared JobLog SSE component + api client), unchanged in behavior. Vite builds
into openadapt_panel/static/ (base "/static/", matching the FastAPI mount); the
built index.html + assets/ are committed and bundled into the wheel, so pip
installs / CI / end-users never need Node.
- frontend/: package.json, vite.config.js (outDir ../openadapt_panel/static,
emptyOutDir, base /static/), src/{main,App,api}.jsx + pages/ + components/.
- static/: hand-written app.js/styles.css removed; Vite output (index.html +
assets/index-*.{js,css}) committed in their place.
- node_modules/ and dist/ gitignored via frontend/.gitignore (root /src/ fix
already keeps frontend/src/ tracked).
Verified: npm run build clean (39 modules), wheel contains static/assets/*,
live uvicorn serves index + both hashed assets (200) with auth intact, and the
Python suite stays green (15 passed / 9 skipped).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… panel Testing the eval flow against the installed openadapt-evals (0.87.2) exposed two real seam bugs, in both the meta-package CLI and the panel that mirrors it: - WAALiveAdapter no longer takes server_url directly; the URL lives on WAALiveConfig. `openadapt eval run --server ...` (and the panel's live eval) called WAALiveAdapter(server_url=...) → TypeError. Now WAALiveAdapter(config=WAALiveConfig(server_url=...)). - compute_metrics reports the task count as 'num_tasks'; cli.py's eval run read metrics['total_tasks'] → KeyError at the end of a live run. Now tolerant of either key; the panel UI shows num_tasks (falling back to total_tasks). Tests (tests/test_panel_seams.py): the eval seam contract now encodes the real API (WAALiveAdapter takes config; WAALiveConfig takes server_url), and a new test runs a tiny real mock eval and asserts the metric keys the UI/CLI read — which is what would have caught the total_tasks KeyError. Verified end-to-end: live mock-eval flow through the panel (POST /api/eval/mock → thread job → SSE log stream → real metrics) succeeds; suite 18 passed / 7 skipped (eval seam + metrics-keys tests now run, having installed openadapt-evals locally). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Running eval/train from the repo root writes benchmark_results/, benchmark_live.json, and training_output/ into the tree; ignore them so they aren't accidentally committed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the panel feel like a clickable desktop app without any native toolchain
(no Rust/Node) — pure Python via pystray/pillow.
- openadapt_panel/desktop.py:
- run_app(): serve in a background thread, open the browser, and sit in the
system tray with Open/Quit. Falls back to the plain server if tray deps are
missing.
- install_desktop_shortcut(): create a Desktop .lnk that runs the tray app via
pythonw (no console window), with a generated .ico. Windows-only for now;
raises NotImplementedError elsewhere with guidance.
- render_icon(): simple generated app/tray icon (no binary asset in git).
- cli.py: `openadapt panel --app` (tray mode) and `--install-shortcut`. Default
`panel` behavior (and the run_panel seam) unchanged.
- pyproject: pystray + pillow added to openadapt-panel deps.
- tests: desktop helpers exposed + render_icon; README documents the flow.
Verified on Windows: --install-shortcut writes ~/Desktop/OpenAdapt Panel.lnk
(Target=pythonw, Args=`panel --app`, custom icon); launching exactly that
command serves (health 200) and stays resident.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New Frontend CI workflow (path-filtered to openadapt-panel/frontend/** and the built static/): sets up Node, npm ci + npm run build, then fails if the committed openadapt_panel/static/ differs from a fresh build — catching edits to frontend/src/ that weren't rebuilt and committed. Keeps the Python CI Node-free. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Double-clicking the Desktop shortcut did nothing: it runs via pythonw, where sys.stdout/stderr are None, and uvicorn's logging setup calls sys.stdout.isatty() → AttributeError at Config construction, killing the process before the server or tray ever started. (Earlier tests missed it by redirecting stdout to a file, which makes it non-None.) - Point missing std streams at ~/.openadapt/panel-console.log so uvicorn (and print/pystray) are safe under pythonw. - Log startup + errors to ~/.openadapt/panel.log so failures aren't invisible. - Write the launch URL (with token) to ~/.openadapt/panel-url.txt — under pythonw the token is otherwise never shown, so a blocked auto-open left no way in. - Open the browser only after polling the server is ready (fixes the race that opened the browser to a "can't reach this page" error), and open more robustly (webbrowser then os.startfile). Best-effort tray startup notification. Verified via pythonw exactly as the shortcut runs: health 200, resident, log + url file written. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
openadapt-capture's recorder module imports matplotlib (via its plotting module) but doesn't declare it, so on a fresh install `from openadapt_capture import Recorder` yields Recorder = None (the package guards the import). The CLI then called Recorder(...) -> "NoneType is not callable", and the panel's record button failed the same way. Root cause of "I cannot record". - capture start: if Recorder is None, print an actionable error (install matplotlib) instead of a cryptic TypeError. - pyproject: add matplotlib to the `capture` extra so `pip install openadapt[capture]` pulls it and recording works out of the box. Diagnosed by installing openadapt-capture and tracing the guarded import to ModuleNotFoundError: matplotlib; after installing it, Recorder loads and a recording DB is produced. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Settings page wrote keys to .env, but agents read them via os.getenv (e.g. openadapt_evals.ApiAgent reads ANTHROPIC_API_KEY) — so a saved key never reached them and live eval failed with "ANTHROPIC_API_KEY is required". - settings.load_env_file(): load .env into os.environ (without overriding already-set vars); called at create_app startup. - settings PUT: also set os.environ for updated keys, so a freshly saved key works immediately without restarting the panel. Verified: PUT a key -> visible in os.environ; reload from .env after clearing also restores it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a local web control panel for OpenAdapt — a browser UI over the
record → train → evaluate CLI, so the pipeline is usable without the terminal.
Implements the blueprint (
docs/control-panel-blueprint.md) end to end (P0–P3).New in-repo sibling
openadapt-panel(FastAPI backend + React/Vite SPA),wired via a new lazy-imported
openadapt panelcommand and apanelextra.What's included
openadapt panelcommand (lazy import;find_specdistinguishes"not installed" from "installed but broken" so wiring bugs surface — openadapt-ml 0.14.1: multiple broken internal imports prevent serve, train, and demo commands #999
discipline). Flags:
--app(tray app),--install-shortcut(Desktop icon).importlibonly — never imports asibling), Captures, Train, Eval, Models, Settings.
CLI calls; long-running capture/train run as
openadaptCLI subprocesses(train stop =
STOP_TRAININGsentinel; capture stop = interrupt signal),eval runs in a worker thread returning structured metrics. Background jobs
stream logs over SSE.
/apiroute.openadapt-panel/frontend/; builds intoopenadapt_panel/static/(committed + wheel-bundled, so pip/CI/end-usersnever need Node — verified the wheel ships
static/assets/*).--install-shortcutwrites a Windows Desktop.lnkthatlaunches a tray-resident app via
pythonw(no console), pure Python(pystray/pillow) — no Rust/Node toolchain.
Fixes found while testing the eval flow (against
openadapt-evals0.87.2)WAALiveAdapter(server_url=...)→WAALiveAdapter(config=WAALiveConfig(server_url=...))in both the CLI and the panel (the old call
TypeErrors on current evals).metrics['total_tasks']→ toleratenum_tasks(KeyError at the end ofopenadapt eval run).Tests
tests/test_panel_seams.py(runs under the existingpytest tests/): behaviortests (auth, routes, settings round-trip, 503-when-absent, desktop helpers) run
everywhere; seam contracts against sibling signatures skip locally and run in CI
(which now installs
./openadapt-panel[dev]). Also added an eval metrics-keystest that would have caught the
total_tasksbug. Locally: 20 passed / 7skipped, ruff + format clean.
Prerequisites handled
.gitignorebaresrc→/src/(it was silently dropping anysrc/dir repo-wide, incl. the panel frontend).Not verified headless (need a machine with siblings + GPU/display)
Live capture (screenshots at import), real training, and live WAA eval. The
mock-eval flow was verified end-to-end through the panel.
🤖 Generated with Claude Code