Add Image v2 Refine for on-device regional edits.
Mask painter and denoise strength sit beside Edit and Compose. The canned hand/chest prompt helper is gone so that text stays yours. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -15,6 +15,7 @@ export type EditV2RunParams = {
|
||||
task: ImageV2Task
|
||||
image: EditImageFile
|
||||
reference: EditImageFile | null
|
||||
mask?: EditImageFile | null
|
||||
prompt: string
|
||||
negative: string
|
||||
steps: number
|
||||
@@ -25,11 +26,15 @@ export type EditV2RunParams = {
|
||||
consistencyModel: number
|
||||
consistencyClip: number
|
||||
megapixels: number
|
||||
strength?: number
|
||||
}
|
||||
|
||||
export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
const library = job.library
|
||||
if (!library) throw new Error('Edit job is missing library metadata')
|
||||
if (params.mode === 'refine' && !params.mask) {
|
||||
throw new Error('Refine requires a mask. Refusing to fall back to Edit.')
|
||||
}
|
||||
if (params.mode === 'compose' && !params.reference) {
|
||||
throw new Error('Compose requires image B. Refusing to fall back to one-image generation.')
|
||||
}
|
||||
@@ -52,23 +57,35 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
await withImageComfyHost(job.imageComfyHost, async () => {
|
||||
emitChainJob(job, {
|
||||
type: 'status',
|
||||
message: params.mode === 'compose' ? 'Uploading stills A and B to Beast...' : 'Uploading still A to Beast...',
|
||||
message: params.mode === 'refine'
|
||||
? 'Uploading canvas and mask to Beast...'
|
||||
: params.mode === 'compose' ? 'Uploading stills A and B to Beast...' : 'Uploading still A to Beast...',
|
||||
progress: 8
|
||||
})
|
||||
const uploaded = await uploadImage(params.image, job.id)
|
||||
const uploadedRef = params.reference
|
||||
const uploadedRef = params.mode === 'refine'
|
||||
? null
|
||||
: params.reference
|
||||
? await uploadImage({
|
||||
...params.reference,
|
||||
filename: `ref_${params.reference.filename || 'image_b.png'}`
|
||||
}, job.id)
|
||||
: null
|
||||
const uploadedMask = params.mode === 'refine' && params.mask
|
||||
? await uploadImage({
|
||||
...params.reference,
|
||||
filename: `ref_${params.reference.filename || 'image_b.png'}`
|
||||
...params.mask,
|
||||
filename: `mask_${params.mask.filename || 'refine-mask.png'}`
|
||||
}, job.id)
|
||||
: null
|
||||
if (job.status === 'cancelled') throw new Error('Job interrupted.')
|
||||
|
||||
emitChainJob(job, {
|
||||
type: 'status',
|
||||
message: params.mode === 'compose'
|
||||
? `Queueing Klein v2 compose (${params.task}) on Beast...`
|
||||
: 'Queueing Klein v2 edit on Beast...',
|
||||
message: params.mode === 'refine'
|
||||
? 'Queueing Klein v2 refine on Beast...'
|
||||
: params.mode === 'compose'
|
||||
? `Queueing Klein v2 compose (${params.task}) on Beast...`
|
||||
: 'Queueing Klein v2 edit on Beast...',
|
||||
progress: 12
|
||||
})
|
||||
await ensureComfyLoraNames('image')
|
||||
@@ -80,6 +97,8 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
negative: params.negative,
|
||||
imageAName: uploaded.name,
|
||||
imageBName: uploadedRef?.name,
|
||||
maskName: uploadedMask?.name,
|
||||
strength: params.strength,
|
||||
snofsModel: params.snofsModel,
|
||||
snofsClip: params.snofsClip,
|
||||
consistencyModel: params.consistencyModel,
|
||||
@@ -137,7 +156,7 @@ export async function runEditV2(job: Job, params: EditV2RunParams) {
|
||||
video: { filename: output.filename, subfolder: output.subfolder, type: output.type },
|
||||
imageName: uploaded.name,
|
||||
imageSubfolder: uploaded.subfolder,
|
||||
extraImageNames: uploadedRef?.name ? [uploadedRef.name] : [],
|
||||
extraImageNames: [uploadedRef?.name, uploadedMask?.name].filter((name): name is string => Boolean(name)),
|
||||
promptId: job.promptId
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user