/** * PipelineControl — start / stop pipeline form. * * Features: * - Free-form source input (webcam index, file path, RTSP URL) * - "Demo" quick-start button — synthetic scene, no model needed * - Optional counting line configuration (up to 3 named lines) * - Confidence threshold */ import { useState } from 'react' const API = '/api' // Default counting line for demo mode: vertical centre line on a 1280×720 frame const DEMO_LINE = { name: 'main', x1: 640, y1: 252, x2: 640, y2: 612 } export default function PipelineControl({ status, onStatusChange, gpu }) { const [source, setSource] = useState('0') const [conf, setConf] = useState('0.35') const [showLines, setShowLines] = useState(false) const [lines, setLines] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const isRunning = status?.running ?? false // ── helpers ────────────────────────────────────────────────────────────── function addLine() { if (lines.length >= 3) return setLines(prev => [ ...prev, { name: `line${prev.length + 1}`, x1: 0, y1: 360, x2: 1280, y2: 360 }, ]) } function removeLine(i) { setLines(prev => prev.filter((_, idx) => idx !== i)) } function updateLine(i, field, value) { setLines(prev => prev.map((l, idx) => idx === i ? { ...l, [field]: field === 'name' ? value : Number(value) } : l ) ) } async function startPipeline(src, linesCfg) { setLoading(true) setError(null) try { const res = await fetch(`${API}/pipeline/start`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ source: src, confidence_threshold: parseFloat(conf), counting_lines: linesCfg, }), }) if (!res.ok) { const body = await res.json().catch(() => ({})) throw new Error(body?.detail ?? `HTTP ${res.status}`) } onStatusChange?.() } catch (err) { setError(err.message) } finally { setLoading(false) } } async function handleStart(e) { e.preventDefault() await startPipeline(source, lines) } async function handleDemo() { await startPipeline('demo', [DEMO_LINE]) } async function handleStop() { setLoading(true) setError(null) try { await fetch(`${API}/pipeline/stop`, { method: 'POST' }) onStatusChange?.() } catch (err) { setError(err.message) } finally { setLoading(false) } } // ── render ──────────────────────────────────────────────────────────────── return (

Pipeline Control

{/* Source */}
setSource(e.target.value)} disabled={isRunning} />
{/* Confidence */}
setConf(e.target.value)} disabled={isRunning} />
{/* Counting lines toggle */} {!isRunning && ( )} {/* Start / Stop buttons */} {!isRunning ? (
) : ( )}
{/* Counting lines editor */} {showLines && !isRunning && (
Counting lines — vehicles are counted when they cross a line
{lines.map((line, i) => (
updateLine(i, 'name', e.target.value)} /> {['x1', 'y1', 'x2', 'y2'].map(f => ( updateLine(i, f, e.target.value)} /> ))}
))} {lines.length < 3 && ( )}
)} {/* CPU-only warning — shown when GPU is confirmed absent and source is not demo */} {gpu === false && !isRunning && source.trim().toLowerCase() !== 'demo' && (
CPU-only mode — no GPU detected. Real video inference will run at roughly 3–8 FPS and may lag behind live sources.{' '} Keep uploaded video clips under 20 seconds for best results, or click Demo for full-speed synthetic playback.
)} {error &&

{error}

} {status && (

{isRunning ? `${status.demo_mode ? '[DEMO] ' : ''}Running — frame ${status.frame_index} · ${status.fps} fps · source: ${status.source}` : 'Pipeline stopped'}

)}
) }