From d4b49df05a36b12d40a34c0837603d1f172f0db7 Mon Sep 17 00:00:00 2001 From: Towsty Date: Sun, 6 Sep 2026 07:35:42 -0500 Subject: [PATCH] Let text-to-video generate without a still: Still/Text lives on the Video card, and Text mode skips the required input image. Co-authored-by: Cursor --- components/StudioKindCards.vue | 28 ++++++++++++++ pages/index.vue | 71 ++++++++++++++++------------------ 2 files changed, 62 insertions(+), 37 deletions(-) diff --git a/components/StudioKindCards.vue b/components/StudioKindCards.vue index dabe601..ff2a823 100644 --- a/components/StudioKindCards.vue +++ b/components/StudioKindCards.vue @@ -38,6 +38,26 @@

MiniMax H3

+
+ + +
(), { ltxEnabled: false, videoEngine: 'minimax', + videoStart: 'still', imageEngine: 'flux', musicEngine: 'ace' }) @@ -135,6 +157,7 @@ const props = withDefaults(defineProps<{ const emit = defineEmits<{ select: [kind: 'video' | 'image'] 'video-engine': [engine: 'minimax' | 'ltx'] + 'video-start': [start: 'still' | 'text'] 'image-engine': [engine: 'flux' | 'krea'] 'music-engine': [engine: 'ace' | 'yue'] }>() @@ -149,6 +172,11 @@ function pickVideo(engine: 'minimax' | 'ltx') { if (props.current !== 'video') emit('select', 'video') } +function pickStart(start: 'still' | 'text') { + emit('video-start', start) + if (props.current !== 'video') emit('select', 'video') +} + function pickImage(engine: 'flux' | 'krea') { emit('image-engine', engine) if (props.current !== 'image') emit('select', 'image') diff --git a/pages/index.vue b/pages/index.vue index 2fd2dcd..6c50b57 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -12,9 +12,11 @@ :current="imageStudio ? 'image' : 'video'" :ltx-enabled="ltxEnabled" :video-engine="videoEngine" + :video-start="videoStart" :image-engine="v2Engine" @select="selectStudioKind" @video-engine="selectVideoEngine" + @video-start="selectVideoStart" @image-engine="setV2Engine" />
@@ -37,11 +39,13 @@ : 'Edit: one still plus text. Compose is a separate graph if you need a second still.') : studioMode === 'edit' ? 'Drop the still to edit. An optional second still applies image 2 onto image 1. Files leave the desktop after save.' - : 'Drop a still, describe the motion, then send the job. Generate stays available — a long shot list takes one queue slot, and you can line up more while it runs.' }}

+ : (textToVideo + ? 'Text-to-video: write a prompt and generate. No start still required. Shot 2+ still continues from the last frame.' + : 'Drop a still, describe the motion, then send the job. Switch the Video card to Text for text-to-video.') }}

{{ textToVideo ? 'Text-to-video does not need a still. Shot 2+ will still extend from the last frame.' : 'Browse the library or upload a new PNG, JPG, or WEBP' }}

+
+ + Text-to-video does not need one. Shot 2+ still extends from the last frame. +
-
-

Start from

-
- - -
-

{{ videoModelHint }}

-
+

{{ videoModelHint }}

MiniMax graph

@@ -7468,6 +7456,11 @@ function selectVideoEngine(engine: VideoEngineId) { applyEngineDefaults(engine) } +function selectVideoStart(start: VideoStartId) { + videoStart.value = start + if (studioMode.value !== 'video') selectStudioKind('video') +} + function applyVideoWorkflow(raw?: string) { const parsed = coerceVideoWorkflow(raw, ltxEnabled.value) videoEngine.value = videoEngineOf(parsed) @@ -7522,15 +7515,17 @@ async function generate() { toast('Choose a library folder before generating.') return } - try { - await ensureVideoInputStill() - } catch { - toast('Could not load the selected still for image-to-video.') - return - } - if (!textToVideo.value && !file.value && !inputStillId.value) { - toast('Load a still first. Image-to-video needs an input still.') - return + if (!textToVideo.value) { + try { + await ensureVideoInputStill() + } catch { + toast('Could not load the selected still for image-to-video.') + return + } + if (!file.value && !inputStillId.value) { + toast('Load a still first. Image-to-video needs an input still.') + return + } } const shots = shotScriptMode.value ? parsedShots.value : null const initialPrompt = (shots?.[0]?.prompt || prompt.value).trim() @@ -7549,8 +7544,10 @@ async function generate() { try { await ensureGenerationNotifyPermission() const body = new FormData() - if (file.value) body.append('image', file.value) - if (inputStillId.value) body.append('stillId', inputStillId.value) + if (!textToVideo.value) { + if (file.value) body.append('image', file.value) + if (inputStillId.value) body.append('stillId', inputStillId.value) + } body.append('prompt', composePromptParts(promptPre.value, initialPrompt, promptPost.value) || initialPrompt) body.append('promptMid', initialPrompt) body.append('promptPre', promptPre.value.trim())