Wire Qwen Turbo toggle through Studio 2 and the runner.
Force 6/1 when Turbo is on, skip negatives, keep PE in front, and fail closed if the r128 LoRA is missing. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -119,6 +119,12 @@
|
||||
<label v-if="form.engine==='qwen21' && (form.mode==='generate' || form.mode==='edit')" class="inline-flex items-center gap-2 text-[var(--type-sm)] text-[var(--muted)]">
|
||||
<input v-model="form.enhancePrompt" type="checkbox" class="accent-[var(--amber)]">Enhance prompt
|
||||
</label>
|
||||
<div v-if="form.engine==='qwen21' && (form.mode==='generate' || form.mode==='edit')" class="flex flex-col gap-1">
|
||||
<label class="inline-flex items-center gap-2 text-[var(--type-sm)] text-[var(--muted)]">
|
||||
<input v-model="form.turbo" type="checkbox" class="accent-[var(--amber)]">Turbo
|
||||
</label>
|
||||
<p v-if="form.turbo" class="text-[11px] text-[var(--muted)]">6-step Viggle distill. CFG stays 1. Enhance prompt recommended.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="field-label">Negative prompt</span>
|
||||
@@ -379,6 +385,7 @@ import {LOAD_GENERATION_PAYLOAD_KEY} from '~/utils/queuedJob'
|
||||
|
||||
const NEG_STORAGE_KEY='studio2-negative-chips'
|
||||
const ENHANCE_STORAGE_KEY='studio2-qwen21-enhance-prompt'
|
||||
const TURBO_STORAGE_KEY='studio2-qwen21-turbo'
|
||||
function readStoredNegatives(){
|
||||
if(!import.meta.client)return [...defaultNegatives]
|
||||
try{
|
||||
@@ -400,6 +407,14 @@ function writeStoredEnhancePrompt(on:boolean){
|
||||
if(!import.meta.client)return
|
||||
try{localStorage.setItem(ENHANCE_STORAGE_KEY,on?'1':'0')}catch{/* ignore */}
|
||||
}
|
||||
function readStoredTurbo(){
|
||||
if(!import.meta.client)return false
|
||||
try{return localStorage.getItem(TURBO_STORAGE_KEY)==='1'}catch{return false}
|
||||
}
|
||||
function writeStoredTurbo(on:boolean){
|
||||
if(!import.meta.client)return
|
||||
try{localStorage.setItem(TURBO_STORAGE_KEY,on?'1':'0')}catch{/* ignore */}
|
||||
}
|
||||
|
||||
const xaigen=isXaigenStudio(useRuntimeConfig().public)
|
||||
const instanceName=String(useRuntimeConfig().public.instanceName || 'AIGen')
|
||||
@@ -419,7 +434,7 @@ const liveMusic=ref<Record<string,any>>({})
|
||||
const negativeDraft=ref('')
|
||||
const filmstripEl=ref<HTMLElement|null>(null)
|
||||
const tileRefs=new Map<string,HTMLElement>()
|
||||
const form=reactive<any>({mode:'generate',engine:'flux',enhancePrompt:readStoredEnhancePrompt(),imageStyles:{positive:[],negative:readStoredNegatives()},lockFace:true,lockOutfit:true,hideInput:xaigen,folderId:'',identityStillId:'',imageAId:'',imageBId:'',maskId:'',startClipId:'',endStillId:'',guides:[],promptSections:sectionsFrom({}),settings:{aspect:'auto',fps:24,steps:24,cfg:4,seed:null,seedMode:'random',duration:5,turbo:true,loraStack:[]}})
|
||||
const form=reactive<any>({mode:'generate',engine:'flux',enhancePrompt:readStoredEnhancePrompt(),turbo:readStoredTurbo(),imageStyles:{positive:[],negative:readStoredNegatives()},lockFace:true,lockOutfit:true,hideInput:xaigen,folderId:'',identityStillId:'',imageAId:'',imageBId:'',maskId:'',startClipId:'',endStillId:'',guides:[],promptSections:sectionsFrom({}),settings:{aspect:'auto',fps:24,steps:24,cfg:4,seed:null,seedMode:'random',duration:5,turbo:true,loraStack:[]}})
|
||||
const music=reactive({tags:DEFAULT_MUSIC_TAGS,lyrics:DEFAULT_MUSIC_LYRICS,duration:60,seed:'',name:''})
|
||||
const tagsPlaceholder=DEFAULT_MUSIC_TAGS,lyricsPlaceholder=DEFAULT_MUSIC_LYRICS
|
||||
let loraMemory=createLoraMemory(xaigen)
|
||||
@@ -456,7 +471,7 @@ watch(engineOptions,opts=>{
|
||||
})
|
||||
watch(()=>form.engine,(engine,prev)=>{
|
||||
if(engine==='qwen21' && prev!=='qwen21'){
|
||||
form.settings.steps=25
|
||||
form.settings.steps=form.turbo?6:25
|
||||
form.settings.cfg=1
|
||||
form.lockFace=false
|
||||
form.lockOutfit=false
|
||||
@@ -464,9 +479,15 @@ watch(()=>form.engine,(engine,prev)=>{
|
||||
}
|
||||
})
|
||||
watch(()=>form.enhancePrompt,on=>writeStoredEnhancePrompt(!!on))
|
||||
watch(()=>form.turbo,on=>{
|
||||
writeStoredTurbo(!!on)
|
||||
if(form.engine!=='qwen21')return
|
||||
form.settings.steps=on?6:25
|
||||
form.settings.cfg=1
|
||||
})
|
||||
watch(()=>[form.mode,form.engine],([mode,engine])=>{
|
||||
if(engine==='qwen21' && mode==='edit'){
|
||||
form.settings.steps=25
|
||||
form.settings.steps=form.turbo?6:25
|
||||
form.settings.cfg=1
|
||||
form.lockFace=false
|
||||
form.lockOutfit=false
|
||||
|
||||
@@ -22,6 +22,9 @@ const rows=computed(()=>{const m=props.metadata||{},s=m.settings||{};const time=
|
||||
{label:'Compiled prompt',value:m.compiledPrompt},
|
||||
{label:'Prompt (raw)',value:m.promptRaw || null},
|
||||
{label:'Enhance prompt',value:m.enhancePrompt==null?null:m.enhancePrompt?'On':'Off'},
|
||||
{label:'Turbo',value:m.turbo==null?null:m.turbo?'On':'Off'},
|
||||
{label:'Turbo LoRA',value:m.lora || null},
|
||||
{label:'Turbo sigmas',value:m.sigmas || null},
|
||||
{label:'Enhance wh_ratio',value:m.enhance?.wh_ratio || null},
|
||||
{label:'Enhance ratio_follow',value:m.enhance?.ratio_follow || null},
|
||||
{label:'Enhance parse_ok',value:m.enhance?.parse_ok==null?null:m.enhance.parse_ok?'Yes':'No'},
|
||||
|
||||
@@ -159,6 +159,8 @@
|
||||
<template v-else>
|
||||
<label class="m-label">Prompt<textarea v-model="ctx.form.promptSections.action" rows="5" class="m-area" placeholder="Describe the image or the shot." /></label>
|
||||
<label v-if="ctx.form.engine==='qwen21' && (ctx.form.mode==='generate' || ctx.form.mode==='edit')" class="m-check"><input v-model="ctx.form.enhancePrompt" type="checkbox"> Enhance prompt</label>
|
||||
<label v-if="ctx.form.engine==='qwen21' && (ctx.form.mode==='generate' || ctx.form.mode==='edit')" class="m-check"><input v-model="ctx.form.turbo" type="checkbox"> Turbo</label>
|
||||
<p v-if="ctx.form.engine==='qwen21' && ctx.form.turbo" class="m-muted">6-step Viggle distill. CFG stays 1. Enhance prompt recommended.</p>
|
||||
<div>
|
||||
<span class="m-label">Negative prompt</span>
|
||||
<div class="m-neg">
|
||||
|
||||
+3
-2
@@ -42,6 +42,7 @@ Restart Comfy **only when idle** (`COMFY_CONTROL_URL/status` → `gpu.busy=false
|
||||
- Edit follows `image_1` via the encode node’s latent (resolution long-edge ~1024). Do not inject a picker EmptyLatentImage onto the edit sampler.
|
||||
- Optional **Enhance prompt** (off by default): runs a separate PE-only Comfy graph (`CLIPLoader` + rewrite node), then frees VRAM and queues the existing T2I/Edit graph with the rewritten prompt. Never loads PE CLIP + DiT together on 16 GB. Fail closed if `parse_ok` is false or the rewrite is empty.
|
||||
- PE weights (int8 only): `text_encoders\qwen3.5_9b_qwen_image_2.1_pe_{t2i,i2i}.int8_convrot.safetensors` — do not replace the image TE `qwen3vl_8b_int8_convrot.safetensors`.
|
||||
- Custom node: `ComfyUI-Qwen-Image-2.1-Prompt-Enhancer` (`QwenImage21_T2IPromptRewrite` / `QwenImage21_EditPromptRewrite`).
|
||||
- Optional **Turbo** (off by default): Viggle DMD LoRA `Qwen-Image-2.1-viggle-turbo-v0.2.1-6step-lora-r128.safetensors` on the same Q8 GGUF via `ViggleTurboLora` + `ViggleTurboSigmas` (`1.0, 0.9375, 0.875, 0.75, 0.5, 0.25`), 6 steps, CFG 1, empty negative. Not a new engine. Enhance prompt stays compatible and is recommended with Turbo.
|
||||
- Custom node: `ComfyUI-Viggle-Turbo` (`viggle_turbo.py`). Do not merge the LoRA with stock `LoraLoaderModelOnly` (lossy on int8/bf16).
|
||||
|
||||
Graphs: `server/assets/studio2_qwen21_t2i.json`, `server/assets/studio2_qwen21_edit.json`, `server/assets/studio2_qwen21_pe_t2i.json`, `server/assets/studio2_qwen21_pe_edit.json`.
|
||||
Graphs: `server/assets/studio2_qwen21_t2i.json`, `server/assets/studio2_qwen21_edit.json`, `server/assets/studio2_qwen21_t2i_turbo.json`, `server/assets/studio2_qwen21_edit_turbo.json`, `server/assets/studio2_qwen21_pe_t2i.json`, `server/assets/studio2_qwen21_pe_edit.json`.
|
||||
|
||||
@@ -10,11 +10,14 @@ import { join } from 'node:path';
|
||||
import template from '../../assets/studio2_minimax_native.json';
|
||||
import qwen21Template from '../../assets/studio2_qwen21_t2i.json'
|
||||
import qwen21EditTemplate from '../../assets/studio2_qwen21_edit.json'
|
||||
import qwen21T2iTurboTemplate from '../../assets/studio2_qwen21_t2i_turbo.json'
|
||||
import qwen21EditTurboTemplate from '../../assets/studio2_qwen21_edit_turbo.json'
|
||||
import qwen21PeT2iTemplate from '../../assets/studio2_qwen21_pe_t2i.json'
|
||||
import qwen21PeEditTemplate from '../../assets/studio2_qwen21_pe_edit.json';
|
||||
import { nativeVideoGraph, attachHeroReference, applyResolvedImageSize } from '~/shared/studio2/graphs.mjs';
|
||||
import { compilePrompt, scopedFile } from '~/shared/studio2/contracts.mjs';
|
||||
import { resolveQwen21Size } from '~/shared/studio2/qwen21-size.mjs';
|
||||
import { QWEN21_TURBO_LORA, QWEN21_TURBO_SIGMAS, QWEN21_TURBO_STEPS } from '~/shared/studio2/qwen21-turbo.mjs';
|
||||
import { createJob, restoreJob, getJob, emitJob, type Job } from '../jobs';
|
||||
import { markStudioLive, onLiveVideoSettled, type StudioJob } from '../studioQueue';
|
||||
import { readRecord, saveRecord, studio2Root, records } from './store';
|
||||
@@ -235,12 +238,20 @@ async function prepareGraph(r: any) {
|
||||
else if (q.engine === 'qwen21') {
|
||||
if (!['generate', 'edit'].includes(q.mode))
|
||||
throw new Error('Qwen 2.1 supports Generate and Edit.')
|
||||
const turbo = !!q.turbo
|
||||
if (turbo) {
|
||||
q.lora = QWEN21_TURBO_LORA
|
||||
q.sigmas = QWEN21_TURBO_SIGMAS
|
||||
s.steps = QWEN21_TURBO_STEPS
|
||||
s.cfg = 1
|
||||
}
|
||||
const negative = turbo ? '' : stylePrompt(q.imageStyles, true)
|
||||
if (q.mode === 'edit') {
|
||||
if (!a) throw new Error('Choose the source still.')
|
||||
let prompt = String(q.compiledPrompt || '')
|
||||
if (!/<image1>/i.test(prompt))
|
||||
prompt = `Keep the subject in <image1>. ${prompt}`.trim()
|
||||
graph = structuredClone(qwen21EditTemplate)
|
||||
graph = structuredClone(turbo ? qwen21EditTurboTemplate : qwen21EditTemplate)
|
||||
graph['10'].inputs.image = a
|
||||
if (b) {
|
||||
graph['11'] = {
|
||||
@@ -251,10 +262,18 @@ async function prepareGraph(r: any) {
|
||||
graph['9'].inputs['images.image_2'] = ['11', 0]
|
||||
}
|
||||
graph['9'].inputs.prompt = prompt
|
||||
graph['9'].inputs.negative_prompt = stylePrompt(q.imageStyles, true)
|
||||
graph['9'].inputs.negative_prompt = negative
|
||||
// Official edit: sampler latent comes from TextEncode (sized from image_1).
|
||||
// Do not inject EmptyLatentImage — a mismatched empty canvas shifts the edit.
|
||||
graph['9'].inputs.resolution = 1024
|
||||
if (turbo) {
|
||||
graph['7'].inputs.lora_name = QWEN21_TURBO_LORA
|
||||
graph['7'].inputs.strength = 1.0
|
||||
graph['31'].inputs.noise_seed = s.seed
|
||||
graph['33'].inputs.nodes = QWEN21_TURBO_SIGMAS
|
||||
graph['36'].inputs.filename_prefix = prefix + '/image'
|
||||
r.sampleLatent = 'qwen21 edit latent'
|
||||
r.graphId = 'studio2_qwen21_edit_turbo.json'
|
||||
} else {
|
||||
graph['15'].inputs.seed = s.seed
|
||||
graph['15'].inputs.steps = s.steps || 25
|
||||
graph['15'].inputs.cfg = s.cfg ?? 1
|
||||
@@ -262,21 +281,30 @@ async function prepareGraph(r: any) {
|
||||
graph['15'].inputs.scheduler = 'simple'
|
||||
graph['21'].inputs.filename_prefix = prefix + '/image'
|
||||
r.sampleLatent = 'qwen21 edit latent'
|
||||
r.graphId = 'studio2_qwen21_edit.json'
|
||||
}
|
||||
r.sampleDenoise = null
|
||||
r.heroReferenceAttached = false
|
||||
r.graphId = 'studio2_qwen21_edit.json'
|
||||
} else {
|
||||
// Bench aspect is law for Generate. PE wh_ratio is advisory only (never sizes the canvas).
|
||||
const size = resolveQwen21Size(s.aspect)
|
||||
s.width = size.width
|
||||
s.height = size.height
|
||||
graph = structuredClone(qwen21Template)
|
||||
graph = structuredClone(turbo ? qwen21T2iTurboTemplate : qwen21Template)
|
||||
graph['9'].inputs.prompt = q.compiledPrompt
|
||||
graph['9'].inputs.negative_prompt = stylePrompt(q.imageStyles, true)
|
||||
// TextEncode conditioning only — EmptyLatentImage (node 16) owns canvas size, matching the official T2I template.
|
||||
graph['9'].inputs.negative_prompt = negative
|
||||
graph['9'].inputs.resolution = 1024
|
||||
graph['16'].inputs.width = size.width
|
||||
graph['16'].inputs.height = size.height
|
||||
if (turbo) {
|
||||
graph['7'].inputs.lora_name = QWEN21_TURBO_LORA
|
||||
graph['7'].inputs.strength = 1.0
|
||||
graph['31'].inputs.noise_seed = s.seed
|
||||
graph['33'].inputs.nodes = QWEN21_TURBO_SIGMAS
|
||||
graph['36'].inputs.filename_prefix = prefix + '/image'
|
||||
r.sampleLatent = 'qwen21 empty latent'
|
||||
r.graphId = 'studio2_qwen21_t2i_turbo.json'
|
||||
} else {
|
||||
graph['15'].inputs.seed = s.seed
|
||||
graph['15'].inputs.steps = s.steps || 25
|
||||
graph['15'].inputs.cfg = s.cfg ?? 1
|
||||
@@ -284,9 +312,10 @@ async function prepareGraph(r: any) {
|
||||
graph['15'].inputs.scheduler = 'simple'
|
||||
graph['21'].inputs.filename_prefix = prefix + '/image'
|
||||
r.sampleLatent = 'qwen21 empty latent'
|
||||
r.graphId = 'studio2_qwen21_t2i.json'
|
||||
}
|
||||
r.sampleDenoise = null
|
||||
r.heroReferenceAttached = false
|
||||
r.graphId = 'studio2_qwen21_t2i.json'
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -344,7 +373,15 @@ async function run(r: any, job: Job) {
|
||||
throw new Error('Cancelled');
|
||||
update(r, 'submitting');
|
||||
emitJob(job, { type: 'status', message: 'Generating…' });
|
||||
const queued = await queuePrompt(graph, job.clientId);
|
||||
let queued: { prompt_id: string; number?: number }
|
||||
try {
|
||||
queued = await queuePrompt(graph, job.clientId);
|
||||
} catch (error: any) {
|
||||
const msg = String(error?.statusMessage || error?.message || error)
|
||||
if (r.request?.turbo && /lora|ViggleTurbo|not in list|value_not_in_list/i.test(msg))
|
||||
throw new Error(`Qwen Turbo LoRA missing on host (${QWEN21_TURBO_LORA}). Install the r128 file under models/loras and restart Comfy.`)
|
||||
throw error
|
||||
}
|
||||
job.promptId = queued.prompt_id;
|
||||
r.promptId = queued.prompt_id;
|
||||
r.renderStartedAt = Date.now();
|
||||
|
||||
@@ -2,6 +2,7 @@ import {normalizeStyles,stylePrompt} from './styles.mjs'
|
||||
import { queueSeeds } from './seed.mjs'
|
||||
import { sectionsFrom, compileSections } from './prompt.mjs'
|
||||
import { QWEN21_ASPECT_IDS, resolveQwen21Size } from './qwen21-size.mjs'
|
||||
import { QWEN21_TURBO_CFG, QWEN21_TURBO_LORA, QWEN21_TURBO_SIGMAS, QWEN21_TURBO_STEPS } from './qwen21-turbo.mjs'
|
||||
export { sectionsFrom } from './prompt.mjs'
|
||||
export const cameraFamilies = ['static','dolly in','dolly out','pan left','pan right','tilt up','tilt down','zoom in','zoom out','orbit left','orbit right']
|
||||
export const identityLock = '<Picture 1> / hero is identity for face, hair, glasses, body, and wardrobe. Keep the person and clothes fully preserved. Start frame is composition, pose, place, and camera only. Do not copy facial drift from the start frame.'
|
||||
@@ -65,12 +66,15 @@ export function validateRequest(raw, xaigen = false) {
|
||||
const frames=Math.ceil((duration*number('fps',24,1,60)-5)/17)*17+5
|
||||
const shotSeeds=queueSeeds(raw.settings,1+(Array.isArray(raw.batch)?raw.batch.length:0))
|
||||
const enhancePrompt = qwen && !video && !!raw.enhancePrompt
|
||||
const turbo = qwen && !video && !!raw.turbo
|
||||
const width = qwenSize ? qwenSize.width : number('width',defaultWidth,256,4096)
|
||||
const height = qwenSize ? qwenSize.height : number('height',defaultHeight,256,4096)
|
||||
return { mode, engine, shotSeeds, imageStyles:video?{positive:[],negative:[]}:normalizeStyles(raw.imageStyles), hideOutput:!!raw.hideOutput, enhancePrompt, lockFace:qwen ? false : raw.lockFace !== false, lockOutfit:qwen ? false : raw.lockOutfit !== false, hideInput:!!raw.hideInput, folderId:String(raw.folderId), identityStillId:qwen ? '' : (raw.identityStillId || ''), imageAId:raw.imageAId || '', imageBId:raw.imageBId || '', maskId:raw.maskId || '', startClipId:raw.startClipId || '', endStillId:raw.endStillId || '',
|
||||
const steps = turbo ? QWEN21_TURBO_STEPS : number('steps',defaultSteps,1,100)
|
||||
const cfg = turbo ? QWEN21_TURBO_CFG : number('cfg',defaultCfg,0,20)
|
||||
return { mode, engine, shotSeeds, imageStyles:video?{positive:[],negative:[]}:normalizeStyles(raw.imageStyles), hideOutput:!!raw.hideOutput, enhancePrompt, turbo, ...(turbo ? { lora: QWEN21_TURBO_LORA, sigmas: QWEN21_TURBO_SIGMAS } : {}), lockFace:qwen ? false : raw.lockFace !== false, lockOutfit:qwen ? false : raw.lockOutfit !== false, hideInput:!!raw.hideInput, folderId:String(raw.folderId), identityStillId:qwen ? '' : (raw.identityStillId || ''), imageAId:raw.imageAId || '', imageBId:raw.imageBId || '', maskId:raw.maskId || '', startClipId:raw.startClipId || '', endStillId:raw.endStillId || '',
|
||||
startFrameSource: raw.startClipId ? {kind:'previous-last-frame',clipId:raw.startClipId} : {kind:'still',stillId:raw.imageAId || ''},
|
||||
promptSections, compiledPrompt:compilePrompt(promptSections,video,{...raw,engine,lockFace:qwen?false:raw.lockFace,lockOutfit:qwen?false:raw.lockOutfit}),
|
||||
settings: { aspect, fps:number('fps',24,1,60), width, height, steps:number('steps',defaultSteps,1,100),cfg:number('cfg',defaultCfg,0,20),seedMode:raw.settings?.seedMode==='locked'?'locked':'random',seed:shotSeeds[0],duration:number('duration',5,1,15),turbo:raw.settings?.turbo !== false, loraStack:qwen?[]:(Array.isArray(raw.settings?.loraStack)?raw.settings.loraStack:[]) },
|
||||
settings: { aspect, fps:number('fps',24,1,60), width, height, steps, cfg, seedMode:raw.settings?.seedMode==='locked'?'locked':'random',seed:shotSeeds[0],duration:number('duration',5,1,15),turbo:raw.settings?.turbo !== false, loraStack:qwen?[]:(Array.isArray(raw.settings?.loraStack)?raw.settings.loraStack:[]) },
|
||||
guides: (Array.isArray(raw.guides)?raw.guides:[]).slice(0,8).map(g=>{ if (!g.stillId || !Number.isInteger(g.frame) || g.frame<1 || g.frame>=frames) fail('Each guide requires a still and positive frame index.'); return {stillId:String(g.stillId),frame:g.frame} }),
|
||||
batch: (Array.isArray(raw.batch)?raw.batch:[]).slice(0,30).map(s=>{const next=sectionsFrom({...promptSections,...(typeof s==='string'?{action:s}:s)});if(!next.action.trim())fail('Every shot needs an action prompt.');return video?next:{action:next.action}}) }
|
||||
}
|
||||
|
||||
@@ -118,6 +118,7 @@ export function hydrateStudio2Job(form, saved, { stillIds = new Set(), clipIds =
|
||||
mode,
|
||||
engine,
|
||||
enhancePrompt: engine === 'qwen21' && !!src.enhancePrompt,
|
||||
turbo: engine === 'qwen21' && !!src.turbo,
|
||||
lockFace: src.lockFace !== false,
|
||||
lockOutfit: src.lockOutfit !== false,
|
||||
hideInput: !!src.hideInput,
|
||||
|
||||
@@ -83,13 +83,18 @@ test('qwen21 supports Generate and Edit with 25/1 defaults and no locks',()=>{
|
||||
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,promptSections:{action:'x'},settings:{aspect:'1:1'}}).enhancePrompt,false)
|
||||
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,[])
|
||||
@@ -115,6 +120,7 @@ test('custom negative chips survive normalizeStyles and bench exposes Load actio
|
||||
assert.match(src,/loadSelectedJob/);assert.match(src,/hydrateStudio2Job/);assert.match(src,/Edit source/)
|
||||
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(src,/\bTurbo\b/);assert.match(src,/form\.turbo/)
|
||||
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/)
|
||||
@@ -123,6 +129,10 @@ test('custom negative chips survive normalizeStyles and bench exposes Load actio
|
||||
assert.doesNotMatch(peEdit,/UnetLoader|KSampler|VAELoader/)
|
||||
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})
|
||||
|
||||
Reference in New Issue
Block a user