feat(ui): useful tooltips on invoke button

This commit is contained in:
psychedelicious
2023-08-20 23:07:55 +10:00
parent 2dfcba8654
commit 990b6b5f6a
7 changed files with 193 additions and 238 deletions

View File

@ -8,8 +8,8 @@ import {
import { memo, ReactNode } from 'react';
export interface IAIButtonProps extends ButtonProps {
tooltip?: string;
tooltipProps?: Omit<TooltipProps, 'children'>;
tooltip?: TooltipProps['label'];
tooltipProps?: Omit<TooltipProps, 'children' | 'label'>;
isChecked?: boolean;
children: ReactNode;
}

View File

@ -9,8 +9,8 @@ import { memo } from 'react';
export type IAIIconButtonProps = IconButtonProps & {
role?: string;
tooltip?: string;
tooltipProps?: Omit<TooltipProps, 'children'>;
tooltip?: TooltipProps['label'];
tooltipProps?: Omit<TooltipProps, 'children' | 'label'>;
isChecked?: boolean;
};

View File

@ -2,71 +2,104 @@ import { createSelector } from '@reduxjs/toolkit';
import { stateSelector } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
// import { validateSeedWeights } from 'common/util/seedWeightPairs';
import { isInvocationNode } from 'features/nodes/types/types';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { forEach } from 'lodash-es';
import { NON_REFINER_BASE_MODELS } from 'services/api/constants';
import { modelsApi } from '../../services/api/endpoints/models';
import { forEach, map } from 'lodash-es';
import { getConnectedEdges } from 'reactflow';
const readinessSelector = createSelector(
const selector = createSelector(
[stateSelector, activeTabNameSelector],
(state, activeTabName) => {
const { generation, system } = state;
const { initialImage } = generation;
const { generation, system, nodes } = state;
const { initialImage, model } = generation;
const { isProcessing, isConnected } = system;
let isReady = true;
const reasonsWhyNotReady: string[] = [];
const reasons: string[] = [];
if (activeTabName === 'img2img' && !initialImage) {
isReady = false;
reasonsWhyNotReady.push('No initial image selected');
}
const { isSuccess: mainModelsSuccessfullyLoaded } =
modelsApi.endpoints.getMainModels.select(NON_REFINER_BASE_MODELS)(state);
if (!mainModelsSuccessfullyLoaded) {
isReady = false;
reasonsWhyNotReady.push('Models are not loaded');
}
// TODO: job queue
// Cannot generate if already processing an image
if (isProcessing) {
isReady = false;
reasonsWhyNotReady.push('System Busy');
reasons.push('System busy');
}
// Cannot generate if not connected
if (!isConnected) {
isReady = false;
reasonsWhyNotReady.push('System Disconnected');
reasons.push('System disconnected');
}
// // Cannot generate variations without valid seed weights
// if (
// shouldGenerateVariations &&
// (!(validateSeedWeights(seedWeights) || seedWeights === '') || seed === -1)
// ) {
// isReady = false;
// reasonsWhyNotReady.push('Seed-Weights badly formatted.');
// }
if (activeTabName === 'img2img' && !initialImage) {
reasons.push('No initial image selected');
}
forEach(state.controlNet.controlNets, (controlNet, id) => {
if (!controlNet.model) {
isReady = false;
reasonsWhyNotReady.push(`ControlNet ${id} has no model selected.`);
if (activeTabName === 'nodes' && nodes.shouldValidateGraph) {
nodes.nodes.forEach((node) => {
if (!isInvocationNode(node)) {
return;
}
const nodeTemplate = nodes.nodeTemplates[node.data.type];
if (!nodeTemplate) {
// Node type not found
reasons.push('Missing node template');
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('Missing field template');
return;
}
if (fieldTemplate.required && !field.value && !hasConnection) {
reasons.push(
`${node.data.label || nodeTemplate.title} -> ${
field.label || fieldTemplate.title
} missing input`
);
return;
}
});
});
} else {
if (!model) {
reasons.push('No model selected');
}
});
// All good
return { isReady, reasonsWhyNotReady };
if (state.controlNet.isEnabled) {
map(state.controlNet.controlNets).forEach((controlNet, i) => {
if (!controlNet.isEnabled) {
return;
}
if (!controlNet.model) {
reasons.push(`ControlNet ${i + 1} has no model selected.`);
}
if (
!controlNet.controlImage ||
(!controlNet.processedControlImage &&
controlNet.processorType !== 'none')
) {
reasons.push(`ControlNet ${i + 1} has no control image`);
}
});
}
}
return { isReady: !reasons.length, isProcessing, reasons };
},
defaultSelectorOptions
);
export const useIsReadyToInvoke = () => {
const { isReady } = useAppSelector(readinessSelector);
return isReady;
const { isReady, isProcessing, reasons } = useAppSelector(selector);
return { isReady, isProcessing, reasons };
};