From 4897ce2a130e2a86c2e39baeca2364d0a5d82654 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Mon, 13 May 2024 21:01:57 +1000 Subject: [PATCH] tidy(ui): remove unused files --- .../listeners/enqueueRequestedLinear.ts | 10 +- .../util/graph/addControlLayersToGraph.ts | 705 ------------------ .../graph/addGenerationTabInitialImage.ts | 79 -- .../nodes/util/graph/addGenerationTabVAE.ts | 37 - .../nodes/util/graph/addHrfToGraph.ts | 356 --------- .../util/graph/buildGenerationTabGraph.ts | 364 ++++----- .../util/graph/buildGenerationTabGraph2.ts | 187 ----- .../util/graph/buildGenerationTabSDXLGraph.ts | 354 ++++----- .../graph/buildGenerationTabSDXLGraph2.ts | 178 ----- .../src/features/nodes/util/graph/metadata.ts | 15 - 10 files changed, 274 insertions(+), 2011 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/nodes/util/graph/addControlLayersToGraph.ts delete mode 100644 invokeai/frontend/web/src/features/nodes/util/graph/addGenerationTabInitialImage.ts delete mode 100644 invokeai/frontend/web/src/features/nodes/util/graph/addGenerationTabVAE.ts delete mode 100644 invokeai/frontend/web/src/features/nodes/util/graph/addHrfToGraph.ts delete mode 100644 invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabGraph2.ts delete mode 100644 invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabSDXLGraph2.ts diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts index a2d9f253a1..195bb5639d 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts @@ -1,8 +1,8 @@ import { enqueueRequested } from 'app/store/actions'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice'; -import { buildGenerationTabGraph2 } from 'features/nodes/util/graph/buildGenerationTabGraph2'; -import { buildGenerationTabSDXLGraph2 } from 'features/nodes/util/graph/buildGenerationTabSDXLGraph2'; +import { buildGenerationTabGraph } from 'features/nodes/util/graph/buildGenerationTabGraph'; +import { buildGenerationTabSDXLGraph } from 'features/nodes/util/graph/buildGenerationTabSDXLGraph'; import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig'; import { queueApi } from 'services/api/endpoints/queue'; @@ -18,10 +18,10 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening) let graph; - if (model && model.base === 'sdxl') { - graph = await buildGenerationTabSDXLGraph2(state); + if (model?.base === 'sdxl') { + graph = await buildGenerationTabSDXLGraph(state); } else { - graph = await buildGenerationTabGraph2(state); + graph = await buildGenerationTabGraph(state); } const batchConfig = prepareLinearUIBatch(state, graph, prepend); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addControlLayersToGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addControlLayersToGraph.ts deleted file mode 100644 index e48b9fb376..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addControlLayersToGraph.ts +++ /dev/null @@ -1,705 +0,0 @@ -import { getStore } from 'app/store/nanostores/store'; -import type { RootState } from 'app/store/store'; -import { - isControlAdapterLayer, - isInitialImageLayer, - isIPAdapterLayer, - isRegionalGuidanceLayer, - rgLayerMaskImageUploaded, -} from 'features/controlLayers/store/controlLayersSlice'; -import type { InitialImageLayer, Layer, RegionalGuidanceLayer } from 'features/controlLayers/store/types'; -import type { - ControlNetConfigV2, - ImageWithDims, - IPAdapterConfigV2, - ProcessorConfig, - T2IAdapterConfigV2, -} from 'features/controlLayers/util/controlAdapters'; -import { getRegionalPromptLayerBlobs } from 'features/controlLayers/util/getLayerBlobs'; -import type { ImageField } from 'features/nodes/types/common'; -import { - CONTROL_NET_COLLECT, - IMAGE_TO_LATENTS, - IP_ADAPTER_COLLECT, - NEGATIVE_CONDITIONING, - NEGATIVE_CONDITIONING_COLLECT, - NOISE, - POSITIVE_CONDITIONING, - POSITIVE_CONDITIONING_COLLECT, - PROMPT_REGION_INVERT_TENSOR_MASK_PREFIX, - PROMPT_REGION_MASK_TO_TENSOR_PREFIX, - PROMPT_REGION_NEGATIVE_COND_PREFIX, - PROMPT_REGION_POSITIVE_COND_INVERTED_PREFIX, - PROMPT_REGION_POSITIVE_COND_PREFIX, - RESIZE, - T2I_ADAPTER_COLLECT, -} from 'features/nodes/util/graph/constants'; -import { upsertMetadata } from 'features/nodes/util/graph/metadata'; -import { size } from 'lodash-es'; -import { getImageDTO, imagesApi } from 'services/api/endpoints/images'; -import type { - BaseModelType, - CollectInvocation, - ControlNetInvocation, - Edge, - ImageDTO, - Invocation, - IPAdapterInvocation, - NonNullableGraph, - T2IAdapterInvocation, -} from 'services/api/types'; -import { assert } from 'tsafe'; - -export const addControlLayersToGraph = async ( - state: RootState, - graph: NonNullableGraph, - denoiseNodeId: string -): Promise => { - const mainModel = state.generation.model; - assert(mainModel, 'Missing main model when building graph'); - const isSDXL = mainModel.base === 'sdxl'; - - // Filter out layers with incompatible base model, missing control image - const validLayers = state.controlLayers.present.layers.filter((l) => isValidLayer(l, mainModel.base)); - - const validControlAdapters = validLayers.filter(isControlAdapterLayer).map((l) => l.controlAdapter); - for (const ca of validControlAdapters) { - addGlobalControlAdapterToGraph(ca, graph, denoiseNodeId); - } - - const validIPAdapters = validLayers.filter(isIPAdapterLayer).map((l) => l.ipAdapter); - for (const ipAdapter of validIPAdapters) { - addGlobalIPAdapterToGraph(ipAdapter, graph, denoiseNodeId); - } - - const initialImageLayers = validLayers.filter(isInitialImageLayer); - assert(initialImageLayers.length <= 1, 'Only one initial image layer allowed'); - if (initialImageLayers[0]) { - addInitialImageLayerToGraph(state, graph, denoiseNodeId, initialImageLayers[0]); - } - // TODO: We should probably just use conditioning collectors by default, and skip all this fanagling with re-routing - // the existing conditioning nodes. - - // With regional prompts we have multiple conditioning nodes which much be routed into collectors. Set those up - const posCondCollectNode: CollectInvocation = { - id: POSITIVE_CONDITIONING_COLLECT, - type: 'collect', - }; - graph.nodes[POSITIVE_CONDITIONING_COLLECT] = posCondCollectNode; - const negCondCollectNode: CollectInvocation = { - id: NEGATIVE_CONDITIONING_COLLECT, - type: 'collect', - }; - graph.nodes[NEGATIVE_CONDITIONING_COLLECT] = negCondCollectNode; - - // Re-route the denoise node's OG conditioning inputs to the collect nodes - const newEdges: Edge[] = []; - for (const edge of graph.edges) { - if (edge.destination.node_id === denoiseNodeId && edge.destination.field === 'positive_conditioning') { - newEdges.push({ - source: edge.source, - destination: { - node_id: POSITIVE_CONDITIONING_COLLECT, - field: 'item', - }, - }); - } else if (edge.destination.node_id === denoiseNodeId && edge.destination.field === 'negative_conditioning') { - newEdges.push({ - source: edge.source, - destination: { - node_id: NEGATIVE_CONDITIONING_COLLECT, - field: 'item', - }, - }); - } else { - newEdges.push(edge); - } - } - graph.edges = newEdges; - - // Connect collectors to the denoise nodes - must happen _after_ rerouting else you get cycles - graph.edges.push({ - source: { - node_id: POSITIVE_CONDITIONING_COLLECT, - field: 'collection', - }, - destination: { - node_id: denoiseNodeId, - field: 'positive_conditioning', - }, - }); - graph.edges.push({ - source: { - node_id: NEGATIVE_CONDITIONING_COLLECT, - field: 'collection', - }, - destination: { - node_id: denoiseNodeId, - field: 'negative_conditioning', - }, - }); - - const validRGLayers = validLayers.filter(isRegionalGuidanceLayer); - const layerIds = validRGLayers.map((l) => l.id); - const blobs = await getRegionalPromptLayerBlobs(layerIds); - assert(size(blobs) === size(layerIds), 'Mismatch between layer IDs and blobs'); - - for (const layer of validRGLayers) { - const blob = blobs[layer.id]; - assert(blob, `Blob for layer ${layer.id} not found`); - // Upload the mask image, or get the cached image if it exists - const { image_name } = await getMaskImage(layer, blob); - - // The main mask-to-tensor node - const maskToTensorNode: Invocation<'alpha_mask_to_tensor'> = { - id: `${PROMPT_REGION_MASK_TO_TENSOR_PREFIX}_${layer.id}`, - type: 'alpha_mask_to_tensor', - image: { - image_name, - }, - }; - graph.nodes[maskToTensorNode.id] = maskToTensorNode; - - if (layer.positivePrompt) { - // The main positive conditioning node - const regionalPositiveCondNode: Invocation<'compel'> | Invocation<'sdxl_compel_prompt'> = isSDXL - ? { - type: 'sdxl_compel_prompt', - id: `${PROMPT_REGION_POSITIVE_COND_PREFIX}_${layer.id}`, - prompt: layer.positivePrompt, - style: layer.positivePrompt, // TODO: Should we put the positive prompt in both fields? - } - : { - type: 'compel', - id: `${PROMPT_REGION_POSITIVE_COND_PREFIX}_${layer.id}`, - prompt: layer.positivePrompt, - }; - graph.nodes[regionalPositiveCondNode.id] = regionalPositiveCondNode; - - // Connect the mask to the conditioning - graph.edges.push({ - source: { node_id: maskToTensorNode.id, field: 'mask' }, - destination: { node_id: regionalPositiveCondNode.id, field: 'mask' }, - }); - - // Connect the conditioning to the collector - graph.edges.push({ - source: { node_id: regionalPositiveCondNode.id, field: 'conditioning' }, - destination: { node_id: posCondCollectNode.id, field: 'item' }, - }); - - // Copy the connections to the "global" positive conditioning node to the regional cond - for (const edge of graph.edges) { - if (edge.destination.node_id === POSITIVE_CONDITIONING && edge.destination.field !== 'prompt') { - graph.edges.push({ - source: edge.source, - destination: { node_id: regionalPositiveCondNode.id, field: edge.destination.field }, - }); - } - } - } - - if (layer.negativePrompt) { - // The main negative conditioning node - const regionalNegativeCondNode: Invocation<'compel'> | Invocation<'sdxl_compel_prompt'> = isSDXL - ? { - type: 'sdxl_compel_prompt', - id: `${PROMPT_REGION_NEGATIVE_COND_PREFIX}_${layer.id}`, - prompt: layer.negativePrompt, - style: layer.negativePrompt, - } - : { - type: 'compel', - id: `${PROMPT_REGION_NEGATIVE_COND_PREFIX}_${layer.id}`, - prompt: layer.negativePrompt, - }; - graph.nodes[regionalNegativeCondNode.id] = regionalNegativeCondNode; - - // Connect the mask to the conditioning - graph.edges.push({ - source: { node_id: maskToTensorNode.id, field: 'mask' }, - destination: { node_id: regionalNegativeCondNode.id, field: 'mask' }, - }); - - // Connect the conditioning to the collector - graph.edges.push({ - source: { node_id: regionalNegativeCondNode.id, field: 'conditioning' }, - destination: { node_id: negCondCollectNode.id, field: 'item' }, - }); - - // Copy the connections to the "global" negative conditioning node to the regional cond - for (const edge of graph.edges) { - if (edge.destination.node_id === NEGATIVE_CONDITIONING && edge.destination.field !== 'prompt') { - graph.edges.push({ - source: edge.source, - destination: { node_id: regionalNegativeCondNode.id, field: edge.destination.field }, - }); - } - } - } - - // If we are using the "invert" auto-negative setting, we need to add an additional negative conditioning node - if (layer.autoNegative === 'invert' && layer.positivePrompt) { - // We re-use the mask image, but invert it when converting to tensor - const invertTensorMaskNode: Invocation<'invert_tensor_mask'> = { - id: `${PROMPT_REGION_INVERT_TENSOR_MASK_PREFIX}_${layer.id}`, - type: 'invert_tensor_mask', - }; - graph.nodes[invertTensorMaskNode.id] = invertTensorMaskNode; - - // Connect the OG mask image to the inverted mask-to-tensor node - graph.edges.push({ - source: { - node_id: maskToTensorNode.id, - field: 'mask', - }, - destination: { - node_id: invertTensorMaskNode.id, - field: 'mask', - }, - }); - - // Create the conditioning node. It's going to be connected to the negative cond collector, but it uses the - // positive prompt - const regionalPositiveCondInvertedNode: Invocation<'compel'> | Invocation<'sdxl_compel_prompt'> = isSDXL - ? { - type: 'sdxl_compel_prompt', - id: `${PROMPT_REGION_POSITIVE_COND_INVERTED_PREFIX}_${layer.id}`, - prompt: layer.positivePrompt, - style: layer.positivePrompt, - } - : { - type: 'compel', - id: `${PROMPT_REGION_POSITIVE_COND_INVERTED_PREFIX}_${layer.id}`, - prompt: layer.positivePrompt, - }; - graph.nodes[regionalPositiveCondInvertedNode.id] = regionalPositiveCondInvertedNode; - // Connect the inverted mask to the conditioning - graph.edges.push({ - source: { node_id: invertTensorMaskNode.id, field: 'mask' }, - destination: { node_id: regionalPositiveCondInvertedNode.id, field: 'mask' }, - }); - // Connect the conditioning to the negative collector - graph.edges.push({ - source: { node_id: regionalPositiveCondInvertedNode.id, field: 'conditioning' }, - destination: { node_id: negCondCollectNode.id, field: 'item' }, - }); - // Copy the connections to the "global" positive conditioning node to our regional node - for (const edge of graph.edges) { - if (edge.destination.node_id === POSITIVE_CONDITIONING && edge.destination.field !== 'prompt') { - graph.edges.push({ - source: edge.source, - destination: { node_id: regionalPositiveCondInvertedNode.id, field: edge.destination.field }, - }); - } - } - } - - const validRegionalIPAdapters: IPAdapterConfigV2[] = layer.ipAdapters.filter((ipa) => - isValidIPAdapter(ipa, mainModel.base) - ); - - for (const ipAdapter of validRegionalIPAdapters) { - addIPAdapterCollectorSafe(graph, denoiseNodeId); - const { id, weight, model, clipVisionModel, method, beginEndStepPct, image } = ipAdapter; - assert(model, 'IP Adapter model is required'); - assert(image, 'IP Adapter image is required'); - - const ipAdapterNode: IPAdapterInvocation = { - id: `ip_adapter_${id}`, - type: 'ip_adapter', - is_intermediate: true, - weight, - method, - ip_adapter_model: model, - clip_vision_model: clipVisionModel, - begin_step_percent: beginEndStepPct[0], - end_step_percent: beginEndStepPct[1], - image: { - image_name: image.name, - }, - }; - - graph.nodes[ipAdapterNode.id] = ipAdapterNode; - - // Connect the mask to the conditioning - graph.edges.push({ - source: { node_id: maskToTensorNode.id, field: 'mask' }, - destination: { node_id: ipAdapterNode.id, field: 'mask' }, - }); - - graph.edges.push({ - source: { node_id: ipAdapterNode.id, field: 'ip_adapter' }, - destination: { - node_id: IP_ADAPTER_COLLECT, - field: 'item', - }, - }); - } - } - - upsertMetadata(graph, { control_layers: { layers: validLayers, version: state.controlLayers.present._version } }); - return validLayers; -}; - -const getMaskImage = async (layer: RegionalGuidanceLayer, blob: Blob): Promise => { - if (layer.uploadedMaskImage) { - const imageDTO = await getImageDTO(layer.uploadedMaskImage.name); - if (imageDTO) { - return imageDTO; - } - } - const { dispatch } = getStore(); - // No cached mask, or the cached image no longer exists - we need to upload the mask image - const file = new File([blob], `${layer.id}_mask.png`, { type: 'image/png' }); - const req = dispatch( - imagesApi.endpoints.uploadImage.initiate({ file, image_category: 'mask', is_intermediate: true }) - ); - req.reset(); - - const imageDTO = await req.unwrap(); - dispatch(rgLayerMaskImageUploaded({ layerId: layer.id, imageDTO })); - return imageDTO; -}; - -const buildControlImage = ( - image: ImageWithDims | null, - processedImage: ImageWithDims | null, - processorConfig: ProcessorConfig | null -): ImageField => { - if (processedImage && processorConfig) { - // We've processed the image in the app - use it for the control image. - return { - image_name: processedImage.name, - }; - } else if (image) { - // No processor selected, and we have an image - the user provided a processed image, use it for the control image. - return { - image_name: image.name, - }; - } - assert(false, 'Attempted to add unprocessed control image'); -}; - -const addGlobalControlAdapterToGraph = ( - controlAdapter: ControlNetConfigV2 | T2IAdapterConfigV2, - graph: NonNullableGraph, - denoiseNodeId: string -) => { - if (controlAdapter.type === 'controlnet') { - addGlobalControlNetToGraph(controlAdapter, graph, denoiseNodeId); - } - if (controlAdapter.type === 't2i_adapter') { - addGlobalT2IAdapterToGraph(controlAdapter, graph, denoiseNodeId); - } -}; - -const addControlNetCollectorSafe = (graph: NonNullableGraph, denoiseNodeId: string) => { - if (graph.nodes[CONTROL_NET_COLLECT]) { - // You see, we've already got one! - return; - } - // Add the ControlNet collector - const controlNetIterateNode: CollectInvocation = { - id: CONTROL_NET_COLLECT, - type: 'collect', - is_intermediate: true, - }; - graph.nodes[CONTROL_NET_COLLECT] = controlNetIterateNode; - graph.edges.push({ - source: { node_id: CONTROL_NET_COLLECT, field: 'collection' }, - destination: { - node_id: denoiseNodeId, - field: 'control', - }, - }); -}; - -const addGlobalControlNetToGraph = (controlNet: ControlNetConfigV2, graph: NonNullableGraph, denoiseNodeId: string) => { - const { id, beginEndStepPct, controlMode, image, model, processedImage, processorConfig, weight } = controlNet; - assert(model, 'ControlNet model is required'); - const controlImage = buildControlImage(image, processedImage, processorConfig); - addControlNetCollectorSafe(graph, denoiseNodeId); - - const controlNetNode: ControlNetInvocation = { - id: `control_net_${id}`, - type: 'controlnet', - is_intermediate: true, - begin_step_percent: beginEndStepPct[0], - end_step_percent: beginEndStepPct[1], - control_mode: controlMode, - resize_mode: 'just_resize', - control_model: model, - control_weight: weight, - image: controlImage, - }; - - graph.nodes[controlNetNode.id] = controlNetNode; - - graph.edges.push({ - source: { node_id: controlNetNode.id, field: 'control' }, - destination: { - node_id: CONTROL_NET_COLLECT, - field: 'item', - }, - }); -}; - -const addT2IAdapterCollectorSafe = (graph: NonNullableGraph, denoiseNodeId: string) => { - if (graph.nodes[T2I_ADAPTER_COLLECT]) { - // You see, we've already got one! - return; - } - // Even though denoise_latents' t2i adapter input is collection or scalar, keep it simple and always use a collect - const t2iAdapterCollectNode: CollectInvocation = { - id: T2I_ADAPTER_COLLECT, - type: 'collect', - is_intermediate: true, - }; - graph.nodes[T2I_ADAPTER_COLLECT] = t2iAdapterCollectNode; - graph.edges.push({ - source: { node_id: T2I_ADAPTER_COLLECT, field: 'collection' }, - destination: { - node_id: denoiseNodeId, - field: 't2i_adapter', - }, - }); -}; - -const addGlobalT2IAdapterToGraph = (t2iAdapter: T2IAdapterConfigV2, graph: NonNullableGraph, denoiseNodeId: string) => { - const { id, beginEndStepPct, image, model, processedImage, processorConfig, weight } = t2iAdapter; - assert(model, 'T2I Adapter model is required'); - const controlImage = buildControlImage(image, processedImage, processorConfig); - addT2IAdapterCollectorSafe(graph, denoiseNodeId); - - const t2iAdapterNode: T2IAdapterInvocation = { - id: `t2i_adapter_${id}`, - type: 't2i_adapter', - is_intermediate: true, - begin_step_percent: beginEndStepPct[0], - end_step_percent: beginEndStepPct[1], - resize_mode: 'just_resize', - t2i_adapter_model: model, - weight: weight, - image: controlImage, - }; - - graph.nodes[t2iAdapterNode.id] = t2iAdapterNode; - - graph.edges.push({ - source: { node_id: t2iAdapterNode.id, field: 't2i_adapter' }, - destination: { - node_id: T2I_ADAPTER_COLLECT, - field: 'item', - }, - }); -}; - -const addIPAdapterCollectorSafe = (graph: NonNullableGraph, denoiseNodeId: string) => { - if (graph.nodes[IP_ADAPTER_COLLECT]) { - // You see, we've already got one! - return; - } - - const ipAdapterCollectNode: CollectInvocation = { - id: IP_ADAPTER_COLLECT, - type: 'collect', - is_intermediate: true, - }; - graph.nodes[IP_ADAPTER_COLLECT] = ipAdapterCollectNode; - graph.edges.push({ - source: { node_id: IP_ADAPTER_COLLECT, field: 'collection' }, - destination: { - node_id: denoiseNodeId, - field: 'ip_adapter', - }, - }); -}; - -const addGlobalIPAdapterToGraph = (ipAdapter: IPAdapterConfigV2, graph: NonNullableGraph, denoiseNodeId: string) => { - addIPAdapterCollectorSafe(graph, denoiseNodeId); - const { id, weight, model, clipVisionModel, method, beginEndStepPct, image } = ipAdapter; - assert(image, 'IP Adapter image is required'); - assert(model, 'IP Adapter model is required'); - - const ipAdapterNode: IPAdapterInvocation = { - id: `ip_adapter_${id}`, - type: 'ip_adapter', - is_intermediate: true, - weight, - method, - ip_adapter_model: model, - clip_vision_model: clipVisionModel, - begin_step_percent: beginEndStepPct[0], - end_step_percent: beginEndStepPct[1], - image: { - image_name: image.name, - }, - }; - - graph.nodes[ipAdapterNode.id] = ipAdapterNode; - - graph.edges.push({ - source: { node_id: ipAdapterNode.id, field: 'ip_adapter' }, - destination: { - node_id: IP_ADAPTER_COLLECT, - field: 'item', - }, - }); -}; - -const addInitialImageLayerToGraph = ( - state: RootState, - graph: NonNullableGraph, - denoiseNodeId: string, - layer: InitialImageLayer -) => { - const { vaePrecision, model } = state.generation; - const { refinerModel, refinerStart } = state.sdxl; - const { width, height } = state.controlLayers.present.size; - assert(layer.isEnabled, 'Initial image layer is not enabled'); - assert(layer.image, 'Initial image layer has no image'); - - const isSDXL = model?.base === 'sdxl'; - const useRefinerStartEnd = isSDXL && Boolean(refinerModel); - - const denoiseNode = graph.nodes[denoiseNodeId]; - assert(denoiseNode?.type === 'denoise_latents', `Missing denoise node or incorrect type: ${denoiseNode?.type}`); - - const { denoisingStrength } = layer; - denoiseNode.denoising_start = useRefinerStartEnd - ? Math.min(refinerStart, 1 - denoisingStrength) - : 1 - denoisingStrength; - denoiseNode.denoising_end = useRefinerStartEnd ? refinerStart : 1; - - const i2lNode: Invocation<'i2l'> = { - type: 'i2l', - id: IMAGE_TO_LATENTS, - is_intermediate: true, - use_cache: true, - fp32: vaePrecision === 'fp32', - }; - - graph.nodes[i2lNode.id] = i2lNode; - graph.edges.push({ - source: { - node_id: IMAGE_TO_LATENTS, - field: 'latents', - }, - destination: { - node_id: denoiseNode.id, - field: 'latents', - }, - }); - - if (layer.image.width !== width || layer.image.height !== height) { - // The init image needs to be resized to the specified width and height before being passed to `IMAGE_TO_LATENTS` - - // Create a resize node, explicitly setting its image - const resizeNode: Invocation<'img_resize'> = { - id: RESIZE, - type: 'img_resize', - image: { - image_name: layer.image.name, - }, - is_intermediate: true, - width, - height, - }; - - graph.nodes[RESIZE] = resizeNode; - - // The `RESIZE` node then passes its image to `IMAGE_TO_LATENTS` - graph.edges.push({ - source: { node_id: RESIZE, field: 'image' }, - destination: { - node_id: IMAGE_TO_LATENTS, - field: 'image', - }, - }); - - // The `RESIZE` node also passes its width and height to `NOISE` - graph.edges.push({ - source: { node_id: RESIZE, field: 'width' }, - destination: { - node_id: NOISE, - field: 'width', - }, - }); - - graph.edges.push({ - source: { node_id: RESIZE, field: 'height' }, - destination: { - node_id: NOISE, - field: 'height', - }, - }); - } else { - // We are not resizing, so we need to set the image on the `IMAGE_TO_LATENTS` node explicitly - i2lNode.image = { - image_name: layer.image.name, - }; - - // Pass the image's dimensions to the `NOISE` node - graph.edges.push({ - source: { node_id: IMAGE_TO_LATENTS, field: 'width' }, - destination: { - node_id: NOISE, - field: 'width', - }, - }); - graph.edges.push({ - source: { node_id: IMAGE_TO_LATENTS, field: 'height' }, - destination: { - node_id: NOISE, - field: 'height', - }, - }); - } - - upsertMetadata(graph, { generation_mode: isSDXL ? 'sdxl_img2img' : 'img2img' }); -}; - -const isValidControlAdapter = (ca: ControlNetConfigV2 | T2IAdapterConfigV2, base: BaseModelType): boolean => { - // Must be have a model that matches the current base and must have a control image - const hasModel = Boolean(ca.model); - const modelMatchesBase = ca.model?.base === base; - const hasControlImage = Boolean(ca.image || (ca.processedImage && ca.processorConfig)); - return hasModel && modelMatchesBase && hasControlImage; -}; - -const isValidIPAdapter = (ipa: IPAdapterConfigV2, base: BaseModelType): boolean => { - // Must be have a model that matches the current base and must have a control image - const hasModel = Boolean(ipa.model); - const modelMatchesBase = ipa.model?.base === base; - const hasImage = Boolean(ipa.image); - return hasModel && modelMatchesBase && hasImage; -}; - -const isValidLayer = (layer: Layer, base: BaseModelType) => { - if (!layer.isEnabled) { - return false; - } - if (isControlAdapterLayer(layer)) { - return isValidControlAdapter(layer.controlAdapter, base); - } - if (isIPAdapterLayer(layer)) { - return isValidIPAdapter(layer.ipAdapter, base); - } - if (isInitialImageLayer(layer)) { - if (!layer.image) { - return false; - } - return true; - } - if (isRegionalGuidanceLayer(layer)) { - if (layer.maskObjects.length === 0) { - // Layer has no mask, meaning any guidance would be applied to an empty region. - return false; - } - const hasTextPrompt = Boolean(layer.positivePrompt) || Boolean(layer.negativePrompt); - const hasIPAdapter = layer.ipAdapters.filter((ipa) => isValidIPAdapter(ipa, base)).length > 0; - return hasTextPrompt || hasIPAdapter; - } - return false; -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addGenerationTabInitialImage.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addGenerationTabInitialImage.ts deleted file mode 100644 index 3a6b124b30..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addGenerationTabInitialImage.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type { RootState } from 'app/store/store'; -import { isInitialImageLayer } from 'features/controlLayers/store/controlLayersSlice'; -import type { ImageField } from 'features/nodes/types/common'; -import type { Graph } from 'features/nodes/util/graph/Graph'; -import { MetadataUtil } from 'features/nodes/util/graph/MetadataUtil'; -import type { Invocation } from 'services/api/types'; - -import { IMAGE_TO_LATENTS, RESIZE } from './constants'; - -/** - * Adds the initial image to the graph and connects it to the denoise and noise nodes. - * @param state The current Redux state - * @param g The graph to add the initial image to - * @param denoise The denoise node in the graph - * @param noise The noise node in the graph - * @returns Whether the initial image was added to the graph - */ -export const addGenerationTabInitialImage = ( - state: RootState, - g: Graph, - denoise: Invocation<'denoise_latents'>, - noise: Invocation<'noise'> -): Invocation<'i2l'> | null => { - // Remove Existing UNet Connections - const { img2imgStrength, vaePrecision, model } = state.generation; - const { refinerModel, refinerStart } = state.sdxl; - const { width, height } = state.controlLayers.present.size; - const initialImageLayer = state.controlLayers.present.layers.find(isInitialImageLayer); - const initialImage = initialImageLayer?.isEnabled ? initialImageLayer?.image : null; - - if (!initialImage) { - return null; - } - - const isSDXL = model?.base === 'sdxl'; - const useRefinerStartEnd = isSDXL && Boolean(refinerModel); - const image: ImageField = { - image_name: initialImage.imageName, - }; - - denoise.denoising_start = useRefinerStartEnd ? Math.min(refinerStart, 1 - img2imgStrength) : 1 - img2imgStrength; - denoise.denoising_end = useRefinerStartEnd ? refinerStart : 1; - - const i2l = g.addNode({ - type: 'i2l', - id: IMAGE_TO_LATENTS, - fp32: vaePrecision === 'fp32', - }); - g.addEdge(i2l, 'latents', denoise, 'latents'); - - if (initialImage.width !== width || initialImage.height !== height) { - // The init image needs to be resized to the specified width and height before being passed to `IMAGE_TO_LATENTS` - const resize = g.addNode({ - id: RESIZE, - type: 'img_resize', - image, - width, - height, - }); - // The `RESIZE` node then passes its image, to `IMAGE_TO_LATENTS` - g.addEdge(resize, 'image', i2l, 'image'); - // The `RESIZE` node also passes its width and height to `NOISE` - g.addEdge(resize, 'width', noise, 'width'); - g.addEdge(resize, 'height', noise, 'height'); - } else { - // We are not resizing, so we need to set the image on the `IMAGE_TO_LATENTS` node explicitly - i2l.image = image; - g.addEdge(i2l, 'width', noise, 'width'); - g.addEdge(i2l, 'height', noise, 'height'); - } - - MetadataUtil.add(g, { - generation_mode: isSDXL ? 'sdxl_img2img' : 'img2img', - strength: img2imgStrength, - init_image: initialImage.imageName, - }); - - return i2l; -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addGenerationTabVAE.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addGenerationTabVAE.ts deleted file mode 100644 index 037924d5cb..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addGenerationTabVAE.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { RootState } from 'app/store/store'; -import type { Graph } from 'features/nodes/util/graph/Graph'; -import { MetadataUtil } from 'features/nodes/util/graph/MetadataUtil'; -import type { Invocation } from 'services/api/types'; - -import { VAE_LOADER } from './constants'; - -export const addGenerationTabVAE = ( - state: RootState, - g: Graph, - modelLoader: Invocation<'main_model_loader'> | Invocation<'sdxl_model_loader'>, - l2i: Invocation<'l2i'>, - i2l: Invocation<'i2l'> | null, - seamless: Invocation<'seamless'> | null -): void => { - const { vae } = state.generation; - - // The seamless helper also adds the VAE loader... so we need to check if it's already there - const shouldAddVAELoader = !g.hasNode(VAE_LOADER) && vae; - const vaeLoader = shouldAddVAELoader - ? g.addNode({ - type: 'vae_loader', - id: VAE_LOADER, - vae_model: vae, - }) - : null; - - const vaeSource = seamless ? seamless : vaeLoader ? vaeLoader : modelLoader; - g.addEdge(vaeSource, 'vae', l2i, 'vae'); - if (i2l) { - g.addEdge(vaeSource, 'vae', i2l, 'vae'); - } - - if (vae) { - MetadataUtil.add(g, { vae }); - } -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/addHrfToGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/addHrfToGraph.ts deleted file mode 100644 index d6709f7058..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/graph/addHrfToGraph.ts +++ /dev/null @@ -1,356 +0,0 @@ -import { logger } from 'app/logging/logger'; -import type { RootState } from 'app/store/store'; -import { roundToMultiple } from 'common/util/roundDownToMultiple'; -import { getBoardField, getIsIntermediate } from 'features/nodes/util/graph/graphBuilderUtils'; -import { selectOptimalDimension } from 'features/parameters/store/generationSlice'; -import type { - DenoiseLatentsInvocation, - Edge, - ESRGANInvocation, - LatentsToImageInvocation, - NoiseInvocation, - NonNullableGraph, -} from 'services/api/types'; - -import { - DENOISE_LATENTS, - DENOISE_LATENTS_HRF, - ESRGAN_HRF, - IMAGE_TO_LATENTS_HRF, - LATENTS_TO_IMAGE, - LATENTS_TO_IMAGE_HRF_HR, - LATENTS_TO_IMAGE_HRF_LR, - MAIN_MODEL_LOADER, - NOISE, - NOISE_HRF, - RESIZE_HRF, - SEAMLESS, - VAE_LOADER, -} from './constants'; -import { setMetadataReceivingNode, upsertMetadata } from './metadata'; - -// Copy certain connections from previous DENOISE_LATENTS to new DENOISE_LATENTS_HRF. -function copyConnectionsToDenoiseLatentsHrf(graph: NonNullableGraph): void { - const destinationFields = [ - 'control', - 'ip_adapter', - 'metadata', - 'unet', - 'positive_conditioning', - 'negative_conditioning', - ]; - const newEdges: Edge[] = []; - - // Loop through the existing edges connected to DENOISE_LATENTS - graph.edges.forEach((edge: Edge) => { - if (edge.destination.node_id === DENOISE_LATENTS && destinationFields.includes(edge.destination.field)) { - // Add a similar connection to DENOISE_LATENTS_HRF - newEdges.push({ - source: { - node_id: edge.source.node_id, - field: edge.source.field, - }, - destination: { - node_id: DENOISE_LATENTS_HRF, - field: edge.destination.field, - }, - }); - } - }); - graph.edges = graph.edges.concat(newEdges); -} - -/** - * Calculates the new resolution for high-resolution features (HRF) based on base model type. - * Adjusts the width and height to maintain the aspect ratio and constrains them by the model's dimension limits, - * rounding down to the nearest multiple of 8. - * - * @param {number} optimalDimension The optimal dimension for the base model. - * @param {number} width The current width to be adjusted for HRF. - * @param {number} height The current height to be adjusted for HRF. - * @return {{newWidth: number, newHeight: number}} The new width and height, adjusted and rounded as needed. - */ -function calculateHrfRes( - optimalDimension: number, - width: number, - height: number -): { newWidth: number; newHeight: number } { - const aspect = width / height; - - const minDimension = Math.floor(optimalDimension * 0.5); - const modelArea = optimalDimension * optimalDimension; // Assuming square images for model_area - - let initWidth; - let initHeight; - - if (aspect > 1.0) { - initHeight = Math.max(minDimension, Math.sqrt(modelArea / aspect)); - initWidth = initHeight * aspect; - } else { - initWidth = Math.max(minDimension, Math.sqrt(modelArea * aspect)); - initHeight = initWidth / aspect; - } - // Cap initial height and width to final height and width. - initWidth = Math.min(width, initWidth); - initHeight = Math.min(height, initHeight); - - const newWidth = roundToMultiple(Math.floor(initWidth), 8); - const newHeight = roundToMultiple(Math.floor(initHeight), 8); - - return { newWidth, newHeight }; -} - -// Adds the high-res fix feature to the given graph. -export const addHrfToGraph = (state: RootState, graph: NonNullableGraph): void => { - // Double check hrf is enabled. - if (!state.hrf.hrfEnabled || state.config.disabledSDFeatures.includes('hrf')) { - return; - } - const log = logger('generation'); - - const { vae, seamlessXAxis, seamlessYAxis } = state.generation; - const { hrfStrength, hrfEnabled, hrfMethod } = state.hrf; - const { width, height } = state.controlLayers.present.size; - const isAutoVae = !vae; - const isSeamlessEnabled = seamlessXAxis || seamlessYAxis; - const optimalDimension = selectOptimalDimension(state); - const { newWidth: hrfWidth, newHeight: hrfHeight } = calculateHrfRes(optimalDimension, width, height); - - // Pre-existing (original) graph nodes. - const originalDenoiseLatentsNode = graph.nodes[DENOISE_LATENTS] as DenoiseLatentsInvocation | undefined; - const originalNoiseNode = graph.nodes[NOISE] as NoiseInvocation | undefined; - const originalLatentsToImageNode = graph.nodes[LATENTS_TO_IMAGE] as LatentsToImageInvocation | undefined; - if (!originalDenoiseLatentsNode) { - log.error('originalDenoiseLatentsNode is undefined'); - return; - } - if (!originalNoiseNode) { - log.error('originalNoiseNode is undefined'); - return; - } - if (!originalLatentsToImageNode) { - log.error('originalLatentsToImageNode is undefined'); - return; - } - - // Change height and width of original noise node to initial resolution. - if (originalNoiseNode) { - originalNoiseNode.width = hrfWidth; - originalNoiseNode.height = hrfHeight; - } - - // Define new nodes and their connections, roughly in order of operations. - graph.nodes[LATENTS_TO_IMAGE_HRF_LR] = { - type: 'l2i', - id: LATENTS_TO_IMAGE_HRF_LR, - fp32: originalLatentsToImageNode?.fp32, - is_intermediate: true, - }; - graph.edges.push( - { - source: { - node_id: DENOISE_LATENTS, - field: 'latents', - }, - destination: { - node_id: LATENTS_TO_IMAGE_HRF_LR, - field: 'latents', - }, - }, - { - source: { - node_id: isSeamlessEnabled ? SEAMLESS : isAutoVae ? MAIN_MODEL_LOADER : VAE_LOADER, - field: 'vae', - }, - destination: { - node_id: LATENTS_TO_IMAGE_HRF_LR, - field: 'vae', - }, - } - ); - - graph.nodes[RESIZE_HRF] = { - id: RESIZE_HRF, - type: 'img_resize', - is_intermediate: true, - width: width, - height: height, - }; - if (hrfMethod === 'ESRGAN') { - let model_name: ESRGANInvocation['model_name'] = 'RealESRGAN_x2plus.pth'; - if ((width * height) / (hrfWidth * hrfHeight) > 2) { - model_name = 'RealESRGAN_x4plus.pth'; - } - graph.nodes[ESRGAN_HRF] = { - id: ESRGAN_HRF, - type: 'esrgan', - model_name, - is_intermediate: true, - }; - graph.edges.push( - { - source: { - node_id: LATENTS_TO_IMAGE_HRF_LR, - field: 'image', - }, - destination: { - node_id: ESRGAN_HRF, - field: 'image', - }, - }, - { - source: { - node_id: ESRGAN_HRF, - field: 'image', - }, - destination: { - node_id: RESIZE_HRF, - field: 'image', - }, - } - ); - } else { - graph.edges.push({ - source: { - node_id: LATENTS_TO_IMAGE_HRF_LR, - field: 'image', - }, - destination: { - node_id: RESIZE_HRF, - field: 'image', - }, - }); - } - - graph.nodes[NOISE_HRF] = { - type: 'noise', - id: NOISE_HRF, - seed: originalNoiseNode?.seed, - use_cpu: originalNoiseNode?.use_cpu, - is_intermediate: true, - }; - graph.edges.push( - { - source: { - node_id: RESIZE_HRF, - field: 'height', - }, - destination: { - node_id: NOISE_HRF, - field: 'height', - }, - }, - { - source: { - node_id: RESIZE_HRF, - field: 'width', - }, - destination: { - node_id: NOISE_HRF, - field: 'width', - }, - } - ); - - graph.nodes[IMAGE_TO_LATENTS_HRF] = { - type: 'i2l', - id: IMAGE_TO_LATENTS_HRF, - is_intermediate: true, - }; - graph.edges.push( - { - source: { - node_id: isSeamlessEnabled ? SEAMLESS : isAutoVae ? MAIN_MODEL_LOADER : VAE_LOADER, - field: 'vae', - }, - destination: { - node_id: IMAGE_TO_LATENTS_HRF, - field: 'vae', - }, - }, - { - source: { - node_id: RESIZE_HRF, - field: 'image', - }, - destination: { - node_id: IMAGE_TO_LATENTS_HRF, - field: 'image', - }, - } - ); - - graph.nodes[DENOISE_LATENTS_HRF] = { - type: 'denoise_latents', - id: DENOISE_LATENTS_HRF, - is_intermediate: true, - cfg_scale: originalDenoiseLatentsNode?.cfg_scale, - scheduler: originalDenoiseLatentsNode?.scheduler, - steps: originalDenoiseLatentsNode?.steps, - denoising_start: 1 - hrfStrength, - denoising_end: 1, - }; - graph.edges.push( - { - source: { - node_id: IMAGE_TO_LATENTS_HRF, - field: 'latents', - }, - destination: { - node_id: DENOISE_LATENTS_HRF, - field: 'latents', - }, - }, - { - source: { - node_id: NOISE_HRF, - field: 'noise', - }, - destination: { - node_id: DENOISE_LATENTS_HRF, - field: 'noise', - }, - } - ); - copyConnectionsToDenoiseLatentsHrf(graph); - - // The original l2i node is unnecessary now, remove it - graph.edges = graph.edges.filter((edge) => edge.destination.node_id !== LATENTS_TO_IMAGE); - delete graph.nodes[LATENTS_TO_IMAGE]; - - graph.nodes[LATENTS_TO_IMAGE_HRF_HR] = { - type: 'l2i', - id: LATENTS_TO_IMAGE_HRF_HR, - fp32: originalLatentsToImageNode?.fp32, - is_intermediate: getIsIntermediate(state), - board: getBoardField(state), - }; - graph.edges.push( - { - source: { - node_id: isSeamlessEnabled ? SEAMLESS : isAutoVae ? MAIN_MODEL_LOADER : VAE_LOADER, - field: 'vae', - }, - destination: { - node_id: LATENTS_TO_IMAGE_HRF_HR, - field: 'vae', - }, - }, - { - source: { - node_id: DENOISE_LATENTS_HRF, - field: 'latents', - }, - destination: { - node_id: LATENTS_TO_IMAGE_HRF_HR, - field: 'latents', - }, - } - ); - upsertMetadata(graph, { - hrf_strength: hrfStrength, - hrf_enabled: hrfEnabled, - hrf_method: hrfMethod, - }); - setMetadataReceivingNode(graph, LATENTS_TO_IMAGE_HRF_HR); -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabGraph.ts index 2a6946708f..a50e722e43 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabGraph.ts @@ -1,17 +1,20 @@ -import { logger } from 'app/logging/logger'; import type { RootState } from 'app/store/store'; import { isInitialImageLayer, isRegionalGuidanceLayer } from 'features/controlLayers/store/controlLayersSlice'; import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers'; -import { addControlLayersToGraph } from 'features/nodes/util/graph/addControlLayersToGraph'; -import { getBoardField, getIsIntermediate } from 'features/nodes/util/graph/graphBuilderUtils'; -import { isNonRefinerMainModelConfig, type NonNullableGraph } from 'services/api/types'; +import { addGenerationTabControlLayers } from 'features/nodes/util/graph/addGenerationTabControlLayers'; +import { addGenerationTabHRF } from 'features/nodes/util/graph/addGenerationTabHRF'; +import { addGenerationTabLoRAs } from 'features/nodes/util/graph/addGenerationTabLoRAs'; +import { addGenerationTabNSFWChecker } from 'features/nodes/util/graph/addGenerationTabNSFWChecker'; +import { addGenerationTabSeamless } from 'features/nodes/util/graph/addGenerationTabSeamless'; +import { addGenerationTabWatermarker } from 'features/nodes/util/graph/addGenerationTabWatermarker'; +import type { GraphType } from 'features/nodes/util/graph/Graph'; +import { Graph } from 'features/nodes/util/graph/Graph'; +import { getBoardField } from 'features/nodes/util/graph/graphBuilderUtils'; +import { MetadataUtil } from 'features/nodes/util/graph/MetadataUtil'; +import type { Invocation } from 'services/api/types'; +import { isNonRefinerMainModelConfig } from 'services/api/types'; +import { assert } from 'tsafe'; -import { addHrfToGraph } from './addHrfToGraph'; -import { addLoRAsToGraph } from './addLoRAsToGraph'; -import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph'; -import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph'; -import { addVAEToGraph } from './addVAEToGraph'; -import { addWatermarkerToGraph } from './addWatermarkerToGraph'; import { CLIP_SKIP, CONTROL_LAYERS_GRAPH, @@ -19,249 +22,166 @@ import { LATENTS_TO_IMAGE, MAIN_MODEL_LOADER, NEGATIVE_CONDITIONING, + NEGATIVE_CONDITIONING_COLLECT, NOISE, POSITIVE_CONDITIONING, - SEAMLESS, + POSITIVE_CONDITIONING_COLLECT, + VAE_LOADER, } from './constants'; -import { addCoreMetadataNode, getModelMetadataField } from './metadata'; +import { getModelMetadataField } from './metadata'; -export const buildGenerationTabGraph = async (state: RootState): Promise => { - const log = logger('nodes'); +export const buildGenerationTabGraph = async (state: RootState): Promise => { const { model, cfgScale: cfg_scale, cfgRescaleMultiplier: cfg_rescale_multiplier, scheduler, steps, - clipSkip, + clipSkip: skipped_layers, shouldUseCpuNoise, vaePrecision, - seamlessXAxis, - seamlessYAxis, seed, + vae, } = state.generation; const { positivePrompt, negativePrompt } = state.controlLayers.present; const { width, height } = state.controlLayers.present.size; - const use_cpu = shouldUseCpuNoise; + assert(model, 'No model found in state'); - if (!model) { - log.error('No model found in state'); - throw new Error('No model found in state'); - } + const g = new Graph(CONTROL_LAYERS_GRAPH); + const modelLoader = g.addNode({ + type: 'main_model_loader', + id: MAIN_MODEL_LOADER, + model, + }); + const clipSkip = g.addNode({ + type: 'clip_skip', + id: CLIP_SKIP, + skipped_layers, + }); + const posCond = g.addNode({ + type: 'compel', + id: POSITIVE_CONDITIONING, + prompt: positivePrompt, + }); + const posCondCollect = g.addNode({ + type: 'collect', + id: POSITIVE_CONDITIONING_COLLECT, + }); + const negCond = g.addNode({ + type: 'compel', + id: NEGATIVE_CONDITIONING, + prompt: negativePrompt, + }); + const negCondCollect = g.addNode({ + type: 'collect', + id: NEGATIVE_CONDITIONING_COLLECT, + }); + const noise = g.addNode({ + type: 'noise', + id: NOISE, + seed, + width, + height, + use_cpu: shouldUseCpuNoise, + }); + const denoise = g.addNode({ + type: 'denoise_latents', + id: DENOISE_LATENTS, + cfg_scale, + cfg_rescale_multiplier, + scheduler, + steps, + denoising_start: 0, + denoising_end: 1, + }); + const l2i = g.addNode({ + type: 'l2i', + id: LATENTS_TO_IMAGE, + fp32: vaePrecision === 'fp32', + board: getBoardField(state), + // This is the terminal node and must always save to gallery. + is_intermediate: false, + use_cache: false, + }); + const vaeLoader = + vae?.base === model.base + ? g.addNode({ + type: 'vae_loader', + id: VAE_LOADER, + vae_model: vae, + }) + : null; - const fp32 = vaePrecision === 'fp32'; - const is_intermediate = true; + let imageOutput: Invocation<'l2i'> | Invocation<'img_nsfw'> | Invocation<'img_watermark'> = l2i; - let modelLoaderNodeId = MAIN_MODEL_LOADER; - - /** - * The easiest way to build linear graphs is to do it in the node editor, then copy and paste the - * full graph here as a template. Then use the parameters from app state and set friendlier node - * ids. - * - * The only thing we need extra logic for is handling randomized seed, control net, and for img2img, - * the `fit` param. These are added to the graph at the end. - */ - - // copy-pasted graph from node editor, filled in with state values & friendly node ids - - const graph: NonNullableGraph = { - id: CONTROL_LAYERS_GRAPH, - nodes: { - [modelLoaderNodeId]: { - type: 'main_model_loader', - id: modelLoaderNodeId, - is_intermediate, - model, - }, - [CLIP_SKIP]: { - type: 'clip_skip', - id: CLIP_SKIP, - skipped_layers: clipSkip, - is_intermediate, - }, - [POSITIVE_CONDITIONING]: { - type: 'compel', - id: POSITIVE_CONDITIONING, - prompt: positivePrompt, - is_intermediate, - }, - [NEGATIVE_CONDITIONING]: { - type: 'compel', - id: NEGATIVE_CONDITIONING, - prompt: negativePrompt, - is_intermediate, - }, - [NOISE]: { - type: 'noise', - id: NOISE, - seed, - width, - height, - use_cpu, - is_intermediate, - }, - [DENOISE_LATENTS]: { - type: 'denoise_latents', - id: DENOISE_LATENTS, - is_intermediate, - cfg_scale, - cfg_rescale_multiplier, - scheduler, - steps, - denoising_start: 0, - denoising_end: 1, - }, - [LATENTS_TO_IMAGE]: { - type: 'l2i', - id: LATENTS_TO_IMAGE, - fp32, - is_intermediate: getIsIntermediate(state), - board: getBoardField(state), - use_cache: false, - }, - }, - edges: [ - // Connect Model Loader to UNet and CLIP Skip - { - source: { - node_id: modelLoaderNodeId, - field: 'unet', - }, - destination: { - node_id: DENOISE_LATENTS, - field: 'unet', - }, - }, - { - source: { - node_id: modelLoaderNodeId, - field: 'clip', - }, - destination: { - node_id: CLIP_SKIP, - field: 'clip', - }, - }, - // Connect CLIP Skip to Conditioning - { - source: { - node_id: CLIP_SKIP, - field: 'clip', - }, - destination: { - node_id: POSITIVE_CONDITIONING, - field: 'clip', - }, - }, - { - source: { - node_id: CLIP_SKIP, - field: 'clip', - }, - destination: { - node_id: NEGATIVE_CONDITIONING, - field: 'clip', - }, - }, - // Connect everything to Denoise Latents - { - source: { - node_id: POSITIVE_CONDITIONING, - field: 'conditioning', - }, - destination: { - node_id: DENOISE_LATENTS, - field: 'positive_conditioning', - }, - }, - { - source: { - node_id: NEGATIVE_CONDITIONING, - field: 'conditioning', - }, - destination: { - node_id: DENOISE_LATENTS, - field: 'negative_conditioning', - }, - }, - { - source: { - node_id: NOISE, - field: 'noise', - }, - destination: { - node_id: DENOISE_LATENTS, - field: 'noise', - }, - }, - // Decode Denoised Latents To Image - { - source: { - node_id: DENOISE_LATENTS, - field: 'latents', - }, - destination: { - node_id: LATENTS_TO_IMAGE, - field: 'latents', - }, - }, - ], - }; + g.addEdge(modelLoader, 'unet', denoise, 'unet'); + g.addEdge(modelLoader, 'clip', clipSkip, 'clip'); + g.addEdge(clipSkip, 'clip', posCond, 'clip'); + g.addEdge(clipSkip, 'clip', negCond, 'clip'); + g.addEdge(posCond, 'conditioning', posCondCollect, 'item'); + g.addEdge(negCond, 'conditioning', negCondCollect, 'item'); + g.addEdge(posCondCollect, 'collection', denoise, 'positive_conditioning'); + g.addEdge(negCondCollect, 'collection', denoise, 'negative_conditioning'); + g.addEdge(noise, 'noise', denoise, 'noise'); + g.addEdge(denoise, 'latents', l2i, 'latents'); const modelConfig = await fetchModelConfigWithTypeGuard(model.key, isNonRefinerMainModelConfig); - addCoreMetadataNode( - graph, - { - generation_mode: 'txt2img', - cfg_scale, - cfg_rescale_multiplier, - height, - width, - positive_prompt: positivePrompt, - negative_prompt: negativePrompt, - model: getModelMetadataField(modelConfig), - seed, - steps, - rand_device: use_cpu ? 'cpu' : 'cuda', - scheduler, - clip_skip: clipSkip, - }, - LATENTS_TO_IMAGE + MetadataUtil.add(g, { + generation_mode: 'txt2img', + cfg_scale, + cfg_rescale_multiplier, + height, + width, + positive_prompt: positivePrompt, + negative_prompt: negativePrompt, + model: getModelMetadataField(modelConfig), + seed, + steps, + rand_device: shouldUseCpuNoise ? 'cpu' : 'cuda', + scheduler, + clip_skip: skipped_layers, + vae: vae ?? undefined, + }); + g.validate(); + + const seamless = addGenerationTabSeamless(state, g, denoise, modelLoader, vaeLoader); + g.validate(); + + addGenerationTabLoRAs(state, g, denoise, modelLoader, seamless, clipSkip, posCond, negCond); + g.validate(); + + // We might get the VAE from the main model, custom VAE, or seamless node. + const vaeSource = seamless ?? vaeLoader ?? modelLoader; + g.addEdge(vaeSource, 'vae', l2i, 'vae'); + + const addedLayers = await addGenerationTabControlLayers( + state, + g, + denoise, + posCond, + negCond, + posCondCollect, + negCondCollect, + noise, + vaeSource ); + g.validate(); - // Add Seamless To Graph - if (seamlessXAxis || seamlessYAxis) { - addSeamlessToLinearGraph(state, graph, modelLoaderNodeId); - modelLoaderNodeId = SEAMLESS; + const isHRFAllowed = !addedLayers.some((l) => isInitialImageLayer(l) || isRegionalGuidanceLayer(l)); + if (isHRFAllowed && state.hrf.hrfEnabled) { + imageOutput = addGenerationTabHRF(state, g, denoise, noise, l2i, vaeSource); } - // add LoRA support - await addLoRAsToGraph(state, graph, DENOISE_LATENTS, modelLoaderNodeId); - - const addedLayers = await addControlLayersToGraph(state, graph, DENOISE_LATENTS); - - // optionally add custom VAE - await addVAEToGraph(state, graph, modelLoaderNodeId); - - const shouldUseHRF = !addedLayers.some((l) => isInitialImageLayer(l) || isRegionalGuidanceLayer(l)); - // High resolution fix. - if (state.hrf.hrfEnabled && shouldUseHRF) { - addHrfToGraph(state, graph); - } - - // NSFW & watermark - must be last thing added to graph if (state.system.shouldUseNSFWChecker) { - // must add before watermarker! - addNSFWCheckerToGraph(state, graph); + imageOutput = addGenerationTabNSFWChecker(g, imageOutput); } if (state.system.shouldUseWatermarker) { - // must add after nsfw checker! - addWatermarkerToGraph(state, graph); + imageOutput = addGenerationTabWatermarker(g, imageOutput); } - return graph; + MetadataUtil.setMetadataReceivingNode(g, imageOutput); + return g.getGraph(); }; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabGraph2.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabGraph2.ts deleted file mode 100644 index f8d6b1a543..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabGraph2.ts +++ /dev/null @@ -1,187 +0,0 @@ -import type { RootState } from 'app/store/store'; -import { isInitialImageLayer, isRegionalGuidanceLayer } from 'features/controlLayers/store/controlLayersSlice'; -import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers'; -import { addGenerationTabControlLayers } from 'features/nodes/util/graph/addGenerationTabControlLayers'; -import { addGenerationTabHRF } from 'features/nodes/util/graph/addGenerationTabHRF'; -import { addGenerationTabLoRAs } from 'features/nodes/util/graph/addGenerationTabLoRAs'; -import { addGenerationTabNSFWChecker } from 'features/nodes/util/graph/addGenerationTabNSFWChecker'; -import { addGenerationTabSeamless } from 'features/nodes/util/graph/addGenerationTabSeamless'; -import { addGenerationTabWatermarker } from 'features/nodes/util/graph/addGenerationTabWatermarker'; -import type { GraphType } from 'features/nodes/util/graph/Graph'; -import { Graph } from 'features/nodes/util/graph/Graph'; -import { getBoardField } from 'features/nodes/util/graph/graphBuilderUtils'; -import { MetadataUtil } from 'features/nodes/util/graph/MetadataUtil'; -import type { Invocation } from 'services/api/types'; -import { isNonRefinerMainModelConfig } from 'services/api/types'; -import { assert } from 'tsafe'; - -import { - CLIP_SKIP, - CONTROL_LAYERS_GRAPH, - DENOISE_LATENTS, - LATENTS_TO_IMAGE, - MAIN_MODEL_LOADER, - NEGATIVE_CONDITIONING, - NEGATIVE_CONDITIONING_COLLECT, - NOISE, - POSITIVE_CONDITIONING, - POSITIVE_CONDITIONING_COLLECT, - VAE_LOADER, -} from './constants'; -import { getModelMetadataField } from './metadata'; - -export const buildGenerationTabGraph2 = async (state: RootState): Promise => { - const { - model, - cfgScale: cfg_scale, - cfgRescaleMultiplier: cfg_rescale_multiplier, - scheduler, - steps, - clipSkip: skipped_layers, - shouldUseCpuNoise, - vaePrecision, - seed, - vae, - } = state.generation; - const { positivePrompt, negativePrompt } = state.controlLayers.present; - const { width, height } = state.controlLayers.present.size; - - assert(model, 'No model found in state'); - - const g = new Graph(CONTROL_LAYERS_GRAPH); - const modelLoader = g.addNode({ - type: 'main_model_loader', - id: MAIN_MODEL_LOADER, - model, - }); - const clipSkip = g.addNode({ - type: 'clip_skip', - id: CLIP_SKIP, - skipped_layers, - }); - const posCond = g.addNode({ - type: 'compel', - id: POSITIVE_CONDITIONING, - prompt: positivePrompt, - }); - const posCondCollect = g.addNode({ - type: 'collect', - id: POSITIVE_CONDITIONING_COLLECT, - }); - const negCond = g.addNode({ - type: 'compel', - id: NEGATIVE_CONDITIONING, - prompt: negativePrompt, - }); - const negCondCollect = g.addNode({ - type: 'collect', - id: NEGATIVE_CONDITIONING_COLLECT, - }); - const noise = g.addNode({ - type: 'noise', - id: NOISE, - seed, - width, - height, - use_cpu: shouldUseCpuNoise, - }); - const denoise = g.addNode({ - type: 'denoise_latents', - id: DENOISE_LATENTS, - cfg_scale, - cfg_rescale_multiplier, - scheduler, - steps, - denoising_start: 0, - denoising_end: 1, - }); - const l2i = g.addNode({ - type: 'l2i', - id: LATENTS_TO_IMAGE, - fp32: vaePrecision === 'fp32', - board: getBoardField(state), - // This is the terminal node and must always save to gallery. - is_intermediate: false, - use_cache: false, - }); - const vaeLoader = - vae?.base === model.base - ? g.addNode({ - type: 'vae_loader', - id: VAE_LOADER, - vae_model: vae, - }) - : null; - - let imageOutput: Invocation<'l2i'> | Invocation<'img_nsfw'> | Invocation<'img_watermark'> = l2i; - - g.addEdge(modelLoader, 'unet', denoise, 'unet'); - g.addEdge(modelLoader, 'clip', clipSkip, 'clip'); - g.addEdge(clipSkip, 'clip', posCond, 'clip'); - g.addEdge(clipSkip, 'clip', negCond, 'clip'); - g.addEdge(posCond, 'conditioning', posCondCollect, 'item'); - g.addEdge(negCond, 'conditioning', negCondCollect, 'item'); - g.addEdge(posCondCollect, 'collection', denoise, 'positive_conditioning'); - g.addEdge(negCondCollect, 'collection', denoise, 'negative_conditioning'); - g.addEdge(noise, 'noise', denoise, 'noise'); - g.addEdge(denoise, 'latents', l2i, 'latents'); - - const modelConfig = await fetchModelConfigWithTypeGuard(model.key, isNonRefinerMainModelConfig); - - MetadataUtil.add(g, { - generation_mode: 'txt2img', - cfg_scale, - cfg_rescale_multiplier, - height, - width, - positive_prompt: positivePrompt, - negative_prompt: negativePrompt, - model: getModelMetadataField(modelConfig), - seed, - steps, - rand_device: shouldUseCpuNoise ? 'cpu' : 'cuda', - scheduler, - clip_skip: skipped_layers, - vae: vae ?? undefined, - }); - g.validate(); - - const seamless = addGenerationTabSeamless(state, g, denoise, modelLoader, vaeLoader); - g.validate(); - - addGenerationTabLoRAs(state, g, denoise, modelLoader, seamless, clipSkip, posCond, negCond); - g.validate(); - - // We might get the VAE from the main model, custom VAE, or seamless node. - const vaeSource = seamless ?? vaeLoader ?? modelLoader; - g.addEdge(vaeSource, 'vae', l2i, 'vae'); - - const addedLayers = await addGenerationTabControlLayers( - state, - g, - denoise, - posCond, - negCond, - posCondCollect, - negCondCollect, - noise, - vaeSource - ); - g.validate(); - - const isHRFAllowed = !addedLayers.some((l) => isInitialImageLayer(l) || isRegionalGuidanceLayer(l)); - if (isHRFAllowed && state.hrf.hrfEnabled) { - imageOutput = addGenerationTabHRF(state, g, denoise, noise, l2i, vaeSource); - } - - if (state.system.shouldUseNSFWChecker) { - imageOutput = addGenerationTabNSFWChecker(g, imageOutput); - } - - if (state.system.shouldUseWatermarker) { - imageOutput = addGenerationTabWatermarker(g, imageOutput); - } - - MetadataUtil.setMetadataReceivingNode(g, imageOutput); - return g.getGraph(); -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabSDXLGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabSDXLGraph.ts index fbf6b3848c..29caf3ac3a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabSDXLGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabSDXLGraph.ts @@ -1,31 +1,33 @@ -import { logger } from 'app/logging/logger'; import type { RootState } from 'app/store/store'; import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers'; -import { addControlLayersToGraph } from 'features/nodes/util/graph/addControlLayersToGraph'; -import { isNonRefinerMainModelConfig, type NonNullableGraph } from 'services/api/types'; +import { addGenerationTabControlLayers } from 'features/nodes/util/graph/addGenerationTabControlLayers'; +import { addGenerationTabNSFWChecker } from 'features/nodes/util/graph/addGenerationTabNSFWChecker'; +import { addGenerationTabSDXLLoRAs } from 'features/nodes/util/graph/addGenerationTabSDXLLoRAs'; +import { addGenerationTabSDXLRefiner } from 'features/nodes/util/graph/addGenerationTabSDXLRefiner'; +import { addGenerationTabSeamless } from 'features/nodes/util/graph/addGenerationTabSeamless'; +import { addGenerationTabWatermarker } from 'features/nodes/util/graph/addGenerationTabWatermarker'; +import { Graph } from 'features/nodes/util/graph/Graph'; +import { MetadataUtil } from 'features/nodes/util/graph/MetadataUtil'; +import type { Invocation, NonNullableGraph } from 'services/api/types'; +import { isNonRefinerMainModelConfig } from 'services/api/types'; +import { assert } from 'tsafe'; -import { addNSFWCheckerToGraph } from './addNSFWCheckerToGraph'; -import { addSDXLLoRAsToGraph } from './addSDXLLoRAstoGraph'; -import { addSDXLRefinerToGraph } from './addSDXLRefinerToGraph'; -import { addSeamlessToLinearGraph } from './addSeamlessToLinearGraph'; -import { addVAEToGraph } from './addVAEToGraph'; -import { addWatermarkerToGraph } from './addWatermarkerToGraph'; import { LATENTS_TO_IMAGE, NEGATIVE_CONDITIONING, + NEGATIVE_CONDITIONING_COLLECT, NOISE, POSITIVE_CONDITIONING, + POSITIVE_CONDITIONING_COLLECT, SDXL_CONTROL_LAYERS_GRAPH, SDXL_DENOISE_LATENTS, SDXL_MODEL_LOADER, - SDXL_REFINER_SEAMLESS, - SEAMLESS, + VAE_LOADER, } from './constants'; -import { getBoardField, getIsIntermediate, getSDXLStylePrompts } from './graphBuilderUtils'; -import { addCoreMetadataNode, getModelMetadataField } from './metadata'; +import { getBoardField, getSDXLStylePrompts } from './graphBuilderUtils'; +import { getModelMetadataField } from './metadata'; export const buildGenerationTabSDXLGraph = async (state: RootState): Promise => { - const log = logger('nodes'); const { model, cfgScale: cfg_scale, @@ -35,244 +37,142 @@ export const buildGenerationTabSDXLGraph = async (state: RootState): Promise | Invocation<'img_nsfw'> | Invocation<'img_watermark'> = l2i; - // copy-pasted graph from node editor, filled in with state values & friendly node ids - const graph: NonNullableGraph = { - id: SDXL_CONTROL_LAYERS_GRAPH, - nodes: { - [modelLoaderNodeId]: { - type: 'sdxl_model_loader', - id: modelLoaderNodeId, - model, - is_intermediate, - }, - [POSITIVE_CONDITIONING]: { - type: 'sdxl_compel_prompt', - id: POSITIVE_CONDITIONING, - prompt: positivePrompt, - style: positiveStylePrompt, - is_intermediate, - }, - [NEGATIVE_CONDITIONING]: { - type: 'sdxl_compel_prompt', - id: NEGATIVE_CONDITIONING, - prompt: negativePrompt, - style: negativeStylePrompt, - is_intermediate, - }, - [NOISE]: { - type: 'noise', - id: NOISE, - seed, - width, - height, - use_cpu, - is_intermediate, - }, - [SDXL_DENOISE_LATENTS]: { - type: 'denoise_latents', - id: SDXL_DENOISE_LATENTS, - cfg_scale, - cfg_rescale_multiplier, - scheduler, - steps, - denoising_start: 0, - denoising_end: refinerModel ? refinerStart : 1, - is_intermediate, - }, - [LATENTS_TO_IMAGE]: { - type: 'l2i', - id: LATENTS_TO_IMAGE, - fp32, - is_intermediate: getIsIntermediate(state), - board: getBoardField(state), - use_cache: false, - }, - }, - edges: [ - // Connect Model Loader to UNet, VAE & CLIP - { - source: { - node_id: modelLoaderNodeId, - field: 'unet', - }, - destination: { - node_id: SDXL_DENOISE_LATENTS, - field: 'unet', - }, - }, - { - source: { - node_id: modelLoaderNodeId, - field: 'clip', - }, - destination: { - node_id: POSITIVE_CONDITIONING, - field: 'clip', - }, - }, - { - source: { - node_id: modelLoaderNodeId, - field: 'clip2', - }, - destination: { - node_id: POSITIVE_CONDITIONING, - field: 'clip2', - }, - }, - { - source: { - node_id: modelLoaderNodeId, - field: 'clip', - }, - destination: { - node_id: NEGATIVE_CONDITIONING, - field: 'clip', - }, - }, - { - source: { - node_id: modelLoaderNodeId, - field: 'clip2', - }, - destination: { - node_id: NEGATIVE_CONDITIONING, - field: 'clip2', - }, - }, - // Connect everything to Denoise Latents - { - source: { - node_id: POSITIVE_CONDITIONING, - field: 'conditioning', - }, - destination: { - node_id: SDXL_DENOISE_LATENTS, - field: 'positive_conditioning', - }, - }, - { - source: { - node_id: NEGATIVE_CONDITIONING, - field: 'conditioning', - }, - destination: { - node_id: SDXL_DENOISE_LATENTS, - field: 'negative_conditioning', - }, - }, - { - source: { - node_id: NOISE, - field: 'noise', - }, - destination: { - node_id: SDXL_DENOISE_LATENTS, - field: 'noise', - }, - }, - // Decode Denoised Latents To Image - { - source: { - node_id: SDXL_DENOISE_LATENTS, - field: 'latents', - }, - destination: { - node_id: LATENTS_TO_IMAGE, - field: 'latents', - }, - }, - ], - }; + g.addEdge(modelLoader, 'unet', denoise, 'unet'); + g.addEdge(modelLoader, 'clip', posCond, 'clip'); + g.addEdge(modelLoader, 'clip', negCond, 'clip'); + g.addEdge(modelLoader, 'clip2', posCond, 'clip2'); + g.addEdge(modelLoader, 'clip2', negCond, 'clip2'); + g.addEdge(posCond, 'conditioning', posCondCollect, 'item'); + g.addEdge(negCond, 'conditioning', negCondCollect, 'item'); + g.addEdge(posCondCollect, 'collection', denoise, 'positive_conditioning'); + g.addEdge(negCondCollect, 'collection', denoise, 'negative_conditioning'); + g.addEdge(noise, 'noise', denoise, 'noise'); + g.addEdge(denoise, 'latents', l2i, 'latents'); const modelConfig = await fetchModelConfigWithTypeGuard(model.key, isNonRefinerMainModelConfig); - addCoreMetadataNode( - graph, - { - generation_mode: 'txt2img', - cfg_scale, - cfg_rescale_multiplier, - height, - width, - positive_prompt: positivePrompt, - negative_prompt: negativePrompt, - model: getModelMetadataField(modelConfig), - seed, - steps, - rand_device: use_cpu ? 'cpu' : 'cuda', - scheduler, - positive_style_prompt: positiveStylePrompt, - negative_style_prompt: negativeStylePrompt, - }, - LATENTS_TO_IMAGE - ); + MetadataUtil.add(g, { + generation_mode: 'txt2img', + cfg_scale, + cfg_rescale_multiplier, + height, + width, + positive_prompt: positivePrompt, + negative_prompt: negativePrompt, + model: getModelMetadataField(modelConfig), + seed, + steps, + rand_device: shouldUseCpuNoise ? 'cpu' : 'cuda', + scheduler, + positive_style_prompt: positiveStylePrompt, + negative_style_prompt: negativeStylePrompt, + vae: vae ?? undefined, + }); + g.validate(); - // Add Seamless To Graph - if (seamlessXAxis || seamlessYAxis) { - addSeamlessToLinearGraph(state, graph, modelLoaderNodeId); - modelLoaderNodeId = SEAMLESS; - } + const seamless = addGenerationTabSeamless(state, g, denoise, modelLoader, vaeLoader); + g.validate(); + + addGenerationTabSDXLLoRAs(state, g, denoise, modelLoader, seamless, posCond, negCond); + g.validate(); + + // We might get the VAE from the main model, custom VAE, or seamless node. + const vaeSource = seamless ?? vaeLoader ?? modelLoader; + g.addEdge(vaeSource, 'vae', l2i, 'vae'); // Add Refiner if enabled if (refinerModel) { - await addSDXLRefinerToGraph(state, graph, SDXL_DENOISE_LATENTS); - if (seamlessXAxis || seamlessYAxis) { - modelLoaderNodeId = SDXL_REFINER_SEAMLESS; - } + await addGenerationTabSDXLRefiner(state, g, denoise, modelLoader, seamless, posCond, negCond, l2i); } - // add LoRA support - await addSDXLLoRAsToGraph(state, graph, SDXL_DENOISE_LATENTS, modelLoaderNodeId); + await addGenerationTabControlLayers( + state, + g, + denoise, + posCond, + negCond, + posCondCollect, + negCondCollect, + noise, + vaeSource + ); - await addControlLayersToGraph(state, graph, SDXL_DENOISE_LATENTS); - - // optionally add custom VAE - await addVAEToGraph(state, graph, modelLoaderNodeId); - - // NSFW & watermark - must be last thing added to graph if (state.system.shouldUseNSFWChecker) { - // must add before watermarker! - addNSFWCheckerToGraph(state, graph); + imageOutput = addGenerationTabNSFWChecker(g, imageOutput); } if (state.system.shouldUseWatermarker) { - // must add after nsfw checker! - addWatermarkerToGraph(state, graph); + imageOutput = addGenerationTabWatermarker(g, imageOutput); } - return graph; + MetadataUtil.setMetadataReceivingNode(g, imageOutput); + return g.getGraph(); }; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabSDXLGraph2.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabSDXLGraph2.ts deleted file mode 100644 index 05fe3d1565..0000000000 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildGenerationTabSDXLGraph2.ts +++ /dev/null @@ -1,178 +0,0 @@ -import type { RootState } from 'app/store/store'; -import { fetchModelConfigWithTypeGuard } from 'features/metadata/util/modelFetchingHelpers'; -import { addGenerationTabControlLayers } from 'features/nodes/util/graph/addGenerationTabControlLayers'; -import { addGenerationTabNSFWChecker } from 'features/nodes/util/graph/addGenerationTabNSFWChecker'; -import { addGenerationTabSDXLLoRAs } from 'features/nodes/util/graph/addGenerationTabSDXLLoRAs'; -import { addGenerationTabSDXLRefiner } from 'features/nodes/util/graph/addGenerationTabSDXLRefiner'; -import { addGenerationTabSeamless } from 'features/nodes/util/graph/addGenerationTabSeamless'; -import { addGenerationTabWatermarker } from 'features/nodes/util/graph/addGenerationTabWatermarker'; -import { Graph } from 'features/nodes/util/graph/Graph'; -import { MetadataUtil } from 'features/nodes/util/graph/MetadataUtil'; -import type { Invocation, NonNullableGraph } from 'services/api/types'; -import { isNonRefinerMainModelConfig } from 'services/api/types'; -import { assert } from 'tsafe'; - -import { - LATENTS_TO_IMAGE, - NEGATIVE_CONDITIONING, - NEGATIVE_CONDITIONING_COLLECT, - NOISE, - POSITIVE_CONDITIONING, - POSITIVE_CONDITIONING_COLLECT, - SDXL_CONTROL_LAYERS_GRAPH, - SDXL_DENOISE_LATENTS, - SDXL_MODEL_LOADER, - VAE_LOADER, -} from './constants'; -import { getBoardField, getSDXLStylePrompts } from './graphBuilderUtils'; -import { getModelMetadataField } from './metadata'; - -export const buildGenerationTabSDXLGraph2 = async (state: RootState): Promise => { - const { - model, - cfgScale: cfg_scale, - cfgRescaleMultiplier: cfg_rescale_multiplier, - scheduler, - seed, - steps, - shouldUseCpuNoise, - vaePrecision, - vae, - } = state.generation; - const { positivePrompt, negativePrompt } = state.controlLayers.present; - const { width, height } = state.controlLayers.present.size; - - const { refinerModel, refinerStart } = state.sdxl; - - assert(model, 'No model found in state'); - - const { positiveStylePrompt, negativeStylePrompt } = getSDXLStylePrompts(state); - - const g = new Graph(SDXL_CONTROL_LAYERS_GRAPH); - const modelLoader = g.addNode({ - type: 'sdxl_model_loader', - id: SDXL_MODEL_LOADER, - model, - }); - const posCond = g.addNode({ - type: 'sdxl_compel_prompt', - id: POSITIVE_CONDITIONING, - prompt: positivePrompt, - style: positiveStylePrompt, - }); - const posCondCollect = g.addNode({ - type: 'collect', - id: POSITIVE_CONDITIONING_COLLECT, - }); - const negCond = g.addNode({ - type: 'sdxl_compel_prompt', - id: NEGATIVE_CONDITIONING, - prompt: negativePrompt, - style: negativeStylePrompt, - }); - const negCondCollect = g.addNode({ - type: 'collect', - id: NEGATIVE_CONDITIONING_COLLECT, - }); - const noise = g.addNode({ type: 'noise', id: NOISE, seed, width, height, use_cpu: shouldUseCpuNoise }); - const denoise = g.addNode({ - type: 'denoise_latents', - id: SDXL_DENOISE_LATENTS, - cfg_scale, - cfg_rescale_multiplier, - scheduler, - steps, - denoising_start: 0, - denoising_end: refinerModel ? refinerStart : 1, - }); - const l2i = g.addNode({ - type: 'l2i', - id: LATENTS_TO_IMAGE, - fp32: vaePrecision === 'fp32', - board: getBoardField(state), - // This is the terminal node and must always save to gallery. - is_intermediate: false, - use_cache: false, - }); - const vaeLoader = - vae?.base === model.base - ? g.addNode({ - type: 'vae_loader', - id: VAE_LOADER, - vae_model: vae, - }) - : null; - - let imageOutput: Invocation<'l2i'> | Invocation<'img_nsfw'> | Invocation<'img_watermark'> = l2i; - - g.addEdge(modelLoader, 'unet', denoise, 'unet'); - g.addEdge(modelLoader, 'clip', posCond, 'clip'); - g.addEdge(modelLoader, 'clip', negCond, 'clip'); - g.addEdge(modelLoader, 'clip2', posCond, 'clip2'); - g.addEdge(modelLoader, 'clip2', negCond, 'clip2'); - g.addEdge(posCond, 'conditioning', posCondCollect, 'item'); - g.addEdge(negCond, 'conditioning', negCondCollect, 'item'); - g.addEdge(posCondCollect, 'collection', denoise, 'positive_conditioning'); - g.addEdge(negCondCollect, 'collection', denoise, 'negative_conditioning'); - g.addEdge(noise, 'noise', denoise, 'noise'); - g.addEdge(denoise, 'latents', l2i, 'latents'); - - const modelConfig = await fetchModelConfigWithTypeGuard(model.key, isNonRefinerMainModelConfig); - - MetadataUtil.add(g, { - generation_mode: 'txt2img', - cfg_scale, - cfg_rescale_multiplier, - height, - width, - positive_prompt: positivePrompt, - negative_prompt: negativePrompt, - model: getModelMetadataField(modelConfig), - seed, - steps, - rand_device: shouldUseCpuNoise ? 'cpu' : 'cuda', - scheduler, - positive_style_prompt: positiveStylePrompt, - negative_style_prompt: negativeStylePrompt, - vae: vae ?? undefined, - }); - g.validate(); - - const seamless = addGenerationTabSeamless(state, g, denoise, modelLoader, vaeLoader); - g.validate(); - - addGenerationTabSDXLLoRAs(state, g, denoise, modelLoader, seamless, posCond, negCond); - g.validate(); - - // We might get the VAE from the main model, custom VAE, or seamless node. - const vaeSource = seamless ?? vaeLoader ?? modelLoader; - g.addEdge(vaeSource, 'vae', l2i, 'vae'); - - // Add Refiner if enabled - if (refinerModel) { - await addGenerationTabSDXLRefiner(state, g, denoise, modelLoader, seamless, posCond, negCond, l2i); - } - - await addGenerationTabControlLayers( - state, - g, - denoise, - posCond, - negCond, - posCondCollect, - negCondCollect, - noise, - vaeSource - ); - - if (state.system.shouldUseNSFWChecker) { - imageOutput = addGenerationTabNSFWChecker(g, imageOutput); - } - - if (state.system.shouldUseWatermarker) { - imageOutput = addGenerationTabWatermarker(g, imageOutput); - } - - MetadataUtil.setMetadataReceivingNode(g, imageOutput); - return g.getGraph(); -}; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/metadata.ts b/invokeai/frontend/web/src/features/nodes/util/graph/metadata.ts index 9e8b5a5a6b..366c8a936e 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/metadata.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/metadata.ts @@ -58,21 +58,6 @@ export const getHasMetadata = (graph: NonNullableGraph): boolean => { return Boolean(metadataNode); }; -export const setMetadataReceivingNode = (graph: NonNullableGraph, nodeId: string) => { - graph.edges = graph.edges.filter((edge) => edge.source.node_id !== METADATA); - - graph.edges.push({ - source: { - node_id: METADATA, - field: 'metadata', - }, - destination: { - node_id: nodeId, - field: 'metadata', - }, - }); -}; - export const getModelMetadataField = ({ key, hash, name, base, type }: AnyModelConfig): ModelIdentifierField => ({ key, hash,