import { useEffect, useMemo, useRef, useState } from "react"; const ACCEPTED_TYPES = ["video/mp4", "video/quicktime"]; const STAGES = [ { id: 1, label: "Frame extraction with FFmpeg", minSec: 0 }, { id: 2, label: "Visual inspection with local vision model", minSec: 8 }, { id: 3, label: "Executive report generation", minSec: 50 }, ]; const initialState = { file: null, isDragging: false, isLoading: false, status: "Drop your walkthrough video", error: null }; function formatTime(seconds) { const m = Math.floor(seconds / 60); const s = seconds % 60; if (m > 0) return `${m}m ${s}s`; return `${s}s`; } function getCurrentStageIndex(elapsed) { let idx = 0; for (let i = STAGES.length - 1; i >= 0; i--) { if (elapsed >= STAGES[i].minSec) { idx = i; break; } } return idx; } export default function App() { const [state, setState] = useState(initialState); const [elapsed, setElapsed] = useState(0); const intervalRef = useRef(null); const stageIndex = getCurrentStageIndex(elapsed); useEffect(() => { if (state.isLoading) { setElapsed(0); intervalRef.current = setInterval(() => { setElapsed((prev) => prev + 1); }, 1000); } else { if (intervalRef.current) clearInterval(intervalRef.current); } return () => { if (intervalRef.current) clearInterval(intervalRef.current); }; }, [state.isLoading]); const filename = useMemo(() => { if (!state.file) return "No file selected"; return state.file.name; }, [state.file]); const onDragOver = (event) => { event.preventDefault(); setState((prev) => ({ ...prev, isDragging: true })); }; const onDragLeave = () => { setState((prev) => ({ ...prev, isDragging: false })); }; const onDrop = (event) => { event.preventDefault(); const droppedFile = event.dataTransfer.files[0]; handleFile(droppedFile); }; const onFileChange = (event) => { const selectedFile = event.target.files[0]; handleFile(selectedFile); }; const handleFile = (file) => { if (!file) return; if (!ACCEPTED_TYPES.includes(file.type)) { setState((prev) => ({ ...prev, error: "Only .mp4 or .mov files are supported.", file: null })); return; } setState((prev) => ({ ...prev, file, error: null })); }; const submitFile = async () => { if (!state.file || state.isLoading) return; setState((prev) => ({ ...prev, isLoading: true, status: "Uploading and analyzing...", error: null })); const formData = new FormData(); formData.append("file", state.file); try { const response = await fetch("/api/generate-report", { method: "POST", body: formData }); if (!response.ok) { const payload = await response.json().catch(() => ({})); throw new Error(payload.detail || "Upload failed"); } const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const anchor = document.createElement("a"); anchor.href = url; anchor.download = "autoinventory-report.pdf"; document.body.appendChild(anchor); anchor.click(); anchor.remove(); window.URL.revokeObjectURL(url); setState((prev) => ({ ...prev, isLoading: false, status: "Report ready for download" })); } catch (error) { setState((prev) => ({ ...prev, isLoading: false, error: error.message || "Something went wrong" })); } }; return (
AutoInventory
Drag and drop a one-minute property video. Our agentic pipeline extracts rooms, fixtures, and conditions into a professional inventory report.
{state.status}
> ) : ( <>{state.status}
MP4 or MOV, up to 1 minute.
> )}Selected
{filename}
Processing stages
{state.error}
)}