diff --git a/pages/index.vue b/pages/index.vue index 67b9d53..95058ca 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -109,7 +109,7 @@

Paste a block. Text before the first marker is shot 1. Put shot 2, shot 3 on their own lines to queue extensions — a line like Shot Type will not start a new shot. Every shot uses the duration slider. - Identity boilerplate is prepended to the first shot only, then concatenated into Reference to Video. + The Comfy identity prefix is prepended to every shot, and the same Picture 1 / extra stills are sent with each shot.

Write camera, action, and audio. That is the dynamic action prompt. We concatenate the <Picture 1> identity prefix in front of it and send the result to Reference to Video, not Image to Video. @@ -140,7 +140,7 @@ {{ index === 0 ? 'Initial' : `Extension ${index}` }} · shot {{ shot.n }} · {{ formatClipDuration(duration) }}

-

{{ shot.prompt }}

+

{{ identityPrompting ? buildIdentityPrompt(shot.prompt, extraIdentityPictures) : shot.prompt }}

@@ -321,7 +321,7 @@ -

Picture 1 is the start still. Extra stills map to Picture 2–5. Empty extra slots reuse the start still so Comfy does not error. Identity refs are not reused on queued extensions.

+

Picture 1 is the start still and stays Picture 1 on every shot, including queued extensions. Extra stills map to Picture 2–5. Empty extra slots reuse the start still.

diff --git a/server/api/generate.post.ts b/server/api/generate.post.ts index f05ebe7..6f8048f 100644 --- a/server/api/generate.post.ts +++ b/server/api/generate.post.ts @@ -309,17 +309,17 @@ async function queueMiniMax( const done = watchComfyJob(job, { persist: params.persist }) job.status = 'uploading' - const uploading = (job.library?.chainIndex || 0) > 0 - ? 'Uploading last frame to ComfyUI...' - : 'Uploading image to ComfyUI...' + const chainIndex = job.library?.chainIndex || 0 + const uploading = params.useIdentityRefs + ? (chainIndex > 0 ? 'Uploading identity stills for next shot...' : 'Uploading image to ComfyUI...') + : (chainIndex > 0 ? 'Uploading last frame to ComfyUI...' : 'Uploading image to ComfyUI...') const queueing = (job.library?.chainIndex || 0) > 0 ? 'Queueing extension on MiniMax H3...' : 'Queueing MiniMax H3 job...' emitChainJob(job, { type: 'status', message: uploading, progress: 4 }) const uploaded = await uploadImage(params.image, job.id) const referenceNames: string[] = ['', '', '', ''] - const chainIndex = job.library?.chainIndex || 0 - if (chainIndex === 0) { + if (params.useIdentityRefs) { for (const [index, ref] of (params.referenceImages || []).entries()) { if (!ref?.data?.length) continue const next = await uploadImage({ @@ -353,8 +353,8 @@ async function queueMiniMax( sound: params.sound, workflow: params.workflow, duration: params.duration, - useIdentityRefs: chainIndex === 0 && params.useIdentityRefs, - referenceImageNames: chainIndex === 0 ? referenceNames : [] + useIdentityRefs: params.useIdentityRefs, + referenceImageNames: params.useIdentityRefs ? referenceNames : [] }) const queued = await queuePrompt(graph, job.clientId) @@ -466,7 +466,9 @@ async function runGeneration( await ensureComfyReady(ready) await queueMiniMax(job, { prompt: ext.prompt, - image: { filename: 'last_frame.png', data: frame, type: 'image/png' }, + image: params.useIdentityRefs + ? params.image + : { filename: 'last_frame.png', data: frame, type: 'image/png' }, width: params.width, height: params.height, steps: params.steps, @@ -481,8 +483,8 @@ async function runGeneration( persist: isLast, workflow: params.workflow, duration: ext.duration, - useIdentityRefs: false, - referenceImages: [] + useIdentityRefs: params.useIdentityRefs, + referenceImages: params.useIdentityRefs ? params.referenceImages : [] }) if (!isLast) { diff --git a/utils/identityPrompt.ts b/utils/identityPrompt.ts index a49a4fc..4f1ef96 100644 --- a/utils/identityPrompt.ts +++ b/utils/identityPrompt.ts @@ -13,11 +13,11 @@ export function identityBoilerplate(extraRefs: number | number[] = 0) { : extras.length === 1 ? ` is a secondary reference angle for profile details.` : ` ${extras.map(n => ``).join(' and ')} are secondary reference angles for profile features only.` - return ` is the exact facial identity, hairstyle, body proportions and clothing of the main character. Preserve this identity with high fidelity even when the subject temporarily leaves the frame and returns.${secondary}` + return ` is the primary identity anchor defining exact facial geometry, hairstyle, and body proportions.${secondary} Strictly maintain the structural proportions of with high fidelity when moving.` } export function looksLikeIdentityPrompt(text: string) { - return /primary identity anchor|exact facial identity/i.test(String(text || '')) + return /^ is the (primary identity anchor|exact facial identity)/i.test(String(text || '').trim()) } export function buildIdentityPrompt(actionPrompt: string, extraRefs: number | number[] = 0) {