Spaces:
Running
Running
import React from 'react' | |
import type { ExamplesData } from './Examples' | |
import { groupByNameAndVariant } from './galleryUtils' | |
import ExampleVariantMetricsTable from './ExampleVariantMetricsTable' | |
import ExampleDetailsSection from './ExampleDetailsSection' | |
import ExampleVariantSelector from './ExampleVariantSelector' | |
import ExampleVariantToggle from './ExampleVariantToggle' | |
interface GalleryProps { | |
selectedModel: string | |
selectedAttack: string | |
examples: { | |
[model: string]: { | |
[attack: string]: ExamplesData[] | |
} | |
} | |
} | |
const VideoGallery: React.FC<GalleryProps> = ({ selectedModel, selectedAttack, examples }) => { | |
const exampleItems = examples[selectedModel][selectedAttack] | |
const grouped = groupByNameAndVariant(exampleItems) | |
const videoNames = Object.keys(grouped) | |
const [selectedVideo, setSelectedVideo] = React.useState(videoNames[0] || '') | |
const variants = grouped[selectedVideo] || {} | |
const variantKeys = Object.keys(variants) | |
const [selectedVariant, setSelectedVariant] = React.useState(variantKeys[0] || '') | |
const [toggleMode, setToggleMode] = React.useState<'wmd' | 'attacked'>('wmd') | |
// Add state for rewind seconds | |
const [rewindSeconds, setRewindSeconds] = React.useState(0.5) | |
// State for video scale | |
const [videoScale, setVideoScale] = React.useState(1) | |
// Playback time ref for syncing position | |
const playbackTimeRef = React.useRef(0) | |
// Refs for all video elements | |
const videoRefs = React.useMemo(() => { | |
const refs: Record<string, React.RefObject<HTMLVideoElement>> = {} | |
variantKeys.forEach((v) => { | |
refs[v] = React.createRef<HTMLVideoElement>() | |
}) | |
return refs | |
}, [variantKeys.join(',')]) | |
// Track which variant is currently playing | |
const [playingVariant, setPlayingVariant] = React.useState<string | null>(null) | |
// Play handler: pause all others, sync time | |
const handlePlay = (variant: string) => { | |
setPlayingVariant(variant) | |
variantKeys.forEach((v) => { | |
if (v !== variant && videoRefs[v]?.current) { | |
videoRefs[v]?.current?.pause() | |
} | |
}) | |
} | |
// Pause handler | |
const handlePause = (variant: string) => { | |
if (playingVariant === variant) setPlayingVariant(null) | |
} | |
// When selectedVariant changes, sync playback position, rewind, and play state | |
React.useEffect(() => { | |
if (!selectedVariant) return | |
// Rewind playbackTimeRef by rewindSeconds, clamp to 0 | |
playbackTimeRef.current = Math.max(0, playbackTimeRef.current - rewindSeconds) | |
// Set all videos to the new time, pause all except selected | |
variantKeys.forEach((v) => { | |
const ref = videoRefs[v]?.current | |
if (ref) { | |
ref.currentTime = playbackTimeRef.current | |
if (v !== selectedVariant) { | |
ref.pause() | |
} | |
} | |
}) | |
// If a video was playing, continue playing the swapped variant | |
if (playingVariant && videoRefs[selectedVariant]?.current) { | |
videoRefs[selectedVariant].current.play() | |
} | |
}, [selectedVariant]) | |
// When the selected video plays, update playbackTimeRef | |
const handleTimeUpdate = (variantKey: string) => { | |
const ref = videoRefs[variantKey]?.current | |
if (ref && variantKey === selectedVariant) { | |
playbackTimeRef.current = ref.currentTime | |
} | |
} | |
React.useEffect(() => { | |
setSelectedVariant(variantKeys[0] || '') | |
}, [selectedVideo]) | |
if (!videoNames.length) { | |
return ( | |
<div className="w-full mt-12 flex items-center justify-center"> | |
<div className="text-gray-500"> | |
No video examples available. Please select another model and attack. | |
</div> | |
</div> | |
) | |
} | |
return ( | |
<div className="w-full overflow-auto" style={{ minHeight: '100vh' }}> | |
<div className="example-display"> | |
<div className="mb-4"> | |
<fieldset className="fieldset"> | |
<legend className="fieldset-legend">Video Example</legend> | |
<select | |
className="select select-bordered" | |
value={selectedVideo || ''} | |
onChange={(e) => { | |
setSelectedVideo(e.target.value || '') | |
}} | |
> | |
{videoNames.map((name) => ( | |
<option key={name} value={name}> | |
{name} | |
</option> | |
))} | |
</select> | |
</fieldset> | |
</div> | |
{selectedVideo && selectedVariant && variants[selectedVariant] && ( | |
<> | |
<ExampleVariantMetricsTable | |
variantMetadatas={Object.fromEntries( | |
variantKeys.map((v) => [v, variants[v]?.metadata || {}]) | |
)} | |
/> | |
<ExampleDetailsSection> | |
<ExampleVariantSelector | |
variantKeys={variantKeys} | |
selectedVariant={selectedVariant} | |
setSelectedVariant={setSelectedVariant} | |
/> | |
<ExampleVariantToggle | |
toggleMode={toggleMode} | |
setToggleMode={setToggleMode} | |
type="button" | |
selectedVariant={selectedVariant} | |
setSelectedVariant={setSelectedVariant} | |
variantKeys={variantKeys} | |
/> | |
<div className="flex items-center gap-4 mt-2"> | |
<label htmlFor="rewind-seconds" className="font-mono text-xs">Rewind Seconds:</label> | |
<input | |
id="rewind-seconds" | |
type="number" | |
min={0} | |
step={0.1} | |
value={rewindSeconds} | |
onChange={(e) => setRewindSeconds(Math.max(0, Number(e.target.value)))} | |
className="input input-bordered input-xs w-20" | |
placeholder="Seconds" | |
/> | |
<label htmlFor="video-scale" className="font-mono text-xs ml-4">Scale:</label> | |
<input | |
id="video-scale" | |
type="range" | |
min={0.3} | |
max={1} | |
step={0.01} | |
value={videoScale} | |
onChange={e => setVideoScale(Number(e.target.value))} | |
className="range range-xs w-40" | |
style={{ verticalAlign: 'middle' }} | |
/> | |
<span className="ml-2 font-mono text-xs">{(videoScale * 100).toFixed(0)}%</span> | |
</div> | |
<div className="flex flex-col items-center gap-4"> | |
{variantKeys.map((variantKey) => | |
variants[variantKey].video_url ? ( | |
<video | |
key={variantKey} | |
ref={videoRefs[variantKey]} | |
controls | |
src={variants[variantKey].video_url} | |
className="example-video" | |
style={{ | |
width: `${videoScale * 100}%`, | |
height: 'auto', | |
display: selectedVariant === variantKey ? 'block' : 'none', | |
maxWidth: '100%', | |
}} | |
onTimeUpdate={() => handleTimeUpdate(variantKey)} | |
onPlay={() => handlePlay(variantKey)} | |
onPause={() => handlePause(variantKey)} | |
/> | |
) : null | |
)} | |
</div> | |
</ExampleDetailsSection> | |
</> | |
)} | |
</div> | |
</div> | |
) | |
} | |
export default VideoGallery | |