mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): useful tooltips on invoke button
This commit is contained in:
parent
2dfcba8654
commit
990b6b5f6a
@ -8,8 +8,8 @@ import {
|
|||||||
import { memo, ReactNode } from 'react';
|
import { memo, ReactNode } from 'react';
|
||||||
|
|
||||||
export interface IAIButtonProps extends ButtonProps {
|
export interface IAIButtonProps extends ButtonProps {
|
||||||
tooltip?: string;
|
tooltip?: TooltipProps['label'];
|
||||||
tooltipProps?: Omit<TooltipProps, 'children'>;
|
tooltipProps?: Omit<TooltipProps, 'children' | 'label'>;
|
||||||
isChecked?: boolean;
|
isChecked?: boolean;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,8 @@ import { memo } from 'react';
|
|||||||
|
|
||||||
export type IAIIconButtonProps = IconButtonProps & {
|
export type IAIIconButtonProps = IconButtonProps & {
|
||||||
role?: string;
|
role?: string;
|
||||||
tooltip?: string;
|
tooltip?: TooltipProps['label'];
|
||||||
tooltipProps?: Omit<TooltipProps, 'children'>;
|
tooltipProps?: Omit<TooltipProps, 'children' | 'label'>;
|
||||||
isChecked?: boolean;
|
isChecked?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2,71 +2,104 @@ import { createSelector } from '@reduxjs/toolkit';
|
|||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
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 { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
import { forEach } from 'lodash-es';
|
import { forEach, map } from 'lodash-es';
|
||||||
import { NON_REFINER_BASE_MODELS } from 'services/api/constants';
|
import { getConnectedEdges } from 'reactflow';
|
||||||
import { modelsApi } from '../../services/api/endpoints/models';
|
|
||||||
|
|
||||||
const readinessSelector = createSelector(
|
const selector = createSelector(
|
||||||
[stateSelector, activeTabNameSelector],
|
[stateSelector, activeTabNameSelector],
|
||||||
(state, activeTabName) => {
|
(state, activeTabName) => {
|
||||||
const { generation, system } = state;
|
const { generation, system, nodes } = state;
|
||||||
const { initialImage } = generation;
|
const { initialImage, model } = generation;
|
||||||
|
|
||||||
const { isProcessing, isConnected } = system;
|
const { isProcessing, isConnected } = system;
|
||||||
|
|
||||||
let isReady = true;
|
const reasons: string[] = [];
|
||||||
const reasonsWhyNotReady: 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
|
// Cannot generate if already processing an image
|
||||||
if (isProcessing) {
|
if (isProcessing) {
|
||||||
isReady = false;
|
reasons.push('System busy');
|
||||||
reasonsWhyNotReady.push('System Busy');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cannot generate if not connected
|
// Cannot generate if not connected
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
isReady = false;
|
reasons.push('System disconnected');
|
||||||
reasonsWhyNotReady.push('System Disconnected');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Cannot generate variations without valid seed weights
|
if (activeTabName === 'img2img' && !initialImage) {
|
||||||
// if (
|
reasons.push('No initial image selected');
|
||||||
// shouldGenerateVariations &&
|
}
|
||||||
// (!(validateSeedWeights(seedWeights) || seedWeights === '') || seed === -1)
|
|
||||||
// ) {
|
|
||||||
// isReady = false;
|
|
||||||
// reasonsWhyNotReady.push('Seed-Weights badly formatted.');
|
|
||||||
// }
|
|
||||||
|
|
||||||
forEach(state.controlNet.controlNets, (controlNet, id) => {
|
if (activeTabName === 'nodes' && nodes.shouldValidateGraph) {
|
||||||
if (!controlNet.model) {
|
nodes.nodes.forEach((node) => {
|
||||||
isReady = false;
|
if (!isInvocationNode(node)) {
|
||||||
reasonsWhyNotReady.push(`ControlNet ${id} has no model selected.`);
|
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
|
if (state.controlNet.isEnabled) {
|
||||||
return { isReady, reasonsWhyNotReady };
|
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
|
defaultSelectorOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
export const useIsReadyToInvoke = () => {
|
export const useIsReadyToInvoke = () => {
|
||||||
const { isReady } = useAppSelector(readinessSelector);
|
const { isReady, isProcessing, reasons } = useAppSelector(selector);
|
||||||
return isReady;
|
return { isReady, isProcessing, reasons };
|
||||||
};
|
};
|
||||||
|
@ -2,10 +2,9 @@ import { Box } from '@chakra-ui/react';
|
|||||||
import { userInvoked } from 'app/store/actions';
|
import { userInvoked } from 'app/store/actions';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import IAIButton, { IAIButtonProps } from 'common/components/IAIButton';
|
import IAIButton, { IAIButtonProps } from 'common/components/IAIButton';
|
||||||
import IAIIconButton, {
|
import { IAIIconButtonProps } from 'common/components/IAIIconButton';
|
||||||
IAIIconButtonProps,
|
import { useIsReadyToInvoke } from 'common/hooks/useIsReadyToInvoke';
|
||||||
} from 'common/components/IAIIconButton';
|
import { InvokeButtonTooltipContent } from 'features/parameters/components/ProcessButtons/InvokeButton';
|
||||||
import { selectIsReadyNodes } from 'features/nodes/store/selectors';
|
|
||||||
import ProgressBar from 'features/system/components/ProgressBar';
|
import ProgressBar from 'features/system/components/ProgressBar';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
@ -14,15 +13,13 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { FaPlay } from 'react-icons/fa';
|
import { FaPlay } from 'react-icons/fa';
|
||||||
|
|
||||||
interface InvokeButton
|
interface InvokeButton
|
||||||
extends Omit<IAIButtonProps | IAIIconButtonProps, 'aria-label'> {
|
extends Omit<IAIButtonProps | IAIIconButtonProps, 'aria-label'> {}
|
||||||
iconButton?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const NodeInvokeButton = (props: InvokeButton) => {
|
const NodeInvokeButton = (props: InvokeButton) => {
|
||||||
const { iconButton = false, ...rest } = props;
|
const { ...rest } = props;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||||
const isReady = useAppSelector(selectIsReadyNodes);
|
const { isReady, isProcessing } = useIsReadyToInvoke();
|
||||||
const handleInvoke = useCallback(() => {
|
const handleInvoke = useCallback(() => {
|
||||||
dispatch(userInvoked('nodes'));
|
dispatch(userInvoked('nodes'));
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
@ -58,37 +55,24 @@ const NodeInvokeButton = (props: InvokeButton) => {
|
|||||||
<ProgressBar />
|
<ProgressBar />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
{iconButton ? (
|
<IAIButton
|
||||||
<IAIIconButton
|
tooltip={<InvokeButtonTooltipContent />}
|
||||||
aria-label={t('parameters.invoke')}
|
aria-label={t('parameters.invoke')}
|
||||||
type="submit"
|
type="submit"
|
||||||
icon={<FaPlay />}
|
isDisabled={!isReady}
|
||||||
isDisabled={!isReady}
|
onClick={handleInvoke}
|
||||||
onClick={handleInvoke}
|
flexGrow={1}
|
||||||
flexGrow={1}
|
w="100%"
|
||||||
w="100%"
|
colorScheme="accent"
|
||||||
tooltip={t('parameters.invoke')}
|
id="invoke-button"
|
||||||
tooltipProps={{ placement: 'bottom' }}
|
leftIcon={isProcessing ? undefined : <FaPlay />}
|
||||||
colorScheme="accent"
|
fontWeight={700}
|
||||||
id="invoke-button"
|
isLoading={isProcessing}
|
||||||
{...rest}
|
loadingText={t('parameters.invoke')}
|
||||||
/>
|
{...rest}
|
||||||
) : (
|
>
|
||||||
<IAIButton
|
Invoke
|
||||||
aria-label={t('parameters.invoke')}
|
</IAIButton>
|
||||||
type="submit"
|
|
||||||
isDisabled={!isReady}
|
|
||||||
onClick={handleInvoke}
|
|
||||||
flexGrow={1}
|
|
||||||
w="100%"
|
|
||||||
colorScheme="accent"
|
|
||||||
id="invoke-button"
|
|
||||||
fontWeight={700}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
Invoke
|
|
||||||
</IAIButton>
|
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
import { addNodePopoverOpened } from 'features/nodes/store/nodesSlice';
|
import { addNodePopoverOpened } from 'features/nodes/store/nodesSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { FaPlus } from 'react-icons/fa';
|
|
||||||
import { Panel } from 'reactflow';
|
import { Panel } from 'reactflow';
|
||||||
|
|
||||||
const TopLeftPanel = () => {
|
const TopLeftPanel = () => {
|
||||||
@ -14,12 +13,9 @@ const TopLeftPanel = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Panel position="top-left">
|
<Panel position="top-left">
|
||||||
<IAIIconButton
|
<IAIButton aria-label="Add Node" onClick={handleOpenAddNodePopover}>
|
||||||
aria-label="Add Node"
|
Add Node
|
||||||
tooltip="Add Node"
|
</IAIButton>
|
||||||
onClick={handleOpenAddNodePopover}
|
|
||||||
icon={<FaPlus />}
|
|
||||||
/>
|
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { stateSelector } from 'app/store/store';
|
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
|
||||||
// import { validateSeedWeights } from 'common/util/seedWeightPairs';
|
|
||||||
import { every } from 'lodash-es';
|
|
||||||
import { getConnectedEdges } from 'reactflow';
|
|
||||||
import { isInvocationNode } from '../types/types';
|
|
||||||
import { NodesState } from './types';
|
|
||||||
|
|
||||||
export const selectIsReadyNodes = createSelector(
|
|
||||||
[stateSelector],
|
|
||||||
(state) => {
|
|
||||||
const { nodes, system } = state;
|
|
||||||
const { isProcessing, isConnected } = system;
|
|
||||||
|
|
||||||
if (isProcessing || !isConnected) {
|
|
||||||
// Cannot generate if already processing an image
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!nodes.shouldValidateGraph) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isGraphReady = every(nodes.nodes, (node) => {
|
|
||||||
if (!isInvocationNode(node)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodeTemplate = nodes.nodeTemplates[node.data.type];
|
|
||||||
|
|
||||||
if (!nodeTemplate) {
|
|
||||||
// Node type not found
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const connectedEdges = getConnectedEdges([node], nodes.edges);
|
|
||||||
|
|
||||||
const isNodeValid = every(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) {
|
|
||||||
// Field type not found
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fieldTemplate.required && !field.value && !hasConnection) {
|
|
||||||
// Required field is empty or does not have a connection
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ok
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return isNodeValid;
|
|
||||||
});
|
|
||||||
|
|
||||||
return isGraphReady;
|
|
||||||
},
|
|
||||||
defaultSelectorOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
export const getNodeAndTemplate = (nodeId: string, nodes: NodesState) => {
|
|
||||||
const node = nodes.nodes.find((node) => node.id === nodeId);
|
|
||||||
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
|
|
||||||
|
|
||||||
return { node, nodeTemplate };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getInputFieldAndTemplate = (
|
|
||||||
nodeId: string,
|
|
||||||
fieldName: string,
|
|
||||||
nodes: NodesState
|
|
||||||
) => {
|
|
||||||
const node = nodes.nodes
|
|
||||||
.filter(isInvocationNode)
|
|
||||||
.find((node) => node.id === nodeId);
|
|
||||||
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
|
|
||||||
|
|
||||||
if (!node || !nodeTemplate) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const field = node.data.inputs[fieldName];
|
|
||||||
const fieldTemplate = nodeTemplate.inputs[fieldName];
|
|
||||||
|
|
||||||
return { field, fieldTemplate };
|
|
||||||
};
|
|
@ -1,4 +1,12 @@
|
|||||||
import { Box, ChakraProps, Tooltip } from '@chakra-ui/react';
|
import {
|
||||||
|
Box,
|
||||||
|
ChakraProps,
|
||||||
|
Divider,
|
||||||
|
Flex,
|
||||||
|
ListItem,
|
||||||
|
Text,
|
||||||
|
UnorderedList,
|
||||||
|
} from '@chakra-ui/react';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { userInvoked } from 'app/store/actions';
|
import { userInvoked } from 'app/store/actions';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
@ -13,7 +21,7 @@ import { clampSymmetrySteps } from 'features/parameters/store/generationSlice';
|
|||||||
import ProgressBar from 'features/system/components/ProgressBar';
|
import ProgressBar from 'features/system/components/ProgressBar';
|
||||||
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
import { selectIsBusy } from 'features/system/store/systemSelectors';
|
||||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||||
import { useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaPlay } from 'react-icons/fa';
|
import { FaPlay } from 'react-icons/fa';
|
||||||
@ -53,9 +61,8 @@ interface InvokeButton
|
|||||||
export default function InvokeButton(props: InvokeButton) {
|
export default function InvokeButton(props: InvokeButton) {
|
||||||
const { iconButton = false, ...rest } = props;
|
const { iconButton = false, ...rest } = props;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const isReady = useIsReadyToInvoke();
|
const { isReady, isProcessing } = useIsReadyToInvoke();
|
||||||
const { isBusy, autoAddBoardId, activeTabName } = useAppSelector(selector);
|
const { activeTabName } = useAppSelector(selector);
|
||||||
const autoAddBoardName = useBoardName(autoAddBoardId);
|
|
||||||
|
|
||||||
const handleInvoke = useCallback(() => {
|
const handleInvoke = useCallback(() => {
|
||||||
dispatch(clampSymmetrySteps());
|
dispatch(clampSymmetrySteps());
|
||||||
@ -94,53 +101,80 @@ export default function InvokeButton(props: InvokeButton) {
|
|||||||
<ProgressBar />
|
<ProgressBar />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
<Tooltip
|
{iconButton ? (
|
||||||
placement="top"
|
<IAIIconButton
|
||||||
hasArrow
|
aria-label={t('parameters.invoke')}
|
||||||
openDelay={500}
|
type="submit"
|
||||||
label={
|
icon={<FaPlay />}
|
||||||
autoAddBoardId ? `Auto-Adding to ${autoAddBoardName}` : undefined
|
isDisabled={!isReady}
|
||||||
}
|
onClick={handleInvoke}
|
||||||
>
|
tooltip={<InvokeButtonTooltipContent />}
|
||||||
{iconButton ? (
|
colorScheme="accent"
|
||||||
<IAIIconButton
|
isLoading={isProcessing}
|
||||||
aria-label={t('parameters.invoke')}
|
id="invoke-button"
|
||||||
type="submit"
|
{...rest}
|
||||||
icon={<FaPlay />}
|
sx={{
|
||||||
isDisabled={!isReady || isBusy}
|
w: 'full',
|
||||||
onClick={handleInvoke}
|
flexGrow: 1,
|
||||||
tooltip={t('parameters.invoke')}
|
...(isProcessing ? IN_PROGRESS_STYLES : {}),
|
||||||
tooltipProps={{ placement: 'top' }}
|
}}
|
||||||
colorScheme="accent"
|
/>
|
||||||
id="invoke-button"
|
) : (
|
||||||
{...rest}
|
<IAIButton
|
||||||
sx={{
|
tooltip={<InvokeButtonTooltipContent />}
|
||||||
w: 'full',
|
aria-label={t('parameters.invoke')}
|
||||||
flexGrow: 1,
|
type="submit"
|
||||||
...(isBusy ? IN_PROGRESS_STYLES : {}),
|
isDisabled={!isReady}
|
||||||
}}
|
onClick={handleInvoke}
|
||||||
/>
|
colorScheme="accent"
|
||||||
) : (
|
id="invoke-button"
|
||||||
<IAIButton
|
leftIcon={isProcessing ? undefined : <FaPlay />}
|
||||||
aria-label={t('parameters.invoke')}
|
isLoading={isProcessing}
|
||||||
type="submit"
|
loadingText={t('parameters.invoke')}
|
||||||
isDisabled={!isReady || isBusy}
|
{...rest}
|
||||||
onClick={handleInvoke}
|
sx={{
|
||||||
colorScheme="accent"
|
w: 'full',
|
||||||
id="invoke-button"
|
flexGrow: 1,
|
||||||
{...rest}
|
fontWeight: 700,
|
||||||
sx={{
|
...(isProcessing ? IN_PROGRESS_STYLES : {}),
|
||||||
w: 'full',
|
}}
|
||||||
flexGrow: 1,
|
>
|
||||||
fontWeight: 700,
|
Invoke
|
||||||
...(isBusy ? IN_PROGRESS_STYLES : {}),
|
</IAIButton>
|
||||||
}}
|
)}
|
||||||
>
|
|
||||||
Invoke
|
|
||||||
</IAIButton>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const InvokeButtonTooltipContent = memo(() => {
|
||||||
|
const { isReady, reasons } = useIsReadyToInvoke();
|
||||||
|
const { autoAddBoardId } = useAppSelector(selector);
|
||||||
|
const autoAddBoardName = useBoardName(autoAddBoardId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex flexDir="column" gap={1}>
|
||||||
|
<Text fontWeight={600}>
|
||||||
|
{isReady ? 'Ready to Invoke' : 'Unable to Invoke'}
|
||||||
|
</Text>
|
||||||
|
{reasons.length > 0 && (
|
||||||
|
<UnorderedList>
|
||||||
|
{reasons.map((reason, i) => (
|
||||||
|
<ListItem key={`${reason}.${i}`}>
|
||||||
|
<Text fontWeight={400}>{reason}</Text>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</UnorderedList>
|
||||||
|
)}
|
||||||
|
<Divider opacity={0.2} />
|
||||||
|
<Text fontWeight={400} fontStyle="oblique 10deg">
|
||||||
|
Adding images to{' '}
|
||||||
|
<Text as="span" fontWeight={600}>
|
||||||
|
{autoAddBoardName || 'Uncategorized'}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
InvokeButtonTooltipContent.displayName = 'InvokeButtonTooltipContent';
|
||||||
|
Loading…
Reference in New Issue
Block a user