// frontend/src/components/ui/ModelPreview.tsx 'use client' import { useEffect, useMemo, useRef, useState } from 'react' import HoverPopover from './HoverPopover' import LiveHlsVideo from './LiveHlsVideo' import { XMarkIcon } from '@heroicons/react/24/outline' type Props = { jobId: string // Optional: wird von außen hochgezählt (z.B. alle 5s). Wenn nicht gesetzt, // tickt die Komponente selbst (weniger Re-Renders im Parent). thumbTick?: number // wie oft (ms) der Thumbnail neu geladen werden soll, wenn thumbTick nicht gesetzt ist autoTickMs?: number blur?: boolean } export default function ModelPreview({ jobId, thumbTick, autoTickMs = 30000, blur = false }: Props) { const blurCls = blur ? 'blur-md' : '' const [localTick, setLocalTick] = useState(0) const [imgError, setImgError] = useState(false) const rootRef = useRef(null) const [inView, setInView] = useState(false) useEffect(() => { // Wenn Parent tickt, kein lokales Ticken if (typeof thumbTick === 'number') return // Nur animieren, wenn im Sichtbereich UND Tab sichtbar if (!inView || document.hidden) return const id = window.setInterval(() => { setLocalTick((t) => t + 1) }, autoTickMs) return () => window.clearInterval(id) }, [thumbTick, autoTickMs, inView]) useEffect(() => { const el = rootRef.current if (!el) return const obs = new IntersectionObserver( (entries) => { const entry = entries[0] setInView(Boolean(entry?.isIntersecting)) }, { root: null, threshold: 0.1, } ) obs.observe(el) return () => obs.disconnect() }, []) const tick = typeof thumbTick === 'number' ? thumbTick : localTick // bei neuem Tick Error-Flag zurücksetzen (damit wir retries erlauben) useEffect(() => { setImgError(false) }, [tick]) // Thumbnail mit Cache-Buster (?v=...) const thumb = useMemo( () => `/api/record/preview?id=${encodeURIComponent(jobId)}&v=${tick}`, [jobId, tick] ) // HLS nur für große Vorschau im Popover const hq = useMemo( () => `/api/record/preview?id=${encodeURIComponent(jobId)}&file=index_hq.m3u8`, [jobId] ) return ( open && (
{/* LIVE badge */}
Live
{/* Close */}
) } >
{!imgError ? ( setImgError(true)} onLoad={() => setImgError(false)} /> ) : (
keine Vorschau
)}
) }