Enable Qwen 2.1 on Edit: wire picker, runner, and source-still validation.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -8,7 +8,8 @@ import { queueSeeds } from '~/shared/studio2/seed.mjs';
|
||||
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
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 { compilePrompt, scopedFile } from '~/shared/studio2/contracts.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> {
|
||||
const prefix = String(useRuntimeConfig().comfyFilenamePrefix).replace(/\/$/, '') + `/studio2/${r.id}/${r.index}`;
|
||||
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);
|
||||
fitted = true;
|
||||
}
|
||||
@@ -129,26 +130,64 @@ async function prepareGraph(r: any) {
|
||||
}
|
||||
}
|
||||
else if (q.engine === 'qwen21') {
|
||||
if (q.mode !== 'generate')
|
||||
throw new Error('Qwen 2.1 is T2I in this build.');
|
||||
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';
|
||||
if (!['generate', 'edit'].includes(q.mode))
|
||||
throw new Error('Qwen 2.1 supports Generate and Edit.')
|
||||
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['10'].inputs.image = a
|
||||
if (b) {
|
||||
graph['11'] = {
|
||||
inputs: { image: b },
|
||||
class_type: 'LoadImage',
|
||||
_meta: { title: 'image_2 optional' }
|
||||
}
|
||||
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.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 {
|
||||
const mode = identityPlan?.mode ?? (q.mode === 'iterate' ? (a ? 'edit' : 'generate') : q.mode);
|
||||
|
||||
Reference in New Issue
Block a user