# Installation and running the demo Everything needed to get the LINE-1 Digital Twin running on a laptop and present it, including the copilot and the offline fallback. If you only want the run-of-show — what to click and what to say — that is [demo-script.md](demo-script.md) and the [slide deck](LINE-1-Digital-Twin-Demo.pptx). This file is setup and operation. --- ## 1. Requirements | | | |---|---| | **Node.js** | 20 or newer. Built and tested on **24.4.1**. | | **npm** | Ships with Node. Tested on **11.13.0**. | | **Disk** | ~250 MB for `node_modules`, plus models if you use Ollama. | | **GPU** | Not required. The 3D view is primitives and runs on integrated graphics. | | **Internet** | Only for `npm install` and the cloud copilot. The demo itself runs fully offline. | Optional, for the fast local copilot: | | | |---|---| | **Ollama** | Any recent version. Needed only for the **Local** copilot mode. | Check what you have: ```bash node -v && npm -v ``` ## 2. Install From the repository root — **not** from `server/` or `web/`. This is an npm workspaces repo, and one install at the root covers both packages. ```bash npm install ``` Expect ~240 packages and about 25 seconds. There should be no build step and no native compilation. ## 3. Configure the copilot (optional) **The demo works with no configuration at all.** With nothing set, the copilot falls back to a deterministic rule engine that answers instantly, offline, and never fails. Skip this section entirely if you just want to see the twin. Copy the template and edit it: ```bash cp .env.example .env ``` The server resolves a provider at startup in this order: 1. **OpenRouter** if `OPENROUTER_API_KEY` is set 2. **Ollama** if it answers at `OLLAMA_HOST` 3. **Rule engine** otherwise ```bash # Cloud — best analysis, slowest and most variable to first token OPENROUTER_API_KEY=sk-or-v1-... OPENROUTER_MODEL=stealth/ox-alpha # Local — fast, needs `ollama serve` running OLLAMA_HOST=http://127.0.0.1:11434 OLLAMA_MODEL=qwen3.5-4b-32k:latest # Force the rule engine, for rehearsing the worst case # COPILOT_PROVIDER=fallback ``` `.env` is gitignored. Keep the key out of commits and out of screen shares. **You do not have to edit this file to change models.** Once running, the in-app settings page (**AI ⚙** in the header) lists every model each provider offers and switches at runtime. See §7. ### If you are using Ollama ```bash ollama serve ollama pull qwen3.5-4b-32k ``` One gotcha worth knowing: Ollama commonly sets `OLLAMA_HOST=0.0.0.0` machine-wide. That is a *bind* address, not a destination — you cannot connect to it. The server normalizes it to `127.0.0.1` automatically, and the settings page shows both the configured value and the address actually being dialled. ## 4. Run it ### For the demo (recommended) ```bash npm run dev ``` Starts the API on **:8787** and the Vite dev server on **:5173**, prefixing each process's output. One Ctrl+C stops both. Open **http://localhost:5173**. You should see, within a couple of seconds: - Header: **Telemetry live** with a green dot, and a source badge reading `simulated` - Header: an **AI ⚙** button naming the active provider - **OEE around 87–90%** — not 100% - All five stations green, **Alarms: all clear** - Trend charts already populated with history The line starts **pre-warmed**: 30 simulated minutes are run before the first frame is served, so the rolling OEE window is full, the anomaly baselines are learned, and the charts have history. You are never presenting a dashboard that just booted. ### Single port, no dev server For a plant-floor box, a clean rehearsal, or handing someone a link: ```bash npm run serve ``` Builds the front end and serves everything from **http://localhost:8787** — one Node process, one port, no Vite. Use `npm run build` and `npm start` separately if you prefer. Once `web/dist` exists, the API process serves it too, so `npm run dev` will log `serving built front end`. That is harmless — but :8787 then shows whatever was last built, which can be stale. In development always use **:5173**, which is served live by Vite. ### Other scripts | Command | What it does | |---|---| | `npm run dev` | API + dev server together (the normal way) | | `npm run dev:server` | API only, on :8787 | | `npm run dev:web` | Front end only, on :5173 (needs the API running) | | `npm run build` | Build the front end to `web/dist` | | `npm start` | Serve API, and `web/dist` if it has been built | | `npm run serve` | `build` then `start` | | `npm run simcheck` | Verify the simulation model (no browser needed) | | `npm run apicheck` | Verify the server API (server must be running) | ## 5. Verify the install Two suites. Neither needs a browser, and both are worth running once after install so you know the machine is good. ```bash npm run simcheck ``` Runs the model headless and asserts what the demo actually claims: signals stay inside their physical ranges, faults move OEE the right way in a controlled A/B against an unfaulted line, a packer stoppage propagates upstream as blocking, a dropped sensor goes stale rather than to zero, setpoint changes are lagged, runs are reproducible from a seed, and the prediction arrives *before* the threshold is crossed. Ends with `ALL CHECKS PASSED`. ```bash npm run apicheck # in a second terminal, with the server running ``` Exercises the WebSocket stream, the control API, fault propagation into the stream, and the copilot in every provider mode — checking answers are grounded in real readings rather than generic prose. Ends with `ALL API CHECKS PASSED`. ## 6. Running the demo The full run-of-show with timings and wording is in [demo-script.md](demo-script.md). This is the mechanical sequence. ### Ten minutes before 1. `npm run dev`, open **http://localhost:5173**. 2. Confirm the header shows **Telemetry live**. 3. **Pick your copilot and measure it.** Open **AI ⚙**, choose a provider, click **Test**. Under 4 s to first token is presentable live; 4–12 s means a noticeable pause; above that, keep it for follow-up questions only. Do this on the demo machine, on the demo network — cloud latency has measured anywhere between 2 s and 36 s on consecutive identical calls. 4. Press **Reset** if anyone has been clicking. The line re-warms to ~87% OEE, all clear, in well under a second. 5. Leave the clock at **1×**. You speed it up during the demo, not before. 6. Rehearse once with **wifi off** so you know what the fallback looks like. ### The sequence | Step | Action | What to watch | |---|---|---| | **1** | Orbit the 3D view. Click **CNC-02**. | Detail charts follow the selection. OEE ~87%. | | **2** | What-if → drag **Oven setpoint** 305 → 330 °C | Zone 2 lags: 316 °C at 10 s, settles ~30 s. Burner duty spikes 68→85→74%. Drag back to 305. | | **3** | What-if → **Inject** *CNC-02 bearing degradation*. Set clock **60×**. | Bearing vibration begins climbing toward the dashed Warn 3.5 line. | | **4** | Wait ~10 simulated minutes | A purple **Trend** card appears: reaches the 4.5 limit in ~40 min of run time, while vibration is still ~2.8. | | **5** | Click **Explain this** on the bearing alarm, then the **work order** chip | The copilot traces vibration → tool wear → rejects → OEE. It is never told which fault you injected. | | **6** | **Clear** the fault, click **Tool change**, clock to **20×** | Reject rate falls, OEE recovers. | | **7** | Copilot selector → **Offline rules**, ask *"Why is OEE down?"* | Same diagnosis in ~40 ms, no model, no network. | ### Resetting between runs **Reset** in the header clears injected faults, learned baselines and client-side history, then re-warms the line. Use it between back-to-back demos rather than restarting the server. ## 7. Switching models at runtime **AI ⚙** in the header opens the settings page. The dashboard keeps streaming behind it, so nothing is interrupted. - **Provider** — Auto / Cloud / Local / Offline rules - **Model** — every model each provider offers. OpenRouter's catalogue arrives live with context length and price per million; the Ollama list comes from your local library with parameter size and quantization. Filter, or type an id. - **Test** — measures time to first token and grades it - **Generation** — temperature, max output tokens, reasoning effort - **Ollama host** — shows the configured value and the address actually dialled Choices persist to `.copilot-settings.json` (gitignored), so a model picked while preparing survives a restart. **Reset to defaults** discards that file and falls back to `.env`. Settings never contain the API key. Note on reasoning models: reasoning tokens count against the output budget on most providers, so a reasoning model on a small budget can spend the lot thinking and return nothing. Keep effort **low** and the budget generous. Ollama thinking models are sent `think: false` for the same reason. ## 8. Troubleshooting **`Disconnected` in the header, charts empty.** The API died. Restart with `npm run dev`; the dashboard reconnects on its own with backoff and the server comes back pre-warmed. No page reload needed. **Port already in use.** Set `PORT` in `.env` for the API. For the dev server, change `server.port` in `web/vite.config.js` — and its proxy target if you moved the API. **Copilot returns nothing, or the panel shows a note about an empty answer.** Expected behaviour, not a break: it fell back to the rule engine and still answered. Usually a reasoning model exhausting its token budget. Raise max output tokens or lower reasoning effort in the settings page, or switch to **Local**. **Copilot says Ollama is unreachable.** `ollama serve` is not running, or the host is wrong. The settings page shows the address being dialled — check that first. `0.0.0.0` in your environment is normal and handled. **OEE reads 100% and there are no alarms.** The pre-warm did not run. Check the startup log for `pre-warmed 30 simulated minutes`. If it is absent, `PREWARM_SEC` has been overridden in `server/ingest/simulatedSource.js`. **Server exits immediately with a long message about MQTT or OPC-UA.** `TELEMETRY_SOURCE` is set to a stub adapter. Set `TELEMETRY_SOURCE=simulated` in `.env`. The adapters are documented stubs — see [architecture.md](architecture.md). **3D view is choppy.** Drop the clock to 20×. The model is fine; the render loop is competing with the projector. The scene loads no external assets, so this is never a network issue. **The line is in a strange state.** Press **Reset**. ## 9. What is where ``` server/ sim/ line.js (part flow, WIP, blocking) · stations.js (physics) faults.js (injectable profiles) · kpi.js (OEE) analytics/ trend.js (least squares) · anomaly.js (frozen baselines) alarms.js (thresholds, latching, predictions) ingest/ source.js (the seam) · simulatedSource.js mqttSource.js, opcuaSource.js (documented stubs) ai/ copilot.js · context.js · rules.js · settings.js web/src/ three/ Scene.jsx, Station.jsx, Conveyor.jsx panels/ KPIs, station strip, charts, alarms, what-if, copilot, settings scripts/ dev.mjs · simcheck.mjs · apicheck.mjs docs/ demo-script.md · architecture.md · installation.md · the deck ``` Connecting real plant data is [architecture.md](architecture.md) — the ingest seam, both adapter stubs, and the four things that bite when the data is real.