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:
2026-09-21 12:32:19 +05:30
parent 1607f4ce74
commit 81e2c605b9
12 changed files with 261 additions and 79 deletions

View File

@@ -15,7 +15,6 @@ export default function Setup({ onDone, onCancel }) {
const [code, setCode] = useState('')
const [busy, setBusy] = useState(null)
const [error, setError] = useState(null)
const [alone, setAlone] = useState(false)
async function submit(e) {
e.preventDefault()
@@ -40,13 +39,66 @@ export default function Setup({ onDone, onCancel }) {
}
}
// First launch: a choice, not a code box. Two thirds of the people who
// open this have no idea what an installation code is; the other third has
// one in their hand. Both must see their own path in the first second.
const [path, setPath] = useState(onCancel ? 'code' : null)
if (path === null) {
return (
<div className="login">
<div className="box wide">
<span className="mark"><img src={logo} alt="" /></span>
<h1>Welcome to Behavision</h1>
<p className="lead">
This PC will watch your shop’s cameras and recognise returning customers.
First, one question: is this shop managed from a head office?
</p>
<div className="choices">
<button type="button" className="choice" onClick={() => setPath('code')}>
<Icon.Cloud size={22} />
<b>Yes — I have an installation code</b>
<span>Head office gave you a code like <em>ABCDEF-123456-…</em>. This PC joins that shop and gets its cameras from there.</span>
</button>
<button type="button" className="choice" onClick={() => setPath('alone')}>
<Icon.Shield size={22} />
<b>No — set up on this PC only</b>
<span>Cameras, customers and recognition stay on this PC. Nothing is sent anywhere. You can link to a head office later.</span>
</button>
</div>
</div>
</div>
)
}
if (path === 'alone') {
return (
<div className="login">
<div className="box">
<span className="mark"><img src={logo} alt="" /></span>
<h1>On this PC only</h1>
<p className="lead">
Behavision will run entirely here. Next you’ll add your camera — it can find it on the network for you — and walk past it once so it can prove it works.
</p>
{error && <div className="err"><Icon.Warning size={15} />{error}</div>}
<button type="button" className="btn primary" disabled={!!busy} onClick={standalone}>
{busy === 'alone' ? 'Setting up…' : 'Continue'}
</button>
<div className="alt">
<button type="button" className="linkbtn" onClick={() => setPath(null)}><Icon.Back size={14} /> Back</button>
</div>
</div>
</div>
)
}
return (
<div className="login">
<div className="box">
<span className="mark"><img src={logo} alt="" /></span>
<h1>{onCancel ? 'Link to head office' : 'Set up this PC'}</h1>
<h1>{onCancel ? 'Link to head office' : 'Join your shop'}</h1>
<p className="lead">
Type the installation code for this shop. You only do this once.
Type the installation code head office gave you. It works once, and this PC becomes that shop.
</p>
<form onSubmit={submit}>
{error && <div className="err"><Icon.Warning size={15} />{error}</div>}
@@ -67,38 +119,12 @@ export default function Setup({ onDone, onCancel }) {
</button>
</form>
<p className="foot">
The code works once. Ask whoever manages your shops for it — they can
create one from the Behavision platform, under the shop.
Don’t have one? Whoever runs head office creates it under the shop: <em>Shops → the shop → Set up a shop PC</em>.
</p>
{/* The second way out of this screen, and the reason it exists.
Recognition, the cameras and this shop's own gallery all run on
this PC and need no server, so a shop with one till and no head
office was being blocked from adding a camera until somebody
issued it a code — the software refusing to do the thing it is
for. Linking later is still one click away, and it keeps the
visits already recorded here. */}
<div className="alt">
{onCancel
? <button type="button" className="linkbtn" onClick={onCancel}>
Not now — go back
</button>
: !alone
? <button type="button" className="linkbtn" onClick={() => setAlone(true)}>
No head office — set this PC up on its own
</button>
: <>
<p className="note">
This PC will watch its cameras and recognise returning
customers on its own. Nothing is sent anywhere. You can link
it to head office later without losing anything recorded
here.
</p>
<button type="button" className="btn" disabled={!!busy}
onClick={standalone}>
{busy === 'alone' ? 'Setting up…' : 'Use this PC on its own'}
</button>
</>}
? <button type="button" className="linkbtn" onClick={onCancel}>Not now — go back</button>
: <button type="button" className="linkbtn" onClick={() => setPath(null)}><Icon.Back size={14} /> Back</button>}
</div>
</div>
</div>