"use client"; import { useEffect, useState } from "react"; import { getJSON } from "@/lib/api"; type Job = { id: string; title: string; status: string; stage: string; error: string | null; video_id: string; }; export function ImportMedia() { const [jobs, setJobs] = useState([]), [message, setMessage] = useState(""), [progress, setProgress] = useState(null), [transcript, setTranscript] = useState(""); async function refresh() { if (sessionStorage.getItem("vm-user-token")) setJobs(await getJSON("/api/jobs")); } useEffect(() => { const timer = setInterval(() => void refresh().catch(() => {}), 2000); return () => clearInterval(timer); }, []); function upload(file: File) { if (!sessionStorage.getItem("vm-user-token")) { setMessage("Connect your private library before importing media."); return; } if (file.size > 64 * 1024 * 1024) { setMessage("Choose a video under 64 MiB."); return; } const xhr = new XMLHttpRequest(); xhr.open("POST", `/api/ingest?filename=${encodeURIComponent(file.name)}`); xhr.setRequestHeader( "Authorization", `Bearer ${sessionStorage.getItem("vm-user-token")}`, ); xhr.upload.onprogress = (e) => { if (e.lengthComputable) setProgress(Math.round((100 * e.loaded) / e.total)); }; xhr.onerror = () => { setMessage("Upload failed; retry when connected."); setProgress(null); }; xhr.onload = () => { setProgress(null); setMessage( xhr.status === 200 ? "Upload queued. Processing continues in the background." : `Upload failed (${xhr.status}).`, ); void refresh(); }; setProgress(0); xhr.send(file); } async function action(j: Job, action: "retry" | "cancel") { try { await getJSON(`/api/jobs/${j.id}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ action, ...(transcript ? { transcript } : {}) }), }); await refresh(); } catch (e) { setMessage(String(e)); } } return (
Import & indexing{" "} 64 MiB · 30 min · one processing worker

Upload a video for proxy, thumbnails, 30-second segments, remote transcription and text indexing. No video is sent to an inference service until you import it. Transcript/audio destinations follow your authorized provider profile.

{ if (e.target.files?.[0]) upload(e.target.files[0]); }} /> {progress !== null && ( )}

{message}