// A stand-in for the Go bindings, for looking at screens in a browser. // // The app only exists inside Wails, so until this there was no way to put a // screen in front of somebody without a Windows build - which is how the // first-run experience went unreviewed. Loaded ONLY by the dev server and only // with ?mock=; a production bundle never contains it. // // ?mock=fresh first launch after install: unclaimed, engine starting // ?mock=standalone chose "run on this PC only", no cameras yet // ?mock=claimed claimed, signed in, one camera, arrivals flowing // // State is in memory and advances as you click, so the flow can be walked. const scenario = new URLSearchParams(location.search).get('mock') if (scenario) { const st = { claimed: scenario === 'claimed', standalone: scenario === 'standalone', logged_in: scenario === 'claimed', cameras: scenario === 'claimed' ? [{id: 'entrance', label: 'Entrance', host: '192.168.1.122', port: 554, path: '/ch0_1.264', username: 'admin', has_password: true, enabled: true}] : [], engine: scenario === 'fresh' ? 'starting' : 'running', startedAt: Date.now(), } const user = {id: 'u1', email: 'suriya@tenext.in', full_name: 'Suriya', role: 'owner', client_id: 'c1', client_name: 'TeNext Retail'} const session = () => ({logged_in: st.logged_in, user: st.logged_in ? user : {}, site_name: st.claimed ? 'TeNext Chennai' : '', claimed: st.claimed, standalone: st.standalone && !st.claimed}) const now = () => new Date().toISOString() const arrivals = () => st.cameras.length === 0 ? [] : [ {type: 'person.seen', ts: now(), camera_id: 'entrance', data: {label: 'Visitor 3', identity_id: 3, similarity: 0.61, gender: 'Male', age: 34, emotion: 'neutral'}}, {type: 'person.new', ts: new Date(Date.now() - 95e3).toISOString(), camera_id: 'entrance', data: {label: 'Visitor 7', identity_id: 7, gender: 'Female', age: 28}}, {type: 'person.seen', ts: new Date(Date.now() - 410e3).toISOString(), camera_id: 'entrance', data: {label: 'Priya', identity_id: 2, similarity: 0.72, gender: 'Female', age: 41}}, ] const delay = (v, ms = 120) => new Promise(r => setTimeout(() => r(v), ms)) const App = { Session: () => delay(session()), Login: (email) => { st.logged_in = true; user.email = email || user.email; return delay(session()) }, Logout: () => { st.logged_in = false; return delay(session()) }, Claim: (code) => code.replace(/[^A-Z0-9]/gi, '').length >= 20 ? (st.claimed = true, st.standalone = false, delay(session(), 900)) : Promise.reject(new Error('That installation code is not valid. Ask for a new one.')), RunStandalone: () => { st.standalone = true; return delay(session()) }, EngineStatus: () => { // The engine takes a minute or two on first run (models download). if (st.engine === 'starting' && Date.now() - st.startedAt > 20000) st.engine = 'running' const cams = Object.fromEntries(st.cameras.map(c => [c.id, true])) return delay({state: st.engine === 'starting' ? 'running' : 'running', reachable: st.engine !== 'starting', recognition_model: st.engine === 'starting' ? '' : 'w600k_r50', cameras: cams, restarts: 0}) }, StartEngine: () => delay({state: 'running', reachable: true}), StopEngine: () => delay({state: 'stopped', reachable: false}), Cameras: () => delay(st.cameras.map(c => ({...c, connected: true, frames: 1200, faces: 9}))), TestCamera: (cam) => delay({ok: Boolean(cam.host), width: 800, height: 448, codec: 'hevc', error: cam.host ? '' : 'no host'}, 1500), SaveCamera: (id, cam) => { const c = {id: id || cam.id || 'cam' + (st.cameras.length + 1), ...cam, has_password: Boolean(cam.password)}; delete c.password; st.cameras = [...st.cameras.filter(x => x.id !== c.id), c]; return delay(c) }, DeleteCamera: (id) => { st.cameras = st.cameras.filter(c => c.id !== id); return delay(null) }, StartPlacementCheck: () => delay({state: 'running'}), PlacementResult: () => delay({state: 'finished', verdict: 'good', headline: 'faces recognised on a walk-past', advice: []}), StreamURL: () => '', Live: () => delay({stats: {cameras: st.cameras.map(c => ({camera_id: c.id, connected: true, pipeline: {best_quality: {n: 40, fraction_below_gate: 0.18}}})), gallery: {identities: st.cameras.length ? 7 : 0, sightings: st.cameras.length ? 44 : 0}}, events: arrivals()}), PipelineStatus: () => delay({webhook_url: 'http://127.0.0.1:53658/events', queued: 0, dropped: 0, claimed: st.claimed, standalone: st.standalone && !st.claimed, broker_up: st.claimed, accepted: st.claimed ? 12 : 0}), LocalIdentities: () => delay([]), LocalSightings: () => delay([]), Footfall: () => delay({total: 0, buckets: []}), Sites: () => delay([]), VisitorHistory: () => delay([]), VisitorPhoto: () => delay({available: false, reason: 'This system is not storing images.'}), ForgetCustomer: () => delay(null), Sales: () => delay([]), Customers: () => delay([{id: 'v1', label: 'Priya', number: 2, first_seen_at: now(), last_seen_at: now(), visits: 6}]), SaveProfile: () => delay(null), RecordPurchase: () => delay(null), Ask: (history) => { const q = history[history.length - 1]?.text ?? '' if (!st.claimed) return Promise.reject(new Error('The assistant is not switched on for this server.')) const text = /camera/i.test(q) ? 'Go to Cameras and press Add camera. The address is on a sticker on the camera itself; pick the make and I fill in the stream path. Test it, save it, then walk past it once so I can tell you whether the placement works.' : /code|install/i.test(q) ? 'Whoever runs head office makes one: open the shop there, press Set up a shop PC, and read the code out. It works once.' : /who|morning|came/i.test(q) ? 'Three people so far: Priya at 13:12 (her sixth visit), a new face at 13:18 I have called Visitor 7, and Visitor 3 just now.' : 'Chennai is online and the door camera is connected, but nobody has proved it yet. Walk past it once with Check placement running and I will tell you if it can actually see faces - until then a quiet screen might just be a badly aimed camera.' return delay({text, used: /camera|code/i.test(q) ? [] : ['site_status', 'cameras']}, 1400) }, } window.go = {main: {App}} }