Keep family pre/post text on shot queues and wrap the mid prompt at Comfy time.

Later shots were starting a new job without those wrappers, so only the first clip got them.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-03 07:17:00 -05:00
co-authored by Cursor
parent f05f8f3597
commit 1206afe8fd
9 changed files with 86 additions and 32 deletions
+15 -7
View File
@@ -53,16 +53,16 @@
</template> </template>
<template v-else> <template v-else>
<label v-if="draft.promptPre" class="block text-sm"> <label class="block text-sm">
<span class="mb-1 block font-medium text-zinc-300">Prompt pre</span> <span class="mb-1 block font-medium text-zinc-300">Pre-text</span>
<textarea v-model="draft.promptPre" rows="2" class="max-h-28 w-full overflow-y-auto rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm outline-none ring-amber-300/40 [overflow-wrap:anywhere] focus:ring-2 disabled:opacity-70" :disabled="!editable" /> <textarea v-model="draft.promptPre" rows="2" class="max-h-28 w-full overflow-y-auto rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm outline-none ring-amber-300/40 [overflow-wrap:anywhere] focus:ring-2 disabled:opacity-70" :disabled="!editable" />
</label> </label>
<label class="block text-sm"> <label class="block text-sm">
<span class="mb-1 block font-medium text-zinc-300">{{ shots.length > 1 ? (job.kind === 'edit' ? 'Pass 1' : 'Shot 1') : 'Prompt' }}</span> <span class="mb-1 block font-medium text-zinc-300">{{ shots.length > 1 ? (job.kind === 'edit' ? 'Pass 1' : 'Shot 1') : 'Prompt' }}</span>
<textarea v-model="draft.prompt" rows="5" class="max-h-48 w-full overflow-y-auto rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm outline-none ring-amber-300/40 [overflow-wrap:anywhere] focus:ring-2 disabled:opacity-70" :disabled="!editable" /> <textarea v-model="draft.prompt" rows="5" class="max-h-48 w-full overflow-y-auto rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm outline-none ring-amber-300/40 [overflow-wrap:anywhere] focus:ring-2 disabled:opacity-70" :disabled="!editable" />
</label> </label>
<label v-if="draft.promptPost" class="block text-sm"> <label class="block text-sm">
<span class="mb-1 block font-medium text-zinc-300">Prompt post</span> <span class="mb-1 block font-medium text-zinc-300">Post-text</span>
<textarea v-model="draft.promptPost" rows="2" class="max-h-28 w-full overflow-y-auto rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm outline-none ring-amber-300/40 [overflow-wrap:anywhere] focus:ring-2 disabled:opacity-70" :disabled="!editable" /> <textarea v-model="draft.promptPost" rows="2" class="max-h-28 w-full overflow-y-auto rounded-xl border border-white/10 bg-zinc-950 px-3 py-2 text-sm outline-none ring-amber-300/40 [overflow-wrap:anywhere] focus:ring-2 disabled:opacity-70" :disabled="!editable" />
</label> </label>
<label v-if="job.kind === 'edit'" class="block text-sm"> <label v-if="job.kind === 'edit'" class="block text-sm">
@@ -164,6 +164,7 @@ import {
type QueuedInspectPayload, type QueuedInspectPayload,
type QueuedShotDraft type QueuedShotDraft
} from '~/utils/queuedJob' } from '~/utils/queuedJob'
import { restorePromptParts } from '~/utils/promptParts'
import { loraMatchesUse, loraStackLabel, persistLoraFields, readLoraStack, type LoraStackItem } from '~/utils/loras' import { loraMatchesUse, loraStackLabel, persistLoraFields, readLoraStack, type LoraStackItem } from '~/utils/loras'
const props = defineProps<{ const props = defineProps<{
@@ -243,6 +244,7 @@ function currentPayload(): QueuedInspectPayload {
const base = { ...(job.value?.payload || {}) } const base = { ...(job.value?.payload || {}) }
base.name = draft.name base.name = draft.name
base.prompt = draft.prompt base.prompt = draft.prompt
base.promptMid = draft.prompt
base.promptPre = draft.promptPre base.promptPre = draft.promptPre
base.promptPost = draft.promptPost base.promptPost = draft.promptPost
base.negative = draft.negative base.negative = draft.negative
@@ -271,9 +273,14 @@ function hydrate(next: QueuedInspectJob) {
job.value = next job.value = next
const payload = next.payload || {} const payload = next.payload || {}
draft.name = payload.name || next.name || '' draft.name = payload.name || next.name || ''
draft.prompt = payload.prompt || next.prompt || '' const parts = restorePromptParts(payload.promptMid || payload.prompt || next.prompt || '', {
draft.promptPre = payload.promptPre || '' pre: payload.promptPre,
draft.promptPost = payload.promptPost || '' prompt: payload.promptMid || payload.prompt || next.prompt || '',
post: payload.promptPost
})
draft.prompt = parts.prompt
draft.promptPre = parts.pre
draft.promptPost = parts.post
draft.negative = payload.negative || '' draft.negative = payload.negative || ''
draft.tags = payload.tags || (next.kind === 'music' ? (payload.prompt || next.prompt) : '') || '' draft.tags = payload.tags || (next.kind === 'music' ? (payload.prompt || next.prompt) : '') || ''
draft.lyrics = payload.lyrics || '' draft.lyrics = payload.lyrics || ''
@@ -363,6 +370,7 @@ async function saveChanges() {
body: { body: {
name: payload.name, name: payload.name,
prompt: job.value.kind === 'music' ? payload.tags : payload.prompt, prompt: job.value.kind === 'music' ? payload.tags : payload.prompt,
promptMid: job.value.kind === 'music' ? undefined : payload.prompt,
tags: payload.tags, tags: payload.tags,
lyrics: payload.lyrics, lyrics: payload.lyrics,
instrumental: payload.instrumental, instrumental: payload.instrumental,
+2 -2
View File
@@ -4,7 +4,6 @@ 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 }[]
@@ -37,7 +36,7 @@ export default defineEventHandler(async (event) => {
const promptMid = (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 prompt = promptMid
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)
@@ -131,6 +130,7 @@ export default defineEventHandler(async (event) => {
kind: 'edit', kind: 'edit',
payload: { payload: {
prompt, prompt,
promptMid: promptMid || undefined,
promptPre: promptPre || undefined, promptPre: promptPre || undefined,
promptPost: promptPost || undefined, promptPost: promptPost || undefined,
name: clipName, name: clipName,
+3 -3
View File
@@ -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, joinPromptParts, persistPromptWrappers } from '~/utils/promptParts' import { composePromptParts, 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 = joinPromptParts(promptPre, resolvedLocks.prompt, promptPost) const prompt = resolvedLocks.prompt
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 +251,7 @@ export default defineEventHandler(async (event) => {
familyId, familyId,
payload: { payload: {
prompt, prompt,
promptMid: promptMid || undefined, promptMid: prompt || undefined,
promptPre: promptPre || undefined, promptPre: promptPre || undefined,
promptPost: promptPost || undefined, promptPost: promptPost || undefined,
name: clipName, name: clipName,
+2 -2
View File
@@ -27,7 +27,6 @@ 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'
@@ -140,7 +139,7 @@ export default defineEventHandler(async (event) => {
const promptMid = 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 prompt = promptMid
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)
@@ -319,6 +318,7 @@ export default defineEventHandler(async (event) => {
kind: 'edit', kind: 'edit',
payload: { payload: {
prompt, prompt,
promptMid: promptMid || undefined,
promptPre: promptPre || undefined, promptPre: promptPre || undefined,
promptPost: promptPost || undefined, promptPost: promptPost || undefined,
name: clipName, name: clipName,
+20 -2
View File
@@ -56,6 +56,8 @@ export interface ShotQueue {
permanenceRefs?: import('~/utils/globalLocks').PermanenceRef[] permanenceRefs?: import('~/utils/globalLocks').PermanenceRef[]
loraName?: string loraName?: string
loraStack?: LoraStackItem[] loraStack?: LoraStackItem[]
promptPre?: string
promptPost?: string
segments: ShotQueueSegment[] segments: ShotQueueSegment[]
lastError?: string lastError?: string
} }
@@ -190,7 +192,9 @@ export async function createShotQueue(params: {
permanenceRefs?: import('~/utils/globalLocks').PermanenceRef[] permanenceRefs?: import('~/utils/globalLocks').PermanenceRef[]
loraName?: string loraName?: string
loraStack?: LoraStackItem[] loraStack?: LoraStackItem[]
initial: { prompt: string; duration: number; permanenceRefs?: import('~/utils/globalLocks').PermanenceRef[]; loraName?: string; loraStack?: LoraStackItem[] } promptPre?: string
promptPost?: string
initial: { prompt: string; promptPre?: string; promptPost?: string; duration: number; permanenceRefs?: import('~/utils/globalLocks').PermanenceRef[]; loraName?: string; loraStack?: LoraStackItem[] }
extensions: QueuedExtension[] extensions: QueuedExtension[]
jobId?: string jobId?: string
}): Promise<ShotQueue> { }): Promise<ShotQueue> {
@@ -227,8 +231,18 @@ export async function createShotQueue(params: {
permanenceRefs: params.permanenceRefs, permanenceRefs: params.permanenceRefs,
loraName: params.loraName, loraName: params.loraName,
loraStack: params.loraStack, loraStack: params.loraStack,
...persistPromptWrappers(params),
segments: [ segments: [
{ index: 0, prompt: params.initial.prompt, duration: params.initial.duration, status: 'running', permanenceRefs: params.initial.permanenceRefs, ...persistLoraFields(params.initial.loraStack || params.initial.loraName) }, {
index: 0,
prompt: params.initial.prompt,
duration: params.initial.duration,
status: 'running',
permanenceRefs: params.initial.permanenceRefs,
...persistLoraFields(params.initial.loraStack || params.initial.loraName),
...persistPromptWrappers(params),
...persistPromptWrappers(params.initial)
},
...params.extensions.map((item, index) => ({ ...params.extensions.map((item, index) => ({
index: index + 1, index: index + 1,
prompt: item.prompt, prompt: item.prompt,
@@ -236,6 +250,7 @@ export async function createShotQueue(params: {
status: 'pending' as const, status: 'pending' as const,
permanenceRefs: item.permanenceRefs, permanenceRefs: item.permanenceRefs,
...persistLoraFields(item.loraStack || item.loraName), ...persistLoraFields(item.loraStack || item.loraName),
...persistPromptWrappers(params),
...persistPromptWrappers(item) ...persistPromptWrappers(item)
})) }))
] ]
@@ -479,6 +494,7 @@ export function listOwnersWithQueues() {
} }
export function remainingFromQueue(queue: ShotQueue): QueuedExtension[] { export function remainingFromQueue(queue: ShotQueue): QueuedExtension[] {
const family = persistPromptWrappers(queue)
return queue.segments return queue.segments
.filter(segment => segment.index > 0) .filter(segment => segment.index > 0)
.map(segment => ({ .map(segment => ({
@@ -486,6 +502,7 @@ export function remainingFromQueue(queue: ShotQueue): QueuedExtension[] {
duration: segment.duration, duration: segment.duration,
permanenceRefs: segment.permanenceRefs, permanenceRefs: segment.permanenceRefs,
...persistLoraFields(segment.loraStack || segment.loraName), ...persistLoraFields(segment.loraStack || segment.loraName),
...family,
...persistPromptWrappers(segment) ...persistPromptWrappers(segment)
})) }))
} }
@@ -503,6 +520,7 @@ export function liveSegmentPrompt(queue: ShotQueue, extensionIndex: number) {
duration: segment?.duration || 5, duration: segment?.duration || 5,
permanenceRefs: segment?.permanenceRefs, permanenceRefs: segment?.permanenceRefs,
...persistLoraFields(segment?.loraStack || segment?.loraName), ...persistLoraFields(segment?.loraStack || segment?.loraName),
...persistPromptWrappers(queue),
...persistPromptWrappers(segment) ...persistPromptWrappers(segment)
} }
} }
+14 -4
View File
@@ -7,6 +7,7 @@ import { fetchLiveQueue } from '~/server/utils/comfy'
import { isLtxWorkflow, isTextToVideo, isXaigenStudio, LTX_DISABLED_MESSAGE, ltxWorkflowEnabled, parseVideoWorkflow, type VideoWorkflowId } from '~/utils/videoModels' import { isLtxWorkflow, isTextToVideo, isXaigenStudio, LTX_DISABLED_MESSAGE, ltxWorkflowEnabled, parseVideoWorkflow, type VideoWorkflowId } from '~/utils/videoModels'
import { persistLoraFields } from '~/server/utils/loras' import { persistLoraFields } from '~/server/utils/loras'
import { resolveLoraStack } from '~/utils/loras' import { resolveLoraStack } from '~/utils/loras'
import { persistPromptWrappers } from '~/utils/promptParts'
import { imageV2StackSpecials } from '~/utils/imageV2' import { imageV2StackSpecials } from '~/utils/imageV2'
import { allowIdentityRefs, type PermanenceRef } from '~/utils/globalLocks' import { allowIdentityRefs, type PermanenceRef } from '~/utils/globalLocks'
@@ -823,6 +824,7 @@ async function startStudioEditJob(item: StudioJob) {
folderLocked: payload.folderLocked, folderLocked: payload.folderLocked,
name: payload.name, name: payload.name,
prompt: payload.prompt, prompt: payload.prompt,
promptMid: payload.promptMid || payload.prompt,
promptPre: payload.promptPre, promptPre: payload.promptPre,
promptPost: payload.promptPost, promptPost: payload.promptPost,
aspect: payload.aspect || 'auto', aspect: payload.aspect || 'auto',
@@ -897,6 +899,7 @@ async function startStudioEditJob(item: StudioJob) {
folderLocked: payload.folderLocked, folderLocked: payload.folderLocked,
name: payload.name, name: payload.name,
prompt: payload.prompt, prompt: payload.prompt,
promptMid: payload.promptMid || payload.prompt,
promptPre: payload.promptPre, promptPre: payload.promptPre,
promptPost: payload.promptPost, promptPost: payload.promptPost,
aspect: payload.aspect || 'auto', aspect: payload.aspect || 'auto',
@@ -1076,8 +1079,8 @@ export async function startStudioJob(item: StudioJob) {
hideInput: payload.hideInput, hideInput: payload.hideInput,
folderLocked: payload.folderLocked, folderLocked: payload.folderLocked,
name: payload.name, name: payload.name,
prompt: payload.prompt, prompt: payload.promptMid || payload.prompt,
promptMid: payload.promptMid, promptMid: payload.promptMid || payload.prompt,
promptPre: payload.promptPre, promptPre: payload.promptPre,
promptPost: payload.promptPost, promptPost: payload.promptPost,
aspect: payload.aspect, aspect: payload.aspect,
@@ -1140,7 +1143,14 @@ export async function startStudioJob(item: StudioJob) {
globalLocks: payload.globalLocks, globalLocks: payload.globalLocks,
permanenceRefs: payload.permanenceRefs, permanenceRefs: payload.permanenceRefs,
...persistLoraFields(payload.loraStack || payload.loraName), ...persistLoraFields(payload.loraStack || payload.loraName),
initial: { prompt: payload.prompt, duration: payload.duration, permanenceRefs: payload.shotPermanenceRefs?.[0], ...persistLoraFields(resolveLoraStack(payload.loraStack || payload.loraName, payload.shotLoraStacks?.[0] || payload.shotLoras?.[0])) }, ...persistPromptWrappers(payload),
initial: {
prompt: payload.promptMid || payload.prompt,
duration: payload.duration,
permanenceRefs: payload.shotPermanenceRefs?.[0],
...persistLoraFields(resolveLoraStack(payload.loraStack || payload.loraName, payload.shotLoraStacks?.[0] || payload.shotLoras?.[0])),
...persistPromptWrappers(payload)
},
extensions: extensions.map((item, index) => ({ extensions: extensions.map((item, index) => ({
...item, ...item,
...persistLoraFields(item.loraStack || item.loraName || payload.shotLoraStacks?.[index + 1] || payload.shotLoras?.[index + 1]) ...persistLoraFields(item.loraStack || item.loraName || payload.shotLoraStacks?.[index + 1] || payload.shotLoras?.[index + 1])
@@ -1169,7 +1179,7 @@ export async function startStudioJob(item: StudioJob) {
} }
void runGeneration(job, { void runGeneration(job, {
prompt: payload.prompt, prompt: payload.promptMid || payload.prompt,
image: stillFile, image: stillFile,
width: payload.width, width: payload.width,
height: payload.height, height: payload.height,
+7 -3
View File
@@ -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, joinPromptParts, resolvePromptWrappers } from '~/utils/promptParts' import { composePromptParts, joinPromptParts, resolvePromptWrappers, wrappedPromptForComfy } 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'
@@ -191,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: joinPromptParts(job.library?.promptPre, params.prompt, job.library?.promptPost), prompt: wrappedPromptForComfy(job.library, job.library?.promptMid, params.prompt),
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]
@@ -566,6 +566,7 @@ export async function startQueueBurst(owner: string, queueId: string, count: num
} }
const source = getClip(owner, clipId) const source = getClip(owner, clipId)
const destFolderLocked = false const destFolderLocked = false
const nextSegment = queue.segments.find(segment => segment.index === lastIndex + 1)
const job = createJob() const job = createJob()
job.kind = 'video' job.kind = 'video'
job.maxStep = queue.steps job.maxStep = queue.steps
@@ -578,7 +579,10 @@ export async function startQueueBurst(owner: string, queueId: string, count: num
hideInput: queue.hideInput, hideInput: queue.hideInput,
folderLocked: destFolderLocked, folderLocked: destFolderLocked,
name: nextFamilyPartName(owner, source), name: nextFamilyPartName(owner, source),
prompt: queue.segments[lastIndex]?.prompt || source.prompt, prompt: nextSegment?.prompt || queue.segments[lastIndex]?.prompt || source.prompt,
promptMid: nextSegment?.prompt || queue.segments[lastIndex]?.prompt || source.prompt,
promptPre: queue.promptPre || source.promptPre,
promptPost: queue.promptPost || source.promptPost,
aspect: queue.aspect, aspect: queue.aspect,
width: queue.width, width: queue.width,
height: queue.height, height: queue.height,
+15 -5
View File
@@ -15,6 +15,11 @@ export function composePromptParts(pre: string, prompt: string, post: string) {
.join('\n\n') .join('\n\n')
} }
export type PromptWrapperFields = {
promptPre?: string
promptPost?: string
}
/** Combine wrappers for Comfy. If mid already includes pre/post, do not wrap twice. */ /** Combine wrappers for Comfy. If mid already includes pre/post, do not wrap twice. */
export function joinPromptParts(pre?: string, mid?: string, post?: string) { export function joinPromptParts(pre?: string, mid?: string, post?: string) {
const parts = restorePromptParts(mid || '', { const parts = restorePromptParts(mid || '', {
@@ -25,6 +30,16 @@ 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. */
export function wrappedPromptForComfy(
wrappers?: PromptWrapperFields | null,
mid?: string,
fallback?: string
) {
const body = sanitizePromptPart(mid || '').trim() || sanitizePromptPart(fallback || '').trim()
return joinPromptParts(wrappers?.promptPre, body, wrappers?.promptPost)
}
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)
} }
@@ -46,11 +61,6 @@ export function restorePromptParts(saved: string, stored?: Partial<PromptParts>
} }
} }
export type PromptWrapperFields = {
promptPre?: string
promptPost?: string
}
function hasWrapperKey(item: object | null | undefined, key: 'promptPre' | 'promptPost') { function hasWrapperKey(item: object | null | undefined, key: 'promptPre' | 'promptPost') {
return Boolean(item && Object.prototype.hasOwnProperty.call(item, key)) return Boolean(item && Object.prototype.hasOwnProperty.call(item, key))
} }
+8 -4
View File
@@ -63,6 +63,7 @@ export type QueuedInspectPayload = {
lyrics?: string lyrics?: string
instrumental?: boolean instrumental?: boolean
lyricsStrength?: number lyricsStrength?: number
musicEngine?: string
} }
export type QueuedInspectJob = { export type QueuedInspectJob = {
@@ -128,9 +129,11 @@ export function applyQueuedShots(kind: string, shots: QueuedShotDraft[], payload
} }
next.prompt = shots[0]?.prompt || '' next.prompt = shots[0]?.prompt || ''
next.duration = Number(shots[0]?.duration) || next.duration || 5 next.duration = Number(shots[0]?.duration) || next.duration || 5
next.extensions = shots.slice(1).map((item) => { next.extensions = shots.slice(1).map((item, index) => {
const fields = persistLoraFields(item.loraStack || item.loraName) const prev = payload.extensions?.[index]
const fields = persistLoraFields(item.loraStack || item.loraName || prev?.loraStack || prev?.loraName)
return { return {
...prev,
prompt: item.prompt || '', prompt: item.prompt || '',
duration: Number(item.duration) || 5, duration: Number(item.duration) || 5,
loraName: fields.loraName, loraName: fields.loraName,
@@ -182,7 +185,7 @@ export function queuedJobCopyText(job: QueuedInspectJob) {
else lines.push(line('Lyrics', payload.lyrics)) else lines.push(line('Lyrics', payload.lyrics))
} else { } else {
if (payload.promptPre) lines.push(line('Prompt pre', payload.promptPre)) if (payload.promptPre) lines.push(line('Prompt pre', payload.promptPre))
lines.push(line('Prompt', payload.prompt || job.prompt)) lines.push(line('Prompt', payload.promptMid || payload.prompt || job.prompt))
if (payload.promptPost) lines.push(line('Prompt post', payload.promptPost)) if (payload.promptPost) lines.push(line('Prompt post', payload.promptPost))
const shots = queuedJobShots(payload, job.kind) const shots = queuedJobShots(payload, job.kind)
if (shots.length > 1) { if (shots.length > 1) {
@@ -220,7 +223,8 @@ export function queuedJobPresetBody(job: QueuedInspectJob, name: string) {
duration: clampMusicDuration(payload.duration), duration: clampMusicDuration(payload.duration),
steps: clampMusicSteps(payload.steps), steps: clampMusicSteps(payload.steps),
seed: payload.seed, seed: payload.seed,
cfg: payload.cfg cfg: payload.cfg,
engine: payload.musicEngine || payload.engine
} }
} }
} }