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:
+12
-9
@@ -6869,9 +6869,10 @@ async function editImage() {
|
|||||||
const body = new FormData()
|
const body = new FormData()
|
||||||
body.append('image', file.value)
|
body.append('image', file.value)
|
||||||
if (editRefFile.value) body.append('image2', editRefFile.value)
|
if (editRefFile.value) body.append('image2', editRefFile.value)
|
||||||
body.append('prompt', prompt.value.trim())
|
body.append('prompt', composePromptParts(promptPre.value, prompt.value.trim(), promptPost.value) || prompt.value.trim())
|
||||||
if (promptPre.value.trim()) body.append('promptPre', promptPre.value.trim())
|
body.append('promptMid', prompt.value.trim())
|
||||||
if (promptPost.value.trim()) body.append('promptPost', promptPost.value.trim())
|
body.append('promptPre', promptPre.value.trim())
|
||||||
|
body.append('promptPost', promptPost.value.trim())
|
||||||
body.append('aspect', aspect.value)
|
body.append('aspect', aspect.value)
|
||||||
body.append('negative', editNegative.value)
|
body.append('negative', editNegative.value)
|
||||||
body.append('steps', String(clampImageSteps(editSteps.value)))
|
body.append('steps', String(clampImageSteps(editSteps.value)))
|
||||||
@@ -7108,9 +7109,10 @@ async function editImageV2() {
|
|||||||
}
|
}
|
||||||
body.append('aspect', aspect.value)
|
body.append('aspect', aspect.value)
|
||||||
body.append('engine', v2Engine.value)
|
body.append('engine', v2Engine.value)
|
||||||
body.append('prompt', prompt.value.trim())
|
body.append('prompt', composePromptParts(promptPre.value, prompt.value.trim(), promptPost.value) || prompt.value.trim())
|
||||||
if (promptPre.value.trim()) body.append('promptPre', promptPre.value.trim())
|
body.append('promptMid', prompt.value.trim())
|
||||||
if (promptPost.value.trim()) body.append('promptPost', promptPost.value.trim())
|
body.append('promptPre', promptPre.value.trim())
|
||||||
|
body.append('promptPost', promptPost.value.trim())
|
||||||
body.append('negative', v2Negative.value)
|
body.append('negative', v2Negative.value)
|
||||||
body.append('snofs_model', String(imageV2EngineLoras().find(item => isImageV2ConceptLora(item.name))?.strengthModel ?? 0))
|
body.append('snofs_model', String(imageV2EngineLoras().find(item => isImageV2ConceptLora(item.name))?.strengthModel ?? 0))
|
||||||
body.append('snofs_clip', String(imageV2EngineLoras().find(item => isImageV2ConceptLora(item.name))?.strengthClip ?? 0))
|
body.append('snofs_clip', String(imageV2EngineLoras().find(item => isImageV2ConceptLora(item.name))?.strengthClip ?? 0))
|
||||||
@@ -7320,9 +7322,10 @@ async function generate() {
|
|||||||
const body = new FormData()
|
const body = new FormData()
|
||||||
if (file.value) body.append('image', file.value)
|
if (file.value) body.append('image', file.value)
|
||||||
if (inputStillId.value) body.append('stillId', inputStillId.value)
|
if (inputStillId.value) body.append('stillId', inputStillId.value)
|
||||||
body.append('prompt', initialPrompt)
|
body.append('prompt', composePromptParts(promptPre.value, initialPrompt, promptPost.value) || initialPrompt)
|
||||||
if (promptPre.value.trim()) body.append('promptPre', promptPre.value.trim())
|
body.append('promptMid', initialPrompt)
|
||||||
if (promptPost.value.trim()) body.append('promptPost', promptPost.value.trim())
|
body.append('promptPre', promptPre.value.trim())
|
||||||
|
body.append('promptPost', promptPost.value.trim())
|
||||||
body.append('aspect', aspect.value)
|
body.append('aspect', aspect.value)
|
||||||
body.append('width', String(outputSize.value.width))
|
body.append('width', String(outputSize.value.width))
|
||||||
body.append('height', String(outputSize.value.height))
|
body.append('height', String(outputSize.value.height))
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { assertImageScaleToTotalPixelsNode, comfyConfigured } from '~/server/uti
|
|||||||
import { imageDimensions } from '~/server/utils/resolution'
|
import { imageDimensions } from '~/server/utils/resolution'
|
||||||
import { parseImageAspect } from '~/utils/imageAspect'
|
import { parseImageAspect } from '~/utils/imageAspect'
|
||||||
import { clampImageCfg, clampImageScaleMegapixels, clampImageSteps, parseImagePassMode, parseImageScaleToTotalPixels } from '~/utils/generationPresets'
|
import { clampImageCfg, clampImageScaleMegapixels, clampImageSteps, parseImagePassMode, parseImageScaleToTotalPixels } from '~/utils/generationPresets'
|
||||||
|
import { ingestPromptParts } from '~/utils/promptParts'
|
||||||
|
|
||||||
function parsePasses(raw: string | undefined) {
|
function parsePasses(raw: string | undefined) {
|
||||||
if (!raw) return [] as { prompt: string }[]
|
if (!raw) return [] as { prompt: string }[]
|
||||||
@@ -33,10 +34,11 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const promptMid = (fields.prompt || '').trim()
|
const posted = ingestPromptParts(fields)
|
||||||
const promptPre = (fields.promptPre || '').trim()
|
const promptMid = posted.promptMid
|
||||||
const promptPost = (fields.promptPost || '').trim()
|
const promptPre = posted.promptPre
|
||||||
const prompt = promptMid
|
const promptPost = posted.promptPost
|
||||||
|
const prompt = posted.prompt
|
||||||
const extraPasses = parsePasses(fields.passes)
|
const extraPasses = parsePasses(fields.passes)
|
||||||
const passMode = parseImagePassMode(fields.passMode)
|
const passMode = parseImagePassMode(fields.passMode)
|
||||||
const aspect = parseImageAspect(fields.aspect)
|
const aspect = parseImageAspect(fields.aspect)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { listStudioLoras, parsePostedLoraStack, parseShotLoraStacks, persistLora
|
|||||||
import { clampVideoCfg } from '~/utils/generationPresets'
|
import { clampVideoCfg } from '~/utils/generationPresets'
|
||||||
import { defaultVideoSteps, isLtxWorkflow, isTextToVideo, LTX_DISABLED_MESSAGE, ltxWorkflowEnabled, parseVideoWorkflow, videoEngineOf } from '~/utils/videoModels'
|
import { defaultVideoSteps, isLtxWorkflow, isTextToVideo, LTX_DISABLED_MESSAGE, ltxWorkflowEnabled, parseVideoWorkflow, videoEngineOf } from '~/utils/videoModels'
|
||||||
import { allowIdentityRefs, normalizePermanenceRefs, resolveGlobalLocks, type PermanenceRef } from '~/utils/globalLocks'
|
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'
|
import type { QueuedExtension } from '~/server/utils/library'
|
||||||
|
|
||||||
function parseDuration(raw: unknown) {
|
function parseDuration(raw: unknown) {
|
||||||
@@ -143,14 +143,18 @@ export default defineEventHandler(async (event) => {
|
|||||||
if (!image && !textToVideo) {
|
if (!image && !textToVideo) {
|
||||||
throw createError({ statusCode: 400, statusMessage: 'An initial image is required' })
|
throw createError({ statusCode: 400, statusMessage: 'An initial image is required' })
|
||||||
}
|
}
|
||||||
const promptMid = (fields.prompt || '').trim()
|
const posted = ingestPromptParts(fields)
|
||||||
const promptPre = (fields.promptPre || '').trim()
|
const promptPre = posted.promptPre
|
||||||
const promptPost = (fields.promptPost || '').trim()
|
const promptPost = posted.promptPost
|
||||||
|
let promptMid = posted.promptMid
|
||||||
if (!composePromptParts(promptPre, promptMid, promptPost)) {
|
if (!composePromptParts(promptPre, promptMid, promptPost)) {
|
||||||
throw createError({ statusCode: 400, statusMessage: 'A motion prompt is required' })
|
throw createError({ statusCode: 400, statusMessage: 'A motion prompt is required' })
|
||||||
}
|
}
|
||||||
const resolvedLocks = resolveGlobalLocks(fields.globalLocks || '', promptMid)
|
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 globalLocks = resolvedLocks.locks
|
||||||
const extensions = parseExtensions(fields.extensions)
|
const extensions = parseExtensions(fields.extensions)
|
||||||
const permanenceRefs = parseFamilyPermanenceRefs(fields.permanenceRefs)
|
const permanenceRefs = parseFamilyPermanenceRefs(fields.permanenceRefs)
|
||||||
@@ -251,7 +255,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
familyId,
|
familyId,
|
||||||
payload: {
|
payload: {
|
||||||
prompt,
|
prompt,
|
||||||
promptMid: prompt || undefined,
|
promptMid: promptMid || undefined,
|
||||||
promptPre: promptPre || undefined,
|
promptPre: promptPre || undefined,
|
||||||
promptPost: promptPost || undefined,
|
promptPost: promptPost || undefined,
|
||||||
name: clipName,
|
name: clipName,
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import {
|
|||||||
type ImageV2Task
|
type ImageV2Task
|
||||||
} from '~/utils/imageV2'
|
} from '~/utils/imageV2'
|
||||||
import { isXaigenStudio } from '~/utils/videoModels'
|
import { isXaigenStudio } from '~/utils/videoModels'
|
||||||
|
import { ingestPromptParts } from '~/utils/promptParts'
|
||||||
import { listStudioLoras, parsePostedLoraStack, persistLoraFields, assertImageV2LoraStack } from '~/server/utils/loras'
|
import { listStudioLoras, parsePostedLoraStack, persistLoraFields, assertImageV2LoraStack } from '~/server/utils/loras'
|
||||||
import { getStill, rememberInputStill, stillPath } from '~/server/utils/library'
|
import { getStill, rememberInputStill, stillPath } from '~/server/utils/library'
|
||||||
import { existsSync, readFileSync } from 'node:fs'
|
import { existsSync, readFileSync } from 'node:fs'
|
||||||
@@ -136,10 +137,11 @@ export default defineEventHandler(async (event) => {
|
|||||||
fields.task,
|
fields.task,
|
||||||
mode === 'generate' ? 't2i' : mode === 'refine' ? 'refine' : 'scene'
|
mode === 'generate' ? 't2i' : mode === 'refine' ? 'refine' : 'scene'
|
||||||
)
|
)
|
||||||
const promptMid = String(fields.prompt || '').trim()
|
const posted = ingestPromptParts(fields)
|
||||||
const promptPre = String(fields.promptPre || '').trim()
|
const promptMid = posted.promptMid
|
||||||
const promptPost = String(fields.promptPost || '').trim()
|
const promptPre = posted.promptPre
|
||||||
const prompt = promptMid
|
const promptPost = posted.promptPost
|
||||||
|
const prompt = posted.prompt
|
||||||
const extraPasses = parsePasses(fields.passes)
|
const extraPasses = parsePasses(fields.passes)
|
||||||
const passMode = parseImagePassMode(fields.passMode)
|
const passMode = parseImagePassMode(fields.passMode)
|
||||||
const aspect = parseImageAspect(fields.aspect)
|
const aspect = parseImageAspect(fields.aspect)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { imageDimensions } from '~/server/utils/resolution'
|
|||||||
import { emitChainJob } from '~/server/utils/watch'
|
import { emitChainJob } from '~/server/utils/watch'
|
||||||
import { saveStill, stillChainName } from '~/server/utils/library'
|
import { saveStill, stillChainName } from '~/server/utils/library'
|
||||||
import { comfyJobPrefix } from '~/utils/outputNames'
|
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 }
|
export type EditImageFile = { filename: string; data: Buffer; type?: string }
|
||||||
|
|
||||||
@@ -32,7 +32,14 @@ export type EditRunParams = {
|
|||||||
export async function runEdit(job: Job, params: EditRunParams) {
|
export async function runEdit(job: Job, params: EditRunParams) {
|
||||||
const library = job.library
|
const library = job.library
|
||||||
if (!library) throw new Error('Edit job is missing library metadata')
|
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 chainTotal = prompts.length
|
||||||
const batch = params.passMode !== 'chain'
|
const batch = params.passMode !== 'chain'
|
||||||
const unit = batch ? 'Batch' : 'Pass'
|
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 (job.status === 'cancelled') throw new Error('Job interrupted.')
|
||||||
if (index > 0 && library.stopAfterCurrent === true) break
|
if (index > 0 && library.stopAfterCurrent === true) break
|
||||||
const mid = (batch || index === 0) ? composeFillPrompt(prompts[index], filled.padded) : prompts[index]
|
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
|
const last = index === prompts.length - 1
|
||||||
if (index > 0) {
|
if (index > 0) {
|
||||||
await ensureComfyReady((status) => {
|
await ensureComfyReady((status) => {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { imageDimensions } from '~/server/utils/resolution'
|
|||||||
import { emitChainJob } from '~/server/utils/watch'
|
import { emitChainJob } from '~/server/utils/watch'
|
||||||
import { saveStill, stillChainName } from '~/server/utils/library'
|
import { saveStill, stillChainName } from '~/server/utils/library'
|
||||||
import { comfyJobPrefix } from '~/utils/outputNames'
|
import { comfyJobPrefix } from '~/utils/outputNames'
|
||||||
import { joinPromptParts } from '~/utils/promptParts'
|
import { restorePromptParts, wrappedPromptForComfy } from '~/utils/promptParts'
|
||||||
import { isXaigenStudio } from '~/utils/videoModels'
|
import { isXaigenStudio } from '~/utils/videoModels'
|
||||||
import {
|
import {
|
||||||
imageV2StackSpecials,
|
imageV2StackSpecials,
|
||||||
@@ -69,7 +69,14 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
|||||||
const extraPasses = canChain
|
const extraPasses = canChain
|
||||||
? (params.passes || []).map(item => ({ prompt: String(item.prompt || '').trim() })).filter(item => item.prompt)
|
? (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 chainTotal = prompts.length
|
||||||
const batch = params.passMode !== 'chain'
|
const batch = params.passMode !== 'chain'
|
||||||
const unit = batch ? 'Batch' : 'Pass'
|
const unit = batch ? 'Batch' : 'Pass'
|
||||||
@@ -218,7 +225,7 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
|||||||
mode: params.mode,
|
mode: params.mode,
|
||||||
engine,
|
engine,
|
||||||
task: params.task,
|
task: params.task,
|
||||||
prompt: joinPromptParts(library.promptPre, prompt, library.promptPost),
|
prompt: wrappedPromptForComfy(library, prompt, index === 0 ? params.prompt : prompt),
|
||||||
negative: params.negative,
|
negative: params.negative,
|
||||||
imageAName: uploaded?.name,
|
imageAName: uploaded?.name,
|
||||||
imageBName: uploadedRef?.name,
|
imageBName: uploadedRef?.name,
|
||||||
|
|||||||
@@ -1179,7 +1179,7 @@ export async function startStudioJob(item: StudioJob) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void runGeneration(job, {
|
void runGeneration(job, {
|
||||||
prompt: payload.promptMid || payload.prompt,
|
prompt: payload.prompt,
|
||||||
image: stillFile,
|
image: stillFile,
|
||||||
width: payload.width,
|
width: payload.width,
|
||||||
height: payload.height,
|
height: payload.height,
|
||||||
|
|||||||
+33
-3
@@ -30,14 +30,44 @@ export function joinPromptParts(pre?: string, mid?: string, post?: string) {
|
|||||||
return composePromptParts(parts.pre, parts.prompt, parts.post)
|
return composePromptParts(parts.pre, parts.prompt, parts.post)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Prefer stored mid so an already-joined prompt is not the source of truth. */
|
/** Prefer stored mid when wrappers exist. If wrappers are missing, use the already-joined fallback. */
|
||||||
export function wrappedPromptForComfy(
|
export function wrappedPromptForComfy(
|
||||||
wrappers?: PromptWrapperFields | null,
|
wrappers?: PromptWrapperFields | null,
|
||||||
mid?: string,
|
mid?: string,
|
||||||
fallback?: string
|
fallback?: string
|
||||||
) {
|
) {
|
||||||
const body = sanitizePromptPart(mid || '').trim() || sanitizePromptPart(fallback || '').trim()
|
const pre = sanitizePromptPart(wrappers?.promptPre || '').trim()
|
||||||
return joinPromptParts(wrappers?.promptPre, body, wrappers?.promptPost)
|
const post = sanitizePromptPart(wrappers?.promptPost || '').trim()
|
||||||
|
const fallbackText = sanitizePromptPart(fallback || '').trim()
|
||||||
|
const midText = sanitizePromptPart(mid || '').trim()
|
||||||
|
if (pre || post) {
|
||||||
|
return composePromptParts(pre, peelPromptWrappers(midText || fallbackText, pre, post), post)
|
||||||
|
}
|
||||||
|
return fallbackText || midText
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ingestPromptParts(fields: {
|
||||||
|
prompt?: unknown
|
||||||
|
promptMid?: unknown
|
||||||
|
promptPre?: unknown
|
||||||
|
promptPost?: unknown
|
||||||
|
}) {
|
||||||
|
const promptPre = sanitizePromptPart(fields.promptPre).trim()
|
||||||
|
const promptPost = sanitizePromptPart(fields.promptPost).trim()
|
||||||
|
const postedPrompt = sanitizePromptPart(fields.prompt).trim()
|
||||||
|
const postedMid = sanitizePromptPart(fields.promptMid).trim()
|
||||||
|
const promptMid = postedMid || restorePromptParts(postedPrompt, {
|
||||||
|
pre: promptPre,
|
||||||
|
prompt: postedPrompt,
|
||||||
|
post: promptPost
|
||||||
|
}).prompt
|
||||||
|
const fromParts = composePromptParts(promptPre, promptMid, promptPost)
|
||||||
|
return {
|
||||||
|
promptPre,
|
||||||
|
promptPost,
|
||||||
|
promptMid,
|
||||||
|
prompt: (promptPre || promptPost) ? fromParts : (postedPrompt || fromParts)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyPromptParts(parts: PromptParts, middle: string) {
|
export function applyPromptParts(parts: PromptParts, middle: string) {
|
||||||
|
|||||||
Reference in New Issue
Block a user