"use client"; import * as React from "react"; import { AnimatePresence, motion } from "framer-motion"; import { Check, FileAudio, ShieldQuestion, X } from "lucide-react"; import { api, bytes, type ReviewItem } from "@/lib/api"; import { useToast } from "@/components/toast"; import { Button } from "@/components/ui/button"; import { Skeletons } from "@/components/skeletons"; import { cn } from "@/lib/utils"; /** * Downloads that finished but did not clearly match what was asked for. * * The file is already on disk — confidence gates the *import*, not the * transfer — so approving is instant and rejecting is a delete. Holding the * download instead would mean the peer has usually vanished by the time * anyone looks. */ export function Review({ onDecided }: { onDecided: () => void }) { const [items, setItems] = React.useState(null); const [err, setErr] = React.useState(null); const load = React.useCallback(() => { api .review() .then((r) => setItems(r.items)) .catch((e) => setErr((e as Error).message)); }, []); React.useEffect(() => { load(); const t = setInterval(load, 8000); return () => clearInterval(t); }, [load]); if (err) return
{err}
; if (!items) return ; if (items.length === 0) return (

Nothing to review

Anything muse wasn’t confident about lands here before it reaches your library. An empty list means every match was unambiguous.

); return (
{items.length} download{items.length === 1 ? "" : "s"} finished but didn’t clearly match the request. Each file is already on disk — approving files it into your library, rejecting deletes it. {items.map((it) => ( ))}
); } function ReviewCard({ item, refresh, onDecided, }: { item: ReviewItem; refresh: () => void; onDecided: () => void; }) { const toast = useToast(); const [busy, setBusy] = React.useState(false); const decide = async (action: "approve" | "reject") => { setBusy(true); try { await api.decide(item.id, action); toast(action === "approve" ? `Filed ${item.title}` : `Deleted ${item.file}`); refresh(); onDecided(); } catch (e) { toast((e as Error).message, true); setBusy(false); } }; const pct = Math.round((item.confidence ?? 0) * 100); const reasons = (item.why ?? "").split(";").map((s) => s.trim()).filter(Boolean); return (
{item.cover ? ( /* eslint-disable-next-line @next/next/no-img-element */ ) : (
)}
{item.title}
{item.artist} {item.album ? ` · ${item.album}` : ""} {item.year ? ` · ${item.year}` : ""}
what was found
{item.file}
{item.size ? (
{bytes(item.size)}
) : null} {reasons.length > 0 && (
    {reasons.map((r) => (
  • {r}
  • ))}
)}
{item.requested_by && ( requested by {item.requested_by} )}
); } /** Confidence as a bar rather than a bare number: the useful question is * "how far below certain is this", which a percentage alone doesn't show. */ function ConfidenceBar({ pct }: { pct: number }) { const tone = pct >= 85 ? "bg-warn" : pct >= 70 ? "bg-warn" : "bg-err"; return (
{pct}% confident
); }