Mention phrases become <imageN> only in the TextEncode string; the inspector never shows Comfy tokens. Co-authored-by: Cursor <cursoragent@cursor.com>
66 lines
2.9 KiB
JavaScript
66 lines
2.9 KiB
JavaScript
/** Qwen Edit / PE-I2I prompt helpers. Start still = <image1>; Hero is never a slot. */
|
|
|
|
export const QWEN21_EDIT_KEEP =
|
|
'Keep the identity, face, body, pose, and likeness of the person in <image1>.\nDo not invent a different person.'
|
|
|
|
export const QWEN21_EDIT_KEEP_CHANGE =
|
|
`${QWEN21_EDIT_KEEP}\nApply only this change:`
|
|
|
|
const EDIT_VERB =
|
|
/\b(?:change|replace|put|swap|keep|remove|add|edit|make|turn|convert|transform|modify|update|set|apply|wear|place|swap|switch)\b/i
|
|
|
|
const DESCRIBE_LEAD =
|
|
/^(?:the image is\b|this is a photograph of\b|a vertical photorealistic\b|a photorealistic\b|this (?:image|photo|picture) (?:shows|depicts|features)\b|the (?:photo|picture) (?:shows|depicts|features)\b)/i
|
|
|
|
/** Fixed identity stanza + the user's raw edit instruction. Always includes <image1>. */
|
|
export function buildQwen21EditKeepStanza(rawInstruction) {
|
|
const raw = String(rawInstruction || '').trim()
|
|
if (!raw) return QWEN21_EDIT_KEEP.trim()
|
|
return `${QWEN21_EDIT_KEEP_CHANGE} ${raw}`.trim()
|
|
}
|
|
|
|
/** Enhance off / refusal: ensure the sample prompt is the keep stanza, never PE-only. */
|
|
export function ensureQwen21EditPrompt(prompt, options = {}) {
|
|
const text = String(prompt || '').trim()
|
|
if (!text) return text
|
|
let next = /Keep the identity, face, body, pose, and likeness of the person in <image1>/i.test(text)
|
|
? text
|
|
: buildQwen21EditKeepStanza(text)
|
|
if (options.hasImage2 && !/<image2>/i.test(next)) {
|
|
next = `${next}\nUse the garment or object in <image2>.`.trim()
|
|
}
|
|
return next
|
|
}
|
|
|
|
/** PE rewrite looks like a T2I observer caption, not an edit directive. */
|
|
export function isQwen21PeEditDescribe(rewritten) {
|
|
const text = String(rewritten || '').trim()
|
|
if (!text) return true
|
|
if (DESCRIBE_LEAD.test(text)) return true
|
|
if (!EDIT_VERB.test(text)) return true
|
|
return false
|
|
}
|
|
|
|
function retagNaturalImageRefs(text) {
|
|
return String(text || '')
|
|
.replace(/\bthe woman in the (?:input )?(?:photo|picture|image)\b/gi, 'the person in <image1>')
|
|
.replace(/\bthe (?:person|man|subject) in the (?:input )?(?:photo|picture|image)\b/gi, 'the person in <image1>')
|
|
.replace(/\bin the (?:input )?(?:photo|picture|image)\b/gi, 'in <image1>')
|
|
.replace(/\bthe (?:input )?(?:photo|picture|image)\b/gi, '<image1>')
|
|
}
|
|
|
|
/**
|
|
* After PE-I2I: never let the PE chunk be the entire prompt.
|
|
* Always lead with the keep/<image1> stanza + raw instruction; append PE only when it is an edit directive.
|
|
*/
|
|
export function stitchQwen21PeEditPrompt(rawInstruction, rewritten) {
|
|
const stanza = buildQwen21EditKeepStanza(rawInstruction)
|
|
let pe = retagNaturalImageRefs(String(rewritten || '').trim()).trim()
|
|
if (!pe) return { prompt: stanza, skippedAsDescribe: false }
|
|
if (isQwen21PeEditDescribe(pe)) {
|
|
return { prompt: stanza, skippedAsDescribe: true }
|
|
}
|
|
// No <image1> in the PE chunk is fine — stanza already has the tag; keep PE as extra detail.
|
|
return { prompt: `${stanza}\n\n${pe}`.trim(), skippedAsDescribe: false }
|
|
}
|