From 81e2c605b9b8d01075f11ee98bc5b54065b24de1 Mon Sep 17 00:00:00 2001 From: Suriyakumarvijayanayagam Date: Mon, 21 Sep 2026 12:32:19 +0530 Subject: [PATCH] The first five minutes, as the product and not as a developer's first run The first launch was a code box with a link under it, then an empty Live screen with 'No cameras' in amber in a far corner, then a form asking for an IP address, and for the first few minutes of all of it the engine silently downloading 275 MB with nothing on screen but a stopped-looking status. Walked in a browser with the new mock; nobody who was not an installer would have got through it. Now: a welcome that asks the one question a shop owner can answer - managed from a head office, or on this PC only - with each path in a sentence; a Getting Started checklist on Live that reads its three steps from the engine and ticks them itself (recognition ready, camera added and connected, camera proven by a walk-past), with the one button for the next step, and that disappears the moment somebody is recognised; and the model download reported as a percentage in the tray, the sidebar and the checklist, parsed by the supervisor from the engine's own progress lines. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj --- agent/pkg/engine/supervisor.go | 38 ++++++++ behavision/model_assets.py | 29 +++++- desktop/app.go | 5 ++ .../frontend/dist/assets/index-Be_Iv2Nz.js | 40 +++++++++ ...{index-CGAmTH1J.css => index-lhDNZRcC.css} | 2 +- .../frontend/dist/assets/index-uOKaPIDr.js | 40 --------- desktop/frontend/dist/index.html | 4 +- desktop/frontend/src/App.jsx | 3 +- desktop/frontend/src/styles.css | 24 +++++ desktop/frontend/src/views/Live.jsx | 63 ++++++++++++- desktop/frontend/src/views/Setup.jsx | 90 ++++++++++++------- desktop/tray.go | 2 + 12 files changed, 261 insertions(+), 79 deletions(-) create mode 100644 desktop/frontend/dist/assets/index-Be_Iv2Nz.js rename desktop/frontend/dist/assets/{index-CGAmTH1J.css => index-lhDNZRcC.css} (95%) delete mode 100644 desktop/frontend/dist/assets/index-uOKaPIDr.js diff --git a/agent/pkg/engine/supervisor.go b/agent/pkg/engine/supervisor.go index 12d6d5a..a7df0c7 100644 --- a/agent/pkg/engine/supervisor.go +++ b/agent/pkg/engine/supervisor.go @@ -21,6 +21,8 @@ import ( "net/http" "os" "os/exec" + "regexp" + "strconv" "strings" "sync" "time" @@ -77,6 +79,7 @@ type Supervisor struct { restarts int cancel context.CancelFunc done chan struct{} + progress Progress } func New(opts Options) *Supervisor { @@ -110,6 +113,37 @@ func (s *Supervisor) Start() { go s.supervise(ctx, done) } +// Progress is what the engine is busy with before it answers - on first run, +// downloading ~275 MB of models. Empty once the engine is up. +type Progress struct { + What string `json:"what"` + Percent int `json:"percent"` +} + +var progressRe = regexp.MustCompile(`download: (.+?) (\d{1,3})%`) + +func (s *Supervisor) noteProgress(line string) { + m := progressRe.FindStringSubmatch(line) + if m == nil { + return + } + pct, _ := strconv.Atoi(m[2]) + s.mu.Lock() + if pct >= 100 { + s.progress = Progress{} + } else { + s.progress = Progress{What: m[1], Percent: pct} + } + s.mu.Unlock() +} + +// Progress reports the current first-run download, if any. +func (s *Supervisor) Progress() Progress { + s.mu.Lock() + defer s.mu.Unlock() + return s.progress +} + // Stop asks the engine to exit and waits for it. func (s *Supervisor) Stop() { s.mu.Lock() @@ -233,6 +267,7 @@ func (s *Supervisor) runOnce(ctx context.Context) error { for sc.Scan() { line := sc.Text() fmt.Fprintln(s.opts.LogWriter, line) + s.noteProgress(line) tailMu.Lock() tail = append(tail, line) if len(tail) > 12 { @@ -244,6 +279,9 @@ func (s *Supervisor) runOnce(ctx context.Context) error { s.setState(Running, nil) waitErr := cmd.Wait() + s.mu.Lock() + s.progress = Progress{} + s.mu.Unlock() <-pumped // A context cancel terminates the child through exec's own handling; the diff --git a/behavision/model_assets.py b/behavision/model_assets.py index 37c273c..29fecb3 100644 --- a/behavision/model_assets.py +++ b/behavision/model_assets.py @@ -35,6 +35,31 @@ _COPY_MAP = { } +def _fetch(url: str, dest: Path, label: str) -> None: + """Download with progress on stdout the supervisor can read. + + On first run this is minutes of nothing: the API is not up yet, so the + app cannot ask the engine what it is doing, and a shop PC that shows a + stopped engine for five minutes after install looks broken. The + supervisor watches for `download: