Store the Edit TextEncode prompt on library stills, not a Start still caption.

Edit + Enhance always leads with the keep/<image1> stanza and typed change; PE describe dumps are dropped, and Typed stays promptRaw.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Towsty
2026-09-26 15:14:20 -05:00
co-authored by Cursor
parent c46e827c68
commit 58c5458c87
9 changed files with 177 additions and 46 deletions
+24 -5
View File
@@ -105,13 +105,27 @@ test('qwen21 supports Generate and Edit with 25/1 defaults and no locks',()=>{
assert.throws(()=>validateRequest({mode:'compose',engine:'qwen21',folderId:'f',imageAId:'s',imageBId:'b',promptSections:{action:'x'}}),/Generate and Edit/)
assert.throws(()=>validateRequest({mode:'iterate',engine:'qwen21',folderId:'f',imageAId:'s',promptSections:{action:'x'}}),/Generate and Edit/)
assert.equal(ensureQwen21EditPrompt('put her in a red leather jacket'),`${QWEN21_EDIT_KEEP_CHANGE} put her in a red leather jacket`)
assert.equal(ensureQwen21EditPrompt('change <image1> outfit'),'change <image1> outfit')
assert.match(stitchQwen21PeEditPrompt('a woman in a red jacket'),/<image1>/)
assert.equal(stitchQwen21PeEditPrompt('keep <image1> add jacket'),'keep <image1> add jacket')
assert.match(stitchQwen21PeEditPrompt('change the jacket on the woman in the photo'),/<image1>/)
assert.doesNotMatch(stitchQwen21PeEditPrompt('change the jacket on the woman in the photo'),/\bthe photo\b/i)
assert.match(ensureQwen21EditPrompt('put her in a red leather jacket'),/<image1>/)
assert.equal(ensureQwen21EditPrompt('Keep the identity, face, body, pose, and likeness of the person in <image1>.\nDo not invent a different person.\nApply only this change: x'),'Keep the identity, face, body, pose, and likeness of the person in <image1>.\nDo not invent a different person.\nApply only this change: x')
const stitchedKeep=stitchQwen21PeEditPrompt('put her in a red leather jacket','Change the outfit on the person in <image1> to a red leather jacket')
assert.match(stitchedKeep.prompt,/Keep the identity/)
assert.match(stitchedKeep.prompt,/<image1>/)
assert.match(stitchedKeep.prompt,/red leather jacket/)
assert.equal(stitchedKeep.skippedAsDescribe,false)
assert.match(stitchedKeep.prompt,/Change the outfit/)
const stitchedDescribe=stitchQwen21PeEditPrompt('put her in a red leather jacket','The image is a photograph of a woman standing outdoors in soft light…')
assert.equal(stitchedDescribe.skippedAsDescribe,true)
assert.match(stitchedDescribe.prompt,/Keep the identity/)
assert.match(stitchedDescribe.prompt,/put her in a red leather jacket/)
assert.doesNotMatch(stitchedDescribe.prompt,/The image is a photograph/)
const stitchedNoTag=stitchQwen21PeEditPrompt('put her in a red leather jacket','Change the jacket on the woman in the photo to red leather')
assert.equal(stitchedNoTag.skippedAsDescribe,false)
assert.match(stitchedNoTag.prompt,/<image1>/)
assert.doesNotMatch(stitchedNoTag.prompt,/\bthe photo\b/i)
assert.match(readFileSync(new URL('../host/qwen21-pe-prompts/system_prompt_edit.txt',import.meta.url),'utf8'),/Single-image edits still use <image1>/)
assert.match(readFileSync(new URL('../host/qwen21-pe-prompts/system_prompt_edit.txt',import.meta.url),'utf8'),/EDIT INSTRUCTION/)
assert.doesNotMatch(readFileSync(new URL('../host/qwen21-pe-prompts/system_prompt_edit.txt',import.meta.url),'utf8'),/do NOT use tags/)
assert.doesNotMatch(readFileSync(new URL('../host/qwen21-pe-prompts/system_prompt_t2i.txt',import.meta.url),'utf8'),/EDIT INSTRUCTION/)
assert.doesNotMatch(readFileSync(new URL('../host/qwen21-pe-prompts/system_prompt_t2i.txt',import.meta.url),'utf8'),/Single-image edits still use <image1>/)
assert.equal(qwen21PeRefusal('corgi in the rain',{positive_prompt:'A photorealistic photograph of a corgi standing in rain…',parse_ok:true}).refused,false)
assert.equal(qwen21PeRefusal('nude adult portrait',{positive_prompt:"I cannot assist with that request.",thinking:'',parse_ok:true}).refused,true)
@@ -144,6 +158,9 @@ test('custom negative chips survive normalizeStyles and bench exposes Load actio
assert.match(src,/\bTurbo\b/);assert.match(src,/form\.turbo/)
assert.match(src,/Qwen Edit uses Start still as/)
assert.match(src,/Hero is Klein-only and is ignored/)
assert.match(readFileSync(new URL('../components/studio2/Details.vue',import.meta.url),'utf8'),/\bTyped\b/)
assert.match(readFileSync(new URL('../utils/libraryGeneration.ts',import.meta.url),'utf8'),/\bTyped\b/)
assert.match(readFileSync(new URL('../utils/libraryGeneration.ts',import.meta.url),'utf8'),/promptRaw/)
const peT2i=readFileSync(new URL('../server/assets/studio2_qwen21_pe_t2i.json',import.meta.url),'utf8')
const peEdit=readFileSync(new URL('../server/assets/studio2_qwen21_pe_edit.json',import.meta.url),'utf8')
assert.match(peT2i,/QwenImage21_T2IPromptRewrite/);assert.match(peT2i,/pe_t2i\.int8_convrot/)
@@ -158,6 +175,8 @@ test('custom negative chips survive normalizeStyles and bench exposes Load actio
const runner=readFileSync(new URL('../server/utils/studio2/runner.ts',import.meta.url),'utf8')
assert.match(runner,/ensureQwen21EditPrompt/)
assert.match(runner,/stitchQwen21PeEditPrompt/)
assert.match(runner,/skippedAsDescribe/)
assert.match(runner,/promptRaw/)
assert.match(runner,/images\.image_1/)
assert.match(runner,/comfyPromptId/)
assert.match(runner,/generateComfyPromptId/)