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:
Towsty
2026-08-28 22:03:20 -05:00
co-authored by Cursor
parent 21fe2412f9
commit a2895b8054
10 changed files with 825 additions and 56 deletions
+33 -5
View File
@@ -1,5 +1,5 @@
export const IMAGE_V2_MODES = ['edit', 'compose'] as const
export const IMAGE_V2_TASKS = ['scene', 'identity', 'outfit', 'face_lock'] as const
export const IMAGE_V2_MODES = ['edit', 'compose', 'refine'] as const
export const IMAGE_V2_TASKS = ['scene', 'identity', 'outfit', 'face_lock', 'refine'] as const
export type ImageV2Mode = (typeof IMAGE_V2_MODES)[number]
export type ImageV2Task = (typeof IMAGE_V2_TASKS)[number]
@@ -18,8 +18,28 @@ export const IMAGE_V2_STRENGTH_STEP = 0.05
export const IMAGE_V2_SNOFS_LORA = 'klein_snofs_v1_4.safetensors'
export const IMAGE_V2_CONSISTENCY_LORA = 'Flux2-Klein-9B-consistency-V2.safetensors'
export const IMAGE_V2_DENOISE_DEFAULT = 0.35
export const IMAGE_V2_DENOISE_MIN = 0.15
export const IMAGE_V2_DENOISE_MAX = 0.75
export const IMAGE_V2_DENOISE_STEP = 0.01
export const IMAGE_V2_REFINE_FACE = {
strength: 0.28,
snofsModel: 0.55,
snofsClip: 0.25,
consistencyModel: 0.75,
consistencyClip: 0.8
}
export const IMAGE_V2_REFINE_HAND = {
strength: 0.4,
snofsModel: 0.65,
snofsClip: 0.3,
consistencyModel: 0.7,
consistencyClip: 0.7
}
export const IMAGE_V2_REFINE_HEAVY = { strength: 0.55 }
export const IMAGE_V2_REFINE_FACE_PROMPT = 'same face as the canvas, same glasses, same cheeks and jaw. Change only the face in the masked area.'
export const IMAGE_V2_ROLE_HEADERS: Record<Exclude<ImageV2Task, 'scene'>, string> = {
export const IMAGE_V2_ROLE_HEADERS: Record<Exclude<ImageV2Task, 'scene' | 'refine'>, string> = {
outfit: 'Person, face, body, pose, and background from image 1. Clothing only from image 2. Fit the outfit from image 2 to the body in image 1. Do not copy image 2’s face, body shape, or pose.',
face_lock: 'Body, pose, and scene from image 1. Exact face from image 2.',
identity: 'Same person as image 1. Use image 2 only to reinforce the face. Follow the user’s pose/scene prompt.'
@@ -42,10 +62,17 @@ export function clampImageV2Strength(raw: unknown, fallback: number) {
return Math.min(IMAGE_V2_STRENGTH_MAX, Math.max(IMAGE_V2_STRENGTH_MIN, Math.round(snapped * 100) / 100))
}
export function clampImageV2Denoise(raw: unknown, fallback = IMAGE_V2_DENOISE_DEFAULT) {
const value = Number(raw)
if (!Number.isFinite(value)) return fallback
const snapped = Math.round(value / IMAGE_V2_DENOISE_STEP) * IMAGE_V2_DENOISE_STEP
return Math.min(IMAGE_V2_DENOISE_MAX, Math.max(IMAGE_V2_DENOISE_MIN, Math.round(snapped * 100) / 100))
}
export function composeImageV2Prompt(mode: ImageV2Mode, task: ImageV2Task, prompt: string) {
const body = String(prompt || '').trim()
if (mode !== 'compose' || task === 'scene') return body
const header = IMAGE_V2_ROLE_HEADERS[task]
if (mode === 'refine' || mode !== 'compose' || task === 'scene') return body
const header = IMAGE_V2_ROLE_HEADERS[task as Exclude<ImageV2Task, 'scene' | 'refine'>]
return header ? `${header}\n\n${body}` : body
}
@@ -61,4 +88,5 @@ export type ImageV2PresetSettings = {
cfg?: number
megapixels?: number
turbo?: boolean
strength?: number
}