mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-17 13:55:04 -05:00
197 lines
6.7 KiB
TypeScript
197 lines
6.7 KiB
TypeScript
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
|
import { useAppSelector } from 'app/store/storeHooks';
|
|
import {
|
|
selectControlAdapterAll,
|
|
selectControlAdaptersSlice,
|
|
} from 'features/controlAdapters/store/controlAdaptersSlice';
|
|
import { isControlNetOrT2IAdapter } from 'features/controlAdapters/store/types';
|
|
import { selectControlLayersSlice } from 'features/controlLayers/store/controlLayersSlice';
|
|
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
|
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
|
|
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
|
import { isInvocationNode } from 'features/nodes/types/invocation';
|
|
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
|
|
import { selectSystemSlice } from 'features/system/store/systemSlice';
|
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
|
import i18n from 'i18next';
|
|
import { forEach } from 'lodash-es';
|
|
import { getConnectedEdges } from 'reactflow';
|
|
|
|
const selector = createMemoizedSelector(
|
|
[
|
|
selectControlAdaptersSlice,
|
|
selectGenerationSlice,
|
|
selectSystemSlice,
|
|
selectNodesSlice,
|
|
selectDynamicPromptsSlice,
|
|
selectControlLayersSlice,
|
|
activeTabNameSelector,
|
|
],
|
|
(controlAdapters, generation, system, nodes, dynamicPrompts, controlLayers, activeTabName) => {
|
|
const { model } = generation;
|
|
const { size } = controlLayers.present;
|
|
const { positivePrompt } = controlLayers.present;
|
|
|
|
const { isConnected } = system;
|
|
|
|
const reasons: string[] = [];
|
|
|
|
// Cannot generate if not connected
|
|
if (!isConnected) {
|
|
reasons.push(i18n.t('parameters.invoke.systemDisconnected'));
|
|
}
|
|
|
|
if (activeTabName === 'workflows') {
|
|
if (nodes.shouldValidateGraph) {
|
|
if (!nodes.nodes.length) {
|
|
reasons.push(i18n.t('parameters.invoke.noNodesInGraph'));
|
|
}
|
|
|
|
nodes.nodes.forEach((node) => {
|
|
if (!isInvocationNode(node)) {
|
|
return;
|
|
}
|
|
|
|
const nodeTemplate = nodes.templates[node.data.type];
|
|
|
|
if (!nodeTemplate) {
|
|
// Node type not found
|
|
reasons.push(i18n.t('parameters.invoke.missingNodeTemplate'));
|
|
return;
|
|
}
|
|
|
|
const connectedEdges = getConnectedEdges([node], nodes.edges);
|
|
|
|
forEach(node.data.inputs, (field) => {
|
|
const fieldTemplate = nodeTemplate.inputs[field.name];
|
|
const hasConnection = connectedEdges.some(
|
|
(edge) => edge.target === node.id && edge.targetHandle === field.name
|
|
);
|
|
|
|
if (!fieldTemplate) {
|
|
reasons.push(i18n.t('parameters.invoke.missingFieldTemplate'));
|
|
return;
|
|
}
|
|
|
|
if (fieldTemplate.required && field.value === undefined && !hasConnection) {
|
|
reasons.push(
|
|
i18n.t('parameters.invoke.missingInputForField', {
|
|
nodeLabel: node.data.label || nodeTemplate.title,
|
|
fieldLabel: field.label || fieldTemplate.title,
|
|
})
|
|
);
|
|
return;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
} else {
|
|
if (dynamicPrompts.prompts.length === 0 && getShouldProcessPrompt(positivePrompt)) {
|
|
reasons.push(i18n.t('parameters.invoke.noPrompts'));
|
|
}
|
|
|
|
if (!model) {
|
|
reasons.push(i18n.t('parameters.invoke.noModelSelected'));
|
|
}
|
|
|
|
if (activeTabName === 'generation') {
|
|
// Handling for generation tab
|
|
controlLayers.present.layers
|
|
.filter((l) => l.isEnabled)
|
|
.flatMap((l) => {
|
|
if (l.type === 'control_adapter_layer') {
|
|
return l.controlAdapter;
|
|
} else if (l.type === 'ip_adapter_layer') {
|
|
return l.ipAdapter;
|
|
} else if (l.type === 'regional_guidance_layer') {
|
|
return l.ipAdapters;
|
|
}
|
|
return [];
|
|
})
|
|
.forEach((ca, i) => {
|
|
const hasNoModel = !ca.model;
|
|
const mismatchedModelBase = ca.model?.base !== model?.base;
|
|
const hasNoImage = !ca.image;
|
|
const imageNotProcessed =
|
|
(ca.type === 'controlnet' || ca.type === 't2i_adapter') && !ca.processedImage && ca.processorConfig;
|
|
|
|
if (hasNoModel) {
|
|
reasons.push(
|
|
i18n.t('parameters.invoke.noModelForControlAdapter', {
|
|
number: i + 1,
|
|
})
|
|
);
|
|
}
|
|
if (mismatchedModelBase) {
|
|
// This should never happen, just a sanity check
|
|
reasons.push(
|
|
i18n.t('parameters.invoke.incompatibleBaseModelForControlAdapter', {
|
|
number: i + 1,
|
|
})
|
|
);
|
|
}
|
|
if (hasNoImage) {
|
|
reasons.push(
|
|
i18n.t('parameters.invoke.noControlImageForControlAdapter', {
|
|
number: i + 1,
|
|
})
|
|
);
|
|
}
|
|
if (imageNotProcessed) {
|
|
reasons.push(
|
|
i18n.t('parameters.invoke.imageNotProcessedForControlAdapter', {
|
|
number: i + 1,
|
|
})
|
|
);
|
|
}
|
|
if (ca.type === 't2i_adapter' && (size.width % 64 !== 0 || size.height % 64 !== 0)) {
|
|
reasons.push(i18n.t('parameters.invoke.t2iAdapterMismatchedDimensions'));
|
|
}
|
|
});
|
|
} else {
|
|
// Handling for all other tabs
|
|
selectControlAdapterAll(controlAdapters)
|
|
.filter((ca) => ca.isEnabled)
|
|
.forEach((ca, i) => {
|
|
if (!ca.isEnabled) {
|
|
return;
|
|
}
|
|
|
|
if (!ca.model) {
|
|
reasons.push(
|
|
i18n.t('parameters.invoke.noModelForControlAdapter', {
|
|
number: i + 1,
|
|
})
|
|
);
|
|
} else if (ca.model.base !== model?.base) {
|
|
// This should never happen, just a sanity check
|
|
reasons.push(
|
|
i18n.t('parameters.invoke.incompatibleBaseModelForControlAdapter', {
|
|
number: i + 1,
|
|
})
|
|
);
|
|
}
|
|
|
|
if (
|
|
!ca.controlImage ||
|
|
(isControlNetOrT2IAdapter(ca) && !ca.processedControlImage && ca.processorType !== 'none')
|
|
) {
|
|
reasons.push(
|
|
i18n.t('parameters.invoke.noControlImageForControlAdapter', {
|
|
number: i + 1,
|
|
})
|
|
);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
return { isReady: !reasons.length, reasons };
|
|
}
|
|
);
|
|
|
|
export const useIsReadyToEnqueue = () => {
|
|
const { isReady, reasons } = useAppSelector(selector);
|
|
return { isReady, reasons };
|
|
};
|