File size: 7,470 Bytes
503a577
 
eb27538
08dfd47
eb27538
 
013aff2
503a577
 
 
 
 
 
 
 
 
 
 
 
 
eb27538
 
 
 
 
 
013aff2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eb27538
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503a577
eb27538
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
503a577
eb27538
 
08dfd47
 
 
 
eb27538
 
08dfd47
 
 
 
 
013aff2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eb27538
013aff2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eb27538
 
 
 
 
 
503a577
 
 
 
 
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
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