Spaces:
Sleeping
Sleeping
File size: 4,719 Bytes
d725335 | 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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | import { Activity, Film, Loader2, Zap } from "lucide-react"
import { useDashboard } from "@/lib/dashboard"
import { mediaUrl } from "@/lib/api"
import { Badge } from "@/components/ui/badge"
import { cn } from "@/lib/utils"
function Stat({
label,
value,
accent,
}: {
label: string
value: string
accent?: boolean
}) {
return (
<div className="rounded-lg border border-border bg-black/20 px-4 py-3">
<div className="font-mono text-[0.625rem] uppercase tracking-[0.16em] text-muted-foreground">
{label}
</div>
<div
className={cn(
"mt-1 font-display text-2xl font-semibold tabular-nums",
accent ? "text-primary" : "text-foreground",
)}
>
{value}
</div>
</div>
)
}
export function PreviewPanel() {
const { result, running, videoPreviewUrl } = useDashboard()
const fired = result?.status === "fired"
const src = result ? mediaUrl(result.video_url) : videoPreviewUrl
return (
<div className="space-y-4">
<div className="group relative aspect-video overflow-hidden rounded-lg border border-border bg-black">
{/* scanline / grid atmosphere */}
<div
className="pointer-events-none absolute inset-0 opacity-[0.07]"
style={{
backgroundImage:
"linear-gradient(oklch(1 0 0 / 0.5) 1px, transparent 1px), linear-gradient(90deg, oklch(1 0 0 / 0.5) 1px, transparent 1px)",
backgroundSize: "32px 32px",
}}
/>
{src ? (
<video
key={src}
src={src}
controls
autoPlay
loop
muted
playsInline
className="absolute inset-0 size-full object-contain"
/>
) : (
<div className="absolute inset-0 flex flex-col items-center justify-center gap-3 text-muted-foreground">
<Film className="size-8 opacity-40" />
<p className="text-sm">Upload a clip and run detection to see the annotated replay</p>
</div>
)}
{/* top HUD */}
<div className="pointer-events-none absolute inset-x-0 top-0 flex items-start justify-between p-3">
<div className="flex gap-2">
{result?.classes?.slice(0, 4).map((c) => (
<span
key={c}
className="rounded-md border border-white/10 bg-black/50 px-2 py-1 font-mono text-[0.625rem] uppercase tracking-wider text-white/70 backdrop-blur-sm"
>
{c}
</span>
))}
</div>
{result &&
(fired ? (
<Badge variant="live">
<span className="size-1.5 rounded-full bg-primary signal-dot" />
automation fired
</Badge>
) : (
<Badge variant="warning">no match</Badge>
))}
</div>
{/* running overlay */}
{running && (
<div className="absolute inset-0 flex items-center justify-center bg-black/60 backdrop-blur-sm">
<div className="flex flex-col items-center gap-3 text-primary">
<Loader2 className="size-7 animate-spin" />
<span className="font-mono text-xs uppercase tracking-widest">running inference</span>
</div>
</div>
)}
{/* fired banner */}
{fired && result && (
<div className="absolute inset-x-0 bottom-0 flex items-center justify-between gap-3 bg-gradient-to-t from-primary/90 to-primary/70 px-4 py-2.5 text-primary-foreground">
<div className="flex items-center gap-2 text-sm font-semibold">
<Zap className="size-4" />
{result.events[0]?.rule} β {result.events[0]?.action}
</div>
<span className="font-mono text-xs tabular-nums">
{result.events[0]?.timestamp_sec.toFixed(1)}s
</span>
</div>
)}
</div>
{/* stat strip */}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4">
<Stat label="Detections" value={result ? `${result.stats.detections}` : "β"} />
<Stat label="Rules" value={result ? `${result.stats.rules}` : "β"} />
<Stat label="Actions" value={result ? `${result.stats.actions}` : "β"} accent={fired} />
<Stat
label="Frames"
value={result ? `${result.stats.processed_frames}` : "β"}
/>
</div>
{!result && (
<div className="flex items-center gap-2 text-xs text-muted-foreground">
<Activity className="size-3.5" />
Engine idle β awaiting first run.
</div>
)}
</div>
)
}
|