Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52a01e0adc | ||
|
|
8609ba24a4 |
@@ -89,7 +89,8 @@
|
|||||||
<label class="inline-flex items-center gap-2"><input v-model="form.lockOutfit" type="checkbox" class="accent-[var(--amber)]">Lock outfit</label>
|
<label class="inline-flex items-center gap-2"><input v-model="form.lockOutfit" type="checkbox" class="accent-[var(--amber)]">Lock outfit</label>
|
||||||
<p class="text-[11px]">hero encode + lock text — not a MiniMax face/clothes split on stills</p>
|
<p class="text-[11px]">hero encode + lock text — not a MiniMax face/clothes split on stills</p>
|
||||||
</div>
|
</div>
|
||||||
<p v-else class="text-[var(--type-sm)] text-[var(--muted)]">Qwen 2.1 is T2I — no hero, locks, or LoRA stack in this build.</p>
|
<p v-else-if="form.mode==='edit'" class="text-[var(--type-sm)] text-[var(--muted)]">Qwen edit uses source as <image1>, not Klein hero-ref. No locks or LoRA stack.</p>
|
||||||
|
<p v-else class="text-[var(--type-sm)] text-[var(--muted)]">Qwen 2.1 — no hero, locks, or LoRA stack.</p>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<span class="field-label">Prompt</span>
|
<span class="field-label">Prompt</span>
|
||||||
@@ -354,7 +355,7 @@ const videoDuration=ref(0)
|
|||||||
const focusedJobId=ref('')
|
const focusedJobId=ref('')
|
||||||
const video=computed(()=>['video','extend'].includes(form.mode))
|
const video=computed(()=>['video','extend'].includes(form.mode))
|
||||||
const kreaSticky=computed(()=>['edit','compose','iterate'].includes(form.mode))
|
const kreaSticky=computed(()=>['edit','compose','iterate'].includes(form.mode))
|
||||||
const qwenOnlyGenerate=computed(()=>form.mode!=='generate')
|
const qwenAllowed=computed(()=>['generate','edit'].includes(form.mode))
|
||||||
const engineOptions=computed(()=>{
|
const engineOptions=computed(()=>{
|
||||||
if(video.value){
|
if(video.value){
|
||||||
return [
|
return [
|
||||||
@@ -365,7 +366,7 @@ const engineOptions=computed(()=>{
|
|||||||
return [
|
return [
|
||||||
{value:'flux',label:'Klein',disabled:false,hint:''},
|
{value:'flux',label:'Klein',disabled:false,hint:''},
|
||||||
{value:'krea',label:'Krea 2',disabled:kreaSticky.value,hint:kreaSticky.value?'sticky hero = Klein':''},
|
{value:'krea',label:'Krea 2',disabled:kreaSticky.value,hint:kreaSticky.value?'sticky hero = Klein':''},
|
||||||
{value:'qwen21',label:'Qwen 2.1',disabled:qwenOnlyGenerate.value,hint:qwenOnlyGenerate.value?'Qwen 2.1 is T2I in this build':''}
|
{value:'qwen21',label:'Qwen 2.1',disabled:!qwenAllowed.value,hint:qwenAllowed.value?'':'Generate and Edit only'}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
watch(engineOptions,opts=>{
|
watch(engineOptions,opts=>{
|
||||||
@@ -381,6 +382,15 @@ watch(()=>form.engine,(engine,prev)=>{
|
|||||||
form.settings.loraStack=[]
|
form.settings.loraStack=[]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
watch(()=>[form.mode,form.engine],([mode,engine])=>{
|
||||||
|
if(engine==='qwen21' && mode==='edit'){
|
||||||
|
form.settings.steps=25
|
||||||
|
form.settings.cfg=1
|
||||||
|
form.lockFace=false
|
||||||
|
form.lockOutfit=false
|
||||||
|
form.settings.loraStack=[]
|
||||||
|
}
|
||||||
|
})
|
||||||
const stills=computed(()=>library.value.stills.filter((s:any)=>s.folderId===form.folderId))
|
const stills=computed(()=>library.value.stills.filter((s:any)=>s.folderId===form.folderId))
|
||||||
const clips=computed(()=>library.value.clips.filter((s:any)=>s.folderId===form.folderId))
|
const clips=computed(()=>library.value.clips.filter((s:any)=>s.folderId===form.folderId))
|
||||||
const stillUrl=(id:string)=>`/api/library/stills/${id}`
|
const stillUrl=(id:string)=>`/api/library/stills/${id}`
|
||||||
|
|||||||
+6
-4
@@ -34,9 +34,11 @@ Restart Comfy **only when idle** (`COMFY_CONTROL_URL/status` → `gpu.busy=false
|
|||||||
## App
|
## App
|
||||||
|
|
||||||
- Engine key: `qwen21` · UI label: **Qwen 2.1**
|
- Engine key: `qwen21` · UI label: **Qwen 2.1**
|
||||||
- Generate (T2I) only in this build. Edit / Compose / Iterate / Video disable with: “Qwen 2.1 is T2I in this build”.
|
- **Generate** (T2I) and **Edit** (same checkpoint, second graph). Compose / Iterate / Video / Extend / Music stay disabled unless a graph exists.
|
||||||
- Sampler defaults: euler / simple / cfg **1** / steps **25** · `ModelSamplingAuraFlow` shift **3.1**
|
- Edit requires Start still → `images.image_1`; prompt slots use `<image1>` / `<image2>`. If the user omits `<image1>`, the runner prepends `Keep the subject in <image1>.`
|
||||||
- Default canvas **1024×1024** (aspect 16:9 / 9:16 / 1:1 → long-edge square via `TextEncodeQwenImage21` resolution; multiples of 32). Drop to 768 if VRAM errors.
|
- Face / outfit locks stay Klein semantics — they do not drive Qwen slots.
|
||||||
|
- Sampler defaults: euler / simple / cfg **1** / steps **25**. Edit uses `QwenImage21Cache` (device auto, dtype int8). T2I uses `ModelSamplingAuraFlow` shift **3.1**.
|
||||||
|
- Default canvas **1024×1024** (aspect 16:9 / 9:16 / 1:1 → long-edge ~1024 via `TextEncodeQwenImage21` resolution; multiples of 32). Edit with aspect `auto` follows `image_1` aspect at ~1024.
|
||||||
- No hero / locks / Klein LoRA stack on this engine.
|
- No hero / locks / Klein LoRA stack on this engine.
|
||||||
|
|
||||||
Graph: `server/assets/studio2_qwen21_t2i.json`.
|
Graphs: `server/assets/studio2_qwen21_t2i.json`, `server/assets/studio2_qwen21_edit.json`.
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"4": {
|
||||||
|
"inputs": {
|
||||||
|
"unet_name": "qwen-image-2.1-Q8_0.gguf"
|
||||||
|
},
|
||||||
|
"class_type": "UnetLoaderGGUF",
|
||||||
|
"_meta": { "title": "Load Qwen 2.1 GGUF" }
|
||||||
|
},
|
||||||
|
"8": {
|
||||||
|
"inputs": {
|
||||||
|
"device": "auto",
|
||||||
|
"dtype": "int8",
|
||||||
|
"model": ["4", 0]
|
||||||
|
},
|
||||||
|
"class_type": "QwenImage21Cache",
|
||||||
|
"_meta": { "title": "Qwen 2.1 Cache" }
|
||||||
|
},
|
||||||
|
"5": {
|
||||||
|
"inputs": {
|
||||||
|
"clip_name": "qwen3vl_8b_int8_convrot.safetensors",
|
||||||
|
"type": "qwen_image",
|
||||||
|
"device": "default"
|
||||||
|
},
|
||||||
|
"class_type": "CLIPLoader",
|
||||||
|
"_meta": { "title": "Load Qwen 2.1 CLIP" }
|
||||||
|
},
|
||||||
|
"6": {
|
||||||
|
"inputs": {
|
||||||
|
"vae_name": "qwen_image_2.1_vae_bf16.safetensors"
|
||||||
|
},
|
||||||
|
"class_type": "VAELoader",
|
||||||
|
"_meta": { "title": "Load Qwen 2.1 VAE" }
|
||||||
|
},
|
||||||
|
"10": {
|
||||||
|
"inputs": {
|
||||||
|
"image": "qwen21_edit_source.png"
|
||||||
|
},
|
||||||
|
"class_type": "LoadImage",
|
||||||
|
"_meta": { "title": "image_1 source" }
|
||||||
|
},
|
||||||
|
"9": {
|
||||||
|
"inputs": {
|
||||||
|
"clip": ["5", 0],
|
||||||
|
"vae": ["6", 0],
|
||||||
|
"prompt": "",
|
||||||
|
"negative_prompt": "",
|
||||||
|
"resolution": 1024,
|
||||||
|
"images.image_1": ["10", 0]
|
||||||
|
},
|
||||||
|
"class_type": "TextEncodeQwenImage21",
|
||||||
|
"_meta": { "title": "Text Encode Qwen Image 2.1 Edit" }
|
||||||
|
},
|
||||||
|
"15": {
|
||||||
|
"inputs": {
|
||||||
|
"seed": 1,
|
||||||
|
"steps": 25,
|
||||||
|
"cfg": 1,
|
||||||
|
"sampler_name": "euler",
|
||||||
|
"scheduler": "simple",
|
||||||
|
"denoise": 1,
|
||||||
|
"model": ["8", 0],
|
||||||
|
"positive": ["9", 0],
|
||||||
|
"negative": ["9", 1],
|
||||||
|
"latent_image": ["9", 2]
|
||||||
|
},
|
||||||
|
"class_type": "KSampler",
|
||||||
|
"_meta": { "title": "KSampler" }
|
||||||
|
},
|
||||||
|
"20": {
|
||||||
|
"inputs": {
|
||||||
|
"samples": ["15", 0],
|
||||||
|
"vae": ["6", 0]
|
||||||
|
},
|
||||||
|
"class_type": "VAEDecode",
|
||||||
|
"_meta": { "title": "VAE Decode" }
|
||||||
|
},
|
||||||
|
"21": {
|
||||||
|
"inputs": {
|
||||||
|
"filename_prefix": "studio2-qwen21-edit",
|
||||||
|
"images": ["20", 0]
|
||||||
|
},
|
||||||
|
"class_type": "SaveImage",
|
||||||
|
"_meta": { "title": "Save Image" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,8 @@ import { queueSeeds } from '~/shared/studio2/seed.mjs';
|
|||||||
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from 'node:fs';
|
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import template from '../../assets/studio2_minimax_native.json';
|
import template from '../../assets/studio2_minimax_native.json';
|
||||||
import qwen21Template from '../../assets/studio2_qwen21_t2i.json';
|
import qwen21Template from '../../assets/studio2_qwen21_t2i.json'
|
||||||
|
import qwen21EditTemplate from '../../assets/studio2_qwen21_edit.json';
|
||||||
import { nativeVideoGraph, attachHeroReference, applyResolvedImageSize } from '~/shared/studio2/graphs.mjs';
|
import { nativeVideoGraph, attachHeroReference, applyResolvedImageSize } from '~/shared/studio2/graphs.mjs';
|
||||||
import { compilePrompt, scopedFile } from '~/shared/studio2/contracts.mjs';
|
import { compilePrompt, scopedFile } from '~/shared/studio2/contracts.mjs';
|
||||||
import { resolveQwen21Size } from '~/shared/studio2/input.mjs';
|
import { resolveQwen21Size } from '~/shared/studio2/input.mjs';
|
||||||
@@ -39,7 +40,7 @@ const NEVER_FIT = new Set(['hero', 'start']);
|
|||||||
async function upload(r: any, name: string, data: Buffer): Promise<string> {
|
async function upload(r: any, name: string, data: Buffer): Promise<string> {
|
||||||
const prefix = String(useRuntimeConfig().comfyFilenamePrefix).replace(/\/$/, '') + `/studio2/${r.id}/${r.index}`;
|
const prefix = String(useRuntimeConfig().comfyFilenamePrefix).replace(/\/$/, '') + `/studio2/${r.id}/${r.index}`;
|
||||||
let fitted = false;
|
let fitted = false;
|
||||||
if (!NEVER_FIT.has(name) && needsFit(data, r.request.settings.width, r.request.settings.height)) {
|
if (!NEVER_FIT.has(name) && r.request?.engine !== 'qwen21' && needsFit(data, r.request.settings.width, r.request.settings.height)) {
|
||||||
data = await fitStill(data, r.request.settings.width, r.request.settings.height);
|
data = await fitStill(data, r.request.settings.width, r.request.settings.height);
|
||||||
fitted = true;
|
fitted = true;
|
||||||
}
|
}
|
||||||
@@ -129,26 +130,64 @@ async function prepareGraph(r: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (q.engine === 'qwen21') {
|
else if (q.engine === 'qwen21') {
|
||||||
if (q.mode !== 'generate')
|
if (!['generate', 'edit'].includes(q.mode))
|
||||||
throw new Error('Qwen 2.1 is T2I in this build.');
|
throw new Error('Qwen 2.1 supports Generate and Edit.')
|
||||||
const size = resolveQwen21Size(s.aspect);
|
if (q.mode === 'edit') {
|
||||||
s.width = size.width;
|
if (!a) throw new Error('Choose the source still.')
|
||||||
s.height = size.height;
|
let prompt = String(q.compiledPrompt || '')
|
||||||
graph = structuredClone(qwen21Template);
|
if (!/<image1>/i.test(prompt))
|
||||||
graph['9'].inputs.prompt = q.compiledPrompt;
|
prompt = `Keep the subject in <image1>. ${prompt}`.trim()
|
||||||
graph['9'].inputs.negative_prompt = stylePrompt(q.imageStyles, true);
|
graph = structuredClone(qwen21EditTemplate)
|
||||||
// TextEncodeQwenImage21 builds the 64-ch empty latent from resolution (square T2I).
|
graph['10'].inputs.image = a
|
||||||
graph['9'].inputs.resolution = Math.max(size.width, size.height);
|
if (b) {
|
||||||
graph['15'].inputs.seed = s.seed;
|
graph['11'] = {
|
||||||
graph['15'].inputs.steps = s.steps || 25;
|
inputs: { image: b },
|
||||||
graph['15'].inputs.cfg = s.cfg ?? 1;
|
class_type: 'LoadImage',
|
||||||
graph['15'].inputs.sampler_name = 'euler';
|
_meta: { title: 'image_2 optional' }
|
||||||
graph['15'].inputs.scheduler = 'simple';
|
}
|
||||||
graph['21'].inputs.filename_prefix = prefix + '/image';
|
graph['9'].inputs['images.image_2'] = ['11', 0]
|
||||||
r.sampleLatent = 'qwen21 textencode latent';
|
}
|
||||||
r.sampleDenoise = null;
|
graph['9'].inputs.prompt = prompt
|
||||||
r.heroReferenceAttached = false;
|
graph['9'].inputs.negative_prompt = stylePrompt(q.imageStyles, true)
|
||||||
r.graphId = 'studio2_qwen21_t2i.json';
|
graph['9'].inputs.resolution = 1024
|
||||||
|
if (s.aspect && s.aspect !== 'auto') {
|
||||||
|
graph['16'] = {
|
||||||
|
inputs: { width: s.width, height: s.height, batch_size: 1 },
|
||||||
|
class_type: 'EmptyLatentImage',
|
||||||
|
_meta: { title: 'Qwen edit canvas' }
|
||||||
|
}
|
||||||
|
graph['15'].inputs.latent_image = ['16', 0]
|
||||||
|
}
|
||||||
|
graph['15'].inputs.seed = s.seed
|
||||||
|
graph['15'].inputs.steps = s.steps || 25
|
||||||
|
graph['15'].inputs.cfg = s.cfg ?? 1
|
||||||
|
graph['15'].inputs.sampler_name = 'euler'
|
||||||
|
graph['15'].inputs.scheduler = 'simple'
|
||||||
|
graph['21'].inputs.filename_prefix = prefix + '/image'
|
||||||
|
r.sampleLatent = s.aspect && s.aspect !== 'auto' ? 'qwen21 empty latent' : 'qwen21 edit latent'
|
||||||
|
r.sampleDenoise = null
|
||||||
|
r.heroReferenceAttached = false
|
||||||
|
r.graphId = 'studio2_qwen21_edit.json'
|
||||||
|
} else {
|
||||||
|
const size = resolveQwen21Size(s.aspect)
|
||||||
|
s.width = size.width
|
||||||
|
s.height = size.height
|
||||||
|
graph = structuredClone(qwen21Template)
|
||||||
|
graph['9'].inputs.prompt = q.compiledPrompt
|
||||||
|
graph['9'].inputs.negative_prompt = stylePrompt(q.imageStyles, true)
|
||||||
|
// TextEncodeQwenImage21 builds the 64-ch empty latent from resolution (square T2I).
|
||||||
|
graph['9'].inputs.resolution = Math.max(size.width, size.height)
|
||||||
|
graph['15'].inputs.seed = s.seed
|
||||||
|
graph['15'].inputs.steps = s.steps || 25
|
||||||
|
graph['15'].inputs.cfg = s.cfg ?? 1
|
||||||
|
graph['15'].inputs.sampler_name = 'euler'
|
||||||
|
graph['15'].inputs.scheduler = 'simple'
|
||||||
|
graph['21'].inputs.filename_prefix = prefix + '/image'
|
||||||
|
r.sampleLatent = 'qwen21 textencode latent'
|
||||||
|
r.sampleDenoise = null
|
||||||
|
r.heroReferenceAttached = false
|
||||||
|
r.graphId = 'studio2_qwen21_t2i.json'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
const mode = identityPlan?.mode ?? (q.mode === 'iterate' ? (a ? 'edit' : 'generate') : q.mode);
|
const mode = identityPlan?.mode ?? (q.mode === 'iterate' ? (a ? 'edit' : 'generate') : q.mode);
|
||||||
|
|||||||
@@ -34,14 +34,14 @@ export function validateRequest(raw, xaigen = false) {
|
|||||||
if (!(video ? ['minimax','ltx'] : ['flux','krea','qwen21']).includes(engine)) fail('Engine does not support this task.')
|
if (!(video ? ['minimax','ltx'] : ['flux','krea','qwen21']).includes(engine)) fail('Engine does not support this task.')
|
||||||
if (engine === 'ltx' && !xaigen) fail('LTX is available only in xAIGen.')
|
if (engine === 'ltx' && !xaigen) fail('LTX is available only in xAIGen.')
|
||||||
if (engine === 'krea' && ['edit','compose','iterate'].includes(mode)) fail('Sticky hero edits require Klein. Krea remains available for Generate and masked Refine; no engine fallback is performed.')
|
if (engine === 'krea' && ['edit','compose','iterate'].includes(mode)) fail('Sticky hero edits require Klein. Krea remains available for Generate and masked Refine; no engine fallback is performed.')
|
||||||
if (engine === 'qwen21' && mode !== 'generate') fail('Qwen 2.1 is T2I in this build.')
|
if (engine === 'qwen21' && !['generate', 'edit'].includes(mode)) fail('Qwen 2.1 supports Generate and Edit.')
|
||||||
if (Array.isArray(raw.batch) && raw.batch.length>30) fail('A batch supports up to 31 shots including the first prompt.')
|
if (Array.isArray(raw.batch) && raw.batch.length>30) fail('A batch supports up to 31 shots including the first prompt.')
|
||||||
if (Array.isArray(raw.guides) && raw.guides.length>8) fail('Use up to eight guide stills.')
|
if (Array.isArray(raw.guides) && raw.guides.length>8) fail('Use up to eight guide stills.')
|
||||||
if (!raw.folderId) fail('Choose a folder.')
|
if (!raw.folderId) fail('Choose a folder.')
|
||||||
if ((video || ['edit','compose','iterate'].includes(mode)) && (raw.lockFace !== false || raw.lockOutfit !== false) && !raw.identityStillId && (video || !raw.imageAId)) fail('Choose a sticky hero still.')
|
if (['edit','compose','refine'].includes(mode) && !raw.imageAId && !(mode==='edit' && engine==='flux' && raw.identityStillId)) fail('Choose the source still.')
|
||||||
|
if ((video || ['edit','compose','iterate'].includes(mode)) && engine !== 'qwen21' && (raw.lockFace !== false || raw.lockOutfit !== false) && !raw.identityStillId && (video || !raw.imageAId)) fail('Choose a sticky hero still.')
|
||||||
if (mode === 'compose' && !raw.imageBId) fail('Compose requires a second still.')
|
if (mode === 'compose' && !raw.imageBId) fail('Compose requires a second still.')
|
||||||
if (mode === 'refine' && !raw.maskId) fail('Refine requires a mask.')
|
if (mode === 'refine' && !raw.maskId) fail('Refine requires a mask.')
|
||||||
if (['edit','compose','refine'].includes(mode) && !raw.imageAId && !(mode==='edit' && engine==='flux' && raw.identityStillId)) fail('Choose the source still.')
|
|
||||||
if (mode === 'extend' && !raw.startClipId) fail('Select the clip in the family strip or Previous clip.')
|
if (mode === 'extend' && !raw.startClipId) fail('Select the clip in the family strip or Previous clip.')
|
||||||
const allSections = sectionsFrom(raw.promptSections || {action:raw.prompt || ''})
|
const allSections = sectionsFrom(raw.promptSections || {action:raw.prompt || ''})
|
||||||
const promptSections = video ? allSections : {action:allSections.action}
|
const promptSections = video ? allSections : {action:allSections.action}
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
import {restoreLoras,loraUse} from './lora-memory.mjs'
|
import {restoreLoras,loraUse} from './lora-memory.mjs'
|
||||||
import { restoreInput } from './contracts.mjs'
|
import { restoreInput } from './contracts.mjs'
|
||||||
export const aspectOptions = [{id:'auto',label:'Automatic',hint:'Match start or hero still'},{id:'16:9',label:'16:9',hint:'1344 × 768 · padded'},{id:'9:16',label:'9:16',hint:'768 × 1344 · padded'},{id:'1:1',label:'1:1',hint:'960 × 960 · padded'}]
|
export const aspectOptions = [{id:'auto',label:'Automatic',hint:'Match start or hero still'},{id:'16:9',label:'16:9',hint:'1344 × 768 · padded'},{id:'9:16',label:'9:16',hint:'768 × 1344 · padded'},{id:'1:1',label:'1:1',hint:'960 × 960 · padded'}]
|
||||||
export function resolveQwen21Size(aspect) {
|
export function resolveQwen21Size(aspect, source) {
|
||||||
const presets = {'16:9':[1024,576],'9:16':[576,1024],'1:1':[1024,1024]}
|
const presets = {'16:9':[1024,576],'9:16':[576,1024],'1:1':[1024,1024]}
|
||||||
if (presets[aspect]) return {width:presets[aspect][0],height:presets[aspect][1]}
|
if (presets[aspect]) return {width:presets[aspect][0],height:presets[aspect][1]}
|
||||||
|
if (source?.width && source?.height) {
|
||||||
|
const ratio = source.width / source.height
|
||||||
|
const width = Math.max(32, Math.round(Math.sqrt(1024 * 1024 * ratio) / 32) * 32)
|
||||||
|
const height = Math.max(32, Math.round(Math.sqrt(1024 * 1024 / ratio) / 32) * 32)
|
||||||
|
return {width, height}
|
||||||
|
}
|
||||||
return {width:1024,height:1024}
|
return {width:1024,height:1024}
|
||||||
}
|
}
|
||||||
export function resolveSize(aspect, source, fallback = {width:1344,height:768}, engine = '') {
|
export function resolveSize(aspect, source, fallback = {width:1344,height:768}, engine = '') {
|
||||||
if (engine === 'qwen21') return resolveQwen21Size(aspect)
|
if (engine === 'qwen21') return resolveQwen21Size(aspect, source)
|
||||||
const presets = {'16:9':[1344,768],'9:16':[768,1344],'1:1':[960,960]}
|
const presets = {'16:9':[1344,768],'9:16':[768,1344],'1:1':[960,960]}
|
||||||
if (presets[aspect]) return {width:presets[aspect][0],height:presets[aspect][1]}
|
if (presets[aspect]) return {width:presets[aspect][0],height:presets[aspect][1]}
|
||||||
if (!source?.width || !source?.height) return {width:fallback.width || 1344,height:fallback.height || 768}
|
if (!source?.width || !source?.height) return {width:fallback.width || 1344,height:fallback.height || 768}
|
||||||
|
|||||||
@@ -74,10 +74,15 @@ test('image chips compile positive and negative independently; video chips do no
|
|||||||
assert.doesNotMatch(compilePrompt(q.promptSections,false,q),/Picture 1/)
|
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'])
|
assert.deepEqual(parseBatch('A wrapped\nsentence\n\nSecond shot\r\n\r\nThird'),['A wrapped\nsentence','Second shot','Third'])
|
||||||
})
|
})
|
||||||
test('qwen21 is generate-only T2I with 25/1 defaults and no locks',()=>{
|
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'}})
|
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.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.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.lockFace,false);assert.equal(q.lockOutfit,false);assert.deepEqual(q.settings.loraStack,[])
|
||||||
assert.throws(()=>validateRequest({mode:'edit',engine:'qwen21',folderId:'f',imageAId:'s',promptSections:{action:'x'}}),/T2I in this build/)
|
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'}}),/source still/)
|
||||||
|
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/)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user