feat(ui): Video playground tab + video capability plumbing

New Video tab in the Playground: model selector filtered to
video_generation/image_to_video capabilities, prompt + negative prompt,
size/frames/fps/steps/guidance/seed knobs (0 = model default), optional
conditioning-image upload (image-to-video), elapsed-time spinner for
the minutes-long blocking call, inline <video> playback + download.
Calls POST /v1/videos/sync (multipart); resolution rides as both
width/height and size so either upstream convention honors it.

Config side: "video" joins the valid capability modalities, and
/v1/models maps text->video to video_generation and image->video to
image_to_video, mirroring the image mappings. Declare e.g.:

  videogen-wan22-5b:
    capabilities:
      in: [text, image]
      out: [video]

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AXQxVhXBw8PwFAtsVrXSmj
This commit is contained in:
2026-07-12 10:53:23 -04:00
parent b83edeb9ef
commit 1934efb0b5
8 changed files with 448 additions and 11 deletions
+11 -6
View File
@@ -296,20 +296,25 @@ func TestConfig_ModelCapabilities_Validate(t *testing.T) {
assert.NoError(t, caps.Validate())
})
t.Run("valid_video_modality", func(t *testing.T) {
caps := ModelCapConfig{In: []string{"text", "image"}, Out: []string{"video"}}
assert.NoError(t, caps.Validate())
})
t.Run("invalid_in_modality", func(t *testing.T) {
caps := ModelCapConfig{In: []string{"video"}}
caps := ModelCapConfig{In: []string{"smell"}}
err := caps.Validate()
assert.Error(t, err)
assert.Contains(t, err.Error(), "capabilities.in")
assert.Contains(t, err.Error(), "video")
assert.Contains(t, err.Error(), "smell")
})
t.Run("invalid_out_modality", func(t *testing.T) {
caps := ModelCapConfig{Out: []string{"video"}}
caps := ModelCapConfig{Out: []string{"smell"}}
err := caps.Validate()
assert.Error(t, err)
assert.Contains(t, err.Error(), "capabilities.out")
assert.Contains(t, err.Error(), "video")
assert.Contains(t, err.Error(), "smell")
})
t.Run("negative_context", func(t *testing.T) {
@@ -327,10 +332,10 @@ models:
capabilities:
in:
- text
- video
- smell
`
_, err := LoadConfigFromReader(strings.NewReader(content))
assert.Error(t, err)
assert.Contains(t, err.Error(), "video")
assert.Contains(t, err.Error(), "smell")
})
}