mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
260 lines
10 KiB
TypeScript
260 lines
10 KiB
TypeScript
import { useStore } from '@nanostores/react';
|
|
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 type { Layer } from 'features/controlLayers/store/types';
|
|
import { selectDynamicPromptsSlice } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
|
import { getShouldProcessPrompt } from 'features/dynamicPrompts/util/getShouldProcessPrompt';
|
|
import { $templates, selectNodesSlice } from 'features/nodes/store/nodesSlice';
|
|
import type { Templates } from 'features/nodes/store/types';
|
|
import { selectWorkflowSettingsSlice } from 'features/nodes/store/workflowSettingsSlice';
|
|
import { isInvocationNode } from 'features/nodes/types/invocation';
|
|
import { selectGenerationSlice } from 'features/parameters/store/generationSlice';
|
|
import { selectUpscalelice } from 'features/parameters/store/upscaleSlice';
|
|
import { selectSystemSlice } from 'features/system/store/systemSlice';
|
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
|
import i18n from 'i18next';
|
|
import { forEach, upperFirst } from 'lodash-es';
|
|
import { useMemo } from 'react';
|
|
import { getConnectedEdges } from 'reactflow';
|
|
|
|
const LAYER_TYPE_TO_TKEY: Record<Layer['type'], string> = {
|
|
initial_image_layer: 'controlLayers.globalInitialImage',
|
|
control_adapter_layer: 'controlLayers.globalControlAdapter',
|
|
ip_adapter_layer: 'controlLayers.globalIPAdapter',
|
|
regional_guidance_layer: 'controlLayers.regionalGuidance',
|
|
};
|
|
|
|
const createSelector = (templates: Templates) =>
|
|
createMemoizedSelector(
|
|
[
|
|
selectControlAdaptersSlice,
|
|
selectGenerationSlice,
|
|
selectSystemSlice,
|
|
selectNodesSlice,
|
|
selectWorkflowSettingsSlice,
|
|
selectDynamicPromptsSlice,
|
|
selectControlLayersSlice,
|
|
activeTabNameSelector,
|
|
selectUpscalelice,
|
|
],
|
|
(
|
|
controlAdapters,
|
|
generation,
|
|
system,
|
|
nodes,
|
|
workflowSettings,
|
|
dynamicPrompts,
|
|
controlLayers,
|
|
activeTabName,
|
|
upscale
|
|
) => {
|
|
const { model } = generation;
|
|
const { size } = controlLayers.present;
|
|
const { positivePrompt } = controlLayers.present;
|
|
|
|
const { isConnected } = system;
|
|
|
|
const reasons: { prefix?: string; content: string }[] = [];
|
|
|
|
// Cannot generate if not connected
|
|
if (!isConnected) {
|
|
reasons.push({ content: i18n.t('parameters.invoke.systemDisconnected') });
|
|
}
|
|
|
|
if (activeTabName === 'workflows') {
|
|
if (workflowSettings.shouldValidateGraph) {
|
|
if (!nodes.nodes.length) {
|
|
reasons.push({ content: i18n.t('parameters.invoke.noNodesInGraph') });
|
|
}
|
|
|
|
nodes.nodes.forEach((node) => {
|
|
if (!isInvocationNode(node)) {
|
|
return;
|
|
}
|
|
|
|
const nodeTemplate = templates[node.data.type];
|
|
|
|
if (!nodeTemplate) {
|
|
// Node type not found
|
|
reasons.push({ content: 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({ content: i18n.t('parameters.invoke.missingFieldTemplate') });
|
|
return;
|
|
}
|
|
|
|
if (fieldTemplate.required && field.value === undefined && !hasConnection) {
|
|
reasons.push({
|
|
content: 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({ content: i18n.t('parameters.invoke.noPrompts') });
|
|
}
|
|
|
|
if (!model) {
|
|
reasons.push({ content: i18n.t('parameters.invoke.noModelSelected') });
|
|
}
|
|
|
|
if (activeTabName === 'generation') {
|
|
// Handling for generation tab
|
|
controlLayers.present.layers
|
|
.filter((l) => l.isEnabled)
|
|
.forEach((l, i) => {
|
|
const layerLiteral = i18n.t('controlLayers.layers_one');
|
|
const layerNumber = i + 1;
|
|
const layerType = i18n.t(LAYER_TYPE_TO_TKEY[l.type]);
|
|
const prefix = `${layerLiteral} #${layerNumber} (${layerType})`;
|
|
const problems: string[] = [];
|
|
if (l.type === 'control_adapter_layer') {
|
|
// Must have model
|
|
if (!l.controlAdapter.model) {
|
|
problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoModelSelected'));
|
|
}
|
|
// Model base must match
|
|
if (l.controlAdapter.model?.base !== model?.base) {
|
|
problems.push(i18n.t('parameters.invoke.layer.controlAdapterIncompatibleBaseModel'));
|
|
}
|
|
// Must have a control image OR, if it has a processor, it must have a processed image
|
|
if (!l.controlAdapter.image) {
|
|
problems.push(i18n.t('parameters.invoke.layer.controlAdapterNoImageSelected'));
|
|
} else if (l.controlAdapter.processorConfig && !l.controlAdapter.processedImage) {
|
|
problems.push(i18n.t('parameters.invoke.layer.controlAdapterImageNotProcessed'));
|
|
}
|
|
// T2I Adapters require images have dimensions that are multiples of 64 (SD1.5) or 32 (SDXL)
|
|
if (l.controlAdapter.type === 't2i_adapter') {
|
|
const multiple = model?.base === 'sdxl' ? 32 : 64;
|
|
if (size.width % multiple !== 0 || size.height % multiple !== 0) {
|
|
problems.push(i18n.t('parameters.invoke.layer.t2iAdapterIncompatibleDimensions', { multiple }));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (l.type === 'ip_adapter_layer') {
|
|
// Must have model
|
|
if (!l.ipAdapter.model) {
|
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
|
|
}
|
|
// Model base must match
|
|
if (l.ipAdapter.model?.base !== model?.base) {
|
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
|
|
}
|
|
// Must have an image
|
|
if (!l.ipAdapter.image) {
|
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
|
|
}
|
|
}
|
|
|
|
if (l.type === 'initial_image_layer') {
|
|
// Must have an image
|
|
if (!l.image) {
|
|
problems.push(i18n.t('parameters.invoke.layer.initialImageNoImageSelected'));
|
|
}
|
|
}
|
|
|
|
if (l.type === 'regional_guidance_layer') {
|
|
// Must have a region
|
|
if (l.maskObjects.length === 0) {
|
|
problems.push(i18n.t('parameters.invoke.layer.rgNoRegion'));
|
|
}
|
|
// Must have at least 1 prompt or IP Adapter
|
|
if (l.positivePrompt === null && l.negativePrompt === null && l.ipAdapters.length === 0) {
|
|
problems.push(i18n.t('parameters.invoke.layer.rgNoPromptsOrIPAdapters'));
|
|
}
|
|
l.ipAdapters.forEach((ipAdapter) => {
|
|
// Must have model
|
|
if (!ipAdapter.model) {
|
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoModelSelected'));
|
|
}
|
|
// Model base must match
|
|
if (ipAdapter.model?.base !== model?.base) {
|
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterIncompatibleBaseModel'));
|
|
}
|
|
// Must have an image
|
|
if (!ipAdapter.image) {
|
|
problems.push(i18n.t('parameters.invoke.layer.ipAdapterNoImageSelected'));
|
|
}
|
|
});
|
|
}
|
|
|
|
if (problems.length) {
|
|
const content = upperFirst(problems.join(', '));
|
|
reasons.push({ prefix, content });
|
|
}
|
|
});
|
|
} else if (activeTabName === 'upscaling') {
|
|
if (!upscale.upscaleInitialImage) {
|
|
reasons.push({ content: 'No Initial image' });
|
|
}
|
|
if (!upscale.upscaleModel) {
|
|
reasons.push({ content: 'No upscale model selected' });
|
|
}
|
|
|
|
if (!upscale.tileControlnetModel) {
|
|
reasons.push({ content: 'No valid tile controlnet available' });
|
|
}
|
|
} else {
|
|
// Handling for all other tabs
|
|
selectControlAdapterAll(controlAdapters)
|
|
.filter((ca) => ca.isEnabled)
|
|
.forEach((ca, i) => {
|
|
if (!ca.isEnabled) {
|
|
return;
|
|
}
|
|
|
|
if (!ca.model) {
|
|
reasons.push({ content: 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({
|
|
content: i18n.t('parameters.invoke.incompatibleBaseModelForControlAdapter', { number: i + 1 }),
|
|
});
|
|
}
|
|
|
|
if (
|
|
!ca.controlImage ||
|
|
(isControlNetOrT2IAdapter(ca) && !ca.processedControlImage && ca.processorType !== 'none')
|
|
) {
|
|
reasons.push({
|
|
content: i18n.t('parameters.invoke.noControlImageForControlAdapter', { number: i + 1 }),
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
return { isReady: !reasons.length, reasons };
|
|
}
|
|
);
|
|
|
|
export const useIsReadyToEnqueue = () => {
|
|
const templates = useStore($templates);
|
|
const selector = useMemo(() => createSelector(templates), [templates]);
|
|
const value = useAppSelector(selector);
|
|
return value;
|
|
};
|