export function kleinIdentityPlan(request) { if(request.engine!=='flux' || ['video','extend'].includes(request.mode)) return null const sourceId=request.mode==='generate' ? request.identityStillId || request.imageAId || '' : request.imageAId || request.identityStillId || '' const mode=['generate','iterate'].includes(request.mode) ? (sourceId?'edit':'generate') : request.mode return {sourceId,mode} } /** Follow the live conditioning chain, including optional hero/compose references. */ function hasSourceReference(graph, link, referenceId) { const seen=new Set() while(Array.isArray(link) && !seen.has(String(link[0]))) { const id=String(link[0]);seen.add(id) const node=graph[id] if(id===referenceId) return node?.class_type==='ReferenceLatent' && node.inputs.latent?.[0]==='11' && graph['11']?.class_type==='VAEEncode' link=node?.inputs?.conditioning } return false } export function assertKleinSample(graph, sourceId) { const sampler=graph['19']?.inputs const latent=graph[sampler?.latent_image?.[0]] if(latent?.class_type==='EmptyFlux2LatentImage') { if(!sourceId) return 'empty' const guider=graph[sampler.guider?.[0]]?.inputs if(hasSourceReference(graph,guider?.positive,'12') && hasSourceReference(graph,guider?.negative,'13')) return 'empty+reference' throw new Error('Klein source reference conditioning is missing from the sampler.') } // Refine retains its dedicated masked-source workflow. if(latent?.class_type==='SetLatentNoiseMask' && latent.inputs.samples?.[0]==='11' && graph['11']?.class_type==='VAEEncode') return 'masked source' throw new Error('Klein edit must sample empty latent with source references, not the source image latent.') } export function applyKleinIdentity(graph, plan) { const sampleLatent=assertKleinSample(graph,plan.sourceId) return {sampleLatent,sampleDenoise:sampleLatent==='masked source' ? graph['17']?.inputs.denoise ?? null : null} }