Initial commit
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user