Deploy web Studio / deploy (push) Successful in 3m3s
✦ Agent in the Studio's top bar opens a floating chat window. The model (any OpenAI-compatible endpoint with tool calling; default B4L's inference server, https://inference.p0.contact/v1) gets the 15 MCP tools plus two page tools (select_object, set_view) and the Studio's state (selection, level, rotation) in its system prompt. Answers and reasoning stream in; tool calls show as rows with arguments and results; object ids link to the object; Stop ends a turn; Undo takes the agent's edits back. The window moves by its title bar and resizes at its top-left corner. - studio/agent.js: shared by both front ends (waits for studio.js's isokit:studio event); MCP over /mcp or isokit.mcp in the web Studio - isokit studio: /api/agent/{config,models,chat} relay the calls on their own threads (ureq, chunked and flushed per read), so the key stays on the machine and the endpoint needs no CORS; settings from ISOKIT_LLM_URL / _KEY / _MODEL or agent.json in the user's config folder - web Studio: calls the endpoint from the page with a key kept in the browser; build_web.py --agent-url names a same-site relay as default; the deploy builds with /llm/v1, which deploy/Caddyfile forwards to the inference server (its nginx refuses the CORS preflight) - tools/mock_llm.py + tools/agent_checks.py: the agent window checks in both check_studio.py and check_web.py
214 lines
11 KiB
Markdown
214 lines
11 KiB
Markdown
# isokit
|
||
|
||
Isometric scene engine for B4L projects: site plans, plants and buildings drawn
|
||
as SimCity-style views that you can pan, zoom, rotate, cut open by storey and
|
||
click for details. One Rust binary does everything:
|
||
|
||
- **Studio**: a GUI editor in the browser for people who don't code. You add
|
||
objects from a palette, drag them into place, and edit their properties in forms.
|
||
- **Web Studio**: the same Studio running entirely in the browser, with the
|
||
engine compiled to WebAssembly. Nothing to install: projects live in the
|
||
browser, or in a folder on disk (Chrome / Edge) that agents edit alongside.
|
||
- **Agent window**: chat with a language model right in the Studio (by default
|
||
B4L's inference server); it edits the scene with the MCP tools while you watch.
|
||
- **MCP server**: AI agents (Hermes and others) edit the same scene with the
|
||
same operations; their changes show up live in an open Studio.
|
||
- **Build**: a self-contained HTML page with every view prerendered, or
|
||
standalone SVGs for documents.
|
||
- **Engine and scripting**: everything you can place is a *prefab* written in
|
||
[Rhai](https://rhai.rs). Projects add their own prefabs, scripts and colours
|
||
without touching the engine.
|
||
|
||
The engine is a Rust port of the hand-written JS views in `cnx_vinyl`
|
||
(fleet calculator), `cnx_scuba` (centre view) and `mooban` (estate view),
|
||
and it keeps their look: same projection, depth sort, face shading, palette and
|
||
viewer behaviour.
|
||
|
||
## Quick start
|
||
|
||
```bash
|
||
cargo build --release # target/release/isokit.exe (one file, ~7 MB)
|
||
|
||
isokit new C:/dev/myproject/site-view # starter project
|
||
isokit studio C:/dev/myproject/site-view # opens the editor in the browser
|
||
isokit build C:/dev/myproject/site-view # dist/index.html, no server needed
|
||
```
|
||
|
||
Try the demo: `isokit studio examples/demo`.
|
||
|
||
### Studio (for everyone)
|
||
|
||
| Do | How |
|
||
|---|---|
|
||
| Add | pick an object on the left, click on the map (Shift-click places several) |
|
||
| Select | click it on the map or in the object list |
|
||
| Move | drag it, or arrow keys by the snap step (0.1 / 0.5 / 1 / 5 m) |
|
||
| Turn | R / Shift+R or ⟲ ⟳ |
|
||
| Edit | the right panel: size, storeys, colours, solar, tooltip text, label, levels |
|
||
| Copy / delete | Ctrl+D / Delete; Ctrl+Z / Ctrl+Y undo and redo |
|
||
| Look | drag the ground to pan, Ctrl+wheel or pinch to zoom, ↺ ↻ rotate, Site / GF / 1F levels |
|
||
|
||
Every change is saved to `scene.json` at once. When the file changes on disk
|
||
(an agent, git pull, another editor), the Studio reloads it, and that change can
|
||
be undone like any other.
|
||
|
||
### Web Studio (no install)
|
||
|
||
```bash
|
||
rustup target add wasm32-unknown-unknown
|
||
cargo install wasm-bindgen-cli --version 0.2.129 # must match Cargo.lock
|
||
python tools/build_web.py # -> dist/web/ (static files, ~2.8 MB, 0.75 MB gzipped)
|
||
```
|
||
|
||
`dist/web/` runs from any static web server (http or https, not `file://`).
|
||
A live build is at https://isokit.buildfor.life/: every push to `master` that
|
||
touches the engine or the front end rebuilds and deploys it
|
||
(`.gitea/workflows/deploy-web.yml`; server setup in `deploy/Caddyfile`).
|
||
The first visit opens a copy of the demo. Everything the desktop Studio does
|
||
works the same, because the page runs the same engine and answers the same
|
||
requests. In addition:
|
||
|
||
| | |
|
||
|---|---|
|
||
| **Projects ▾** | list, open, delete; new project; demo copy; export as `.zip`; import a `.zip` or a project folder (drop it on the strip, or pick it) |
|
||
| **Open folder live** | Chrome / Edge: edits are written straight to the folder's files, and changes made there (an agent running `isokit mcp <folder>`, an editor, `git pull`) show up within 1.5 s and can be undone |
|
||
| **{ } Code** | edit `scene.rhai`, `lib/*.rhai` prefabs, `theme.toml`, `isokit.toml`; Ctrl+S applies, script errors show with file and line |
|
||
| **Build page** | downloads the self-contained HTML page (a folder project gets `dist/index.html` like `isokit build`) |
|
||
| **Preview** | the built page in a new tab |
|
||
|
||
Projects in the browser are stored in IndexedDB (per browser and site; export
|
||
them to keep a copy). Two tabs on the same project follow each other. The 15
|
||
MCP tools also run inside the page: `await isokit.mcp({jsonrpc: "2.0", id: 1,
|
||
method: "tools/list"})`, and browsers with WebMCP (`navigator.modelContext`)
|
||
get them as page tools.
|
||
|
||
### Agent window
|
||
|
||
**✦ Agent** in the Studio's top bar opens a chat with a language model that
|
||
edits the scene for you: "plant a row of trees along the road", "make the
|
||
workshop one storey taller", "what is on this site?". It works with the 15 MCP
|
||
tools below plus two page tools (point at an object, turn the view), streams
|
||
its answer and its reasoning, and shows each tool call (click one for the
|
||
arguments and the result, click an object id to select it). Every edit lands
|
||
on the map at once, and Undo takes it back. Stop ends a turn; the window moves
|
||
by its title bar and resizes at its top-left corner.
|
||
|
||
Any OpenAI-compatible endpoint with tool calling works (vLLM, llama.cpp,
|
||
Ollama, LM Studio, hosted APIs). The default is B4L's inference server,
|
||
`https://inference.p0.contact/v1`; ⚙ sets endpoint, key, model and whether
|
||
the model thinks first.
|
||
|
||
- `isokit studio` passes the calls through its own server (`/api/agent/*`), so
|
||
the key never reaches the page and the endpoint needs no CORS. Settings:
|
||
`ISOKIT_LLM_URL`, `ISOKIT_LLM_KEY`, `ISOKIT_LLM_MODEL`, or the window's ⚙,
|
||
which saves them in `%APPDATA%\isokit\agent.json` (`~/.config/isokit` elsewhere).
|
||
- The web Studio calls the endpoint from the page; the key stays in that
|
||
browser. The endpoint must allow the page (CORS, preflight included), or the
|
||
site relays it: https://isokit.buildfor.life/ is built with
|
||
`--agent-url /llm/v1`, a same-site path that Caddy forwards to B4L's
|
||
inference server (`deploy/Caddyfile`). The relay adds no key: each browser
|
||
enters its own once.
|
||
|
||
### Agents (MCP)
|
||
|
||
While the Studio runs, it also serves MCP over HTTP at `http://127.0.0.1:7878/mcp`.
|
||
Without the Studio, run `isokit mcp <project>` over stdio. In Hermes
|
||
(`config.yaml`):
|
||
|
||
```yaml
|
||
mcp_servers:
|
||
isokit:
|
||
command: C:\dev\isokit\target\release\isokit.exe
|
||
args: [mcp, C:\dev\myproject\site-view]
|
||
```
|
||
|
||
Tools: `project_info`, `list_prefabs`, `list_objects`, `get_object`,
|
||
`add_object`, `update_object`, `remove_object`, `duplicate_object`,
|
||
`apply_ops` (batch, one undo step), `set_params`, `render_view`,
|
||
`check_project`, `build_html`, `undo`, `redo`. Bad input comes back with a
|
||
message that says what is allowed, e.g. the list of types or a prop's range.
|
||
|
||
## A project
|
||
|
||
```text
|
||
isokit.toml title, levels, parameters, theme files, prefab libraries
|
||
scene.json the objects (edited by the Studio and agents)
|
||
scene.rhai optional script, drawn before the objects
|
||
lib/*.rhai optional project prefabs (listed under prelude)
|
||
theme.toml optional colours and materials
|
||
```
|
||
|
||
An object in `scene.json`:
|
||
|
||
```json
|
||
{ "id": "hall", "type": "building", "name": "Workshop", "x": -12, "y": 6, "rot": 90,
|
||
"props": { "w": 24, "d": 12, "floors": 2, "pv": true },
|
||
"tag": "Workshop", "info": "Two storeys, solar on the roof", "levels": [] }
|
||
```
|
||
|
||
Coordinates are metres: x points east, y north, z up. `x, y` is the centre of
|
||
the footprint, and `rot` turns the object in 90° steps. Levels: `site` (or any
|
||
other id) shows buildings closed, while `gf`, `1f`, `2f` … cut them open at that
|
||
storey.
|
||
|
||
## Commands
|
||
|
||
| Command | |
|
||
|---|---|
|
||
| `isokit new <dir>` | starter project |
|
||
| `isokit studio [dir] [--port N] [--no-open]` | GUI editor + `/mcp` + `/view` live preview + the agent window's link to the model |
|
||
| `isokit mcp [dir]` | MCP over stdio |
|
||
| `isokit build [dir] [-o file]` | self-contained HTML (all levels × 4 rotations) |
|
||
| `isokit render [dir] -o x.svg [--level gf] [--rot 2] [--light]` | one view as SVG |
|
||
| `isokit check [dir]` | lint: unknown types, props out of range, script warnings, NaN, node budget, unstyled classes |
|
||
| `isokit prefabs [dir]` | object types and their properties |
|
||
|
||
## Architecture
|
||
|
||
```text
|
||
crates/isokit-core projection (4 rotations), primitives, sprites, depth sort, SVG (no deps but serde)
|
||
crates/isokit-theme TOML palettes / materials / rules -> CSS; built-in default theme
|
||
crates/isokit-script Rhai runtime: drawing API, prefab library (stdlib/*.rhai), scene document driver
|
||
crates/isokit-project project folder, document ops, export, check, Workspace (undo, reload), MCP tools
|
||
crates/isokit-cli the `isokit` binary: Studio HTTP server, MCP stdio, build/render/check
|
||
crates/isokit-wasm the engine for the browser (wasm-bindgen): a project in memory behind the Studio API
|
||
viewer/ isokit-view.js/.css: pan, zoom, levels, rotation, tooltips (any page can embed it)
|
||
studio/ the Studio GUI (served by `isokit studio`, and by the web Studio); agent.js = agent window
|
||
web/ web Studio shell: WASM backend for studio.js, projects, folders, zip, code editor
|
||
examples/demo demo project
|
||
deploy/Caddyfile the web Studio's vhost on the buildfor.life web server (s1)
|
||
```
|
||
|
||
The layers are independent. A Rust program can draw with `isokit-core` alone.
|
||
`isokit-script` reads sources from memory, with no disk access, and a
|
||
`Workspace` reads and writes through a `Store` (a folder on disk, or memory),
|
||
so the same code runs natively and in the browser. The Studio, the web Studio
|
||
and MCP all apply the same `ops::Op` JSON operations through a `Workspace`, and
|
||
both Studio front ends speak the JSON API in `isokit_project::api`.
|
||
|
||
Writing prefabs and scripts: [docs/SCRIPTING.md](docs/SCRIPTING.md).
|
||
Plans: [docs/ROADMAP.md](docs/ROADMAP.md).
|
||
|
||
## Development
|
||
|
||
```bash
|
||
cargo test # engine, runtime, ops, demo, MCP
|
||
cargo clippy --all-targets -- -D warnings
|
||
ISOKIT_LLM_URL=http://127.0.0.1:7932/v1 ISOKIT_LLM_KEY=test-key ISOKIT_CONFIG_DIR=<scratch> \
|
||
isokit studio <copy of examples/demo> --port 7890 --no-open & # agent window on tools/mock_llm.py
|
||
uv run --with playwright python tools/check_studio.py <shot-dir> http://127.0.0.1:7890 # GUI + MCP + agent window end to end (Edge)
|
||
cargo clippy -p isokit-wasm --target wasm32-unknown-unknown -- -D warnings
|
||
python tools/build_web.py && uv run --with playwright python tools/check_web.py <shot-dir> # web Studio end to end
|
||
```
|
||
|
||
The agent window checks run on `tools/mock_llm.py`, a scripted stand-in for an
|
||
OpenAI-compatible server that the check scripts start on port 7932.
|
||
|
||
`python tools/build_web.py --skip-cargo` re-copies the front end (studio/,
|
||
viewer/, web/) without rebuilding the WASM. Code that runs in the browser must
|
||
not use `std::time` (it traps on wasm32-unknown-unknown): use `web_time`.
|
||
|
||
Set `ISOKIT_ASSETS=C:/dev/isokit` to serve `viewer/` and `studio/` from disk, so
|
||
you can edit the GUI without rebuilding. `tools/css2theme.py` converted the
|
||
original views' CSS into `crates/isokit-theme/themes/default.toml`.
|