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 <[email protected]>
Claude-Session: https://claude.ai/code/session_01AXQxVhXBw8PwFAtsVrXSmj
This commit is contained in:
2026-07-12 10:53:23 -04:00
co-authored by Claude Fable 5
parent b83edeb9ef
commit 1934efb0b5
8 changed files with 448 additions and 11 deletions
+6 -1
View File
@@ -2,12 +2,13 @@
import { persistentStore } from "../stores/persistent";
import ChatInterface from "../components/playground/ChatInterface.svelte";
import ImageInterface from "../components/playground/ImageInterface.svelte";
import VideoInterface from "../components/playground/VideoInterface.svelte";
import AudioInterface from "../components/playground/AudioInterface.svelte";
import SpeechInterface from "../components/playground/SpeechInterface.svelte";
import RerankInterface from "../components/playground/RerankInterface.svelte";
import ConcurrencyInterface from "../components/playground/ConcurrencyInterface.svelte";
type Tab = "chat" | "images" | "speech" | "audio" | "rerank" | "concurrency";
type Tab = "chat" | "images" | "video" | "speech" | "audio" | "rerank" | "concurrency";
const selectedTabStore = persistentStore<Tab>("playground-selected-tab", "chat");
let mobileMenuOpen = $state(false);
@@ -15,6 +16,7 @@
const tabs: { id: Tab; label: string }[] = [
{ id: "chat", label: "Chat" },
{ id: "images", label: "Images" },
{ id: "video", label: "Video" },
{ id: "speech", label: "Speech" },
{ id: "audio", label: "Transcription" },
{ id: "rerank", label: "Rerank" },
@@ -92,6 +94,9 @@
<div class="h-full" class:tab-hidden={$selectedTabStore !== "images"}>
<ImageInterface />
</div>
<div class="h-full" class:tab-hidden={$selectedTabStore !== "video"}>
<VideoInterface />
</div>
<div class="h-full" class:tab-hidden={$selectedTabStore !== "speech"}>
<SpeechInterface />
</div>