Remove clears the Photos array and backing image fields; Add opens the picker with disk upload and drops empty drafts on cancel. Co-authored-by: Cursor <cursoragent@cursor.com>
225 lines
19 KiB
JavaScript
225 lines
19 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import {readFileSync} from 'node:fs'
|
|
import ts from 'typescript'
|
|
import {folderAssets,reconcileSelection,activeJobs,previewHidden} from '../shared/studio2/viewer.mjs'
|
|
import {useAsHero,useAsSource,restoreAll,hydrateStudio2Job} from '../shared/studio2/input.mjs'
|
|
import {toggleStyle,parseBatch,stylePrompt} from '../shared/studio2/styles.mjs'
|
|
import {compilePrompt,validateRequest} from '../shared/studio2/contracts.mjs'
|
|
import {cachedLoras} from '../shared/studio2/lora-cache.mjs'
|
|
import {resolveQwen21Size} from '../shared/studio2/qwen21-size.mjs'
|
|
import {ensureQwen21EditPrompt,stitchQwen21PeEditPrompt,QWEN21_EDIT_KEEP_CHANGE} from '../shared/studio2/qwen21-edit.mjs'
|
|
import {injectQwenEditMentionTags,applyPhotosToRequest} from '../shared/studio2/photos.mjs'
|
|
import {qwen21PeRefusal} from '../shared/studio2/qwen21-pe.mjs'
|
|
|
|
test('deleted library files never return from job outputs; selection falls back or clears',()=>{
|
|
const deleted={id:'gone',folderId:'f'},keep={id:'keep',folderId:'f',createdAt:2};const jobs=[{request:{folderId:'f'},outputs:[deleted]}]
|
|
let assets=folderAssets({stills:[keep],clips:[]},jobs,'f');assert.deepEqual(assets.map(a=>a.id),['keep']);assert.equal(reconcileSelection(deleted,assets).id,'keep');assert.equal(reconcileSelection(deleted,[]),null)
|
|
})
|
|
test('bench includes only active execution phases and clear finished retains internal cleanup records',()=>{
|
|
assert.deepEqual(activeJobs(['waiting','waking','enhancing','submitting','rendering','saving','complete','cancelled','failed'].map(state=>({state}))).map(j=>j.state),['waking','enhancing','submitting','rendering','saving'])
|
|
const rows=[{id:'done',state:'complete',request:{folderId:'f'},cleanupPending:[{}]},{id:'active',state:'rendering',request:{folderId:'f'}}],saved=[]
|
|
const src=readFileSync(new URL('../server/api/studio-2/clear-finished.post.ts',import.meta.url),'utf8').replace(/^import .*;\r?\n/gm,'').replace('export default ','const handler=')
|
|
const js=ts.transpileModule(src,{compilerOptions:{target:ts.ScriptTarget.ES2022}}).outputText
|
|
const fn=new Function('defineEventHandler','assertLibraryOwner','listStudioJobs','records','assertFolderAccess','saveRecord',js+';return handler')(f=>f,()=>({owner:'o'}),()=>[],()=>rows,()=>{},r=>saved.push(r))
|
|
assert.equal(fn({}).cleared,1);assert.equal(rows[0].dismissed,true);assert.equal(rows[0].cleanupPending.length,1);assert.equal(rows[1].dismissed,undefined)
|
|
})
|
|
test('hidden xAIGen canvas and family render no image or video elements',async()=>{
|
|
const revealed=new Set()
|
|
assert.equal(previewHidden({id:'a',role:'input'},revealed,true),true)
|
|
assert.equal(previewHidden({id:'a',role:'input'},new Set(['a']),true),false)
|
|
assert.equal(previewHidden({id:'a'},revealed,false),false)
|
|
const src=readFileSync(new URL('../components/studio2/Bench.vue',import.meta.url),'utf8')
|
|
assert.match(src,/aria-label="Current family outputs"/)
|
|
assert.match(src,/preview hidden/)
|
|
assert.match(src,/Reveal all/)
|
|
assert.match(src,/pickerRevealAll/)
|
|
assert.doesNotMatch(src,/Hide input stills/)
|
|
assert.doesNotMatch(src,/Hide output preview/)
|
|
})
|
|
test('cached LoRAs survive sleeping GPU and failed explicit refresh',async()=>{
|
|
const cache={image:['Klein/portrait.safetensors'],video:['motion.safetensors']};let queried=0
|
|
const opts={read:()=>cache,write:()=>assert.fail('Do not erase cache'),live:async()=>{queried++;throw Error('Comfy asleep')}}
|
|
assert.deepEqual(await cachedLoras(opts),cache);assert.equal(queried,1)
|
|
assert.deepEqual(await cachedLoras({...opts,fresh:true}),cache);assert.equal(queried,2)
|
|
})
|
|
test('bench load and Refresh always live-scan and replace disk so new Comfy names appear',async()=>{
|
|
let store={image:['old.safetensors'],video:['old-vid.safetensors']}
|
|
const next=await cachedLoras({
|
|
read:()=>store,
|
|
write:value=>{store=value},
|
|
live:async()=>({image:['old.safetensors','klein-pokemon_anime_style_klein9b.safetensors'],video:['old-vid.safetensors']}),
|
|
fresh:false
|
|
})
|
|
assert.ok(next.image.includes('klein-pokemon_anime_style_klein9b.safetensors'))
|
|
assert.deepEqual(store.image,next.image)
|
|
const removed=await cachedLoras({
|
|
read:()=>store,
|
|
write:value=>{store=value},
|
|
live:async()=>({image:['klein-pokemon_anime_style_klein9b.safetensors'],video:[]}),
|
|
fresh:true
|
|
})
|
|
assert.deepEqual(removed,{image:['klein-pokemon_anime_style_klein9b.safetensors'],video:[]})
|
|
})
|
|
test('bench mounts and Refresh LoRAs request a live scan',()=>{
|
|
const src=readFileSync(new URL('../components/studio2/Bench.vue',import.meta.url),'utf8')
|
|
assert.match(src,/void loadLoras\(true\)/)
|
|
assert.match(src,/loadLoras\(true\)/)
|
|
assert.match(src,/\/api\/studio-2\/loras'\+\(fresh\?'\?fresh=1':''\)/)
|
|
})
|
|
test('source/hero actions are independent and full input restores structured settings',()=>{
|
|
const form={identityStillId:'hero',imageAId:'source',promptSections:{action:'old'},settings:{}}
|
|
assert.equal(useAsHero(form,{id:'new'}).imageAId,'source');assert.equal(useAsSource(form,{id:'new'}).identityStillId,'hero')
|
|
const saved={mode:'edit',identityStillId:'h',imageAId:'s',imageStyles:{positive:['Comic'],negative:['Skin pores']},promptSections:{action:'New',cameraBeats:[{moves:['hold']}]},settings:{steps:24,loraStack:[{name:'example.safetensors',strengthModel:.4,strengthClip:.6}]}}
|
|
const restored=restoreAll(form,{id:'result',studio2:saved});assert.equal(restored.imageAId,'result');assert.equal(restored.identityStillId,'h');assert.deepEqual(restored.settings.loraStack,saved.settings.loraStack);assert.deepEqual(restored.imageStyles,saved.imageStyles);assert.equal(restored.promptSections.action,'New');const clip=restoreAll(form,{id:'clip',kind:'clip',studio2:{...saved,mode:'video',engine:'minimax'}});assert.equal(clip.startClipId,'clip');assert.equal(clip.mode,'extend');assert.equal(clip.promptSections.action,'New')
|
|
})
|
|
test('image chips compile positive and negative independently; video chips do not leak',()=>{
|
|
let styles=toggleStyle({},'Comic');styles=toggleStyle(styles,'Skin pores',true);styles=toggleStyle(styles,'Comic',true);assert.equal(stylePrompt(styles),'');assert.match(stylePrompt(styles,true),/Comic/)
|
|
styles=toggleStyle(styles,'Photorealistic');const q=validateRequest({mode:'edit',engine:'flux',folderId:'f',imageAId:'s',imageStyles:styles,promptSections:{action:'Restaurant',cameraBeats:[{moves:['hold']}],locks:'VIDEO ONLY'}})
|
|
assert.match(q.compiledPrompt,/Photorealistic/);assert.doesNotMatch(q.compiledPrompt,/Comic|pores|tripod|hold|VIDEO ONLY/i);assert.deepEqual(q.imageStyles,styles)
|
|
assert.doesNotMatch(compilePrompt(q.promptSections,false,q),/Picture 1/)
|
|
assert.deepEqual(parseBatch('A wrapped\nsentence\n\nSecond shot\r\n\r\nThird'),['A wrapped\nsentence','Second shot','Third'])
|
|
})
|
|
test('qwen21 supports Generate and Edit with 25/1 defaults and no locks',()=>{
|
|
const q=validateRequest({mode:'generate',engine:'qwen21',folderId:'f',promptSections:{action:'a red bicycle'},settings:{aspect:'1:1'}})
|
|
assert.equal(q.engine,'qwen21');assert.equal(q.settings.steps,25);assert.equal(q.settings.cfg,1)
|
|
assert.equal(q.settings.width,1024);assert.equal(q.settings.height,1024)
|
|
assert.equal(q.lockFace,false);assert.equal(q.lockOutfit,false);assert.deepEqual(q.settings.loraStack,[])
|
|
assert.equal(q.enhancePrompt,false)
|
|
assert.equal(q.turbo,false)
|
|
const wide=validateRequest({mode:'generate',engine:'qwen21',folderId:'f',promptSections:{action:'wide'},settings:{aspect:'16:9'}})
|
|
assert.equal(wide.settings.width,1536);assert.equal(wide.settings.height,864)
|
|
const tall=validateRequest({mode:'generate',engine:'qwen21',folderId:'f',promptSections:{action:'tall'},settings:{aspect:'9:16'}})
|
|
assert.equal(tall.settings.width,864);assert.equal(tall.settings.height,1536)
|
|
const turbo=validateRequest({mode:'generate',engine:'qwen21',folderId:'f',turbo:true,promptSections:{action:'fast'},settings:{aspect:'1:1',steps:25,cfg:4}})
|
|
assert.equal(turbo.turbo,true);assert.equal(turbo.settings.steps,6);assert.equal(turbo.settings.cfg,1)
|
|
assert.match(String(turbo.lora),/viggle-turbo-v0\.2\.1-6step-lora-r128/)
|
|
assert.match(String(turbo.sigmas),/0\.9375/)
|
|
const enhanced=validateRequest({mode:'generate',engine:'qwen21',folderId:'f',enhancePrompt:true,promptSections:{action:'corgi'},settings:{aspect:'1:1'}})
|
|
assert.equal(enhanced.enhancePrompt,true)
|
|
assert.equal(validateRequest({mode:'generate',engine:'flux',folderId:'f',enhancePrompt:true,turbo:true,promptSections:{action:'x'},settings:{aspect:'1:1'}}).turbo,false)
|
|
const edit=validateRequest({mode:'edit',engine:'qwen21',folderId:'f',imageAId:'s',promptSections:{action:'change the sky'},settings:{aspect:'auto'}})
|
|
assert.equal(edit.mode,'edit');assert.equal(edit.settings.steps,25);assert.equal(edit.settings.cfg,1)
|
|
assert.equal(edit.lockFace,false);assert.deepEqual(edit.settings.loraStack,[])
|
|
assert.throws(()=>validateRequest({mode:'edit',engine:'qwen21',folderId:'f',promptSections:{action:'x'}}),/Photo to change/)
|
|
assert.throws(()=>validateRequest({mode:'edit',engine:'qwen21',folderId:'f',identityStillId:'hero',promptSections:{action:'x'}}),/Photo to change/)
|
|
const qwenPhotos=validateRequest({mode:'edit',engine:'qwen21',folderId:'f',photos:[{stillId:'person',role:'change'},{stillId:'dress',role:'outfit'}],promptSections:{action:'Put her in that dress.'},settings:{aspect:'auto'}})
|
|
assert.equal(qwenPhotos.imageAId,'person')
|
|
assert.equal(qwenPhotos.imageBId,'dress')
|
|
assert.equal(qwenPhotos.identityStillId,'')
|
|
assert.match(qwenPhotos.compiledPrompt,/Put her in that dress/)
|
|
assert.doesNotMatch(String(qwenPhotos.promptSections.action),/<image1>/)
|
|
const afterRemove=applyPhotosToRequest({mode:'edit',engine:'qwen21',photos:[],imageAId:'stale-person',imageBId:'stale-dress'})
|
|
assert.equal(afterRemove.imageAId,'')
|
|
assert.equal(afterRemove.imageBId,'')
|
|
assert.deepEqual(afterRemove.photos,[])
|
|
assert.throws(()=>validateRequest({mode:'edit',engine:'qwen21',folderId:'f',photos:[],imageAId:'stale',promptSections:{action:'x'}}),/Photo to change/)
|
|
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.match(ensureQwen21EditPrompt('put her in a red leather jacket'),/<image1>/)
|
|
assert.match(ensureQwen21EditPrompt('put her in that dress',{hasImage2:true}),/<image2>/)
|
|
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')
|
|
assert.match(injectQwenEditMentionTags('Put her in the outfit photo',[{stillId:'a',role:'change'},{stillId:'b',role:'outfit'}]),/<image2>/)
|
|
assert.match(injectQwenEditMentionTags('Change this photo',[{stillId:'a',role:'change'}]),/<image1>/)
|
|
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)
|
|
assert.equal(qwen21PeRefusal('put her in a red leather jacket',{positive_prompt:'',parse_ok:false}).refused,true)
|
|
assert.equal(qwen21PeRefusal('corgi wearing sunglasses on a beach',{positive_prompt:'A soft scene.',parse_ok:true}).refused,true)
|
|
assert.match(readFileSync(new URL('../host/qwen21-pe-prompts/system_prompt_t2i.txt',import.meta.url),'utf8'),/Adult and explicit briefs/)
|
|
assert.match(readFileSync(new URL('../host/qwen21-pe-prompts/system_prompt_edit.txt',import.meta.url),'utf8'),/Adult and explicit briefs/)
|
|
assert.match(readFileSync(new URL('../scripts/setup-qwen21.ps1',import.meta.url),'utf8'),/qwen21-pe-prompts/)
|
|
assert.match(readFileSync(new URL('../server/utils/studio2/runner.ts',import.meta.url),'utf8'),/qwen21PeRefusal/)
|
|
assert.match(readFileSync(new URL('../components/studio2/Details.vue',import.meta.url),'utf8'),/Enhance skipped \(model refused\)/)
|
|
})
|
|
test('hydrateStudio2Job restores LoRA strengths, locked seed, and clears missing stills',()=>{
|
|
const form={mode:'generate',engine:'flux',identityStillId:'',imageAId:'',promptSections:{action:''},imageStyles:{positive:[],negative:[]},settings:{aspect:'auto',seed:null,seedMode:'random',loraStack:[]},guides:[]}
|
|
const saved={mode:'edit',engine:'flux',identityStillId:'hero1',imageAId:'gone',lockFace:true,lockOutfit:false,promptSections:{action:'pokemon trainer'},imageStyles:{positive:[],negative:['blurry','custom bad']},settings:{aspect:'16:9',seed:42,steps:24,cfg:4,loraStack:[{name:'klein-pokemon.safetensors',strengthModel:1.6,strengthClip:1}]}}
|
|
const {form:next,missing}=hydrateStudio2Job(form,saved,{stillIds:new Set(['hero1']),clipIds:new Set()})
|
|
assert.equal(next.mode,'edit');assert.equal(next.identityStillId,'hero1');assert.equal(next.imageAId,'')
|
|
assert.equal(next.settings.seed,42);assert.equal(next.settings.seedMode,'locked');assert.equal(next.settings.aspect,'16:9')
|
|
assert.deepEqual(next.settings.loraStack,[{name:'klein-pokemon.safetensors',strengthModel:1.6,strengthClip:1}])
|
|
assert.deepEqual(next.imageStyles.negative,['blurry','custom bad'])
|
|
assert.match(missing,/start missing/)
|
|
})
|
|
test('custom negative chips survive normalizeStyles and bench exposes Load actions',()=>{
|
|
let styles={positive:[],negative:['blurry','my watermark']}
|
|
styles=toggleStyle(styles,'deformed',true)
|
|
assert.deepEqual(styles.negative,['blurry','my watermark','deformed'])
|
|
const src=readFileSync(new URL('../components/studio2/Bench.vue',import.meta.url),'utf8')
|
|
const slot=readFileSync(new URL('../components/studio2/AssetSlot.vue',import.meta.url),'utf8')
|
|
assert.match(src,/loadSelectedJob/);assert.match(src,/hydrateStudio2Job/);assert.match(src,/Photos/)
|
|
assert.match(src,/photosPlanState\.emptyHint/)
|
|
assert.match(src,/aspect:'auto'/);assert.match(src,/strip-actions/);assert.match(src,/addNegative/)
|
|
assert.match(src,/Enhance prompt/);assert.match(src,/enhancePrompt/)
|
|
assert.match(slot,/Mention in prompt/);assert.match(slot,/This photo is/);assert.match(slot,/Replace/)
|
|
assert.match(slot,/Choose a photo/)
|
|
assert.match(src,/uploadPickerFile/);assert.match(src,/setPhotoStill/);assert.match(src,/discardEmptyPhotoDraft/)
|
|
assert.match(src,/Upload from disk/)
|
|
assert.doesNotMatch(src,/Hero still/)
|
|
assert.doesNotMatch(src,/Edit source/)
|
|
assert.doesNotMatch(src,/Identity \/ reference/)
|
|
assert.doesNotMatch(src,/Qwen Edit uses Start still/)
|
|
assert.doesNotMatch(src,/Hero is Klein-only/)
|
|
assert.doesNotMatch(slot,/<image1>/)
|
|
assert.match(src,/\bTurbo\b/);assert.match(src,/form\.turbo/)
|
|
const photosSrc=readFileSync(new URL('../shared/studio2/photos.mjs',import.meta.url),'utf8')
|
|
assert.match(photosSrc,/Photo to change/)
|
|
assert.match(photosSrc,/mentionPhrase/)
|
|
assert.match(photosSrc,/Add a photo, then say what it is/)
|
|
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/)
|
|
assert.match(peEdit,/QwenImage21_EditPromptRewrite/);assert.match(peEdit,/pe_i2i\.int8_convrot/)
|
|
assert.doesNotMatch(peT2i,/UnetLoader|KSampler|VAELoader/)
|
|
assert.doesNotMatch(peEdit,/UnetLoader|KSampler|VAELoader/)
|
|
assert.match(peEdit,/"image_1":\s*\["10",\s*0\]/)
|
|
const editGraph=readFileSync(new URL('../server/assets/studio2_qwen21_edit.json',import.meta.url),'utf8')
|
|
assert.match(editGraph,/"images\.image_1":\s*\["10",\s*0\]/)
|
|
assert.match(editGraph,/"latent_image":\s*\["9",\s*2\]/)
|
|
assert.doesNotMatch(editGraph,/EmptyLatentImage/)
|
|
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,/injectQwenEditMentionTags/)
|
|
assert.match(runner,/applyPhotosToRequest/)
|
|
assert.match(runner,/skippedAsDescribe/)
|
|
assert.match(runner,/promptRaw/)
|
|
assert.match(runner,/images\.image_1/)
|
|
assert.match(runner,/comfyPromptId/)
|
|
assert.match(runner,/generateComfyPromptId/)
|
|
assert.doesNotMatch(runner,/Keep the subject in <image1>/)
|
|
const t2i=readFileSync(new URL('../server/assets/studio2_qwen21_t2i.json',import.meta.url),'utf8')
|
|
assert.match(t2i,/EmptyLatentImage/);assert.match(t2i,/"latent_image":\s*\["16",\s*0\]/)
|
|
const turboT2i=readFileSync(new URL('../server/assets/studio2_qwen21_t2i_turbo.json',import.meta.url),'utf8')
|
|
const turboEdit=readFileSync(new URL('../server/assets/studio2_qwen21_edit_turbo.json',import.meta.url),'utf8')
|
|
assert.match(turboT2i,/ViggleTurboLora/);assert.match(turboT2i,/ViggleTurboSigmas/);assert.match(turboT2i,/SamplerCustomAdvanced/)
|
|
assert.match(turboT2i,/0\.9375/);assert.match(turboEdit,/ViggleTurboLora/);assert.match(turboEdit,/"latent":\s*\["9",\s*2\]/)
|
|
assert.deepEqual(resolveQwen21Size('16:9'),{width:1536,height:864})
|
|
assert.deepEqual(resolveQwen21Size('1:1'),{width:1024,height:1024})
|
|
assert.deepEqual(resolveQwen21Size('9:16'),{width:864,height:1536})
|
|
})
|