Run image edit on Beast with Flux.2 Klein and stop routing jobs to Sidecar.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,61 +1,99 @@
|
||||
import template from '../assets/workflow_qwen_edit.json'
|
||||
import template from '../assets/workflow_flux2_klein_edit.json'
|
||||
|
||||
export const DEFAULT_EDIT_NEGATIVE = 'blurry, low quality, deformed, bad anatomy, watermark, text, extra fingers'
|
||||
const BYPASS = 4
|
||||
const IMAGE1 = '76'
|
||||
const IMAGE2 = '81'
|
||||
const SINGLE_SAVE = '9'
|
||||
const DUAL_SAVE = '94'
|
||||
const SINGLE_PROMPT = '75:74'
|
||||
const SINGLE_NEGATIVE = '75:67'
|
||||
const SINGLE_NOISE = '75:73'
|
||||
const SINGLE_SCHEDULER = '75:62'
|
||||
const SINGLE_CFG = '75:63'
|
||||
const DUAL_PROMPT = '92:113'
|
||||
const DUAL_NEGATIVE = '92:87'
|
||||
const DUAL_NOISE = '92:105'
|
||||
const DUAL_SCHEDULER = '92:115'
|
||||
const DUAL_CFG = '92:114'
|
||||
|
||||
type WorkflowNode = { class_type: string; inputs: Record<string, unknown>; _meta?: { title?: string } }
|
||||
type WorkflowNode = { class_type: string; inputs: Record<string, unknown>; mode?: number; _meta?: { title?: string } }
|
||||
type WorkflowGraph = Record<string, WorkflowNode>
|
||||
|
||||
export interface EditParams {
|
||||
prompt: string
|
||||
negative?: string
|
||||
imageName: string
|
||||
referenceImageName?: string
|
||||
steps: number
|
||||
seed: number
|
||||
cfg: number
|
||||
denoise: number
|
||||
samplerName?: string
|
||||
scheduler?: string
|
||||
filenamePrefix?: string
|
||||
}
|
||||
|
||||
export function buildEditWorkflow(params: EditParams) {
|
||||
const graph = structuredClone(template) as WorkflowGraph
|
||||
const loadImage = nodeByClass(graph, 'LoadImage') || graph['4']
|
||||
const qwenPrompt = nodeByClass(graph, 'TextEncodeQwenImageEdit') || graph['17']
|
||||
const sampler = nodeByClass(graph, 'KSampler') || graph['8']
|
||||
const save = nodeByClass(graph, 'SaveImage') || graph['10']
|
||||
if (loadImage) loadImage.inputs.image = params.imageName
|
||||
if (qwenPrompt) {
|
||||
if ('prompt' in qwenPrompt.inputs) qwenPrompt.inputs.prompt = params.prompt
|
||||
if ('text' in qwenPrompt.inputs) qwenPrompt.inputs.text = params.prompt
|
||||
const dual = Boolean(params.referenceImageName)
|
||||
const prompt = String(params.prompt || '').trim()
|
||||
const negative = String(params.negative || '')
|
||||
const prefix = params.filenamePrefix || 'aigen-edit'
|
||||
|
||||
setImage(graph, IMAGE1, params.imageName)
|
||||
setImage(graph, IMAGE2, params.referenceImageName || '')
|
||||
setText(graph, SINGLE_PROMPT, prompt)
|
||||
setText(graph, DUAL_PROMPT, prompt)
|
||||
setText(graph, SINGLE_NEGATIVE, negative)
|
||||
setText(graph, DUAL_NEGATIVE, negative)
|
||||
setInput(graph, SINGLE_NOISE, 'noise_seed', params.seed)
|
||||
setInput(graph, DUAL_NOISE, 'noise_seed', params.seed)
|
||||
setInput(graph, SINGLE_SCHEDULER, 'steps', params.steps)
|
||||
setInput(graph, DUAL_SCHEDULER, 'steps', params.steps)
|
||||
setInput(graph, SINGLE_CFG, 'cfg', params.cfg)
|
||||
setInput(graph, DUAL_CFG, 'cfg', params.cfg)
|
||||
setInput(graph, SINGLE_SAVE, 'filename_prefix', prefix)
|
||||
setInput(graph, DUAL_SAVE, 'filename_prefix', prefix)
|
||||
|
||||
for (const [id, node] of Object.entries(graph)) {
|
||||
if (!node) continue
|
||||
if (id === IMAGE1) {
|
||||
delete node.mode
|
||||
continue
|
||||
}
|
||||
const dualNode = id === IMAGE2 || id === DUAL_SAVE || id.startsWith('92:')
|
||||
const singleNode = id === SINGLE_SAVE || id.startsWith('75:')
|
||||
if (dual ? singleNode : dualNode) node.mode = BYPASS
|
||||
else delete node.mode
|
||||
}
|
||||
if (sampler) {
|
||||
sampler.inputs.seed = params.seed
|
||||
sampler.inputs.steps = params.steps
|
||||
sampler.inputs.cfg = params.cfg
|
||||
sampler.inputs.denoise = params.denoise
|
||||
sampler.inputs.sampler_name = params.samplerName || 'euler'
|
||||
sampler.inputs.scheduler = params.scheduler || 'simple'
|
||||
}
|
||||
if (save && params.filenamePrefix) save.inputs.filename_prefix = params.filenamePrefix
|
||||
|
||||
return graph
|
||||
}
|
||||
|
||||
function nodeByClass(graph: WorkflowGraph, classType: string, index = 0) {
|
||||
const matches = Object.values(graph).filter(node => node?.class_type === classType)
|
||||
return matches[index]
|
||||
function setImage(graph: WorkflowGraph, id: string, name: string) {
|
||||
if (graph[id]) graph[id].inputs.image = name
|
||||
}
|
||||
|
||||
function setText(graph: WorkflowGraph, id: string, text: string) {
|
||||
if (graph[id]) graph[id].inputs.text = text
|
||||
}
|
||||
|
||||
function setInput(graph: WorkflowGraph, id: string, key: string, value: unknown) {
|
||||
if (graph[id]) graph[id].inputs[key] = value
|
||||
}
|
||||
|
||||
export const EDIT_NODE_LABELS: Record<string, string> = {
|
||||
'1': 'Loading Qwen Edit UNET',
|
||||
'11': 'Applying Lightning LoRA',
|
||||
'2': 'Loading Qwen CLIP',
|
||||
'3': 'Loading Qwen VAE',
|
||||
'4': 'Loading input still',
|
||||
'17': 'Encoding Qwen edit prompt',
|
||||
'19': 'Preparing negative',
|
||||
'7': 'Encoding still',
|
||||
'8': 'Sampling edit',
|
||||
'9': 'Decoding still',
|
||||
'10': 'Saving still'
|
||||
'76': 'Loading image 1',
|
||||
'81': 'Loading image 2',
|
||||
'75:70': 'Loading Flux.2 Klein',
|
||||
'75:71': 'Loading CLIP',
|
||||
'75:72': 'Loading VAE',
|
||||
'75:74': 'Encoding prompt',
|
||||
'75:64': 'Sampling edit',
|
||||
'75:65': 'Decoding still',
|
||||
'9': 'Saving still',
|
||||
'92:106': 'Loading Flux.2 Klein',
|
||||
'92:111': 'Loading CLIP',
|
||||
'92:107': 'Loading VAE',
|
||||
'92:113': 'Encoding prompt',
|
||||
'92:103': 'Sampling two-image edit',
|
||||
'92:104': 'Decoding still',
|
||||
'94': 'Saving still'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user