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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user