Join pre/prompt/post into the first MiniMax prompt, not only later shots.

The motion prompt field is what Comfy actually runs. Wrappers are now composed into that string on submit, and shot 1 falls back to it if the separate pre/post fields do not arrive.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-03 07:24:00 -05:00
co-authored by Cursor
parent 1206afe8fd
commit ce85e7f513
8 changed files with 88 additions and 33 deletions
+6 -4
View File
@@ -4,6 +4,7 @@ import { assertImageScaleToTotalPixelsNode, comfyConfigured } from '~/server/uti
import { imageDimensions } from '~/server/utils/resolution'
import { parseImageAspect } from '~/utils/imageAspect'
import { clampImageCfg, clampImageScaleMegapixels, clampImageSteps, parseImagePassMode, parseImageScaleToTotalPixels } from '~/utils/generationPresets'
import { ingestPromptParts } from '~/utils/promptParts'
function parsePasses(raw: string | undefined) {
if (!raw) return [] as { prompt: string }[]
@@ -33,10 +34,11 @@ export default defineEventHandler(async (event) => {
}
}
const promptMid = (fields.prompt || '').trim()
const promptPre = (fields.promptPre || '').trim()
const promptPost = (fields.promptPost || '').trim()
const prompt = promptMid
const posted = ingestPromptParts(fields)
const promptMid = posted.promptMid
const promptPre = posted.promptPre
const promptPost = posted.promptPost
const prompt = posted.prompt
const extraPasses = parsePasses(fields.passes)
const passMode = parseImagePassMode(fields.passMode)
const aspect = parseImageAspect(fields.aspect)
+10 -6
View File
@@ -4,7 +4,7 @@ import { listStudioLoras, parsePostedLoraStack, parseShotLoraStacks, persistLora
import { clampVideoCfg } from '~/utils/generationPresets'
import { defaultVideoSteps, isLtxWorkflow, isTextToVideo, LTX_DISABLED_MESSAGE, ltxWorkflowEnabled, parseVideoWorkflow, videoEngineOf } from '~/utils/videoModels'
import { allowIdentityRefs, normalizePermanenceRefs, resolveGlobalLocks, type PermanenceRef } from '~/utils/globalLocks'
import { composePromptParts, persistPromptWrappers } from '~/utils/promptParts'
import { composePromptParts, ingestPromptParts, persistPromptWrappers } from '~/utils/promptParts'
import type { QueuedExtension } from '~/server/utils/library'
function parseDuration(raw: unknown) {
@@ -143,14 +143,18 @@ export default defineEventHandler(async (event) => {
if (!image && !textToVideo) {
throw createError({ statusCode: 400, statusMessage: 'An initial image is required' })
}
const promptMid = (fields.prompt || '').trim()
const promptPre = (fields.promptPre || '').trim()
const promptPost = (fields.promptPost || '').trim()
const posted = ingestPromptParts(fields)
const promptPre = posted.promptPre
const promptPost = posted.promptPost
let promptMid = posted.promptMid
if (!composePromptParts(promptPre, promptMid, promptPost)) {
throw createError({ statusCode: 400, statusMessage: 'A motion prompt is required' })
}
const resolvedLocks = resolveGlobalLocks(fields.globalLocks || '', promptMid)
const prompt = resolvedLocks.prompt
promptMid = resolvedLocks.prompt
const prompt = (promptPre || promptPost)
? composePromptParts(promptPre, promptMid, promptPost)
: (posted.prompt || promptMid)
const globalLocks = resolvedLocks.locks
const extensions = parseExtensions(fields.extensions)
const permanenceRefs = parseFamilyPermanenceRefs(fields.permanenceRefs)
@@ -251,7 +255,7 @@ export default defineEventHandler(async (event) => {
familyId,
payload: {
prompt,
promptMid: prompt || undefined,
promptMid: promptMid || undefined,
promptPre: promptPre || undefined,
promptPost: promptPost || undefined,
name: clipName,
+6 -4
View File
@@ -27,6 +27,7 @@ import {
type ImageV2Task
} from '~/utils/imageV2'
import { isXaigenStudio } from '~/utils/videoModels'
import { ingestPromptParts } from '~/utils/promptParts'
import { listStudioLoras, parsePostedLoraStack, persistLoraFields, assertImageV2LoraStack } from '~/server/utils/loras'
import { getStill, rememberInputStill, stillPath } from '~/server/utils/library'
import { existsSync, readFileSync } from 'node:fs'
@@ -136,10 +137,11 @@ export default defineEventHandler(async (event) => {
fields.task,
mode === 'generate' ? 't2i' : mode === 'refine' ? 'refine' : 'scene'
)
const promptMid = String(fields.prompt || '').trim()
const promptPre = String(fields.promptPre || '').trim()
const promptPost = String(fields.promptPost || '').trim()
const prompt = promptMid
const posted = ingestPromptParts(fields)
const promptMid = posted.promptMid
const promptPre = posted.promptPre
const promptPost = posted.promptPost
const prompt = posted.prompt
const extraPasses = parsePasses(fields.passes)
const passMode = parseImagePassMode(fields.passMode)
const aspect = parseImageAspect(fields.aspect)
+10 -3
View File
@@ -8,7 +8,7 @@ import { imageDimensions } from '~/server/utils/resolution'
import { emitChainJob } from '~/server/utils/watch'
import { saveStill, stillChainName } from '~/server/utils/library'
import { comfyJobPrefix } from '~/utils/outputNames'
import { joinPromptParts } from '~/utils/promptParts'
import { restorePromptParts, wrappedPromptForComfy } from '~/utils/promptParts'
export type EditImageFile = { filename: string; data: Buffer; type?: string }
@@ -32,7 +32,14 @@ export type EditRunParams = {
export async function runEdit(job: Job, params: EditRunParams) {
const library = job.library
if (!library) throw new Error('Edit job is missing library metadata')
const prompts = [params.prompt, ...params.passes.map(item => item.prompt)]
const prompts = [
library.promptMid || restorePromptParts(params.prompt, {
pre: library.promptPre,
prompt: params.prompt,
post: library.promptPost
}).prompt,
...params.passes.map(item => item.prompt)
]
const chainTotal = prompts.length
const batch = params.passMode !== 'chain'
const unit = batch ? 'Batch' : 'Pass'
@@ -68,7 +75,7 @@ export async function runEdit(job: Job, params: EditRunParams) {
if (job.status === 'cancelled') throw new Error('Job interrupted.')
if (index > 0 && library.stopAfterCurrent === true) break
const mid = (batch || index === 0) ? composeFillPrompt(prompts[index], filled.padded) : prompts[index]
const prompt = joinPromptParts(library.promptPre, mid, library.promptPost)
const prompt = wrappedPromptForComfy(library, mid, index === 0 ? params.prompt : mid)
const last = index === prompts.length - 1
if (index > 0) {
await ensureComfyReady((status) => {
+10 -3
View File
@@ -8,7 +8,7 @@ import { imageDimensions } from '~/server/utils/resolution'
import { emitChainJob } from '~/server/utils/watch'
import { saveStill, stillChainName } from '~/server/utils/library'
import { comfyJobPrefix } from '~/utils/outputNames'
import { joinPromptParts } from '~/utils/promptParts'
import { restorePromptParts, wrappedPromptForComfy } from '~/utils/promptParts'
import { isXaigenStudio } from '~/utils/videoModels'
import {
imageV2StackSpecials,
@@ -69,7 +69,14 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
const extraPasses = canChain
? (params.passes || []).map(item => ({ prompt: String(item.prompt || '').trim() })).filter(item => item.prompt)
: []
const prompts = [params.prompt, ...extraPasses.map(item => item.prompt)]
const prompts = [
library.promptMid || restorePromptParts(params.prompt, {
pre: library.promptPre,
prompt: params.prompt,
post: library.promptPost
}).prompt,
...extraPasses.map(item => item.prompt)
]
const chainTotal = prompts.length
const batch = params.passMode !== 'chain'
const unit = batch ? 'Batch' : 'Pass'
@@ -218,7 +225,7 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
mode: params.mode,
engine,
task: params.task,
prompt: joinPromptParts(library.promptPre, prompt, library.promptPost),
prompt: wrappedPromptForComfy(library, prompt, index === 0 ? params.prompt : prompt),
negative: params.negative,
imageAName: uploaded?.name,
imageBName: uploadedRef?.name,
+1 -1
View File
@@ -1179,7 +1179,7 @@ export async function startStudioJob(item: StudioJob) {
}
void runGeneration(job, {
prompt: payload.promptMid || payload.prompt,
prompt: payload.prompt,
image: stillFile,
width: payload.width,
height: payload.height,