Join pre/prompt/post for Comfy, stop zero-size generates snapping to a small square, and give the phone library a real closeable preview.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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 { joinPromptParts } from '~/utils/promptParts'
|
||||
|
||||
function parsePasses(raw: string | undefined) {
|
||||
if (!raw) return [] as { prompt: string }[]
|
||||
@@ -33,16 +34,17 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
}
|
||||
|
||||
const prompt = (fields.prompt || '').trim()
|
||||
const promptMid = (fields.prompt || '').trim()
|
||||
const promptPre = (fields.promptPre || '').trim()
|
||||
const promptPost = (fields.promptPost || '').trim()
|
||||
const prompt = joinPromptParts(promptPre, promptMid, promptPost)
|
||||
const extraPasses = parsePasses(fields.passes)
|
||||
const passMode = parseImagePassMode(fields.passMode)
|
||||
const aspect = parseImageAspect(fields.aspect)
|
||||
if (!image) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'An input image is required' })
|
||||
}
|
||||
if (!prompt && aspect === 'auto') {
|
||||
if (!promptMid && aspect === 'auto') {
|
||||
throw createError({ statusCode: 400, statusMessage: 'An edit prompt is required' })
|
||||
}
|
||||
if (extraPasses.some(item => !item.prompt)) {
|
||||
|
||||
@@ -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, joinPromptParts, persistPromptWrappers } from '~/utils/promptParts'
|
||||
import type { QueuedExtension } from '~/server/utils/library'
|
||||
|
||||
function parseDuration(raw: unknown) {
|
||||
@@ -150,7 +150,7 @@ export default defineEventHandler(async (event) => {
|
||||
throw createError({ statusCode: 400, statusMessage: 'A motion prompt is required' })
|
||||
}
|
||||
const resolvedLocks = resolveGlobalLocks(fields.globalLocks || '', promptMid)
|
||||
const prompt = resolvedLocks.prompt
|
||||
const prompt = joinPromptParts(promptPre, resolvedLocks.prompt, promptPost)
|
||||
const globalLocks = resolvedLocks.locks
|
||||
const extensions = parseExtensions(fields.extensions)
|
||||
const permanenceRefs = parseFamilyPermanenceRefs(fields.permanenceRefs)
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
type ImageV2Task
|
||||
} from '~/utils/imageV2'
|
||||
import { isXaigenStudio } from '~/utils/videoModels'
|
||||
import { joinPromptParts } 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'
|
||||
@@ -135,13 +136,14 @@ export default defineEventHandler(async (event) => {
|
||||
fields.task,
|
||||
mode === 'generate' ? 't2i' : mode === 'refine' ? 'refine' : 'scene'
|
||||
)
|
||||
const prompt = String(fields.prompt || '').trim()
|
||||
const promptMid = String(fields.prompt || '').trim()
|
||||
const promptPre = String(fields.promptPre || '').trim()
|
||||
const promptPost = String(fields.promptPost || '').trim()
|
||||
const prompt = joinPromptParts(promptPre, promptMid, promptPost)
|
||||
const extraPasses = parsePasses(fields.passes)
|
||||
const passMode = parseImagePassMode(fields.passMode)
|
||||
const aspect = parseImageAspect(fields.aspect)
|
||||
if (!prompt && (mode === 'generate' || aspect === 'auto')) {
|
||||
if (!promptMid && (mode === 'generate' || aspect === 'auto')) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'A prompt is required' })
|
||||
}
|
||||
if (extraPasses.length && (mode === 'refine' || mode === 'generate')) {
|
||||
|
||||
@@ -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 { composePromptParts } from '~/utils/promptParts'
|
||||
import { joinPromptParts } from '~/utils/promptParts'
|
||||
|
||||
export type EditImageFile = { filename: string; data: Buffer; type?: string }
|
||||
|
||||
@@ -68,7 +68,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 = composePromptParts(library.promptPre || '', mid, library.promptPost || '')
|
||||
const prompt = joinPromptParts(library.promptPre, mid, library.promptPost)
|
||||
const last = index === prompts.length - 1
|
||||
if (index > 0) {
|
||||
await ensureComfyReady((status) => {
|
||||
|
||||
@@ -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 { composePromptParts } from '~/utils/promptParts'
|
||||
import { joinPromptParts } from '~/utils/promptParts'
|
||||
import { parseImageV2Engine, type ImageV2Engine, type ImageV2Mode, type ImageV2Task } from '~/utils/imageV2'
|
||||
import type { EditImageFile } from '~/server/utils/imageChain'
|
||||
|
||||
@@ -195,7 +195,7 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
mode: params.mode,
|
||||
engine,
|
||||
task: params.task,
|
||||
prompt: composePromptParts(library.promptPre || '', prompt, library.promptPost || ''),
|
||||
prompt: joinPromptParts(library.promptPre, prompt, library.promptPost),
|
||||
negative: params.negative,
|
||||
imageAName: uploaded?.name,
|
||||
imageBName: uploadedRef?.name,
|
||||
@@ -211,13 +211,14 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
megapixels: params.megapixels,
|
||||
width: params.width,
|
||||
height: params.height,
|
||||
aspect: params.aspect,
|
||||
turbo: params.turbo === true,
|
||||
filenamePrefix: chainTotal > 1 ? `${comfyJobPrefix(job.id)}_p${index + 1}` : comfyJobPrefix(job.id),
|
||||
kreaUnetName: krea?.unet,
|
||||
kreaClipName: krea?.clip,
|
||||
kreaVaeName: krea?.vae,
|
||||
kreaConceptLora: krea?.conceptLora,
|
||||
loraStack: params.loraStack
|
||||
loraStack: params.loraStack?.length ? params.loraStack : (library.loraStack || undefined)
|
||||
})
|
||||
const queued = await queuePrompt(built.graph, job.clientId)
|
||||
job.promptId = queued.prompt_id
|
||||
|
||||
@@ -70,6 +70,7 @@ export interface ImageV2BuildParams {
|
||||
strength?: number
|
||||
width?: number
|
||||
height?: number
|
||||
aspect?: string
|
||||
turbo?: boolean
|
||||
snofsModel?: number
|
||||
snofsClip?: number
|
||||
@@ -458,7 +459,7 @@ function buildKreaGenerateWorkflow(params: ImageV2BuildParams) {
|
||||
const graph = structuredClone(kreaGenerateTemplate) as WorkflowGraph
|
||||
const prompt = composeImageV2Prompt('generate', 't2i', params.prompt)
|
||||
const negative = String(params.negative || '')
|
||||
const size = clampImageV2Size(params.width, params.height)
|
||||
const size = clampImageV2Size(params.width, params.height, params.aspect)
|
||||
const steps = clampImageSteps(params.steps, IMAGE_V2_KREA_STEPS)
|
||||
const cfg = clampImageCfg(params.cfg, IMAGE_V2_KREA_CFG)
|
||||
const workflowFile = IMAGE_V2_KREA_GENERATE_WORKFLOW
|
||||
@@ -520,7 +521,7 @@ export function buildImageV2Workflow(params: ImageV2BuildParams) {
|
||||
const steps = clampImageSteps(params.steps, 24)
|
||||
const cfg = clampImageCfg(params.cfg, 4)
|
||||
const strength = refine ? clampImageV2Denoise(params.strength, IMAGE_V2_DENOISE_DEFAULT) : undefined
|
||||
const size = generate ? clampImageV2Size(params.width, params.height) : null
|
||||
const size = generate ? clampImageV2Size(params.width, params.height, params.aspect) : null
|
||||
const workflowFile = imageV2WorkflowFile(params.mode, engine)
|
||||
|
||||
if (!generate) setInput(graph, LOAD_A, 'image', params.imageAName || '')
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
updateShotQueue
|
||||
} from '~/server/utils/shotQueue'
|
||||
import { composeShotPrompt, allowIdentityRefs, type PermanenceRef } from '~/utils/globalLocks'
|
||||
import { composePromptParts, resolvePromptWrappers } from '~/utils/promptParts'
|
||||
import { composePromptParts, joinPromptParts, resolvePromptWrappers } from '~/utils/promptParts'
|
||||
import type { QueuedExtension } from '~/server/utils/library'
|
||||
import { persistLoraFields, ensureComfyLoraNames } from '~/server/utils/loras'
|
||||
import { readLoraStack, resolveLoraStack } from '~/utils/loras'
|
||||
@@ -150,7 +150,10 @@ export async function queueMiniMax(
|
||||
job.status = 'uploading'
|
||||
const chainIndex = job.library?.chainIndex || 0
|
||||
const graphId = chainIndex > 0 ? workflowForExtension(params.workflow) : params.workflow
|
||||
if (job.library) Object.assign(job.library, persistLoraFields(params.loraStack || params.loraName || job.library.loraStack || job.library.loraName))
|
||||
if (job.library) {
|
||||
const stack = resolveLoraStack(job.library.loraStack || job.library.loraName, params.loraStack || params.loraName)
|
||||
if (stack.length) Object.assign(job.library, persistLoraFields(stack))
|
||||
}
|
||||
const engineName = isLtxWorkflow(graphId) ? 'LTX-2.3' : 'MiniMax H3'
|
||||
const hasImage = Boolean(params.image?.data?.length)
|
||||
const uploading = !hasImage
|
||||
@@ -188,7 +191,7 @@ export async function queueMiniMax(
|
||||
const shotIndex = job.library?.chainIndex || 0
|
||||
const composedPrompt = composeShotPrompt({
|
||||
globalLocks: job.library?.globalLocks || params.globalLocks,
|
||||
prompt: composePromptParts(job.library?.promptPre || '', params.prompt, job.library?.promptPost || ''),
|
||||
prompt: joinPromptParts(job.library?.promptPre, params.prompt, job.library?.promptPost),
|
||||
shotIndex,
|
||||
familyRefs: job.library?.permanenceRefs || params.permanenceRefs,
|
||||
shotRefs: job.library?.shotPermanenceRefs?.[shotIndex] || params.shotPermanenceRefs?.[shotIndex]
|
||||
@@ -417,7 +420,7 @@ export async function continueQueuedExtensions(
|
||||
break
|
||||
}
|
||||
await queueMiniMax(job, {
|
||||
prompt: ext.prompt,
|
||||
prompt: joinPromptParts(wrappers.promptPre, ext.prompt, wrappers.promptPost),
|
||||
image: { filename: 'last_frame.png', data: frame, type: 'image/png' },
|
||||
width: params.width,
|
||||
height: params.height,
|
||||
|
||||
Reference in New Issue
Block a user