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:
+3
-3
@@ -6686,7 +6686,7 @@ async function editImage() {
|
|||||||
if (inputStillId.value) body.append('stillId', inputStillId.value)
|
if (inputStillId.value) body.append('stillId', inputStillId.value)
|
||||||
if (extraPasses.length) body.append('passes', JSON.stringify(extraPasses))
|
if (extraPasses.length) body.append('passes', JSON.stringify(extraPasses))
|
||||||
body.append('passMode', imagePassMode.value)
|
body.append('passMode', imagePassMode.value)
|
||||||
if (imageLoraStack.value.length) body.append('loraStack', JSON.stringify(imageLoraStack.value))
|
body.append('loraStack', JSON.stringify(imageLoraStack.value))
|
||||||
const downloadName = clipFileName(clipName.value.trim() || autoOutputName.value)
|
const downloadName = clipFileName(clipName.value.trim() || autoOutputName.value)
|
||||||
const started = await $fetch<{
|
const started = await $fetch<{
|
||||||
jobId: string
|
jobId: string
|
||||||
@@ -6942,7 +6942,7 @@ async function editImageV2() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (v2Stack.length) body.append('loraStack', JSON.stringify(v2Stack))
|
body.append('loraStack', JSON.stringify(v2Stack))
|
||||||
const downloadName = clipFileName(clipName.value.trim() || autoOutputName.value)
|
const downloadName = clipFileName(clipName.value.trim() || autoOutputName.value)
|
||||||
const started = await $fetch<{
|
const started = await $fetch<{
|
||||||
jobId: string
|
jobId: string
|
||||||
@@ -7154,7 +7154,7 @@ async function generate() {
|
|||||||
const shotLoraStacksList = shots
|
const shotLoraStacksList = shots
|
||||||
? shots.map(shot => shotLoraStacks.value[shot.n] || [])
|
? shots.map(shot => shotLoraStacks.value[shot.n] || [])
|
||||||
: [shotLoraStacks.value[1] || [], ...queued.map(item => item.loraStack || [])]
|
: [shotLoraStacks.value[1] || [], ...queued.map(item => item.loraStack || [])]
|
||||||
if (videoLoraStack.value.length) body.append('loraStack', JSON.stringify(videoLoraStack.value))
|
body.append('loraStack', JSON.stringify(videoLoraStack.value))
|
||||||
body.append('shotLoraStacks', JSON.stringify(shotLoraStacksList))
|
body.append('shotLoraStacks', JSON.stringify(shotLoraStacksList))
|
||||||
if (activeDraftId) body.append('draftId', activeDraftId)
|
if (activeDraftId) body.append('draftId', activeDraftId)
|
||||||
if (queued.length) {
|
if (queued.length) {
|
||||||
|
|||||||
+25
-2
@@ -200,8 +200,23 @@
|
|||||||
</template>
|
</template>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<aside class="glass w-full shrink-0 overflow-y-auto rounded-3xl p-4 sm:p-5 lg:h-full lg:w-[22rem]">
|
<aside
|
||||||
|
class="glass overflow-y-auto p-4 sm:p-5"
|
||||||
|
:class="focused
|
||||||
|
? 'fixed inset-0 z-[60] rounded-none lg:static lg:z-auto lg:h-full lg:w-[22rem] lg:shrink-0 lg:rounded-3xl'
|
||||||
|
: 'hidden w-full shrink-0 rounded-3xl lg:block lg:h-full lg:w-[22rem]'"
|
||||||
|
>
|
||||||
<template v-if="focused">
|
<template v-if="focused">
|
||||||
|
<div class="mb-3 flex items-center justify-between lg:hidden">
|
||||||
|
<p class="text-sm text-zinc-400">Preview</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="rounded-full border border-white/20 bg-zinc-950 px-3 py-1.5 text-sm text-amber-100"
|
||||||
|
@click="clearFocus"
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="relative overflow-hidden rounded-2xl bg-zinc-900">
|
<div class="relative overflow-hidden rounded-2xl bg-zinc-900">
|
||||||
<span v-if="detailsHidden" class="sensitive-glow block aspect-video w-full" />
|
<span v-if="detailsHidden" class="sensitive-glow block aspect-video w-full" />
|
||||||
<video
|
<video
|
||||||
@@ -494,7 +509,7 @@ const previewVideo = computed(() => {
|
|||||||
const previewStillSrc = computed(() => {
|
const previewStillSrc = computed(() => {
|
||||||
if (focused.value?.kind !== 'still' || !focused.value.stillGroup) return ''
|
if (focused.value?.kind !== 'still' || !focused.value.stillGroup) return ''
|
||||||
const id = focusedPartId.value || focused.value.stillGroup.head.id
|
const id = focusedPartId.value || focused.value.stillGroup.head.id
|
||||||
return `/api/library/stills/${id}?thumb=1`
|
return `/api/library/stills/${id}`
|
||||||
})
|
})
|
||||||
const focusedParts = computed(() => {
|
const focusedParts = computed(() => {
|
||||||
if (focused.value?.kind === 'clip') return focused.value.group?.parts || []
|
if (focused.value?.kind === 'clip') return focused.value.group?.parts || []
|
||||||
@@ -719,6 +734,13 @@ function clearSelection() {
|
|||||||
lastSelectedKey.value = ''
|
lastSelectedKey.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearFocus() {
|
||||||
|
focusedKey.value = ''
|
||||||
|
focusedPartId.value = ''
|
||||||
|
detailsHidden.value = false
|
||||||
|
cancelRename()
|
||||||
|
}
|
||||||
|
|
||||||
function selectAll() {
|
function selectAll() {
|
||||||
selectedKeys.value = entries.value.map(entry => entry.key)
|
selectedKeys.value = entries.value.map(entry => entry.key)
|
||||||
lastSelectedKey.value = selectedKeys.value.at(-1) || ''
|
lastSelectedKey.value = selectedKeys.value.at(-1) || ''
|
||||||
@@ -978,6 +1000,7 @@ function onKey(event: KeyboardEvent) {
|
|||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
if (pendingDelete.value) pendingDelete.value = null
|
if (pendingDelete.value) pendingDelete.value = null
|
||||||
else if (renaming.value) cancelRename()
|
else if (renaming.value) cancelRename()
|
||||||
|
else if (focusedKey.value) clearFocus()
|
||||||
else clearSelection()
|
else clearSelection()
|
||||||
}
|
}
|
||||||
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'a' && !renaming.value) {
|
if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === 'a' && !renaming.value) {
|
||||||
|
|||||||
@@ -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 { joinPromptParts } 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,16 +34,17 @@ export default defineEventHandler(async (event) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const prompt = (fields.prompt || '').trim()
|
const promptMid = (fields.prompt || '').trim()
|
||||||
const promptPre = (fields.promptPre || '').trim()
|
const promptPre = (fields.promptPre || '').trim()
|
||||||
const promptPost = (fields.promptPost || '').trim()
|
const promptPost = (fields.promptPost || '').trim()
|
||||||
|
const prompt = joinPromptParts(promptPre, promptMid, promptPost)
|
||||||
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)
|
||||||
if (!image) {
|
if (!image) {
|
||||||
throw createError({ statusCode: 400, statusMessage: 'An input image is required' })
|
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' })
|
throw createError({ statusCode: 400, statusMessage: 'An edit prompt is required' })
|
||||||
}
|
}
|
||||||
if (extraPasses.some(item => !item.prompt)) {
|
if (extraPasses.some(item => !item.prompt)) {
|
||||||
|
|||||||
@@ -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, joinPromptParts, 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) {
|
||||||
@@ -150,7 +150,7 @@ export default defineEventHandler(async (event) => {
|
|||||||
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
|
const prompt = joinPromptParts(promptPre, resolvedLocks.prompt, promptPost)
|
||||||
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)
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
type ImageV2Task
|
type ImageV2Task
|
||||||
} from '~/utils/imageV2'
|
} from '~/utils/imageV2'
|
||||||
import { isXaigenStudio } from '~/utils/videoModels'
|
import { isXaigenStudio } from '~/utils/videoModels'
|
||||||
|
import { joinPromptParts } 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'
|
||||||
@@ -135,13 +136,14 @@ export default defineEventHandler(async (event) => {
|
|||||||
fields.task,
|
fields.task,
|
||||||
mode === 'generate' ? 't2i' : mode === 'refine' ? 'refine' : 'scene'
|
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 promptPre = String(fields.promptPre || '').trim()
|
||||||
const promptPost = String(fields.promptPost || '').trim()
|
const promptPost = String(fields.promptPost || '').trim()
|
||||||
|
const prompt = joinPromptParts(promptPre, promptMid, promptPost)
|
||||||
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)
|
||||||
if (!prompt && (mode === 'generate' || aspect === 'auto')) {
|
if (!promptMid && (mode === 'generate' || aspect === 'auto')) {
|
||||||
throw createError({ statusCode: 400, statusMessage: 'A prompt is required' })
|
throw createError({ statusCode: 400, statusMessage: 'A prompt is required' })
|
||||||
}
|
}
|
||||||
if (extraPasses.length && (mode === 'refine' || mode === 'generate')) {
|
if (extraPasses.length && (mode === 'refine' || mode === 'generate')) {
|
||||||
|
|||||||
@@ -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 { composePromptParts } from '~/utils/promptParts'
|
import { joinPromptParts } from '~/utils/promptParts'
|
||||||
|
|
||||||
export type EditImageFile = { filename: string; data: Buffer; type?: string }
|
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 (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 = composePromptParts(library.promptPre || '', mid, library.promptPost || '')
|
const prompt = joinPromptParts(library.promptPre, mid, library.promptPost)
|
||||||
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 { composePromptParts } from '~/utils/promptParts'
|
import { joinPromptParts } from '~/utils/promptParts'
|
||||||
import { parseImageV2Engine, type ImageV2Engine, type ImageV2Mode, type ImageV2Task } from '~/utils/imageV2'
|
import { parseImageV2Engine, type ImageV2Engine, type ImageV2Mode, type ImageV2Task } from '~/utils/imageV2'
|
||||||
import type { EditImageFile } from '~/server/utils/imageChain'
|
import type { EditImageFile } from '~/server/utils/imageChain'
|
||||||
|
|
||||||
@@ -195,7 +195,7 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
|||||||
mode: params.mode,
|
mode: params.mode,
|
||||||
engine,
|
engine,
|
||||||
task: params.task,
|
task: params.task,
|
||||||
prompt: composePromptParts(library.promptPre || '', prompt, library.promptPost || ''),
|
prompt: joinPromptParts(library.promptPre, prompt, library.promptPost),
|
||||||
negative: params.negative,
|
negative: params.negative,
|
||||||
imageAName: uploaded?.name,
|
imageAName: uploaded?.name,
|
||||||
imageBName: uploadedRef?.name,
|
imageBName: uploadedRef?.name,
|
||||||
@@ -211,13 +211,14 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
|||||||
megapixels: params.megapixels,
|
megapixels: params.megapixels,
|
||||||
width: params.width,
|
width: params.width,
|
||||||
height: params.height,
|
height: params.height,
|
||||||
|
aspect: params.aspect,
|
||||||
turbo: params.turbo === true,
|
turbo: params.turbo === true,
|
||||||
filenamePrefix: chainTotal > 1 ? `${comfyJobPrefix(job.id)}_p${index + 1}` : comfyJobPrefix(job.id),
|
filenamePrefix: chainTotal > 1 ? `${comfyJobPrefix(job.id)}_p${index + 1}` : comfyJobPrefix(job.id),
|
||||||
kreaUnetName: krea?.unet,
|
kreaUnetName: krea?.unet,
|
||||||
kreaClipName: krea?.clip,
|
kreaClipName: krea?.clip,
|
||||||
kreaVaeName: krea?.vae,
|
kreaVaeName: krea?.vae,
|
||||||
kreaConceptLora: krea?.conceptLora,
|
kreaConceptLora: krea?.conceptLora,
|
||||||
loraStack: params.loraStack
|
loraStack: params.loraStack?.length ? params.loraStack : (library.loraStack || undefined)
|
||||||
})
|
})
|
||||||
const queued = await queuePrompt(built.graph, job.clientId)
|
const queued = await queuePrompt(built.graph, job.clientId)
|
||||||
job.promptId = queued.prompt_id
|
job.promptId = queued.prompt_id
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export interface ImageV2BuildParams {
|
|||||||
strength?: number
|
strength?: number
|
||||||
width?: number
|
width?: number
|
||||||
height?: number
|
height?: number
|
||||||
|
aspect?: string
|
||||||
turbo?: boolean
|
turbo?: boolean
|
||||||
snofsModel?: number
|
snofsModel?: number
|
||||||
snofsClip?: number
|
snofsClip?: number
|
||||||
@@ -458,7 +459,7 @@ function buildKreaGenerateWorkflow(params: ImageV2BuildParams) {
|
|||||||
const graph = structuredClone(kreaGenerateTemplate) as WorkflowGraph
|
const graph = structuredClone(kreaGenerateTemplate) as WorkflowGraph
|
||||||
const prompt = composeImageV2Prompt('generate', 't2i', params.prompt)
|
const prompt = composeImageV2Prompt('generate', 't2i', params.prompt)
|
||||||
const negative = String(params.negative || '')
|
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 steps = clampImageSteps(params.steps, IMAGE_V2_KREA_STEPS)
|
||||||
const cfg = clampImageCfg(params.cfg, IMAGE_V2_KREA_CFG)
|
const cfg = clampImageCfg(params.cfg, IMAGE_V2_KREA_CFG)
|
||||||
const workflowFile = IMAGE_V2_KREA_GENERATE_WORKFLOW
|
const workflowFile = IMAGE_V2_KREA_GENERATE_WORKFLOW
|
||||||
@@ -520,7 +521,7 @@ export function buildImageV2Workflow(params: ImageV2BuildParams) {
|
|||||||
const steps = clampImageSteps(params.steps, 24)
|
const steps = clampImageSteps(params.steps, 24)
|
||||||
const cfg = clampImageCfg(params.cfg, 4)
|
const cfg = clampImageCfg(params.cfg, 4)
|
||||||
const strength = refine ? clampImageV2Denoise(params.strength, IMAGE_V2_DENOISE_DEFAULT) : undefined
|
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)
|
const workflowFile = imageV2WorkflowFile(params.mode, engine)
|
||||||
|
|
||||||
if (!generate) setInput(graph, LOAD_A, 'image', params.imageAName || '')
|
if (!generate) setInput(graph, LOAD_A, 'image', params.imageAName || '')
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import {
|
|||||||
updateShotQueue
|
updateShotQueue
|
||||||
} from '~/server/utils/shotQueue'
|
} from '~/server/utils/shotQueue'
|
||||||
import { composeShotPrompt, allowIdentityRefs, type PermanenceRef } from '~/utils/globalLocks'
|
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 type { QueuedExtension } from '~/server/utils/library'
|
||||||
import { persistLoraFields, ensureComfyLoraNames } from '~/server/utils/loras'
|
import { persistLoraFields, ensureComfyLoraNames } from '~/server/utils/loras'
|
||||||
import { readLoraStack, resolveLoraStack } from '~/utils/loras'
|
import { readLoraStack, resolveLoraStack } from '~/utils/loras'
|
||||||
@@ -150,7 +150,10 @@ export async function queueMiniMax(
|
|||||||
job.status = 'uploading'
|
job.status = 'uploading'
|
||||||
const chainIndex = job.library?.chainIndex || 0
|
const chainIndex = job.library?.chainIndex || 0
|
||||||
const graphId = chainIndex > 0 ? workflowForExtension(params.workflow) : params.workflow
|
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 engineName = isLtxWorkflow(graphId) ? 'LTX-2.3' : 'MiniMax H3'
|
||||||
const hasImage = Boolean(params.image?.data?.length)
|
const hasImage = Boolean(params.image?.data?.length)
|
||||||
const uploading = !hasImage
|
const uploading = !hasImage
|
||||||
@@ -188,7 +191,7 @@ export async function queueMiniMax(
|
|||||||
const shotIndex = job.library?.chainIndex || 0
|
const shotIndex = job.library?.chainIndex || 0
|
||||||
const composedPrompt = composeShotPrompt({
|
const composedPrompt = composeShotPrompt({
|
||||||
globalLocks: job.library?.globalLocks || params.globalLocks,
|
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,
|
shotIndex,
|
||||||
familyRefs: job.library?.permanenceRefs || params.permanenceRefs,
|
familyRefs: job.library?.permanenceRefs || params.permanenceRefs,
|
||||||
shotRefs: job.library?.shotPermanenceRefs?.[shotIndex] || params.shotPermanenceRefs?.[shotIndex]
|
shotRefs: job.library?.shotPermanenceRefs?.[shotIndex] || params.shotPermanenceRefs?.[shotIndex]
|
||||||
@@ -417,7 +420,7 @@ export async function continueQueuedExtensions(
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
await queueMiniMax(job, {
|
await queueMiniMax(job, {
|
||||||
prompt: ext.prompt,
|
prompt: joinPromptParts(wrappers.promptPre, ext.prompt, wrappers.promptPost),
|
||||||
image: { filename: 'last_frame.png', data: frame, type: 'image/png' },
|
image: { filename: 'last_frame.png', data: frame, type: 'image/png' },
|
||||||
width: params.width,
|
width: params.width,
|
||||||
height: params.height,
|
height: params.height,
|
||||||
|
|||||||
+1
-1
@@ -126,7 +126,7 @@ export function clampImageV2Denoise(raw: unknown, fallback = IMAGE_V2_DENOISE_DE
|
|||||||
|
|
||||||
export function snapImageV2Side(raw: unknown, fallback = IMAGE_V2_GENERATE_WIDTH) {
|
export function snapImageV2Side(raw: unknown, fallback = IMAGE_V2_GENERATE_WIDTH) {
|
||||||
const value = Number(raw)
|
const value = Number(raw)
|
||||||
if (!Number.isFinite(value)) return fallback
|
if (!Number.isFinite(value) || value <= 0) return fallback
|
||||||
return IMAGE_V2_SIZE_SIDES.reduce((best, side) => (
|
return IMAGE_V2_SIZE_SIDES.reduce((best, side) => (
|
||||||
Math.abs(side - value) < Math.abs(best - value) ? side : best
|
Math.abs(side - value) < Math.abs(best - value) ? side : best
|
||||||
), IMAGE_V2_SIZE_SIDES[0])
|
), IMAGE_V2_SIZE_SIDES[0])
|
||||||
|
|||||||
+21
-5
@@ -15,6 +15,16 @@ export function composePromptParts(pre: string, prompt: string, post: string) {
|
|||||||
.join('\n\n')
|
.join('\n\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Combine wrappers for Comfy. If mid already includes pre/post, do not wrap twice. */
|
||||||
|
export function joinPromptParts(pre?: string, mid?: string, post?: string) {
|
||||||
|
const parts = restorePromptParts(mid || '', {
|
||||||
|
pre: pre || '',
|
||||||
|
prompt: mid || '',
|
||||||
|
post: post || ''
|
||||||
|
})
|
||||||
|
return composePromptParts(parts.pre, parts.prompt, parts.post)
|
||||||
|
}
|
||||||
|
|
||||||
export function applyPromptParts(parts: PromptParts, middle: string) {
|
export function applyPromptParts(parts: PromptParts, middle: string) {
|
||||||
return composePromptParts(parts.pre, middle, parts.post)
|
return composePromptParts(parts.pre, middle, parts.post)
|
||||||
}
|
}
|
||||||
@@ -45,7 +55,7 @@ function hasWrapperKey(item: object | null | undefined, key: 'promptPre' | 'prom
|
|||||||
return Boolean(item && Object.prototype.hasOwnProperty.call(item, key))
|
return Boolean(item && Object.prototype.hasOwnProperty.call(item, key))
|
||||||
}
|
}
|
||||||
|
|
||||||
/** First source that actually sent the key wins, including empty string. Omitted keys fall through. */
|
/** First non-empty wrapper wins. Blank strings fall through so a shot can inherit family pre/post. */
|
||||||
export function resolvePromptWrappers(
|
export function resolvePromptWrappers(
|
||||||
...sources: Array<PromptWrapperFields | null | undefined>
|
...sources: Array<PromptWrapperFields | null | undefined>
|
||||||
) {
|
) {
|
||||||
@@ -56,12 +66,18 @@ export function resolvePromptWrappers(
|
|||||||
for (const source of sources) {
|
for (const source of sources) {
|
||||||
if (!source) continue
|
if (!source) continue
|
||||||
if (!sawPre && hasWrapperKey(source, 'promptPre') && source.promptPre != null) {
|
if (!sawPre && hasWrapperKey(source, 'promptPre') && source.promptPre != null) {
|
||||||
promptPre = sanitizePromptPart(source.promptPre)
|
const value = sanitizePromptPart(source.promptPre)
|
||||||
sawPre = true
|
if (value.trim()) {
|
||||||
|
promptPre = value
|
||||||
|
sawPre = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!sawPost && hasWrapperKey(source, 'promptPost') && source.promptPost != null) {
|
if (!sawPost && hasWrapperKey(source, 'promptPost') && source.promptPost != null) {
|
||||||
promptPost = sanitizePromptPart(source.promptPost)
|
const value = sanitizePromptPart(source.promptPost)
|
||||||
sawPost = true
|
if (value.trim()) {
|
||||||
|
promptPost = value
|
||||||
|
sawPost = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (sawPre && sawPost) break
|
if (sawPre && sawPost) break
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user