File size: 1,814 Bytes
1c0c94d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | // Single source for all HTTP. Root-relative paths work in dev (Vite proxy) and prod (same origin).
async function getJSON(url) {
const r = await fetch(url)
if (!r.ok) throw new Error(`${r.status} ${r.statusText}`)
return r.json()
}
async function postJSON(url, body) {
const r = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
if (!r.ok) throw new Error(`${r.status} ${r.statusText}`)
return r.json()
}
export const getRuntime = () => getJSON('/runtime')
export const getAnalytics = () => getJSON('/analytics')
export const getImage = (id) => getJSON(`/images/${id}`)
export const getViolation = (id) => getJSON(`/violations/${id}`)
export const getReviewQueue = () => getJSON('/review-queue')
export const uploadImage = async (file, cameraId) => {
const fd = new FormData()
fd.append('file', file)
const qs = cameraId ? `?camera_id=${encodeURIComponent(cameraId)}` : ''
const r = await fetch(`/images${qs}`, { method: 'POST', body: fd })
if (!r.ok) throw new Error((await r.text()) || `${r.status}`)
return r.json()
}
export const listViolations = (params = {}) => {
const qs = new URLSearchParams()
for (const [k, v] of Object.entries(params)) if (v) qs.set(k, v)
return getJSON(`/violations?${qs.toString()}`)
}
export const reviewViolation = (id, decision, reviewer, note) =>
postJSON(`/violations/${id}/review`, { decision, reviewer, note })
// Poll an image until processing finishes (or 90 ticks).
export const pollImage = async (id, onTick) => {
for (let i = 0; i < 90; i++) {
const d = await getImage(id)
onTick?.(d)
if (['completed', 'failed', 'undeterminable'].includes(d.status)) return d
await new Promise((r) => setTimeout(r, 1200))
}
return getImage(id)
}
|