diff --git a/invokeai/app/invocations/canvas.py b/invokeai/app/invocations/canvas.py
new file mode 100644
index 00000000000..cf13c3334ff
--- /dev/null
+++ b/invokeai/app/invocations/canvas.py
@@ -0,0 +1,27 @@
+from invokeai.app.invocations.baseinvocation import BaseInvocation, invocation
+from invokeai.app.invocations.fields import FieldDescriptions, ImageField, InputField
+from invokeai.app.invocations.primitives import ImageOutput
+from invokeai.app.services.shared.invocation_context import InvocationContext
+
+
+@invocation(
+ "canvas_output",
+ title="Canvas Output",
+ tags=["canvas", "output", "image"],
+ category="canvas",
+ version="1.0.0",
+ use_cache=False,
+)
+class CanvasOutputInvocation(BaseInvocation):
+ """Outputs an image to the canvas staging area.
+
+ Use this node in workflows intended for canvas workflow integration.
+ Connect the final image of your workflow to this node to send it
+ to the canvas staging area when run via 'Run Workflow on Canvas'."""
+
+ image: ImageField = InputField(description=FieldDescriptions.image)
+
+ def invoke(self, context: InvocationContext) -> ImageOutput:
+ image = context.images.get_pil(self.image.image_name)
+ image_dto = context.images.save(image=image)
+ return ImageOutput.build(image_dto)
diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index 047d5a40077..9ba645eef88 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -2377,6 +2377,27 @@
"pullBboxIntoReferenceImageError": "Problem Pulling BBox Into ReferenceImage",
"addAdjustments": "Add Adjustments",
"removeAdjustments": "Remove Adjustments",
+ "workflowIntegration": {
+ "title": "Run Workflow on Canvas",
+ "description": "Select a workflow with a Canvas Output node and an image parameter to run on the current canvas layer. You can adjust parameters before executing. The result will be added back to the canvas.",
+ "execute": "Execute Workflow",
+ "executing": "Executing...",
+ "runWorkflow": "Run Workflow",
+ "filteringWorkflows": "Filtering workflows...",
+ "loadingWorkflows": "Loading workflows...",
+ "noWorkflowsFound": "No workflows found.",
+ "noWorkflowsWithImageField": "No compatible workflows found. A workflow needs a Form Builder with an image input field and a Canvas Output node.",
+ "selectWorkflow": "Select Workflow",
+ "selectPlaceholder": "Choose a workflow...",
+ "unnamedWorkflow": "Unnamed Workflow",
+ "loadingParameters": "Loading workflow parameters...",
+ "noFormBuilderError": "This workflow has no form builder and cannot be used. Please select a different workflow.",
+ "imageFieldSelected": "This field will receive the canvas image",
+ "imageFieldNotSelected": "Click to use this field for canvas image",
+ "executionStarted": "Workflow execution started",
+ "executionStartedDescription": "The result will appear in the staging area when complete.",
+ "executionFailed": "Failed to execute workflow"
+ },
"compositeOperation": {
"label": "Blend Mode",
"add": "Add Blend Mode",
diff --git a/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx b/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx
index 5c1446662ef..ef0747707ff 100644
--- a/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx
+++ b/invokeai/frontend/web/src/app/components/GlobalModalIsolator.tsx
@@ -1,6 +1,7 @@
import { GlobalImageHotkeys } from 'app/components/GlobalImageHotkeys';
import ChangeBoardModal from 'features/changeBoardModal/components/ChangeBoardModal';
import { CanvasPasteModal } from 'features/controlLayers/components/CanvasPasteModal';
+import { CanvasWorkflowIntegrationModal } from 'features/controlLayers/components/CanvasWorkflowIntegration/CanvasWorkflowIntegrationModal';
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { CropImageModal } from 'features/cropper/components/CropImageModal';
import { DeleteImageModal } from 'features/deleteImageModal/components/DeleteImageModal';
@@ -51,6 +52,7 @@ export const GlobalModalIsolator = memo(() => {
+
diff --git a/invokeai/frontend/web/src/app/logging/logger.ts b/invokeai/frontend/web/src/app/logging/logger.ts
index 6c843068df3..d20ef77090f 100644
--- a/invokeai/frontend/web/src/app/logging/logger.ts
+++ b/invokeai/frontend/web/src/app/logging/logger.ts
@@ -16,6 +16,7 @@ const $logger = atom(Roarr.child(BASE_CONTEXT));
export const zLogNamespace = z.enum([
'canvas',
+ 'canvas-workflow-integration',
'config',
'dnd',
'events',
diff --git a/invokeai/frontend/web/src/app/store/store.ts b/invokeai/frontend/web/src/app/store/store.ts
index 8f077baaea5..f24d2d0105c 100644
--- a/invokeai/frontend/web/src/app/store/store.ts
+++ b/invokeai/frontend/web/src/app/store/store.ts
@@ -25,6 +25,7 @@ import { canvasSettingsSliceConfig } from 'features/controlLayers/store/canvasSe
import { canvasSliceConfig } from 'features/controlLayers/store/canvasSlice';
import { canvasSessionSliceConfig } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { canvasTextSliceConfig } from 'features/controlLayers/store/canvasTextSlice';
+import { canvasWorkflowIntegrationSliceConfig } from 'features/controlLayers/store/canvasWorkflowIntegrationSlice';
import { lorasSliceConfig } from 'features/controlLayers/store/lorasSlice';
import { paramsSliceConfig } from 'features/controlLayers/store/paramsSlice';
import { refImagesSliceConfig } from 'features/controlLayers/store/refImagesSlice';
@@ -67,6 +68,7 @@ const SLICE_CONFIGS = {
[canvasSettingsSliceConfig.slice.reducerPath]: canvasSettingsSliceConfig,
[canvasTextSliceConfig.slice.reducerPath]: canvasTextSliceConfig,
[canvasSliceConfig.slice.reducerPath]: canvasSliceConfig,
+ [canvasWorkflowIntegrationSliceConfig.slice.reducerPath]: canvasWorkflowIntegrationSliceConfig,
[changeBoardModalSliceConfig.slice.reducerPath]: changeBoardModalSliceConfig,
[dynamicPromptsSliceConfig.slice.reducerPath]: dynamicPromptsSliceConfig,
[gallerySliceConfig.slice.reducerPath]: gallerySliceConfig,
@@ -98,6 +100,7 @@ const ALL_REDUCERS = {
canvasSliceConfig.slice.reducer,
canvasSliceConfig.undoableConfig?.reduxUndoOptions
),
+ [canvasWorkflowIntegrationSliceConfig.slice.reducerPath]: canvasWorkflowIntegrationSliceConfig.slice.reducer,
[changeBoardModalSliceConfig.slice.reducerPath]: changeBoardModalSliceConfig.slice.reducer,
[dynamicPromptsSliceConfig.slice.reducerPath]: dynamicPromptsSliceConfig.slice.reducer,
[gallerySliceConfig.slice.reducerPath]: gallerySliceConfig.slice.reducer,
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/CanvasWorkflowIntegrationModal.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/CanvasWorkflowIntegrationModal.tsx
new file mode 100644
index 00000000000..94a123fa91a
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/CanvasWorkflowIntegrationModal.tsx
@@ -0,0 +1,93 @@
+import {
+ Button,
+ ButtonGroup,
+ Flex,
+ Heading,
+ Modal,
+ ModalBody,
+ ModalCloseButton,
+ ModalContent,
+ ModalFooter,
+ ModalHeader,
+ ModalOverlay,
+ Spacer,
+ Spinner,
+ Text,
+} from '@invoke-ai/ui-library';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import {
+ canvasWorkflowIntegrationClosed,
+ selectCanvasWorkflowIntegrationIsOpen,
+ selectCanvasWorkflowIntegrationIsProcessing,
+ selectCanvasWorkflowIntegrationSelectedWorkflowId,
+} from 'features/controlLayers/store/canvasWorkflowIntegrationSlice';
+import { memo, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+
+import { CanvasWorkflowIntegrationParameterPanel } from './CanvasWorkflowIntegrationParameterPanel';
+import { CanvasWorkflowIntegrationWorkflowSelector } from './CanvasWorkflowIntegrationWorkflowSelector';
+import { useCanvasWorkflowIntegrationExecute } from './useCanvasWorkflowIntegrationExecute';
+
+export const CanvasWorkflowIntegrationModal = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+
+ const isOpen = useAppSelector(selectCanvasWorkflowIntegrationIsOpen);
+ const isProcessing = useAppSelector(selectCanvasWorkflowIntegrationIsProcessing);
+ const selectedWorkflowId = useAppSelector(selectCanvasWorkflowIntegrationSelectedWorkflowId);
+
+ const { execute, canExecute } = useCanvasWorkflowIntegrationExecute();
+
+ const onClose = useCallback(() => {
+ if (!isProcessing) {
+ dispatch(canvasWorkflowIntegrationClosed());
+ }
+ }, [dispatch, isProcessing]);
+
+ const onExecute = useCallback(() => {
+ execute();
+ }, [execute]);
+
+ return (
+
+
+
+
+ {t('controlLayers.workflowIntegration.title')}
+
+
+
+
+
+
+ {t('controlLayers.workflowIntegration.description')}
+
+
+
+
+ {selectedWorkflowId && }
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+});
+
+CanvasWorkflowIntegrationModal.displayName = 'CanvasWorkflowIntegrationModal';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/CanvasWorkflowIntegrationParameterPanel.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/CanvasWorkflowIntegrationParameterPanel.tsx
new file mode 100644
index 00000000000..f59a6c45edb
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/CanvasWorkflowIntegrationParameterPanel.tsx
@@ -0,0 +1,13 @@
+import { Box } from '@invoke-ai/ui-library';
+import { WorkflowFormPreview } from 'features/controlLayers/components/CanvasWorkflowIntegration/WorkflowFormPreview';
+import { memo } from 'react';
+
+export const CanvasWorkflowIntegrationParameterPanel = memo(() => {
+ return (
+
+
+
+ );
+});
+
+CanvasWorkflowIntegrationParameterPanel.displayName = 'CanvasWorkflowIntegrationParameterPanel';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/CanvasWorkflowIntegrationWorkflowSelector.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/CanvasWorkflowIntegrationWorkflowSelector.tsx
new file mode 100644
index 00000000000..30bc60605c6
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/CanvasWorkflowIntegrationWorkflowSelector.tsx
@@ -0,0 +1,92 @@
+import { Flex, FormControl, FormLabel, Select, Spinner, Text } from '@invoke-ai/ui-library';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import {
+ canvasWorkflowIntegrationWorkflowSelected,
+ selectCanvasWorkflowIntegrationSelectedWorkflowId,
+} from 'features/controlLayers/store/canvasWorkflowIntegrationSlice';
+import type { ChangeEvent } from 'react';
+import { memo, useCallback, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useListWorkflowsInfiniteInfiniteQuery } from 'services/api/endpoints/workflows';
+
+import { useFilteredWorkflows } from './useFilteredWorkflows';
+
+export const CanvasWorkflowIntegrationWorkflowSelector = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+
+ const selectedWorkflowId = useAppSelector(selectCanvasWorkflowIntegrationSelectedWorkflowId);
+ const { data: workflowsData, isLoading } = useListWorkflowsInfiniteInfiniteQuery(
+ {
+ per_page: 100, // Get a reasonable number of workflows
+ page: 0,
+ },
+ {
+ selectFromResult: ({ data, isLoading }) => ({
+ data,
+ isLoading,
+ }),
+ }
+ );
+
+ const workflows = useMemo(() => {
+ if (!workflowsData) {
+ return [];
+ }
+ // Flatten all pages into a single list
+ return workflowsData.pages.flatMap((page) => page.items);
+ }, [workflowsData]);
+
+ // Filter workflows to only show those with ImageFields
+ const { filteredWorkflows, isFiltering } = useFilteredWorkflows(workflows);
+
+ const onChange = useCallback(
+ (e: ChangeEvent) => {
+ const workflowId = e.target.value || null;
+ dispatch(canvasWorkflowIntegrationWorkflowSelected({ workflowId }));
+ },
+ [dispatch]
+ );
+
+ if (isLoading || isFiltering) {
+ return (
+
+
+
+ {isFiltering
+ ? t('controlLayers.workflowIntegration.filteringWorkflows')
+ : t('controlLayers.workflowIntegration.loadingWorkflows')}
+
+
+ );
+ }
+
+ if (filteredWorkflows.length === 0) {
+ return (
+
+ {workflows.length === 0
+ ? t('controlLayers.workflowIntegration.noWorkflowsFound')
+ : t('controlLayers.workflowIntegration.noWorkflowsWithImageField')}
+
+ );
+ }
+
+ return (
+
+ {t('controlLayers.workflowIntegration.selectWorkflow')}
+
+
+ );
+});
+
+CanvasWorkflowIntegrationWorkflowSelector.displayName = 'CanvasWorkflowIntegrationWorkflowSelector';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/WorkflowFieldRenderer.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/WorkflowFieldRenderer.tsx
new file mode 100644
index 00000000000..2d91be13bfa
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/WorkflowFieldRenderer.tsx
@@ -0,0 +1,548 @@
+import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
+import {
+ Combobox,
+ Flex,
+ FormControl,
+ FormLabel,
+ IconButton,
+ Input,
+ Radio,
+ Select,
+ Switch,
+ Text,
+ Textarea,
+} from '@invoke-ai/ui-library';
+import { useStore } from '@nanostores/react';
+import { skipToken } from '@reduxjs/toolkit/query';
+import { logger } from 'app/logging/logger';
+import { EMPTY_ARRAY } from 'app/store/constants';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { UploadImageIconButton } from 'common/hooks/useImageUploadButton';
+import {
+ canvasWorkflowIntegrationFieldValueChanged,
+ canvasWorkflowIntegrationImageFieldSelected,
+ selectCanvasWorkflowIntegrationFieldValues,
+ selectCanvasWorkflowIntegrationSelectedImageFieldKey,
+ selectCanvasWorkflowIntegrationSelectedWorkflowId,
+} from 'features/controlLayers/store/canvasWorkflowIntegrationSlice';
+import { DndImage } from 'features/dnd/DndImage';
+import { ModelFieldCombobox } from 'features/nodes/components/flow/nodes/Invocation/fields/inputs/ModelFieldCombobox';
+import { $templates } from 'features/nodes/store/nodesSlice';
+import type { NodeFieldElement } from 'features/nodes/types/workflow';
+import { SCHEDULER_OPTIONS } from 'features/parameters/types/constants';
+import { isParameterScheduler } from 'features/parameters/types/parameterSchemas';
+import type { ChangeEvent } from 'react';
+import { memo, useCallback, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiTrashSimpleBold } from 'react-icons/pi';
+import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
+import { useGetImageDTOQuery } from 'services/api/endpoints/images';
+import { modelConfigsAdapterSelectors, useGetModelConfigsQuery } from 'services/api/endpoints/models';
+import { useGetWorkflowQuery } from 'services/api/endpoints/workflows';
+import type { AnyModelConfig, ImageDTO } from 'services/api/types';
+
+const log = logger('canvas-workflow-integration');
+
+interface WorkflowFieldRendererProps {
+ el: NodeFieldElement;
+}
+
+export const WorkflowFieldRenderer = memo(({ el }: WorkflowFieldRendererProps) => {
+ const dispatch = useAppDispatch();
+ const { t } = useTranslation();
+ const selectedWorkflowId = useAppSelector(selectCanvasWorkflowIntegrationSelectedWorkflowId);
+ const fieldValues = useAppSelector(selectCanvasWorkflowIntegrationFieldValues);
+ const selectedImageFieldKey = useAppSelector(selectCanvasWorkflowIntegrationSelectedImageFieldKey);
+ const templates = useStore($templates);
+
+ const { data: workflow } = useGetWorkflowQuery(selectedWorkflowId!, {
+ skip: !selectedWorkflowId,
+ });
+
+ // Load boards and models for BoardField and ModelIdentifierField
+ const { data: boardsData } = useListAllBoardsQuery({ include_archived: true });
+ const { data: modelsData, isLoading: isLoadingModels } = useGetModelConfigsQuery();
+
+ const { fieldIdentifier } = el.data;
+ const fieldKey = `${fieldIdentifier.nodeId}.${fieldIdentifier.fieldName}`;
+
+ log.debug({ fieldIdentifier, fieldKey }, 'Rendering workflow field');
+
+ // Get the node, field instance, and field template
+ const { field, fieldTemplate } = useMemo(() => {
+ if (!workflow?.workflow.nodes) {
+ log.warn('No workflow nodes found');
+ return { field: null, fieldTemplate: null };
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const foundNode = workflow.workflow.nodes.find((n: any) => n.data.id === fieldIdentifier.nodeId);
+ if (!foundNode) {
+ log.warn({ nodeId: fieldIdentifier.nodeId }, 'Node not found');
+ return { field: null, fieldTemplate: null };
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const foundField = (foundNode.data as any).inputs[fieldIdentifier.fieldName];
+ if (!foundField) {
+ log.warn({ nodeId: fieldIdentifier.nodeId, fieldName: fieldIdentifier.fieldName }, 'Field not found in node');
+ return { field: null, fieldTemplate: null };
+ }
+
+ // Get the field template from the invocation templates
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const nodeType = (foundNode.data as any).type;
+ const template = templates[nodeType];
+ if (!template) {
+ log.warn({ nodeType }, 'No template found for node type');
+ return { field: foundField, fieldTemplate: null };
+ }
+
+ const foundFieldTemplate = template.inputs[fieldIdentifier.fieldName];
+ if (!foundFieldTemplate) {
+ log.warn({ nodeType, fieldName: fieldIdentifier.fieldName }, 'Field template not found');
+ return { field: foundField, fieldTemplate: null };
+ }
+
+ return { field: foundField, fieldTemplate: foundFieldTemplate };
+ }, [workflow, fieldIdentifier, templates]);
+
+ // Get the current value from Redux or fallback to field default
+ const currentValue = useMemo(() => {
+ if (fieldValues && fieldKey in fieldValues) {
+ return fieldValues[fieldKey];
+ }
+
+ return field?.value ?? fieldTemplate?.default ?? '';
+ }, [fieldValues, fieldKey, field, fieldTemplate]);
+
+ // Get field type from the template
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const fieldType = fieldTemplate ? (fieldTemplate as any).type?.name : null;
+
+ const handleChange = useCallback(
+ (value: unknown) => {
+ dispatch(canvasWorkflowIntegrationFieldValueChanged({ fieldName: fieldKey, value }));
+ },
+ [dispatch, fieldKey]
+ );
+
+ const handleStringChange = useCallback(
+ (e: ChangeEvent) => {
+ handleChange(e.target.value);
+ },
+ [handleChange]
+ );
+
+ const handleNumberChange = useCallback(
+ (e: ChangeEvent) => {
+ const val = fieldType === 'IntegerField' ? parseInt(e.target.value, 10) : parseFloat(e.target.value);
+ handleChange(isNaN(val) ? 0 : val);
+ },
+ [handleChange, fieldType]
+ );
+
+ const handleBooleanChange = useCallback(
+ (e: ChangeEvent) => {
+ handleChange(e.target.checked);
+ },
+ [handleChange]
+ );
+
+ const handleSelectChange = useCallback(
+ (e: ChangeEvent) => {
+ handleChange(e.target.value);
+ },
+ [handleChange]
+ );
+
+ // SchedulerField handlers
+ const handleSchedulerChange = useCallback(
+ (v) => {
+ if (!isParameterScheduler(v?.value)) {
+ return;
+ }
+ handleChange(v.value);
+ },
+ [handleChange]
+ );
+
+ const schedulerValue = useMemo(() => SCHEDULER_OPTIONS.find((o) => o.value === currentValue), [currentValue]);
+
+ // BoardField handlers
+ const handleBoardChange = useCallback(
+ (v) => {
+ if (!v) {
+ return;
+ }
+ const value = v.value === 'auto' || v.value === 'none' ? v.value : { board_id: v.value };
+ handleChange(value);
+ },
+ [handleChange]
+ );
+
+ const boardOptions = useMemo(() => {
+ const _options: ComboboxOption[] = [
+ { label: t('common.auto'), value: 'auto' },
+ { label: `${t('common.none')} (${t('boards.uncategorized')})`, value: 'none' },
+ ];
+ if (boardsData) {
+ for (const board of boardsData) {
+ _options.push({
+ label: board.board_name,
+ value: board.board_id,
+ });
+ }
+ }
+ return _options;
+ }, [boardsData, t]);
+
+ const boardValue = useMemo(() => {
+ const _value = currentValue;
+ const autoOption = boardOptions[0];
+ const noneOption = boardOptions[1];
+ if (!_value || _value === 'auto') {
+ return autoOption;
+ }
+ if (_value === 'none') {
+ return noneOption;
+ }
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const boardId = typeof _value === 'object' ? (_value as any).board_id : _value;
+ const boardOption = boardOptions.find((o) => o.value === boardId);
+ return boardOption ?? autoOption;
+ }, [currentValue, boardOptions]);
+
+ const noOptionsMessage = useCallback(() => t('boards.noMatching'), [t]);
+
+ // ModelIdentifierField handlers
+ const handleModelChange = useCallback(
+ (value: AnyModelConfig | null) => {
+ if (!value) {
+ return;
+ }
+ handleChange(value);
+ },
+ [handleChange]
+ );
+
+ const modelConfigs = useMemo(() => {
+ if (!modelsData) {
+ return EMPTY_ARRAY;
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const ui_model_base = fieldTemplate ? (fieldTemplate as any)?.ui_model_base : null;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const ui_model_type = fieldTemplate ? (fieldTemplate as any)?.ui_model_type : null;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const ui_model_variant = fieldTemplate ? (fieldTemplate as any)?.ui_model_variant : null;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const ui_model_format = fieldTemplate ? (fieldTemplate as any)?.ui_model_format : null;
+
+ if (!ui_model_base && !ui_model_type) {
+ return modelConfigsAdapterSelectors.selectAll(modelsData);
+ }
+
+ return modelConfigsAdapterSelectors.selectAll(modelsData).filter((config) => {
+ if (ui_model_base && !ui_model_base.includes(config.base)) {
+ return false;
+ }
+ if (ui_model_type && !ui_model_type.includes(config.type)) {
+ return false;
+ }
+ if (ui_model_variant && 'variant' in config && config.variant && !ui_model_variant.includes(config.variant)) {
+ return false;
+ }
+ if (ui_model_format && !ui_model_format.includes(config.format)) {
+ return false;
+ }
+ return true;
+ });
+ }, [modelsData, fieldTemplate]);
+
+ // ImageField handler
+ const handleImageFieldSelect = useCallback(() => {
+ dispatch(canvasWorkflowIntegrationImageFieldSelected({ fieldKey }));
+ }, [dispatch, fieldKey]);
+
+ if (!field || !fieldTemplate) {
+ log.warn({ fieldIdentifier }, 'Field or template is null - not rendering');
+ return null;
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const label = (field as any)?.label || (fieldTemplate as any)?.title || fieldIdentifier.fieldName;
+
+ // Log the entire field structure to understand its shape
+ log.debug(
+ { fieldType, label, currentValue, fieldStructure: field, fieldTemplateStructure: fieldTemplate },
+ 'Field info'
+ );
+
+ // ImageField - allow user to select which one receives the canvas image
+ if (fieldType === 'ImageField') {
+ return (
+
+ );
+ }
+
+ // Render different input types based on field type
+ if (fieldType === 'StringField') {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const isTextarea = (fieldTemplate as any)?.ui_component === 'textarea';
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const isRequired = (fieldTemplate as any)?.required ?? false;
+
+ if (isTextarea) {
+ return (
+
+ {label}
+
+
+ );
+ }
+
+ return (
+
+ {label}
+
+
+ );
+ }
+
+ if (fieldType === 'IntegerField' || fieldType === 'FloatField') {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const min = (fieldTemplate as any)?.minimum;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const max = (fieldTemplate as any)?.maximum;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const step = fieldType === 'IntegerField' ? 1 : ((fieldTemplate as any)?.multipleOf ?? 0.01);
+
+ return (
+
+ {label}
+
+
+
+
+ );
+ }
+
+ if (fieldType === 'BooleanField') {
+ return (
+
+ {label}
+
+
+ );
+ }
+
+ if (fieldType === 'EnumField') {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const options = (fieldTemplate as any)?.options ?? (fieldTemplate as any)?.ui_choice_labels ?? [];
+ const optionsList = Array.isArray(options) ? options : Object.keys(options);
+
+ return (
+
+ {label}
+
+
+ );
+ }
+
+ if (fieldType === 'SchedulerField') {
+ return (
+
+ {label}
+
+
+ );
+ }
+
+ if (fieldType === 'BoardField') {
+ return (
+
+ {label}
+
+
+ );
+ }
+
+ if (fieldType === 'ModelIdentifierField') {
+ return (
+
+ {label}
+
+
+ );
+ }
+
+ // For other field types, show a read-only message
+ log.warn(`Unsupported field type "${fieldType}" for field "${label}" - showing as read-only`);
+ return (
+
+ {label}
+
+
+ );
+});
+
+WorkflowFieldRenderer.displayName = 'WorkflowFieldRenderer';
+
+// Separate component for ImageField to avoid conditional hooks
+interface ImageFieldComponentProps {
+ label: string;
+ fieldKey: string;
+ currentValue: unknown;
+ selectedImageFieldKey: string | null;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ fieldTemplate: any;
+ handleImageFieldSelect: () => void;
+ handleChange: (value: unknown) => void;
+}
+
+const ImageFieldComponent = memo(
+ ({
+ label,
+ fieldKey,
+ currentValue,
+ selectedImageFieldKey,
+ fieldTemplate,
+ handleImageFieldSelect,
+ handleChange,
+ }: ImageFieldComponentProps) => {
+ const { t } = useTranslation();
+
+ const isSelected = selectedImageFieldKey === fieldKey;
+
+ // Get image from field values (uploaded image) or from workflow field (default/saved image)
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const imageValue = currentValue as any;
+ const imageName = imageValue?.image_name;
+
+ const { currentData: imageDTO } = useGetImageDTOQuery(imageName ?? skipToken);
+
+ const handleImageUpload = useCallback(
+ (uploadedImage: ImageDTO) => {
+ handleChange(uploadedImage);
+ },
+ [handleChange]
+ );
+
+ const handleImageClear = useCallback(() => {
+ handleChange(undefined);
+ }, [handleChange]);
+
+ return (
+
+
+
+
+ {label}
+
+
+
+ {isSelected
+ ? t('controlLayers.workflowIntegration.imageFieldSelected')
+ : t('controlLayers.workflowIntegration.imageFieldNotSelected')}
+
+
+ {/* Show image upload/preview for non-selected fields */}
+ {!isSelected && (
+
+ {!imageDTO && (
+
+ )}
+ {imageDTO && (
+
+
+
+ {`${imageDTO.width}x${imageDTO.height}`}
+
+ }
+ onClick={handleImageClear}
+ size="sm"
+ variant="ghost"
+ colorScheme="error"
+ />
+
+ )}
+
+ )}
+
+ );
+ }
+);
+
+ImageFieldComponent.displayName = 'ImageFieldComponent';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/WorkflowFormPreview.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/WorkflowFormPreview.tsx
new file mode 100644
index 00000000000..be8ee0668e9
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/WorkflowFormPreview.tsx
@@ -0,0 +1,289 @@
+import type { SystemStyleObject } from '@invoke-ai/ui-library';
+import { Box, Flex, Spinner, Text } from '@invoke-ai/ui-library';
+import { useStore } from '@nanostores/react';
+import { logger } from 'app/logging/logger';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { WorkflowFieldRenderer } from 'features/controlLayers/components/CanvasWorkflowIntegration/WorkflowFieldRenderer';
+import {
+ canvasWorkflowIntegrationImageFieldSelected,
+ selectCanvasWorkflowIntegrationFieldValues,
+ selectCanvasWorkflowIntegrationSelectedImageFieldKey,
+ selectCanvasWorkflowIntegrationSelectedWorkflowId,
+} from 'features/controlLayers/store/canvasWorkflowIntegrationSlice';
+import {
+ ContainerContextProvider,
+ DepthContextProvider,
+ useContainerContext,
+ useDepthContext,
+} from 'features/nodes/components/sidePanel/builder/contexts';
+import { DividerElement } from 'features/nodes/components/sidePanel/builder/DividerElement';
+import { HeadingElement } from 'features/nodes/components/sidePanel/builder/HeadingElement';
+import { TextElement } from 'features/nodes/components/sidePanel/builder/TextElement';
+import { $templates } from 'features/nodes/store/nodesSlice';
+import type { FormElement } from 'features/nodes/types/workflow';
+import {
+ CONTAINER_CLASS_NAME,
+ isContainerElement,
+ isDividerElement,
+ isHeadingElement,
+ isNodeFieldElement,
+ isTextElement,
+ ROOT_CONTAINER_CLASS_NAME,
+} from 'features/nodes/types/workflow';
+import { memo, useEffect, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { useGetWorkflowQuery } from 'services/api/endpoints/workflows';
+
+const log = logger('canvas-workflow-integration');
+
+const rootViewModeSx: SystemStyleObject = {
+ borderRadius: 'base',
+ w: 'full',
+ h: 'full',
+ gap: 2,
+ display: 'flex',
+ flex: 1,
+ maxW: '768px',
+ '&[data-self-layout="column"]': {
+ flexDir: 'column',
+ alignItems: 'stretch',
+ },
+ '&[data-self-layout="row"]': {
+ flexDir: 'row',
+ alignItems: 'flex-start',
+ },
+};
+
+const containerViewModeSx: SystemStyleObject = {
+ gap: 2,
+ '&[data-self-layout="column"]': {
+ flexDir: 'column',
+ alignItems: 'stretch',
+ },
+ '&[data-self-layout="row"]': {
+ flexDir: 'row',
+ alignItems: 'flex-start',
+ overflowX: 'auto',
+ overflowY: 'visible',
+ h: 'min-content',
+ flexShrink: 0,
+ },
+ '&[data-parent-layout="column"]': {
+ w: 'full',
+ h: 'min-content',
+ },
+ '&[data-parent-layout="row"]': {
+ flex: '1 1 0',
+ minW: 32,
+ },
+};
+
+export const WorkflowFormPreview = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const selectedWorkflowId = useAppSelector(selectCanvasWorkflowIntegrationSelectedWorkflowId);
+ const selectedImageFieldKey = useAppSelector(selectCanvasWorkflowIntegrationSelectedImageFieldKey);
+ const fieldValues = useAppSelector(selectCanvasWorkflowIntegrationFieldValues);
+ const templates = useStore($templates);
+
+ const { data: workflow, isLoading } = useGetWorkflowQuery(selectedWorkflowId!, {
+ skip: !selectedWorkflowId,
+ });
+
+ const elements = useMemo((): Record => {
+ if (!workflow?.workflow.form) {
+ return {};
+ }
+ const els = workflow.workflow.form.elements as Record;
+ log.debug({ elementCount: Object.keys(els).length, elementIds: Object.keys(els) }, 'Form elements loaded');
+ return els;
+ }, [workflow]);
+
+ const rootElementId = useMemo((): string => {
+ if (!workflow?.workflow.form) {
+ return '';
+ }
+ const rootId = workflow.workflow.form.rootElementId as string;
+ log.debug({ rootElementId: rootId }, 'Root element ID');
+ return rootId;
+ }, [workflow]);
+
+ // Auto-select the image field if there's only one unfilled ImageField
+ useEffect(() => {
+ // Don't auto-select if user already selected one
+ if (selectedImageFieldKey) {
+ return;
+ }
+ if (!workflow?.workflow.nodes || Object.keys(elements).length === 0) {
+ return;
+ }
+
+ const unfilledImageFieldKeys: string[] = [];
+
+ for (const element of Object.values(elements)) {
+ if (!isNodeFieldElement(element)) {
+ continue;
+ }
+
+ const { fieldIdentifier } = element.data;
+ const fieldKey = `${fieldIdentifier.nodeId}.${fieldIdentifier.fieldName}`;
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const node = workflow.workflow.nodes.find((n: any) => n.data?.id === fieldIdentifier.nodeId);
+ if (!node) {
+ continue;
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const nodeType = (node.data as any)?.type;
+ const template = templates[nodeType];
+ if (!template?.inputs) {
+ continue;
+ }
+
+ const fieldTemplate = template.inputs[fieldIdentifier.fieldName];
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const fieldType = (fieldTemplate as any)?.type?.name;
+ if (fieldType !== 'ImageField') {
+ continue;
+ }
+
+ // Check if the field already has a value
+ const hasReduxValue = fieldValues && fieldKey in fieldValues && fieldValues[fieldKey]?.image_name;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const fieldInstance = (node.data as any)?.inputs?.[fieldIdentifier.fieldName];
+ const hasWorkflowValue = fieldInstance?.value?.image_name;
+
+ if (!hasReduxValue && !hasWorkflowValue) {
+ unfilledImageFieldKeys.push(fieldKey);
+ }
+ }
+
+ if (unfilledImageFieldKeys.length === 1) {
+ log.debug({ fieldKey: unfilledImageFieldKeys[0] }, 'Auto-selecting the only unfilled ImageField');
+ dispatch(canvasWorkflowIntegrationImageFieldSelected({ fieldKey: unfilledImageFieldKeys[0]! }));
+ }
+ }, [workflow, elements, templates, selectedImageFieldKey, fieldValues, dispatch]);
+
+ if (isLoading) {
+ return (
+
+
+ {t('controlLayers.workflowIntegration.loadingParameters')}
+
+ );
+ }
+
+ if (!workflow) {
+ return null;
+ }
+
+ // If workflow has no form builder, it should have been filtered out
+ // This is a fallback in case something went wrong
+ if (Object.keys(elements).length === 0 || !rootElementId) {
+ return (
+
+ {t('controlLayers.workflowIntegration.noFormBuilderError')}
+
+ );
+ }
+
+ const rootElement = elements[rootElementId];
+
+ if (!rootElement || !isContainerElement(rootElement)) {
+ return null;
+ }
+
+ const { id, data } = rootElement;
+ const { children, layout } = data;
+
+ return (
+
+
+
+ {children.map((childId) => (
+
+ ))}
+
+
+
+ );
+});
+WorkflowFormPreview.displayName = 'WorkflowFormPreview';
+
+const FormElementComponentPreview = memo(({ id, elements }: { id: string; elements: Record }) => {
+ const el = elements[id];
+
+ if (!el) {
+ log.warn({ id }, 'Element not found in elements map');
+ return null;
+ }
+
+ log.debug({ id, type: el.type }, 'Rendering form element');
+
+ if (isContainerElement(el)) {
+ return ;
+ }
+
+ if (isDividerElement(el)) {
+ return ;
+ }
+
+ if (isHeadingElement(el)) {
+ return ;
+ }
+
+ if (isTextElement(el)) {
+ return ;
+ }
+
+ if (isNodeFieldElement(el)) {
+ return ;
+ }
+
+ // If we get here, it's an unknown element type
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ log.warn({ id, type: (el as any).type }, 'Unknown element type - not rendering');
+ return null;
+});
+FormElementComponentPreview.displayName = 'FormElementComponentPreview';
+
+const ContainerElementPreview = memo(({ el, elements }: { el: FormElement; elements: Record }) => {
+ const { t } = useTranslation();
+ const depth = useDepthContext();
+ const containerCtx = useContainerContext();
+
+ if (!isContainerElement(el)) {
+ return null;
+ }
+
+ const { id, data } = el;
+ const { children, layout } = data;
+
+ return (
+
+
+
+ {children.map((childId) => (
+
+ ))}
+ {children.length === 0 && (
+
+
+ {t('workflows.builder.emptyContainer')}
+
+
+ )}
+
+
+
+ );
+});
+ContainerElementPreview.displayName = 'ContainerElementPreview';
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/useCanvasWorkflowIntegrationExecute.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/useCanvasWorkflowIntegrationExecute.tsx
new file mode 100644
index 00000000000..42b7d06d64b
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/useCanvasWorkflowIntegrationExecute.tsx
@@ -0,0 +1,302 @@
+import { logger } from 'app/logging/logger';
+import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
+import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
+import { selectCanvasSessionId } from 'features/controlLayers/store/canvasStagingAreaSlice';
+import {
+ canvasWorkflowIntegrationClosed,
+ canvasWorkflowIntegrationProcessingCompleted,
+ canvasWorkflowIntegrationProcessingStarted,
+ selectCanvasWorkflowIntegrationFieldValues,
+ selectCanvasWorkflowIntegrationSelectedImageFieldKey,
+ selectCanvasWorkflowIntegrationSelectedWorkflowId,
+ selectCanvasWorkflowIntegrationSourceEntityIdentifier,
+} from 'features/controlLayers/store/canvasWorkflowIntegrationSlice';
+import { CANVAS_OUTPUT_PREFIX, getPrefixedId } from 'features/nodes/util/graph/graphBuilderUtils';
+import { toast } from 'features/toast/toast';
+import { useCallback, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { queueApi } from 'services/api/endpoints/queue';
+import { useLazyGetWorkflowQuery } from 'services/api/endpoints/workflows';
+
+const log = logger('canvas-workflow-integration');
+
+export const useCanvasWorkflowIntegrationExecute = () => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const canvasManager = useCanvasManager();
+
+ const selectedWorkflowId = useAppSelector(selectCanvasWorkflowIntegrationSelectedWorkflowId);
+ const sourceEntityIdentifier = useAppSelector(selectCanvasWorkflowIntegrationSourceEntityIdentifier);
+ const fieldValues = useAppSelector(selectCanvasWorkflowIntegrationFieldValues);
+ const selectedImageFieldKey = useAppSelector(selectCanvasWorkflowIntegrationSelectedImageFieldKey);
+ const canvasSessionId = useAppSelector(selectCanvasSessionId);
+
+ const [getWorkflow] = useLazyGetWorkflowQuery();
+
+ const canExecute = useMemo(() => {
+ return Boolean(selectedWorkflowId && sourceEntityIdentifier);
+ }, [selectedWorkflowId, sourceEntityIdentifier]);
+
+ const execute = useCallback(async () => {
+ if (!selectedWorkflowId || !sourceEntityIdentifier || !canvasManager) {
+ return;
+ }
+
+ try {
+ dispatch(canvasWorkflowIntegrationProcessingStarted());
+
+ // 1. Extract the canvas layer as an image
+ const adapter = canvasManager.getAdapter(sourceEntityIdentifier);
+ if (!adapter) {
+ throw new Error('Could not find canvas entity adapter');
+ }
+
+ const rect = adapter.transformer.getRelativeRect();
+ const imageDTO = await adapter.renderer.rasterize({ rect, attrs: { filters: [], opacity: 1 } });
+
+ // 2. Fetch the workflow
+ const { data: workflow } = await getWorkflow(selectedWorkflowId);
+ if (!workflow) {
+ throw new Error('Failed to load workflow');
+ }
+
+ // 3. Build the workflow graph with the canvas image
+ // Use the user-selected ImageField, or find one automatically
+ let imageFieldIdentifier: { nodeId: string; fieldName: string } | undefined;
+
+ // Method 1: Use user-selected ImageField (preferred)
+ if (selectedImageFieldKey) {
+ const [nodeId, fieldName] = selectedImageFieldKey.split('.');
+ if (nodeId && fieldName) {
+ imageFieldIdentifier = { nodeId, fieldName };
+ }
+ }
+
+ // Method 2: Search through form elements for an ImageField (fallback)
+ if (!imageFieldIdentifier && workflow.workflow.form && workflow.workflow.form.elements) {
+ for (const element of Object.values(workflow.workflow.form.elements)) {
+ if (element.type !== 'node-field') {
+ continue;
+ }
+
+ const fieldIdentifier = element.data?.fieldIdentifier;
+ if (!fieldIdentifier) {
+ continue;
+ }
+
+ // @ts-expect-error - node data type is complex
+ const node = workflow.workflow.nodes.find((n) => n.data.id === fieldIdentifier.nodeId);
+ if (!node) {
+ continue;
+ }
+
+ // @ts-expect-error - node.data type is complex
+ if (node.data.type === 'image') {
+ imageFieldIdentifier = fieldIdentifier;
+ break;
+ }
+
+ // Check if field type is ImageField
+ // @ts-expect-error - field type is complex
+ const field = node.data.inputs[fieldIdentifier.fieldName];
+ if (field?.type?.name === 'ImageField') {
+ imageFieldIdentifier = fieldIdentifier;
+ break;
+ }
+ }
+ }
+
+ // Method 3: Fallback to exposedFields
+ if (!imageFieldIdentifier && workflow.workflow.exposedFields) {
+ imageFieldIdentifier = workflow.workflow.exposedFields.find((fieldIdentifier) => {
+ // @ts-expect-error - node data type is complex
+ const node = workflow.workflow.nodes.find((n) => n.data.id === fieldIdentifier.nodeId);
+ if (!node) {
+ return false;
+ }
+
+ // @ts-expect-error - node.data type is complex
+ if (node.data.type === 'image') {
+ return true;
+ }
+
+ // Check if field type is ImageField
+ // @ts-expect-error - field type is complex
+ const field = node.data.inputs[fieldIdentifier.fieldName];
+ return field?.type?.name === 'ImageField';
+ });
+ }
+
+ if (!imageFieldIdentifier) {
+ throw new Error('Workflow does not have an image input field in the Form Builder');
+ }
+
+ // Update the workflow nodes with canvas image and user field values
+ const updatedWorkflow = {
+ ...workflow.workflow,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ nodes: workflow.workflow.nodes.map((node: any) => {
+ const nodeId = node.data.id;
+ let updatedInputs = { ...node.data.inputs };
+ const updatedData = { ...node.data };
+ let hasChanges = false;
+
+ // Apply image field if this is the image node
+ if (nodeId === imageFieldIdentifier.nodeId) {
+ updatedInputs[imageFieldIdentifier.fieldName] = {
+ ...updatedInputs[imageFieldIdentifier.fieldName],
+ value: imageDTO,
+ };
+ hasChanges = true;
+ }
+
+ // Apply any field values from Redux state
+ if (fieldValues) {
+ Object.entries(fieldValues).forEach(([fieldKey, value]) => {
+ const [fieldNodeId, fieldName] = fieldKey.split('.');
+ if (fieldNodeId && fieldName && fieldNodeId === nodeId && updatedInputs[fieldName]) {
+ updatedInputs[fieldName] = {
+ ...updatedInputs[fieldName],
+ value: value,
+ };
+ hasChanges = true;
+ }
+ });
+ }
+
+ // If anything was modified, return updated node
+ if (hasChanges) {
+ updatedData.inputs = updatedInputs;
+ return {
+ ...node,
+ data: updatedData,
+ };
+ }
+
+ return node;
+ }),
+ };
+
+ // Validate that the workflow has a canvas_output node
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const hasCanvasOutputNode = updatedWorkflow.nodes.some((node: any) => node.data?.type === 'canvas_output');
+ if (!hasCanvasOutputNode) {
+ throw new Error('Workflow does not have a Canvas Output node');
+ }
+
+ // 4. Convert workflow to graph format
+ const graphNodes: Record = {};
+ const nodeIdMapping: Record = {}; // Map original IDs to new IDs
+
+ for (const node of updatedWorkflow.nodes) {
+ const nodeData = node.data;
+ const isCanvasOutputNode = nodeData.type === 'canvas_output';
+
+ // Prefix canvas_output node IDs so the staging area can find them
+ const nodeId = isCanvasOutputNode ? getPrefixedId(CANVAS_OUTPUT_PREFIX) : nodeData.id;
+ nodeIdMapping[nodeData.id] = nodeId;
+
+ const invocation: Record = {
+ id: nodeId,
+ type: nodeData.type,
+ // Canvas output nodes are always intermediate (they go to the staging area, not gallery)
+ is_intermediate: isCanvasOutputNode ? true : (nodeData.isIntermediate ?? false),
+ use_cache: nodeData.useCache ?? true,
+ };
+
+ // Add input values to the invocation
+ for (const [fieldName, fieldData] of Object.entries(nodeData.inputs)) {
+ const fieldValue = (fieldData as { value?: unknown }).value;
+ if (fieldValue === undefined) {
+ continue;
+ }
+
+ // The frontend stores board fields as 'auto', 'none', or { board_id: string }.
+ // The backend expects null or { board_id: string }. Translate accordingly.
+ if (fieldName === 'board') {
+ if (fieldValue === 'auto' || fieldValue === 'none' || fieldValue === null) {
+ continue;
+ }
+ }
+
+ invocation[fieldName] = fieldValue;
+ }
+
+ graphNodes[nodeId] = invocation;
+ }
+
+ // Convert edges to graph format, using the node ID mapping
+ const edgesArray = updatedWorkflow.edges as Array<{
+ source: string;
+ target: string;
+ sourceHandle: string;
+ targetHandle: string;
+ }>;
+ const graphEdges = edgesArray.map((edge) => ({
+ source: {
+ node_id: nodeIdMapping[edge.source] || edge.source,
+ field: edge.sourceHandle,
+ },
+ destination: {
+ node_id: nodeIdMapping[edge.target] || edge.target,
+ field: edge.targetHandle,
+ },
+ }));
+
+ const graph = {
+ id: workflow.workflow.id || workflow.workflow_id || 'temp',
+ nodes: graphNodes,
+ edges: graphEdges,
+ };
+
+ log.debug({ workflowName: workflow.name, destination: canvasSessionId }, 'Enqueueing workflow on canvas');
+
+ await dispatch(
+ queueApi.endpoints.enqueueBatch.initiate({
+ batch: {
+ workflow: updatedWorkflow,
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ graph: graph as any,
+ runs: 1,
+ origin: 'canvas_workflow_integration',
+ destination: canvasSessionId,
+ },
+ prepend: false,
+ })
+ ).unwrap();
+
+ // 5. Close the modal and show success message
+ // Results will appear in the staging area where user can accept/discard them
+ toast({
+ status: 'success',
+ title: t('controlLayers.workflowIntegration.executionStarted'),
+ description: t('controlLayers.workflowIntegration.executionStartedDescription'),
+ });
+
+ dispatch(canvasWorkflowIntegrationClosed());
+ } catch (error) {
+ log.error('Error executing workflow');
+ dispatch(canvasWorkflowIntegrationProcessingCompleted());
+ toast({
+ status: 'error',
+ title: t('controlLayers.workflowIntegration.executionFailed'),
+ description: error instanceof Error ? error.message : 'Unknown error',
+ });
+ }
+ }, [
+ selectedWorkflowId,
+ sourceEntityIdentifier,
+ canvasManager,
+ dispatch,
+ getWorkflow,
+ t,
+ fieldValues,
+ selectedImageFieldKey,
+ canvasSessionId,
+ ]);
+
+ return {
+ execute,
+ canExecute,
+ };
+};
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/useFilteredWorkflows.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/useFilteredWorkflows.tsx
new file mode 100644
index 00000000000..f28b4a4ee5a
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/useFilteredWorkflows.tsx
@@ -0,0 +1,107 @@
+import { logger } from 'app/logging/logger';
+import { useAppDispatch } from 'app/store/storeHooks';
+import { useCallback, useEffect, useState } from 'react';
+import { workflowsApi } from 'services/api/endpoints/workflows';
+import type { paths } from 'services/api/schema';
+
+import { workflowHasImageField } from './workflowHasImageField';
+
+const log = logger('canvas-workflow-integration');
+
+type WorkflowListItem =
+ paths['/api/v1/workflows/']['get']['responses']['200']['content']['application/json']['items'][number];
+
+interface UseFilteredWorkflowsResult {
+ filteredWorkflows: WorkflowListItem[];
+ isFiltering: boolean;
+}
+
+/**
+ * Hook that filters workflows to only include those with at least one ImageField
+ * @param workflows The list of workflows to filter
+ * @returns Filtered list of workflows that have ImageFields
+ */
+export function useFilteredWorkflows(workflows: WorkflowListItem[]): UseFilteredWorkflowsResult {
+ const dispatch = useAppDispatch();
+ const [filteredWorkflows, setFilteredWorkflows] = useState([]);
+ const [isFiltering, setIsFiltering] = useState(false);
+
+ const filterWorkflows = useCallback(async () => {
+ if (workflows.length === 0) {
+ setFilteredWorkflows([]);
+ return;
+ }
+
+ setIsFiltering(true);
+
+ try {
+ // Load all workflows in parallel and check for ImageFields
+ const workflowChecks = await Promise.all(
+ workflows.map(async (workflow) => {
+ try {
+ // Fetch the full workflow data using dispatch
+ const result = await dispatch(
+ workflowsApi.endpoints.getWorkflow.initiate(workflow.workflow_id, {
+ subscribe: false,
+ forceRefetch: false,
+ })
+ );
+
+ // Get the data from the result
+ const data = 'data' in result ? result.data : undefined;
+
+ const hasImageField = workflowHasImageField(data);
+
+ log.debug(
+ { workflowId: workflow.workflow_id, name: workflow.name, hasImageField },
+ 'Checked workflow for ImageField'
+ );
+
+ // Clean up the subscription
+ if ('unsubscribe' in result && typeof result.unsubscribe === 'function') {
+ result.unsubscribe();
+ }
+
+ return {
+ workflow,
+ hasImageField,
+ };
+ } catch (error) {
+ log.error(
+ {
+ error: error instanceof Error ? error.message : String(error),
+ workflowId: workflow.workflow_id,
+ },
+ 'Error checking workflow for ImageField'
+ );
+ return {
+ workflow,
+ hasImageField: false,
+ };
+ }
+ })
+ );
+
+ // Filter to only include workflows with ImageFields
+ const filtered = workflowChecks.filter((check) => check.hasImageField).map((check) => check.workflow);
+
+ log.debug({ totalWorkflows: workflows.length, filteredCount: filtered.length }, 'Filtered workflows');
+
+ setFilteredWorkflows(filtered);
+ } catch (error) {
+ log.error({ error: error instanceof Error ? error.message : String(error) }, 'Error filtering workflows');
+ setFilteredWorkflows([]);
+ } finally {
+ setIsFiltering(false);
+ }
+ }, [workflows, dispatch]);
+
+ useEffect(() => {
+ filterWorkflows();
+ }, [filterWorkflows]);
+
+ return {
+ filteredWorkflows,
+ isFiltering,
+ };
+}
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/workflowHasImageField.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/workflowHasImageField.tsx
new file mode 100644
index 00000000000..fc535f7b192
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/workflowHasImageField.tsx
@@ -0,0 +1,86 @@
+import { logger } from 'app/logging/logger';
+import { $templates } from 'features/nodes/store/nodesSlice';
+import { isNodeFieldElement } from 'features/nodes/types/workflow';
+import type { paths } from 'services/api/schema';
+
+const log = logger('canvas-workflow-integration');
+
+type WorkflowResponse =
+ paths['/api/v1/workflows/i/{workflow_id}']['get']['responses']['200']['content']['application/json'];
+
+/**
+ * Checks if a workflow is compatible with canvas workflow integration.
+ * Requirements:
+ * 1. Has a Form Builder (allows users to modify parameters)
+ * 2. Has a canvas_output node (explicit canvas output target)
+ * 3. Has at least one ImageField in the Form Builder (receives the canvas image)
+ * @param workflow The workflow to check
+ * @returns true if the workflow meets all requirements, false otherwise
+ */
+export function workflowHasImageField(workflow: WorkflowResponse | undefined): boolean {
+ if (!workflow?.workflow) {
+ log.debug('No workflow data provided');
+ return false;
+ }
+
+ // Only workflows with Form Builder are supported
+ // Workflows without Form Builder don't allow changing models and other parameters
+ if (!workflow.workflow.form?.elements) {
+ log.debug('Workflow has no form builder - excluding from list');
+ return false;
+ }
+
+ // Must have a canvas_output node to define where the output image goes
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const hasCanvasOutputNode = workflow.workflow.nodes?.some((n: any) => (n.data as any)?.type === 'canvas_output');
+ if (!hasCanvasOutputNode) {
+ log.debug('Workflow has no canvas_output node - excluding from list');
+ return false;
+ }
+
+ const templates = $templates.get();
+ const elements = workflow.workflow.form.elements;
+
+ log.debug('Workflow has form builder and canvas_output node, checking form elements for ImageField');
+
+ for (const [elementId, element] of Object.entries(elements)) {
+ if (isNodeFieldElement(element)) {
+ const { fieldIdentifier } = element.data;
+
+ // Find the node that contains this field
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const node = workflow.workflow.nodes?.find((n: any) => n.data?.id === fieldIdentifier.nodeId);
+ if (!node) {
+ continue;
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const nodeType = (node.data as any)?.type;
+ if (!nodeType) {
+ continue;
+ }
+
+ const template = templates[nodeType];
+ if (!template?.inputs) {
+ continue;
+ }
+
+ const fieldTemplate = template.inputs[fieldIdentifier.fieldName];
+ if (!fieldTemplate) {
+ continue;
+ }
+
+ // Check if this is an ImageField
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const fieldType = (fieldTemplate as any).type?.name;
+ if (fieldType === 'ImageField') {
+ log.debug({ elementId, fieldName: fieldIdentifier.fieldName }, 'Found ImageField in workflow form');
+ return true;
+ }
+ }
+ }
+
+ // If we have a form but no ImageFields were found in it, return false
+ log.debug('Workflow has form builder but no ImageField found in form elements');
+ return false;
+}
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItems.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItems.tsx
index fd3f56a03ad..3c8081d9214 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItems.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/RasterLayer/RasterLayerMenuItems.tsx
@@ -6,6 +6,7 @@ import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/c
import { CanvasEntityMenuItemsDuplicate } from 'features/controlLayers/components/common/CanvasEntityMenuItemsDuplicate';
import { CanvasEntityMenuItemsFilter } from 'features/controlLayers/components/common/CanvasEntityMenuItemsFilter';
import { CanvasEntityMenuItemsMergeDown } from 'features/controlLayers/components/common/CanvasEntityMenuItemsMergeDown';
+import { CanvasEntityMenuItemsRunWorkflow } from 'features/controlLayers/components/common/CanvasEntityMenuItemsRunWorkflow';
import { CanvasEntityMenuItemsSave } from 'features/controlLayers/components/common/CanvasEntityMenuItemsSave';
import { CanvasEntityMenuItemsSelectObject } from 'features/controlLayers/components/common/CanvasEntityMenuItemsSelectObject';
import { CanvasEntityMenuItemsTransform } from 'features/controlLayers/components/common/CanvasEntityMenuItemsTransform';
@@ -25,6 +26,7 @@ export const RasterLayerMenuItems = memo(() => {
+
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/QueueItemPreviewMini.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/QueueItemPreviewMini.tsx
index 0d874605923..ffaa5814928 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/QueueItemPreviewMini.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/QueueItemPreviewMini.tsx
@@ -13,10 +13,10 @@ import {
import { DndImage } from 'features/dnd/DndImage';
import { toast } from 'features/toast/toast';
import { memo, useCallback, useMemo } from 'react';
-import type { S } from 'services/api/types';
-import { useOutputImageDTO, useStagingAreaContext } from './context';
+import { useStagingAreaContext } from './context';
import { QueueItemNumber } from './QueueItemNumber';
+import type { StagingEntry } from './state';
const sx = {
cursor: 'pointer',
@@ -37,21 +37,23 @@ const sx = {
} satisfies SystemStyleObject;
type Props = {
- item: S['SessionQueueItem'];
+ entry: StagingEntry;
index: number;
};
-export const QueueItemPreviewMini = memo(({ item, index }: Props) => {
+export const QueueItemPreviewMini = memo(({ entry, index }: Props) => {
const ctx = useStagingAreaContext();
const dispatch = useAppDispatch();
- const $isSelected = useMemo(() => ctx.buildIsSelectedComputed(item.item_id), [ctx, item.item_id]);
+ const $isSelected = useMemo(
+ () => ctx.buildIsSelectedComputed(entry.item.item_id, entry.imageIndex),
+ [ctx, entry.item.item_id, entry.imageIndex]
+ );
const isSelected = useStore($isSelected);
- const imageDTO = useOutputImageDTO(item.item_id);
const autoSwitch = useAppSelector(selectStagingAreaAutoSwitch);
const onClick = useCallback(() => {
- ctx.select(item.item_id);
- }, [ctx, item.item_id]);
+ ctx.select(entry.item.item_id, entry.imageIndex);
+ }, [ctx, entry.item.item_id, entry.imageIndex]);
const onDoubleClick = useCallback(() => {
if (autoSwitch !== 'off') {
@@ -63,8 +65,8 @@ export const QueueItemPreviewMini = memo(({ item, index }: Props) => {
}, [autoSwitch, dispatch]);
const onLoad = useCallback(() => {
- ctx.onImageLoaded(item.item_id);
- }, [ctx, item.item_id]);
+ ctx.onImageLoaded(entry.item.item_id);
+ }, [ctx, entry.item.item_id]);
return (
{
onClick={onClick}
onDoubleClick={onDoubleClick}
>
-
- {imageDTO && }
-
+
+ {entry.imageDTO && }
+
-
+
);
});
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaItemsList.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaItemsList.tsx
index b1507b9b489..b30f646be4f 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaItemsList.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/StagingAreaItemsList.tsx
@@ -8,10 +8,10 @@ import type { CSSProperties, RefObject } from 'react';
import { memo, useCallback, useEffect, useRef, useState } from 'react';
import type { Components, ComputeItemKey, ItemContent, ListRange, VirtuosoHandle, VirtuosoProps } from 'react-virtuoso';
import { Virtuoso } from 'react-virtuoso';
-import type { S } from 'services/api/types';
import { useStagingAreaContext } from './context';
import { getQueueItemElementId, STAGING_AREA_THUMBNAIL_STRIP_HEIGHT } from './shared';
+import type { StagingEntry } from './state';
const log = logger('system');
@@ -141,7 +141,7 @@ export const StagingAreaItemsList = memo(() => {
const rangeRef = useRef({ startIndex: 0, endIndex: 0 });
const rootRef = useRef(null);
- const items = useStore(ctx.$items);
+ const entries = useStore(ctx.$entries);
const scrollerRef = useScrollableStagingArea(rootRef);
@@ -172,9 +172,9 @@ export const StagingAreaItemsList = memo(() => {
return (
-
+
ref={virtuosoRef}
- data={items}
+ data={entries}
horizontalDirection
style={virtuosoStyles}
computeItemKey={computeItemKey}
@@ -183,19 +183,19 @@ export const StagingAreaItemsList = memo(() => {
components={components}
rangeChanged={onRangeChanged}
// Virtuoso expects the ref to be of HTMLElement | null | Window, but overlayscrollbars doesn't allow Window
- scrollerRef={scrollerRef as VirtuosoProps['scrollerRef']}
+ scrollerRef={scrollerRef as VirtuosoProps['scrollerRef']}
/>
);
});
StagingAreaItemsList.displayName = 'StagingAreaItemsList';
-const computeItemKey: ComputeItemKey = (_, item: S['SessionQueueItem']) => {
- return item.item_id;
+const computeItemKey: ComputeItemKey = (_, entry: StagingEntry) => {
+ return `${entry.item.item_id}-${entry.imageIndex}`;
};
-const itemContent: ItemContent = (index, item) => (
-
+const itemContent: ItemContent = (index, entry) => (
+
);
const listSx = {
@@ -207,7 +207,7 @@ const listSx = {
},
};
-const components: Components = {
+const components: Components = {
List: forwardRef(({ context: _, ...rest }, ref) => {
const canvasManager = useCanvasManager();
const shouldShowStagedImage = useStore(canvasManager.stagingArea.$shouldShowStagedImage);
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/context.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/context.tsx
index 4b5e1c438c6..7dfb7f45c3c 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/context.tsx
+++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/context.tsx
@@ -75,14 +75,15 @@ export const StagingAreaContextProvider = memo(({ children, sessionId }: PropsWi
onAccept: (item, imageDTO) => {
const bboxRect = selectBboxRect(store.getState());
const { x, y } = bboxRect;
- const imageObject = imageDTOToImageObject(imageDTO);
const selectedEntityIdentifier = selectSelectedEntityIdentifier(store.getState());
+
+ const imageObject = imageDTOToImageObject(imageDTO);
const overrides: Partial = {
position: { x, y },
objects: [imageObject],
};
-
store.dispatch(rasterLayerAdded({ overrides, isSelected: selectedEntityIdentifier?.type === 'raster_layer' }));
+
store.dispatch(canvasSessionReset());
store.dispatch(
queueApi.endpoints.cancelQueueItemsByDestination.initiate({ destination: sessionId }, { track: false })
@@ -122,12 +123,6 @@ export const useStagingAreaContext = () => {
return ctx;
};
-export const useOutputImageDTO = (itemId: number) => {
- const ctx = useStagingAreaContext();
- const allProgressData = useStore(ctx.$progressData, { keys: [itemId] });
- return allProgressData[itemId]?.imageDTO ?? null;
-};
-
export const useProgressDatum = (itemId: number): ProgressData => {
const ctx = useStagingAreaContext();
const allProgressData = useStore(ctx.$progressData, { keys: [itemId] });
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/shared.test.ts b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/shared.test.ts
index f16b9023164..af35d519b22 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/shared.test.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/shared.test.ts
@@ -1,7 +1,7 @@
import type { S } from 'services/api/types';
import { describe, expect, it } from 'vitest';
-import { getOutputImageName, getProgressMessage, getQueueItemElementId } from './shared';
+import { getOutputImageNames, getProgressMessage, getQueueItemElementId } from './shared';
describe('StagingAreaApi Utility Functions', () => {
describe('getProgressMessage', () => {
@@ -34,7 +34,7 @@ describe('StagingAreaApi Utility Functions', () => {
});
});
- describe('getOutputImageName', () => {
+ describe('getOutputImageNames', () => {
it('should extract image name from completed queue item', () => {
const queueItem: S['SessionQueueItem'] = {
item_id: 1,
@@ -61,10 +61,10 @@ describe('StagingAreaApi Utility Functions', () => {
},
} as unknown as S['SessionQueueItem'];
- expect(getOutputImageName(queueItem)).toBe('test-output.png');
+ expect(getOutputImageNames(queueItem)).toEqual(['test-output.png']);
});
- it('should return null when no canvas output node found', () => {
+ it('should return empty array when no canvas output node found', () => {
const queueItem = {
item_id: 1,
status: 'completed',
@@ -93,10 +93,10 @@ describe('StagingAreaApi Utility Functions', () => {
},
} as unknown as S['SessionQueueItem'];
- expect(getOutputImageName(queueItem)).toBe(null);
+ expect(getOutputImageNames(queueItem)).toEqual([]);
});
- it('should return null when output node has no results', () => {
+ it('should return empty array when output node has no results', () => {
const queueItem: S['SessionQueueItem'] = {
item_id: 1,
status: 'completed',
@@ -116,10 +116,10 @@ describe('StagingAreaApi Utility Functions', () => {
},
} as unknown as S['SessionQueueItem'];
- expect(getOutputImageName(queueItem)).toBe(null);
+ expect(getOutputImageNames(queueItem)).toEqual([]);
});
- it('should return null when results contain no image fields', () => {
+ it('should return empty array when results contain no image fields', () => {
const queueItem: S['SessionQueueItem'] = {
item_id: 1,
status: 'completed',
@@ -144,10 +144,10 @@ describe('StagingAreaApi Utility Functions', () => {
},
} as unknown as S['SessionQueueItem'];
- expect(getOutputImageName(queueItem)).toBe(null);
+ expect(getOutputImageNames(queueItem)).toEqual([]);
});
- it('should handle multiple outputs and return first image', () => {
+ it('should collect images from multiple canvas_output nodes', () => {
const queueItem: S['SessionQueueItem'] = {
item_id: 1,
status: 'completed',
@@ -161,15 +161,17 @@ describe('StagingAreaApi Utility Functions', () => {
session: {
id: 'test-session',
source_prepared_mapping: {
- canvas_output: ['output-node-id'],
+ 'canvas_output:abc123': ['output-node-1'],
+ 'canvas_output:def456': ['output-node-2'],
},
results: {
- 'output-node-id': {
- text: 'some text',
- first_image: {
+ 'output-node-1': {
+ image: {
image_name: 'first-image.png',
},
- second_image: {
+ },
+ 'output-node-2': {
+ image: {
image_name: 'second-image.png',
},
},
@@ -177,8 +179,8 @@ describe('StagingAreaApi Utility Functions', () => {
},
} as unknown as S['SessionQueueItem'];
- const result = getOutputImageName(queueItem);
- expect(result).toBe('first-image.png');
+ const result = getOutputImageNames(queueItem);
+ expect(result).toEqual(['first-image.png', 'second-image.png']);
});
it('should handle empty session mapping', () => {
@@ -199,7 +201,7 @@ describe('StagingAreaApi Utility Functions', () => {
},
} as unknown as S['SessionQueueItem'];
- expect(getOutputImageName(queueItem)).toBe(null);
+ expect(getOutputImageNames(queueItem)).toEqual([]);
});
});
});
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/shared.ts b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/shared.ts
index fd294e2dcbf..51702d60c9b 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/shared.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/shared.ts
@@ -16,21 +16,24 @@ export const DROP_SHADOW = 'drop-shadow(0px 0px 4px rgb(0, 0, 0)) drop-shadow(0p
export const getQueueItemElementId = (index: number) => `queue-item-preview-${index}`;
export const STAGING_AREA_THUMBNAIL_STRIP_HEIGHT = '72px';
-export const getOutputImageName = (item: S['SessionQueueItem']) => {
- const nodeId = Object.entries(item.session.source_prepared_mapping).find(([nodeId]) =>
- isCanvasOutputNodeId(nodeId)
- )?.[1][0];
- const output = nodeId ? item.session.results[nodeId] : undefined;
+export const getOutputImageNames = (item: S['SessionQueueItem']): string[] => {
+ const imageNames: string[] = [];
- if (!output) {
- return null;
- }
-
- for (const [_name, value] of objectEntries(output)) {
- if (isImageField(value)) {
- return value.image_name;
+ for (const [sourceNodeId, preparedNodeIds] of Object.entries(item.session.source_prepared_mapping)) {
+ if (!isCanvasOutputNodeId(sourceNodeId)) {
+ continue;
+ }
+ const nodeId = preparedNodeIds[0];
+ const output = nodeId ? item.session.results[nodeId] : undefined;
+ if (!output) {
+ continue;
+ }
+ for (const [_name, value] of objectEntries(output)) {
+ if (isImageField(value)) {
+ imageNames.push(value.image_name);
+ }
}
}
- return null;
+ return imageNames;
};
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/state.test.ts b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/state.test.ts
index b248c65bf71..1f9687e35aa 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/state.test.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/state.test.ts
@@ -41,18 +41,34 @@ describe('StagingAreaApi', () => {
expect(api.$items.get()).toEqual([]);
expect(api.$progressData.get()).toEqual({});
expect(api.$selectedItemId.get()).toBe(null);
+ expect(api.$selectedImageIndex.get()).toBe(0);
});
});
describe('Computed Values', () => {
- it('should compute item count correctly', () => {
+ it('should compute item count as entry count for single-output items', () => {
expect(api.$itemCount.get()).toBe(0);
const items = [createMockQueueItem({ item_id: 1 })];
api.$items.set(items);
+ // Item with no imageDTOs produces 1 entry
expect(api.$itemCount.get()).toBe(1);
});
+ it('should compute item count as entry count for multi-output items', () => {
+ const items = [createMockQueueItem({ item_id: 1 })];
+ api.$items.set(items);
+ api.$progressData.setKey(1, {
+ itemId: 1,
+ progressEvent: null,
+ progressImage: null,
+ imageDTOs: [createMockImageDTO({ image_name: 'a.png' }), createMockImageDTO({ image_name: 'b.png' })],
+ imageLoaded: false,
+ });
+ // 2 imageDTOs = 2 entries
+ expect(api.$itemCount.get()).toBe(2);
+ });
+
it('should compute hasItems correctly', () => {
expect(api.$hasItems.get()).toBe(false);
@@ -95,7 +111,7 @@ describe('StagingAreaApi', () => {
itemId: 1,
progressEvent: null,
progressImage: null,
- imageDTO,
+ imageDTOs: [imageDTO],
imageLoaded: false,
});
@@ -111,6 +127,95 @@ describe('StagingAreaApi', () => {
});
});
+ describe('Entries Computed', () => {
+ it('should produce one entry per item when each has 0 or 1 imageDTOs', () => {
+ const items = [createMockQueueItem({ item_id: 1 }), createMockQueueItem({ item_id: 2 })];
+ api.$items.set(items);
+
+ const entries = api.$entries.get();
+ expect(entries).toHaveLength(2);
+ expect(entries[0]?.item.item_id).toBe(1);
+ expect(entries[0]?.imageIndex).toBe(0);
+ expect(entries[0]?.imageDTO).toBe(null);
+ expect(entries[1]?.item.item_id).toBe(2);
+ expect(entries[1]?.imageIndex).toBe(0);
+ });
+
+ it('should produce one entry per item when each has exactly 1 imageDTO', () => {
+ const imageDTO = createMockImageDTO({ image_name: 'img.png' });
+ const items = [createMockQueueItem({ item_id: 1 })];
+ api.$items.set(items);
+ api.$progressData.setKey(1, {
+ itemId: 1,
+ progressEvent: null,
+ progressImage: null,
+ imageDTOs: [imageDTO],
+ imageLoaded: false,
+ });
+
+ const entries = api.$entries.get();
+ expect(entries).toHaveLength(1);
+ expect(entries[0]?.imageDTO).toBe(imageDTO);
+ expect(entries[0]?.imageIndex).toBe(0);
+ });
+
+ it('should produce multiple entries for items with multiple imageDTOs', () => {
+ const img1 = createMockImageDTO({ image_name: 'a.png' });
+ const img2 = createMockImageDTO({ image_name: 'b.png' });
+ const items = [createMockQueueItem({ item_id: 1 })];
+ api.$items.set(items);
+ api.$progressData.setKey(1, {
+ itemId: 1,
+ progressEvent: null,
+ progressImage: null,
+ imageDTOs: [img1, img2],
+ imageLoaded: false,
+ });
+
+ const entries = api.$entries.get();
+ expect(entries).toHaveLength(2);
+ expect(entries[0]?.item.item_id).toBe(1);
+ expect(entries[0]?.imageDTO).toBe(img1);
+ expect(entries[0]?.imageIndex).toBe(0);
+ expect(entries[1]?.item.item_id).toBe(1);
+ expect(entries[1]?.imageDTO).toBe(img2);
+ expect(entries[1]?.imageIndex).toBe(1);
+ });
+
+ it('should interleave entries from multiple items', () => {
+ const img1a = createMockImageDTO({ image_name: 'a1.png' });
+ const img1b = createMockImageDTO({ image_name: 'a2.png' });
+ const img2 = createMockImageDTO({ image_name: 'b1.png' });
+ const items = [createMockQueueItem({ item_id: 1 }), createMockQueueItem({ item_id: 2 })];
+ api.$items.set(items);
+ api.$progressData.setKey(1, {
+ itemId: 1,
+ progressEvent: null,
+ progressImage: null,
+ imageDTOs: [img1a, img1b],
+ imageLoaded: false,
+ });
+ api.$progressData.setKey(2, {
+ itemId: 2,
+ progressEvent: null,
+ progressImage: null,
+ imageDTOs: [img2],
+ imageLoaded: false,
+ });
+
+ const entries = api.$entries.get();
+ expect(entries).toHaveLength(3);
+ // Item 1 entries first
+ expect(entries[0]?.item.item_id).toBe(1);
+ expect(entries[0]?.imageDTO).toBe(img1a);
+ expect(entries[1]?.item.item_id).toBe(1);
+ expect(entries[1]?.imageDTO).toBe(img1b);
+ // Then item 2
+ expect(entries[2]?.item.item_id).toBe(2);
+ expect(entries[2]?.imageDTO).toBe(img2);
+ });
+ });
+
describe('Selection Methods', () => {
beforeEach(() => {
const items = [
@@ -124,9 +229,16 @@ describe('StagingAreaApi', () => {
it('should select item by ID', () => {
api.select(2);
expect(api.$selectedItemId.get()).toBe(2);
+ expect(api.$selectedImageIndex.get()).toBe(0);
expect(mockApp.onSelect).toHaveBeenCalledWith(2);
});
+ it('should select item by ID and imageIndex', () => {
+ api.select(2, 1);
+ expect(api.$selectedItemId.get()).toBe(2);
+ expect(api.$selectedImageIndex.get()).toBe(1);
+ });
+
it('should select next item', () => {
api.$selectedItemId.set(1);
api.selectNext();
@@ -184,6 +296,124 @@ describe('StagingAreaApi', () => {
});
});
+ describe('Entry-Based Navigation', () => {
+ it('should navigate through entries of multi-output items', () => {
+ const img1 = createMockImageDTO({ image_name: 'a.png' });
+ const img2 = createMockImageDTO({ image_name: 'b.png' });
+ const items = [createMockQueueItem({ item_id: 1 })];
+ api.$items.set(items);
+ api.$progressData.setKey(1, {
+ itemId: 1,
+ progressEvent: null,
+ progressImage: null,
+ imageDTOs: [img1, img2],
+ imageLoaded: false,
+ });
+ api.$selectedItemId.set(1);
+ api.$selectedImageIndex.set(0);
+
+ // Should be on first entry (imageIndex 0)
+ expect(api.$selectedItem.get()?.imageDTO).toBe(img1);
+ expect(api.$selectedItem.get()?.index).toBe(0);
+
+ // Navigate to next entry (imageIndex 1, same item)
+ api.selectNext();
+ expect(api.$selectedItemId.get()).toBe(1);
+ expect(api.$selectedImageIndex.get()).toBe(1);
+ expect(api.$selectedItem.get()?.imageDTO).toBe(img2);
+ expect(api.$selectedItem.get()?.index).toBe(1);
+
+ // Navigate past last entry - should wrap to first
+ api.selectNext();
+ expect(api.$selectedItemId.get()).toBe(1);
+ expect(api.$selectedImageIndex.get()).toBe(0);
+ expect(api.$selectedItem.get()?.imageDTO).toBe(img1);
+ });
+
+ it('should navigate across items and their entries', () => {
+ const img1a = createMockImageDTO({ image_name: 'a1.png' });
+ const img1b = createMockImageDTO({ image_name: 'a2.png' });
+ const img2 = createMockImageDTO({ image_name: 'b1.png' });
+
+ const items = [createMockQueueItem({ item_id: 1 }), createMockQueueItem({ item_id: 2 })];
+ api.$items.set(items);
+ api.$progressData.setKey(1, {
+ itemId: 1,
+ progressEvent: null,
+ progressImage: null,
+ imageDTOs: [img1a, img1b],
+ imageLoaded: false,
+ });
+ api.$progressData.setKey(2, {
+ itemId: 2,
+ progressEvent: null,
+ progressImage: null,
+ imageDTOs: [img2],
+ imageLoaded: false,
+ });
+
+ // Start on first entry
+ api.$selectedItemId.set(1);
+ api.$selectedImageIndex.set(0);
+
+ // entries: [item1/img0, item1/img1, item2/img0]
+ expect(api.$entries.get()).toHaveLength(3);
+
+ // Next -> item1, imageIndex 1
+ api.selectNext();
+ expect(api.$selectedItemId.get()).toBe(1);
+ expect(api.$selectedImageIndex.get()).toBe(1);
+
+ // Next -> item2, imageIndex 0
+ api.selectNext();
+ expect(api.$selectedItemId.get()).toBe(2);
+ expect(api.$selectedImageIndex.get()).toBe(0);
+
+ // Next -> wraps to item1, imageIndex 0
+ api.selectNext();
+ expect(api.$selectedItemId.get()).toBe(1);
+ expect(api.$selectedImageIndex.get()).toBe(0);
+
+ // Prev -> wraps to item2, imageIndex 0
+ api.selectPrev();
+ expect(api.$selectedItemId.get()).toBe(2);
+ expect(api.$selectedImageIndex.get()).toBe(0);
+ });
+
+ it('should select correct entry with selectFirst and selectLast', () => {
+ const img1a = createMockImageDTO({ image_name: 'a1.png' });
+ const img1b = createMockImageDTO({ image_name: 'a2.png' });
+ const img2 = createMockImageDTO({ image_name: 'b1.png' });
+
+ const items = [createMockQueueItem({ item_id: 1 }), createMockQueueItem({ item_id: 2 })];
+ api.$items.set(items);
+ api.$progressData.setKey(1, {
+ itemId: 1,
+ progressEvent: null,
+ progressImage: null,
+ imageDTOs: [img1a, img1b],
+ imageLoaded: false,
+ });
+ api.$progressData.setKey(2, {
+ itemId: 2,
+ progressEvent: null,
+ progressImage: null,
+ imageDTOs: [img2],
+ imageLoaded: false,
+ });
+
+ api.selectLast();
+ expect(api.$selectedItemId.get()).toBe(2);
+ expect(api.$selectedImageIndex.get()).toBe(0);
+ expect(api.$selectedItem.get()?.imageDTO).toBe(img2);
+
+ api.selectFirst();
+ expect(api.$selectedItemId.get()).toBe(1);
+ expect(api.$selectedImageIndex.get()).toBe(0);
+ expect(api.$selectedItem.get()?.imageDTO).toBe(img1a);
+ });
+ });
+
describe('Discard Methods', () => {
beforeEach(() => {
const items = [
@@ -201,6 +431,7 @@ describe('StagingAreaApi', () => {
api.discardSelected();
expect(api.$selectedItemId.get()).toBe(3);
+ expect(api.$selectedImageIndex.get()).toBe(0);
expect(mockApp.onDiscard).toHaveBeenCalledWith(selectedItem?.item);
});
@@ -238,6 +469,7 @@ describe('StagingAreaApi', () => {
api.discardAll();
expect(api.$selectedItemId.get()).toBe(null);
+ expect(api.$selectedImageIndex.get()).toBe(0);
expect(mockApp.onDiscardAll).toHaveBeenCalled();
});
@@ -247,6 +479,14 @@ describe('StagingAreaApi', () => {
api.$selectedItemId.set(1);
expect(api.$discardSelectedIsEnabled.get()).toBe(true);
});
+
+ it('should reset selectedImageIndex when discarding', () => {
+ api.$selectedItemId.set(1);
+ api.$selectedImageIndex.set(2);
+ api.discardSelected();
+
+ expect(api.$selectedImageIndex.get()).toBe(0);
+ });
});
describe('Accept Methods', () => {
@@ -256,13 +496,13 @@ describe('StagingAreaApi', () => {
api.$selectedItemId.set(1);
});
- it('should accept selected item when image is available', () => {
+ it('should accept selected item with single imageDTO', () => {
const imageDTO = createMockImageDTO();
api.$progressData.setKey(1, {
itemId: 1,
progressEvent: null,
progressImage: null,
- imageDTO,
+ imageDTOs: [imageDTO],
imageLoaded: false,
});
@@ -272,12 +512,34 @@ describe('StagingAreaApi', () => {
expect(mockApp.onAccept).toHaveBeenCalledWith(selectedItem?.item, imageDTO);
});
+ it('should accept the correct imageDTO from a multi-output entry', () => {
+ const img1 = createMockImageDTO({ image_name: 'a.png' });
+ const img2 = createMockImageDTO({ image_name: 'b.png' });
+ api.$progressData.setKey(1, {
+ itemId: 1,
+ progressEvent: null,
+ progressImage: null,
+ imageDTOs: [img1, img2],
+ imageLoaded: false,
+ });
+
+ // Select the second image
+ api.$selectedImageIndex.set(1);
+
+ const selectedItem = api.$selectedItem.get();
+ expect(selectedItem?.imageDTO).toBe(img2);
+
+ api.acceptSelected();
+
+ expect(mockApp.onAccept).toHaveBeenCalledWith(selectedItem?.item, img2);
+ });
+
it('should do nothing when no image is available', () => {
api.$progressData.setKey(1, {
itemId: 1,
progressEvent: null,
progressImage: null,
- imageDTO: null,
+ imageDTOs: [],
imageLoaded: false,
});
@@ -301,7 +563,7 @@ describe('StagingAreaApi', () => {
itemId: 1,
progressEvent: null,
progressImage: null,
- imageDTO,
+ imageDTOs: [imageDTO],
imageLoaded: false,
});
@@ -339,7 +601,7 @@ describe('StagingAreaApi', () => {
itemId: 1,
progressEvent: null,
progressImage: null,
- imageDTO: createMockImageDTO(),
+ imageDTOs: [createMockImageDTO()],
imageLoaded: false,
});
@@ -352,7 +614,7 @@ describe('StagingAreaApi', () => {
const progressData = api.$progressData.get();
expect(progressData[1]?.progressEvent).toBe(progressEvent);
- expect(progressData[1]?.imageDTO).toBeTruthy();
+ expect(progressData[1]?.imageDTOs.length).toBeGreaterThan(0);
});
});
@@ -438,7 +700,7 @@ describe('StagingAreaApi', () => {
await api.onItemsChangedEvent(items);
const progressData = api.$progressData.get();
- expect(progressData[1]?.imageDTO).toBe(imageDTO);
+ expect(progressData[1]?.imageDTOs[0]).toBe(imageDTO);
});
it('should handle auto-switch on completion', async () => {
@@ -473,7 +735,7 @@ describe('StagingAreaApi', () => {
itemId: 999,
progressEvent: null,
progressImage: null,
- imageDTO: null,
+ imageDTOs: [],
imageLoaded: false,
});
@@ -490,7 +752,7 @@ describe('StagingAreaApi', () => {
itemId: 1,
progressEvent: createMockProgressEvent({ item_id: 1 }),
progressImage: null,
- imageDTO: createMockImageDTO(),
+ imageDTOs: [createMockImageDTO()],
imageLoaded: false,
});
@@ -501,7 +763,7 @@ describe('StagingAreaApi', () => {
const progressData = api.$progressData.get();
expect(progressData[1]?.progressEvent).toBe(null);
expect(progressData[1]?.progressImage).toBe(null);
- expect(progressData[1]?.imageDTO).toBe(null);
+ expect(progressData[1]?.imageDTOs).toEqual([]);
});
});
@@ -547,18 +809,34 @@ describe('StagingAreaApi', () => {
});
describe('Utility Methods', () => {
- it('should build isSelected computed correctly', () => {
+ it('should build isSelected computed correctly for default imageIndex', () => {
const isSelected = api.buildIsSelectedComputed(1);
expect(isSelected.get()).toBe(false);
api.$selectedItemId.set(1);
+ api.$selectedImageIndex.set(0);
expect(isSelected.get()).toBe(true);
});
+
+ it('should build isSelected computed correctly with specific imageIndex', () => {
+ const isSelected0 = api.buildIsSelectedComputed(1, 0);
+ const isSelected1 = api.buildIsSelectedComputed(1, 1);
+
+ api.$selectedItemId.set(1);
+ api.$selectedImageIndex.set(0);
+ expect(isSelected0.get()).toBe(true);
+ expect(isSelected1.get()).toBe(false);
+
+ api.$selectedImageIndex.set(1);
+ expect(isSelected0.get()).toBe(false);
+ expect(isSelected1.get()).toBe(true);
+ });
});
describe('Cleanup', () => {
it('should reset all state on cleanup', () => {
api.$selectedItemId.set(1);
+ api.$selectedImageIndex.set(2);
api.$items.set([createMockQueueItem({ item_id: 1 })]);
api.$lastStartedItemId.set(1);
api.$lastCompletedItemId.set(1);
@@ -566,13 +844,14 @@ describe('StagingAreaApi', () => {
itemId: 1,
progressEvent: null,
progressImage: null,
- imageDTO: null,
+ imageDTOs: [],
imageLoaded: false,
});
api.cleanup();
expect(api.$selectedItemId.get()).toBe(null);
+ expect(api.$selectedImageIndex.get()).toBe(0);
expect(api.$items.get()).toEqual([]);
expect(api.$lastStartedItemId.get()).toBe(null);
expect(api.$lastCompletedItemId.get()).toBe(null);
@@ -622,13 +901,13 @@ describe('StagingAreaApi', () => {
expect(progressData[1]?.progressEvent).toBe(progressEvent);
});
- it('should preserve imageDTO when updating progress', () => {
+ it('should preserve imageDTOs when updating progress', () => {
const imageDTO = createMockImageDTO();
api.$progressData.setKey(1, {
itemId: 1,
progressEvent: null,
progressImage: null,
- imageDTO,
+ imageDTOs: [imageDTO],
imageLoaded: false,
});
@@ -640,7 +919,7 @@ describe('StagingAreaApi', () => {
api.onInvocationProgressEvent(progressEvent);
const progressData = api.$progressData.get();
- expect(progressData[1]?.imageDTO).toBe(imageDTO);
+ expect(progressData[1]?.imageDTOs[0]).toBe(imageDTO);
expect(progressData[1]?.progressEvent).toBe(progressEvent);
});
});
@@ -712,6 +991,118 @@ describe('StagingAreaApi', () => {
expect(api.$selectedItem.get()?.item.item_id).toBe(2);
});
+ it('should not let stale async call overwrite newer data with fewer images', async () => {
+ const imageDTO1 = createMockImageDTO({ image_name: 'img1.png' });
+ const imageDTO2 = createMockImageDTO({ image_name: 'img2.png' });
+ mockApp._setImageDTO('img1.png', imageDTO1);
+ mockApp._setImageDTO('img2.png', imageDTO2);
+
+ // Simulates optimistic update: status=completed but only 1 result in session.results
+ const itemsStale = [
+ createMockQueueItem({
+ item_id: 1,
+ status: 'completed',
+ session: {
+ id: sessionId,
+ source_prepared_mapping: { 'canvas_output:a': ['node-1'] },
+ results: { 'node-1': { image: { image_name: 'img1.png' } } },
+ },
+ }),
+ ];
+
+ // Simulates full refetch: both results available
+ const itemsFull = [
+ createMockQueueItem({
+ item_id: 1,
+ status: 'completed',
+ session: {
+ id: sessionId,
+ source_prepared_mapping: { 'canvas_output:a': ['node-1'], 'canvas_output:b': ['node-2'] },
+ results: {
+ 'node-1': { image: { image_name: 'img1.png' } },
+ 'node-2': { image: { image_name: 'img2.png' } },
+ },
+ },
+ }),
+ ];
+
+ // Fire both concurrently (stale optimistic update then full refetch)
+ const promise1 = api.onItemsChangedEvent(itemsStale);
+ const promise2 = api.onItemsChangedEvent(itemsFull);
+ await Promise.all([promise1, promise2]);
+
+ const progressData = api.$progressData.get();
+ expect(progressData[1]?.imageDTOs).toHaveLength(2);
+ expect(progressData[1]?.imageDTOs[0]).toBe(imageDTO1);
+ expect(progressData[1]?.imageDTOs[1]).toBe(imageDTO2);
+ });
+
+ it('should load all images from multiple canvas_output nodes', async () => {
+ const imageDTO1 = createMockImageDTO({ image_name: 'output1.png' });
+ const imageDTO2 = createMockImageDTO({ image_name: 'output2.png' });
+ mockApp._setImageDTO('output1.png', imageDTO1);
+ mockApp._setImageDTO('output2.png', imageDTO2);
+
+ const items = [
+ createMockQueueItem({
+ item_id: 1,
+ status: 'completed',
+ session: {
+ id: sessionId,
+ source_prepared_mapping: {
+ 'canvas_output:abc': ['prepared-1'],
+ 'canvas_output:def': ['prepared-2'],
+ },
+ results: {
+ 'prepared-1': { image: { image_name: 'output1.png' } },
+ 'prepared-2': { image: { image_name: 'output2.png' } },
+ },
+ },
+ }),
+ ];
+
+ await api.onItemsChangedEvent(items);
+
+ const progressData = api.$progressData.get();
+ expect(progressData[1]?.imageDTOs).toHaveLength(2);
+ expect(progressData[1]?.imageDTOs[0]).toBe(imageDTO1);
+ expect(progressData[1]?.imageDTOs[1]).toBe(imageDTO2);
+ });
+
+ it('should create separate entries for multiple canvas_output images', async () => {
+ const imageDTO1 = createMockImageDTO({ image_name: 'output1.png' });
+ const imageDTO2 = createMockImageDTO({ image_name: 'output2.png' });
+ mockApp._setImageDTO('output1.png', imageDTO1);
+ mockApp._setImageDTO('output2.png', imageDTO2);
+
+ const items = [
+ createMockQueueItem({
+ item_id: 1,
+ status: 'completed',
+ session: {
+ id: sessionId,
+ source_prepared_mapping: {
+ 'canvas_output:abc': ['prepared-1'],
+ 'canvas_output:def': ['prepared-2'],
+ },
+ results: {
+ 'prepared-1': { image: { image_name: 'output1.png' } },
+ 'prepared-2': { image: { image_name: 'output2.png' } },
+ },
+ },
+ }),
+ ];
+
+ await api.onItemsChangedEvent(items);
+
+ const entries = api.$entries.get();
+ expect(entries).toHaveLength(2);
+ expect(entries[0]?.imageDTO).toBe(imageDTO1);
+ expect(entries[0]?.imageIndex).toBe(0);
+ expect(entries[1]?.imageDTO).toBe(imageDTO2);
+ expect(entries[1]?.imageIndex).toBe(1);
+ });
+
it('should handle multiple progress events for same item', () => {
const event1 = createMockProgressEvent({
item_id: 1,
@@ -740,7 +1131,7 @@ describe('StagingAreaApi', () => {
itemId: i,
progressEvent: null,
progressImage: null,
- imageDTO: null,
+ imageDTOs: [],
imageLoaded: false,
});
}
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/state.ts b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/state.ts
index b6412b048ec..834a3c475c4 100644
--- a/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/state.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/components/StagingArea/state.ts
@@ -6,7 +6,7 @@ import { atom, computed, map } from 'nanostores';
import type { ImageDTO, S } from 'services/api/types';
import { objectEntries } from 'tsafe';
-import { getOutputImageName } from './shared';
+import { getOutputImageNames } from './shared';
/**
* Interface for the app-level API that the StagingAreaApi depends on.
@@ -34,14 +34,23 @@ export type ProgressData = {
itemId: number;
progressEvent: S['InvocationProgressEvent'] | null;
progressImage: ProgressImage | null;
- imageDTO: ImageDTO | null;
+ imageDTOs: ImageDTO[];
imageLoaded: boolean;
};
-/** Combined data for the currently selected item */
+/** A single entry in the staging area. Each canvas_output image is a separate entry. */
+export type StagingEntry = {
+ item: S['SessionQueueItem'];
+ imageDTO: ImageDTO | null;
+ imageIndex: number;
+ progressData: ProgressData;
+};
+
+/** Combined data for the currently selected entry */
export type SelectedItemData = {
item: S['SessionQueueItem'];
index: number;
+ imageDTO: ImageDTO | null;
progressData: ProgressData;
};
@@ -50,7 +59,7 @@ export const getInitialProgressData = (itemId: number): ProgressData => ({
itemId,
progressEvent: null,
progressImage: null,
- imageDTO: null,
+ imageDTOs: [],
imageLoaded: false,
});
type ProgressDataMap = Record;
@@ -58,8 +67,7 @@ type ProgressDataMap = Record;
/**
* API for managing the Canvas Staging Area - a view of the image generation queue.
* Provides reactive state management for pending, in-progress, and completed images.
- * Users can accept images to place on canvas, discard them, navigate between items,
- * and configure auto-switching behavior.
+ * Each canvas_output node produces a separate entry that can be individually navigated and accepted.
*/
export class StagingAreaApi {
/** The current session ID. */
@@ -71,6 +79,9 @@ export class StagingAreaApi {
/** A set of subscriptions to be cleaned up when we are finished with a session */
_subscriptions = new Set<() => void>();
+ /** Generation counter to prevent stale async writes in onItemsChangedEvent */
+ _itemsEventGeneration = 0;
+
/** Item ID of the last started item. Used for auto-switch on start. */
$lastStartedItemId = atom(null);
@@ -86,8 +97,38 @@ export class StagingAreaApi {
/** ID of the currently selected queue item, or null if none selected. */
$selectedItemId = atom(null);
- /** Total number of items in the queue. */
- $itemCount = computed([this.$items], (items) => items.length);
+ /** Index of the selected image within the selected queue item (for multi-output items). */
+ $selectedImageIndex = atom(0);
+
+ /**
+ * Flat list of staging entries. Each canvas_output image from a queue item becomes
+ * a separate entry. Items with 0 or 1 output images produce a single entry.
+ */
+ $entries = computed([this.$items, this.$progressData], (items, progressData) => {
+ const entries: StagingEntry[] = [];
+ for (const item of items) {
+ const datum = progressData[item.item_id] ?? getInitialProgressData(item.item_id);
+ if (datum.imageDTOs.length <= 1) {
+ entries.push({
+ item,
+ imageDTO: datum.imageDTOs[0] ?? null,
+ imageIndex: 0,
+ progressData: datum,
+ });
+ } else {
+ for (let i = 0; i < datum.imageDTOs.length; i++) {
+ const imageDTO = datum.imageDTOs[i];
+ if (imageDTO) {
+ entries.push({ item, imageDTO, imageIndex: i, progressData: datum });
+ }
+ }
+ }
+ }
+ return entries;
+ });
+
+ /** Total number of entries (each canvas_output image counts separately). */
+ $itemCount = computed([this.$entries], (entries) => entries.length);
/** Whether there are any items in the queue. */
$hasItems = computed([this.$items], (items) => items.length > 0);
@@ -97,113 +138,153 @@ export class StagingAreaApi {
items.some((item) => item.status === 'pending' || item.status === 'in_progress')
);
- /** The currently selected queue item with its index and progress data, or null if none selected. */
+ /** The currently selected entry with its global index, or null if none selected. */
$selectedItem = computed(
- [this.$items, this.$selectedItemId, this.$progressData],
- (items, selectedItemId, progressData) => {
- if (items.length === 0) {
+ [this.$entries, this.$selectedItemId, this.$selectedImageIndex],
+ (entries, selectedItemId, selectedImageIndex) => {
+ if (entries.length === 0 || selectedItemId === null) {
return null;
}
- if (selectedItemId === null) {
- return null;
+
+ // Find the entry matching (selectedItemId, selectedImageIndex)
+ let targetEntry: StagingEntry | null = null;
+ let globalIndex = -1;
+ let imageIdxWithinItem = 0;
+
+ for (let i = 0; i < entries.length; i++) {
+ const entry = entries[i]!;
+ if (entry.item.item_id === selectedItemId) {
+ if (imageIdxWithinItem === selectedImageIndex) {
+ targetEntry = entry;
+ globalIndex = i;
+ break;
+ }
+ imageIdxWithinItem++;
+ }
+ }
+
+ // Fallback: select first entry for this item
+ if (!targetEntry) {
+ for (let i = 0; i < entries.length; i++) {
+ const entry = entries[i]!;
+ if (entry.item.item_id === selectedItemId) {
+ targetEntry = entry;
+ globalIndex = i;
+ break;
+ }
+ }
}
- const item = items.find(({ item_id }) => item_id === selectedItemId);
- if (!item) {
+
+ if (!targetEntry || globalIndex === -1) {
return null;
}
return {
- item,
- index: items.findIndex(({ item_id }) => item_id === selectedItemId),
- progressData: progressData[selectedItemId] || getInitialProgressData(selectedItemId),
+ item: targetEntry.item,
+ index: globalIndex,
+ imageDTO: targetEntry.imageDTO,
+ progressData: targetEntry.progressData,
};
}
);
- /** The ImageDTO of the currently selected item, or null if none available. */
+ /** The ImageDTO of the currently selected entry, or null if none available. */
$selectedItemImageDTO = computed([this.$selectedItem], (selectedItem) => {
- return selectedItem?.progressData.imageDTO ?? null;
+ return selectedItem?.imageDTO ?? null;
});
- /** The index of the currently selected item, or null if none selected. */
+ /** The global entry index of the currently selected entry, or null if none selected. */
$selectedItemIndex = computed([this.$selectedItem], (selectedItem) => {
return selectedItem?.index ?? null;
});
- /** Selects a queue item by ID. */
- select = (itemId: number) => {
+ /** Selects a queue item by ID, optionally at a specific image index. */
+ select = (itemId: number, imageIndex: number = 0) => {
this.$selectedItemId.set(itemId);
+ this.$selectedImageIndex.set(imageIndex);
this._app?.onSelect?.(itemId);
};
- /** Selects the next item in the queue, wrapping to the first item if at the end. */
+ /** Selects the next entry, cycling through all entries across all items. */
selectNext = () => {
const selectedItem = this.$selectedItem.get();
if (selectedItem === null) {
return;
}
- const items = this.$items.get();
- const nextIndex = (selectedItem.index + 1) % items.length;
- const nextItem = items[nextIndex];
- if (!nextItem) {
+ const entries = this.$entries.get();
+ if (entries.length <= 1) {
+ return;
+ }
+ const nextIndex = (selectedItem.index + 1) % entries.length;
+ const nextEntry = entries[nextIndex];
+ if (!nextEntry) {
return;
}
- this.$selectedItemId.set(nextItem.item_id);
+ this.$selectedItemId.set(nextEntry.item.item_id);
+ this.$selectedImageIndex.set(nextEntry.imageIndex);
this._app?.onSelectNext?.();
};
- /** Selects the previous item in the queue, wrapping to the last item if at the beginning. */
+ /** Selects the previous entry, cycling through all entries across all items. */
selectPrev = () => {
const selectedItem = this.$selectedItem.get();
if (selectedItem === null) {
return;
}
- const items = this.$items.get();
- const prevIndex = (selectedItem.index - 1 + items.length) % items.length;
- const prevItem = items[prevIndex];
- if (!prevItem) {
+ const entries = this.$entries.get();
+ if (entries.length <= 1) {
return;
}
- this.$selectedItemId.set(prevItem.item_id);
+ const prevIndex = (selectedItem.index - 1 + entries.length) % entries.length;
+ const prevEntry = entries[prevIndex];
+ if (!prevEntry) {
+ return;
+ }
+ this.$selectedItemId.set(prevEntry.item.item_id);
+ this.$selectedImageIndex.set(prevEntry.imageIndex);
this._app?.onSelectPrev?.();
};
- /** Selects the first item in the queue. */
+ /** Selects the first entry. */
selectFirst = () => {
- const items = this.$items.get();
- const first = items.at(0);
+ const entries = this.$entries.get();
+ const first = entries[0];
if (!first) {
return;
}
- this.$selectedItemId.set(first.item_id);
+ this.$selectedItemId.set(first.item.item_id);
+ this.$selectedImageIndex.set(first.imageIndex);
this._app?.onSelectFirst?.();
};
- /** Selects the last item in the queue. */
+ /** Selects the last entry. */
selectLast = () => {
- const items = this.$items.get();
- const last = items.at(-1);
+ const entries = this.$entries.get();
+ const last = entries.at(-1);
if (!last) {
return;
}
- this.$selectedItemId.set(last.item_id);
+ this.$selectedItemId.set(last.item.item_id);
+ this.$selectedImageIndex.set(last.imageIndex);
this._app?.onSelectLast?.();
};
- /** Discards the currently selected item and selects the next available item. */
+ /** Discards the queue item of the currently selected entry and selects the next available entry. */
discardSelected = () => {
const selectedItem = this.$selectedItem.get();
if (selectedItem === null) {
return;
}
const items = this.$items.get();
- const nextIndex = clamp(selectedItem.index + 1, 0, items.length - 1);
- const nextItem = items[nextIndex];
+ const itemIndex = items.findIndex((i) => i.item_id === selectedItem.item.item_id);
+ const nextItemIndex = clamp(itemIndex + 1, 0, items.length - 1);
+ const nextItem = items[nextItemIndex];
if (nextItem) {
this.$selectedItemId.set(nextItem.item_id);
} else {
this.$selectedItemId.set(null);
}
+ this.$selectedImageIndex.set(0);
this._app?.onDiscard?.(selectedItem.item);
};
@@ -231,30 +312,22 @@ export class StagingAreaApi {
/** Discards all items in the queue. */
discardAll = () => {
this.$selectedItemId.set(null);
+ this.$selectedImageIndex.set(0);
this._app?.onDiscardAll?.();
};
- /** Accepts the currently selected item if an image is available. */
+ /** Accepts the currently selected entry if an image is available. */
acceptSelected = () => {
const selectedItem = this.$selectedItem.get();
- if (selectedItem === null) {
- return;
- }
- const progressData = this.$progressData.get();
- const datum = progressData[selectedItem.item.item_id];
- if (!datum || !datum.imageDTO) {
+ if (selectedItem === null || !selectedItem.imageDTO) {
return;
}
- this._app?.onAccept?.(selectedItem.item, datum.imageDTO);
+ this._app?.onAccept?.(selectedItem.item, selectedItem.imageDTO);
};
/** Whether the accept selected action is enabled. */
- $acceptSelectedIsEnabled = computed([this.$selectedItem, this.$progressData], (selectedItem, progressData) => {
- if (selectedItem === null) {
- return false;
- }
- const datum = progressData[selectedItem.item.item_id];
- return !!datum && !!datum.imageDTO;
+ $acceptSelectedIsEnabled = computed([this.$selectedItem], (selectedItem) => {
+ return selectedItem !== null && selectedItem.imageDTO !== null;
});
/** Sets the auto-switch mode. */
@@ -297,6 +370,10 @@ export class StagingAreaApi {
* handles auto-selection, and implements auto-switch behavior.
*/
onItemsChangedEvent = async (items: S['SessionQueueItem'][]) => {
+ // Increment generation counter. If a newer call starts while we're awaiting,
+ // we'll detect it and avoid overwriting with stale data.
+ const generation = ++this._itemsEventGeneration;
+
const oldItems = this.$items.get();
if (items === oldItems) {
@@ -306,9 +383,11 @@ export class StagingAreaApi {
if (items.length === 0) {
// If there are no items, cannot have a selected item.
this.$selectedItemId.set(null);
+ this.$selectedImageIndex.set(0);
} else if (this.$selectedItemId.get() === null && items.length > 0) {
// If there is no selected item but there are items, select the first one.
this.$selectedItemId.set(items[0]?.item_id ?? null);
+ this.$selectedImageIndex.set(0);
}
const progressData = this.$progressData.get();
@@ -328,7 +407,7 @@ export class StagingAreaApi {
...(datum ?? getInitialProgressData(item.item_id)),
progressEvent: null,
progressImage: null,
- imageDTO: null,
+ imageDTOs: [],
});
continue;
}
@@ -336,31 +415,57 @@ export class StagingAreaApi {
if (item.status === 'in_progress') {
if (this.$lastStartedItemId.get() === item.item_id && this._app?.getAutoSwitch() === 'switch_on_start') {
this.$selectedItemId.set(item.item_id);
+ this.$selectedImageIndex.set(0);
this.$lastStartedItemId.set(null);
}
continue;
}
if (item.status === 'completed') {
- if (datum?.imageDTO) {
+ const outputImageNames = getOutputImageNames(item);
+ if (outputImageNames.length === 0) {
continue;
}
- const outputImageName = getOutputImageName(item);
- if (!outputImageName) {
+ // Check current progress data (not the snapshot) to account for concurrent updates
+ const currentDatum = this.$progressData.get()[item.item_id];
+ if (currentDatum && currentDatum.imageDTOs.length === outputImageNames.length) {
continue;
}
- const imageDTO = await this._app?.getImageDTO(outputImageName);
- if (!imageDTO) {
+ const imageDTOs: ImageDTO[] = [];
+ for (const imageName of outputImageNames) {
+ const imageDTO = await this._app?.getImageDTO(imageName);
+ if (imageDTO) {
+ imageDTOs.push(imageDTO);
+ }
+ }
+ if (imageDTOs.length === 0) {
+ continue;
+ }
+
+ // After async work, check if a newer event has started processing.
+ // If so, abort to let the newer call handle the update with fresher data.
+ if (generation !== this._itemsEventGeneration) {
+ return;
+ }
+
+ // Re-read progress data to avoid overwriting a better result from a concurrent call
+ const latestDatum = this.$progressData.get()[item.item_id];
+ if (latestDatum && latestDatum.imageDTOs.length >= imageDTOs.length) {
continue;
}
this.$progressData.setKey(item.item_id, {
- ...(datum ?? getInitialProgressData(item.item_id)),
- imageDTO,
+ ...(latestDatum ?? getInitialProgressData(item.item_id)),
+ imageDTOs,
});
}
}
+ // After async work, check if a newer event has started processing
+ if (generation !== this._itemsEventGeneration) {
+ return;
+ }
+
const selectedItemId = this.$selectedItemId.get();
if (selectedItemId !== null && !items.find(({ item_id }) => item_id === selectedItemId)) {
// If the selected item no longer exists, select the next best item.
@@ -370,15 +475,18 @@ export class StagingAreaApi {
const nextItem = items[nextItemIndex] ?? items[nextItemIndex - 1];
if (nextItem) {
this.$selectedItemId.set(nextItem.item_id);
+ this.$selectedImageIndex.set(0);
}
} else {
// Next, if there is an in-progress item, select that.
const inProgressItem = items.find(({ status }) => status === 'in_progress');
if (inProgressItem) {
this.$selectedItemId.set(inProgressItem.item_id);
+ this.$selectedImageIndex.set(0);
}
// Finally just select the first item.
this.$selectedItemId.set(items[0]?.item_id ?? null);
+ this.$selectedImageIndex.set(0);
}
}
@@ -393,6 +501,7 @@ export class StagingAreaApi {
// This is the load logic mentioned in the comment in the QueueItemStatusChangedEvent handler above.
if (this.$lastCompletedItemId.get() === item.item_id && this._app?.getAutoSwitch() === 'switch_on_finish') {
this.$selectedItemId.set(item.item_id);
+ this.$selectedImageIndex.set(0);
this.$lastCompletedItemId.set(null);
}
const datum = this.$progressData.get()[item.item_id];
@@ -402,20 +511,22 @@ export class StagingAreaApi {
});
};
- /** Creates a computed value that returns true if the given item ID is selected. */
- buildIsSelectedComputed = (itemId: number) => {
- return computed([this.$selectedItemId], (selectedItemId) => {
- return selectedItemId === itemId;
+ /** Creates a computed value that returns true if the given item ID and image index is selected. */
+ buildIsSelectedComputed = (itemId: number, imageIndex: number = 0) => {
+ return computed([this.$selectedItemId, this.$selectedImageIndex], (selectedItemId, selectedImageIndex) => {
+ return selectedItemId === itemId && selectedImageIndex === imageIndex;
});
};
/** Cleans up all state and unsubscribes from all events. */
cleanup = () => {
+ this._itemsEventGeneration++;
this.$lastStartedItemId.set(null);
this.$lastCompletedItemId.set(null);
this.$items.set([]);
this.$progressData.set({});
this.$selectedItemId.set(null);
+ this.$selectedImageIndex.set(0);
this._subscriptions.forEach((unsubscribe) => unsubscribe());
this._subscriptions.clear();
};
diff --git a/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsRunWorkflow.tsx b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsRunWorkflow.tsx
new file mode 100644
index 00000000000..64a53091a27
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/common/CanvasEntityMenuItemsRunWorkflow.tsx
@@ -0,0 +1,25 @@
+import { MenuItem } from '@invoke-ai/ui-library';
+import { useAppDispatch } from 'app/store/storeHooks';
+import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
+import { canvasWorkflowIntegrationOpened } from 'features/controlLayers/store/canvasWorkflowIntegrationSlice';
+import { memo, useCallback } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiFlowArrowBold } from 'react-icons/pi';
+
+export const CanvasEntityMenuItemsRunWorkflow = memo(() => {
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+ const entityIdentifier = useEntityIdentifierContext();
+
+ const onClick = useCallback(() => {
+ dispatch(canvasWorkflowIntegrationOpened({ sourceEntityIdentifier: entityIdentifier }));
+ }, [dispatch, entityIdentifier]);
+
+ return (
+ }>
+ {t('controlLayers.workflowIntegration.runWorkflow')}
+
+ );
+});
+
+CanvasEntityMenuItemsRunWorkflow.displayName = 'CanvasEntityMenuItemsRunWorkflow';
diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStagingAreaModule.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStagingAreaModule.ts
index 569f5c29542..2a686963273 100644
--- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStagingAreaModule.ts
+++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStagingAreaModule.ts
@@ -156,8 +156,8 @@ export class CanvasStagingAreaModule extends CanvasModuleBase {
return;
}
- if (selectedItem.progressData.imageDTO) {
- this.$imageSrc.set({ type: 'imageName', data: selectedItem.progressData.imageDTO.image_name });
+ if (selectedItem.imageDTO) {
+ this.$imageSrc.set({ type: 'imageName', data: selectedItem.imageDTO.image_name });
return;
} else if (selectedItem.progressData?.progressImage) {
this.$imageSrc.set({ type: 'dataURL', data: selectedItem.progressData.progressImage.dataURL });
diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasWorkflowIntegrationSlice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasWorkflowIntegrationSlice.ts
new file mode 100644
index 00000000000..0056885f0fa
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasWorkflowIntegrationSlice.ts
@@ -0,0 +1,134 @@
+import type { PayloadAction, Selector } from '@reduxjs/toolkit';
+import { createSelector, createSlice } from '@reduxjs/toolkit';
+import type { RootState } from 'app/store/store';
+import type { SliceConfig } from 'app/store/types';
+import { isPlainObject } from 'es-toolkit';
+import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
+import { assert } from 'tsafe';
+import z from 'zod';
+
+const zCanvasWorkflowIntegrationState = z.object({
+ _version: z.literal(1),
+ isOpen: z.boolean(),
+ selectedWorkflowId: z.string().nullable(),
+ sourceEntityIdentifier: z
+ .object({
+ type: z.enum(['raster_layer', 'control_layer', 'regional_guidance', 'inpaint_mask']),
+ id: z.string(),
+ })
+ .nullable(),
+ fieldValues: z.record(z.string(), z.any()).nullable(),
+ // Which ImageField to use for canvas image (format: "nodeId.fieldName")
+ selectedImageFieldKey: z.string().nullable(),
+ isProcessing: z.boolean(),
+});
+
+type CanvasWorkflowIntegrationState = z.infer;
+
+const getInitialState = (): CanvasWorkflowIntegrationState => ({
+ _version: 1,
+ isOpen: false,
+ selectedWorkflowId: null,
+ sourceEntityIdentifier: null,
+ fieldValues: null,
+ selectedImageFieldKey: null,
+ isProcessing: false,
+});
+
+const slice = createSlice({
+ name: 'canvasWorkflowIntegration',
+ initialState: getInitialState(),
+ reducers: {
+ canvasWorkflowIntegrationOpened: (
+ state,
+ action: PayloadAction<{ sourceEntityIdentifier: CanvasEntityIdentifier }>
+ ) => {
+ state.isOpen = true;
+ state.sourceEntityIdentifier = action.payload.sourceEntityIdentifier;
+ state.selectedWorkflowId = null;
+ state.fieldValues = null;
+ },
+ canvasWorkflowIntegrationClosed: (state) => {
+ state.isOpen = false;
+ state.selectedWorkflowId = null;
+ state.sourceEntityIdentifier = null;
+ state.fieldValues = null;
+ state.selectedImageFieldKey = null;
+ state.isProcessing = false;
+ },
+ canvasWorkflowIntegrationWorkflowSelected: (state, action: PayloadAction<{ workflowId: string | null }>) => {
+ state.selectedWorkflowId = action.payload.workflowId;
+ // Reset field values when switching workflows
+ state.fieldValues = null;
+ state.selectedImageFieldKey = null;
+ },
+ canvasWorkflowIntegrationImageFieldSelected: (state, action: PayloadAction<{ fieldKey: string | null }>) => {
+ state.selectedImageFieldKey = action.payload.fieldKey;
+ },
+ canvasWorkflowIntegrationFieldValueChanged: (
+ state,
+ action: PayloadAction<{ fieldName: string; value: unknown }>
+ ) => {
+ if (!state.fieldValues) {
+ state.fieldValues = {};
+ }
+ state.fieldValues[action.payload.fieldName] = action.payload.value;
+ },
+ canvasWorkflowIntegrationFieldValuesReset: (state) => {
+ state.fieldValues = null;
+ },
+ canvasWorkflowIntegrationProcessingStarted: (state) => {
+ state.isProcessing = true;
+ },
+ canvasWorkflowIntegrationProcessingCompleted: (state) => {
+ state.isProcessing = false;
+ },
+ },
+});
+
+export const {
+ canvasWorkflowIntegrationOpened,
+ canvasWorkflowIntegrationClosed,
+ canvasWorkflowIntegrationWorkflowSelected,
+ canvasWorkflowIntegrationImageFieldSelected,
+ canvasWorkflowIntegrationFieldValueChanged,
+ canvasWorkflowIntegrationProcessingStarted,
+ canvasWorkflowIntegrationProcessingCompleted,
+} = slice.actions;
+
+export const canvasWorkflowIntegrationSliceConfig: SliceConfig = {
+ slice,
+ schema: zCanvasWorkflowIntegrationState,
+ getInitialState,
+ persistConfig: {
+ migrate: (state) => {
+ assert(isPlainObject(state));
+ if (!('_version' in state)) {
+ state._version = 1;
+ }
+ return zCanvasWorkflowIntegrationState.parse(state);
+ },
+ persistDenylist: ['isOpen', 'isProcessing', 'sourceEntityIdentifier'],
+ },
+};
+
+const selectCanvasWorkflowIntegrationSlice = (state: RootState) => state.canvasWorkflowIntegration;
+const createCanvasWorkflowIntegrationSelector = (selector: Selector) =>
+ createSelector(selectCanvasWorkflowIntegrationSlice, selector);
+
+export const selectCanvasWorkflowIntegrationIsOpen = createCanvasWorkflowIntegrationSelector((state) => state.isOpen);
+export const selectCanvasWorkflowIntegrationSelectedWorkflowId = createCanvasWorkflowIntegrationSelector(
+ (state) => state.selectedWorkflowId
+);
+export const selectCanvasWorkflowIntegrationSourceEntityIdentifier = createCanvasWorkflowIntegrationSelector(
+ (state) => state.sourceEntityIdentifier
+);
+export const selectCanvasWorkflowIntegrationFieldValues = createCanvasWorkflowIntegrationSelector(
+ (state) => state.fieldValues
+);
+export const selectCanvasWorkflowIntegrationSelectedImageFieldKey = createCanvasWorkflowIntegrationSelector(
+ (state) => state.selectedImageFieldKey
+);
+export const selectCanvasWorkflowIntegrationIsProcessing = createCanvasWorkflowIntegrationSelector(
+ (state) => state.isProcessing
+);
diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts b/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts
index 0424f175066..4ef65c989a9 100644
--- a/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts
+++ b/invokeai/frontend/web/src/features/nodes/util/graph/graphBuilderUtils.ts
@@ -1,6 +1,8 @@
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store';
import { getPrefixedId } from 'features/controlLayers/konva/util';
+
+export { getPrefixedId };
import { selectSaveAllImagesToGallery } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectCanvasSessionId } from 'features/controlLayers/store/canvasStagingAreaSlice';
import {
@@ -205,7 +207,7 @@ export const getInfill = (
assert(false, 'Unknown infill method');
};
-const CANVAS_OUTPUT_PREFIX = 'canvas_output';
+export const CANVAS_OUTPUT_PREFIX = 'canvas_output';
export const isMainModelWithoutUnet = (modelLoader: Invocation) => {
return (
diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts
index 550a0fa2dd4..7ae0c77bdf9 100644
--- a/invokeai/frontend/web/src/services/api/schema.ts
+++ b/invokeai/frontend/web/src/services/api/schema.ts
@@ -4473,6 +4473,44 @@ export type components = {
*/
type: "canny_edge_detection";
};
+ /**
+ * Canvas Output
+ * @description Outputs an image to the canvas staging area.
+ *
+ * Use this node in workflows intended for canvas workflow integration.
+ * Connect the final image of your workflow to this node to send it
+ * to the canvas staging area when run via 'Run Workflow on Canvas'.
+ */
+ CanvasOutputInvocation: {
+ /**
+ * Id
+ * @description The id of this instance of an invocation. Must be unique among all instances of invocations.
+ */
+ id: string;
+ /**
+ * Is Intermediate
+ * @description Whether or not this is an intermediate invocation.
+ * @default false
+ */
+ is_intermediate?: boolean;
+ /**
+ * Use Cache
+ * @description Whether or not to use the cache
+ * @default false
+ */
+ use_cache?: boolean;
+ /**
+ * @description The image to process
+ * @default null
+ */
+ image?: components["schemas"]["ImageField"] | null;
+ /**
+ * type
+ * @default canvas_output
+ * @constant
+ */
+ type: "canvas_output";
+ };
/**
* Canvas Paste Back
* @description Combines two images by using the mask provided. Intended for use on the Unified Canvas.
@@ -10748,7 +10786,7 @@ export type components = {
* @description The nodes in this graph
*/
nodes?: {
- [key: string]: components["schemas"]["AddInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"];
+ [key: string]: components["schemas"]["AddInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"];
};
/**
* Edges
@@ -14032,7 +14070,7 @@ export type components = {
* Invocation
* @description The ID of the invocation
*/
- invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"];
+ invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"];
/**
* Invocation Source Id
* @description The ID of the prepared invocation's source node
@@ -14096,7 +14134,7 @@ export type components = {
* Invocation
* @description The ID of the invocation
*/
- invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"];
+ invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"];
/**
* Invocation Source Id
* @description The ID of the prepared invocation's source node
@@ -14131,6 +14169,7 @@ export type components = {
calculate_image_tiles_even_split: components["schemas"]["CalculateImageTilesOutput"];
calculate_image_tiles_min_overlap: components["schemas"]["CalculateImageTilesOutput"];
canny_edge_detection: components["schemas"]["ImageOutput"];
+ canvas_output: components["schemas"]["ImageOutput"];
canvas_paste_back: components["schemas"]["ImageOutput"];
canvas_v2_mask_and_crop: components["schemas"]["ImageOutput"];
clip_skip: components["schemas"]["CLIPSkipInvocationOutput"];
@@ -14404,7 +14443,7 @@ export type components = {
* Invocation
* @description The ID of the invocation
*/
- invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"];
+ invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"];
/**
* Invocation Source Id
* @description The ID of the prepared invocation's source node
@@ -14479,7 +14518,7 @@ export type components = {
* Invocation
* @description The ID of the invocation
*/
- invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"];
+ invocation: components["schemas"]["AddInvocation"] | components["schemas"]["AlphaMaskToTensorInvocation"] | components["schemas"]["ApplyMaskTensorToImageInvocation"] | components["schemas"]["ApplyMaskToImageInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["BoundingBoxInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["CannyEdgeDetectionInvocation"] | components["schemas"]["CanvasOutputInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CanvasV2MaskAndCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CogView4DenoiseInvocation"] | components["schemas"]["CogView4ImageToLatentsInvocation"] | components["schemas"]["CogView4LatentsToImageInvocation"] | components["schemas"]["CogView4ModelLoaderInvocation"] | components["schemas"]["CogView4TextEncoderInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["ContentShuffleInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["CropImageToBoundingBoxInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DWOpenposeDetectionInvocation"] | components["schemas"]["DecodeInvisibleWatermarkInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["DenoiseLatentsMetaInvocation"] | components["schemas"]["DepthAnythingDepthEstimationInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ExpandMaskWithFadeInvocation"] | components["schemas"]["FLUXLoRACollectionLoader"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["FloatBatchInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["FloatGenerator"] | components["schemas"]["FloatInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["Flux2DenoiseInvocation"] | components["schemas"]["Flux2KleinLoRACollectionLoader"] | components["schemas"]["Flux2KleinLoRALoaderInvocation"] | components["schemas"]["Flux2KleinModelLoaderInvocation"] | components["schemas"]["Flux2KleinTextEncoderInvocation"] | components["schemas"]["Flux2VaeDecodeInvocation"] | components["schemas"]["Flux2VaeEncodeInvocation"] | components["schemas"]["FluxControlLoRALoaderInvocation"] | components["schemas"]["FluxControlNetInvocation"] | components["schemas"]["FluxDenoiseInvocation"] | components["schemas"]["FluxDenoiseLatentsMetaInvocation"] | components["schemas"]["FluxFillInvocation"] | components["schemas"]["FluxIPAdapterInvocation"] | components["schemas"]["FluxKontextConcatenateImagesInvocation"] | components["schemas"]["FluxKontextInvocation"] | components["schemas"]["FluxLoRALoaderInvocation"] | components["schemas"]["FluxModelLoaderInvocation"] | components["schemas"]["FluxReduxInvocation"] | components["schemas"]["FluxTextEncoderInvocation"] | components["schemas"]["FluxVaeDecodeInvocation"] | components["schemas"]["FluxVaeEncodeInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["GetMaskBoundingBoxInvocation"] | components["schemas"]["GroundingDinoInvocation"] | components["schemas"]["HEDEdgeDetectionInvocation"] | components["schemas"]["HeuristicResizeInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["IfInvocation"] | components["schemas"]["ImageBatchInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["ImageGenerator"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["ImageMaskToTensorInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["ImageNoiseInvocation"] | components["schemas"]["ImagePanelLayoutInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IntegerBatchInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["IntegerGenerator"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["InvertTensorMaskInvocation"] | components["schemas"]["InvokeAdjustImageHuePlusInvocation"] | components["schemas"]["InvokeEquivalentAchromaticLightnessInvocation"] | components["schemas"]["InvokeImageBlendInvocation"] | components["schemas"]["InvokeImageCompositorInvocation"] | components["schemas"]["InvokeImageDilateOrErodeInvocation"] | components["schemas"]["InvokeImageEnhanceInvocation"] | components["schemas"]["InvokeImageValueThresholdsInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["LineartAnimeEdgeDetectionInvocation"] | components["schemas"]["LineartEdgeDetectionInvocation"] | components["schemas"]["LlavaOnevisionVllmInvocation"] | components["schemas"]["LoRACollectionLoader"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["LoRASelectorInvocation"] | components["schemas"]["MLSDDetectionInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["MaskTensorToImageInvocation"] | components["schemas"]["MediaPipeFaceDetectionInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MetadataFieldExtractorInvocation"] | components["schemas"]["MetadataFromImageInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MetadataItemLinkedInvocation"] | components["schemas"]["MetadataToBoolCollectionInvocation"] | components["schemas"]["MetadataToBoolInvocation"] | components["schemas"]["MetadataToControlnetsInvocation"] | components["schemas"]["MetadataToFloatCollectionInvocation"] | components["schemas"]["MetadataToFloatInvocation"] | components["schemas"]["MetadataToIPAdaptersInvocation"] | components["schemas"]["MetadataToIntegerCollectionInvocation"] | components["schemas"]["MetadataToIntegerInvocation"] | components["schemas"]["MetadataToLorasCollectionInvocation"] | components["schemas"]["MetadataToLorasInvocation"] | components["schemas"]["MetadataToModelInvocation"] | components["schemas"]["MetadataToSDXLLorasInvocation"] | components["schemas"]["MetadataToSDXLModelInvocation"] | components["schemas"]["MetadataToSchedulerInvocation"] | components["schemas"]["MetadataToStringCollectionInvocation"] | components["schemas"]["MetadataToStringInvocation"] | components["schemas"]["MetadataToT2IAdaptersInvocation"] | components["schemas"]["MetadataToVAEInvocation"] | components["schemas"]["ModelIdentifierInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["NormalMapInvocation"] | components["schemas"]["PBRMapsInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PasteImageIntoBoundingBoxInvocation"] | components["schemas"]["PiDiNetEdgeDetectionInvocation"] | components["schemas"]["PromptTemplateInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["RectangleMaskInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["SD3DenoiseInvocation"] | components["schemas"]["SD3ImageToLatentsInvocation"] | components["schemas"]["SD3LatentsToImageInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["SDXLLoRACollectionLoader"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["Sd3ModelLoaderInvocation"] | components["schemas"]["Sd3TextEncoderInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["SegmentAnythingInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["SpandrelImageToImageAutoscaleInvocation"] | components["schemas"]["SpandrelImageToImageInvocation"] | components["schemas"]["StringBatchInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["StringGenerator"] | components["schemas"]["StringInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["TiledMultiDiffusionDenoiseLatents"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["ZImageControlInvocation"] | components["schemas"]["ZImageDenoiseInvocation"] | components["schemas"]["ZImageDenoiseMetaInvocation"] | components["schemas"]["ZImageImageToLatentsInvocation"] | components["schemas"]["ZImageLatentsToImageInvocation"] | components["schemas"]["ZImageLoRACollectionLoader"] | components["schemas"]["ZImageLoRALoaderInvocation"] | components["schemas"]["ZImageModelLoaderInvocation"] | components["schemas"]["ZImageSeedVarianceEnhancerInvocation"] | components["schemas"]["ZImageTextEncoderInvocation"];
/**
* Invocation Source Id
* @description The ID of the prepared invocation's source node
diff --git a/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
index e2ee74dcad1..14bdf343ec4 100644
--- a/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
+++ b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
@@ -1,6 +1,7 @@
import { logger } from 'app/logging/logger';
import type { AppDispatch, AppGetState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
+import { canvasWorkflowIntegrationProcessingCompleted } from 'features/controlLayers/store/canvasWorkflowIntegrationSlice';
import {
selectAutoSwitch,
selectGalleryView,
@@ -13,8 +14,10 @@ import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks
import { isImageField, isImageFieldCollection } from 'features/nodes/types/common';
import { zNodeStatus } from 'features/nodes/types/invocation';
import type { LRUCache } from 'lru-cache';
+import { LIST_ALL_TAG } from 'services/api';
import { boardsApi } from 'services/api/endpoints/boards';
import { getImageDTOSafe, imagesApi } from 'services/api/endpoints/images';
+import { queueApi } from 'services/api/endpoints/queue';
import type { ImageDTO, S } from 'services/api/types';
import { getCategories } from 'services/api/util';
import { insertImageIntoNamesResult } from 'services/api/util/optimisticUpdates';
@@ -217,6 +220,27 @@ export const buildOnInvocationComplete = (
return imageDTOs;
};
+ const clearCanvasWorkflowIntegrationProcessing = (data: S['InvocationCompleteEvent']) => {
+ // Check if this is a canvas workflow integration result
+ // Results go to staging area automatically via destination = canvasSessionId
+ if (data.origin !== 'canvas_workflow_integration') {
+ return;
+ }
+ // Clear processing state so the modal loading spinner stops
+ dispatch(canvasWorkflowIntegrationProcessingCompleted());
+
+ // Check if this invocation produced an image output
+ const hasImageOutput = objectEntries(data.result).some(([_name, value]) => {
+ return isImageField(value) || isImageFieldCollection(value);
+ });
+
+ // Only invalidate if this invocation produced an image - this ensures the staging area
+ // gets updated immediately when output images are available, without invalidating on every invocation
+ if (hasImageOutput) {
+ dispatch(queueApi.util.invalidateTags([{ type: 'SessionQueueItem', id: LIST_ALL_TAG }]));
+ }
+ };
+
return async (data: S['InvocationCompleteEvent']) => {
if (finishedQueueItemIds.has(data.item_id)) {
log.trace({ data } as JsonObject, `Received event for already-finished queue item ${data.item_id}`);
@@ -236,6 +260,10 @@ export const buildOnInvocationComplete = (
upsertExecutionState(_nodeExecutionState.nodeId, _nodeExecutionState);
}
+ // Clear canvas workflow integration processing state if needed
+ clearCanvasWorkflowIntegrationProcessing(data);
+
+ // Add images to gallery (canvas workflow integration results go to staging area automatically)
await addImagesToGallery(data);
$lastProgressEvent.set(null);
diff --git a/invokeai/frontend/web/src/services/events/setEventListeners.tsx b/invokeai/frontend/web/src/services/events/setEventListeners.tsx
index 6fb50e4902d..59a9ff2afcc 100644
--- a/invokeai/frontend/web/src/services/events/setEventListeners.tsx
+++ b/invokeai/frontend/web/src/services/events/setEventListeners.tsx
@@ -5,6 +5,7 @@ import type { AppStore } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
import { forEach, isNil, round } from 'es-toolkit/compat';
import { allEntitiesDeleted, controlLayerRecalled } from 'features/controlLayers/store/canvasSlice';
+import { canvasWorkflowIntegrationProcessingCompleted } from 'features/controlLayers/store/canvasWorkflowIntegrationSlice';
import { loraAllDeleted, loraRecalled } from 'features/controlLayers/store/lorasSlice';
import {
heightChanged,
@@ -160,6 +161,10 @@ export const setEventListeners = ({ socket, store, setIsConnected }: SetEventLis
};
upsertExecutionState(nes.nodeId, nes);
}
+ // Clear canvas workflow integration processing state on error
+ if (data.origin === 'canvas_workflow_integration') {
+ dispatch(canvasWorkflowIntegrationProcessingCompleted());
+ }
});
const onInvocationComplete = buildOnInvocationComplete(getState, dispatch, finishedQueueItemIds);
@@ -407,6 +412,25 @@ export const setEventListeners = ({ socket, store, setIsConnected }: SetEventLis
})
);
+ // Optimistically update the listAllQueueItems cache for this destination so the canvas
+ // staging area immediately reflects status changes without waiting for a tag-based refetch
+ if (destination) {
+ dispatch(
+ queueApi.util.updateQueryData('listAllQueueItems', { destination }, (draft) => {
+ const item = draft.find((i) => i.item_id === item_id);
+ if (item) {
+ item.status = status;
+ item.started_at = started_at;
+ item.updated_at = updated_at;
+ item.completed_at = completed_at;
+ item.error_type = error_type;
+ item.error_message = error_message;
+ item.error_traceback = error_traceback;
+ }
+ })
+ );
+ }
+
// Invalidate caches for things we cannot easily update
// Invalidate SessionQueueStatus to refetch with user-specific counts
const tagsToInvalidate: ApiTagDescription[] = [