mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(nodes): add invocation classifications
Invocations now have a classification: - Stable: LTS - Beta: LTS planned, API may change - Prototype: No LTS planned, API may change, may be removed entirely The `@invocation` decorator has a new arg `classification`, and an enum `Classification` is added to `baseinvocation.py`. The default is Stable; this is a non-breaking change. The classification is presented in the node header as a hammer icon (Beta) or flask icon (prototype). The icon has a tooltip briefly describing the classification.
This commit is contained in:
@ -0,0 +1,68 @@
|
||||
import { Icon, Tooltip } from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaFlask } from 'react-icons/fa';
|
||||
import { useNodeClassification } from 'features/nodes/hooks/useNodeClassification';
|
||||
import { Classification } from 'features/nodes/types/common';
|
||||
import { FaHammer } from 'react-icons/fa6';
|
||||
|
||||
interface Props {
|
||||
nodeId: string;
|
||||
}
|
||||
|
||||
const InvocationNodeClassificationIcon = ({ nodeId }: Props) => {
|
||||
const classification = useNodeClassification(nodeId);
|
||||
|
||||
if (!classification || classification === 'stable') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
label={<ClassificationTooltipContent classification={classification} />}
|
||||
placement="top"
|
||||
shouldWrapChildren
|
||||
>
|
||||
<Icon
|
||||
as={getIcon(classification)}
|
||||
sx={{
|
||||
display: 'block',
|
||||
boxSize: 4,
|
||||
color: 'base.400',
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(InvocationNodeClassificationIcon);
|
||||
|
||||
const ClassificationTooltipContent = memo(
|
||||
({ classification }: { classification: Classification }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (classification === 'beta') {
|
||||
return t('nodes.betaDesc');
|
||||
}
|
||||
|
||||
if (classification === 'prototype') {
|
||||
return t('nodes.prototypeDesc');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
);
|
||||
|
||||
ClassificationTooltipContent.displayName = 'ClassificationTooltipContent';
|
||||
|
||||
const getIcon = (classification: Classification) => {
|
||||
if (classification === 'beta') {
|
||||
return FaHammer;
|
||||
}
|
||||
|
||||
if (classification === 'prototype') {
|
||||
return FaFlask;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
@ -5,6 +5,7 @@ import NodeTitle from 'features/nodes/components/flow/nodes/common/NodeTitle';
|
||||
import InvocationNodeCollapsedHandles from './InvocationNodeCollapsedHandles';
|
||||
import InvocationNodeInfoIcon from './InvocationNodeInfoIcon';
|
||||
import InvocationNodeStatusIndicator from './InvocationNodeStatusIndicator';
|
||||
import InvocationNodeClassificationIcon from 'features/nodes/components/flow/nodes/Invocation/InvocationNodeClassificationIcon';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
@ -31,6 +32,7 @@ const InvocationNodeHeader = ({ nodeId, isOpen }: Props) => {
|
||||
}}
|
||||
>
|
||||
<NodeCollapseButton nodeId={nodeId} isOpen={isOpen} />
|
||||
<InvocationNodeClassificationIcon nodeId={nodeId} />
|
||||
<NodeTitle nodeId={nodeId} />
|
||||
<Flex alignItems="center">
|
||||
<InvocationNodeStatusIndicator nodeId={nodeId} />
|
||||
|
@ -0,0 +1,23 @@
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useNodeClassification = (nodeId: string) => {
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createMemoizedSelector(stateSelector, ({ nodes }) => {
|
||||
const node = nodes.nodes.find((node) => node.id === nodeId);
|
||||
if (!isInvocationNode(node)) {
|
||||
return false;
|
||||
}
|
||||
const nodeTemplate = nodes.nodeTemplates[node?.data.type ?? ''];
|
||||
return nodeTemplate?.classification;
|
||||
}),
|
||||
[nodeId]
|
||||
);
|
||||
|
||||
const title = useAppSelector(selector);
|
||||
return title;
|
||||
};
|
@ -19,6 +19,9 @@ export const zColorField = z.object({
|
||||
});
|
||||
export type ColorField = z.infer<typeof zColorField>;
|
||||
|
||||
export const zClassification = z.enum(['stable', 'beta', 'prototype']);
|
||||
export type Classification = z.infer<typeof zClassification>;
|
||||
|
||||
export const zSchedulerField = z.enum([
|
||||
'euler',
|
||||
'deis',
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Edge, Node } from 'reactflow';
|
||||
import { z } from 'zod';
|
||||
import { zProgressImage } from './common';
|
||||
import { zClassification, zProgressImage } from './common';
|
||||
import {
|
||||
zFieldInputInstance,
|
||||
zFieldInputTemplate,
|
||||
@ -21,6 +21,7 @@ export const zInvocationTemplate = z.object({
|
||||
version: zSemVer,
|
||||
useCache: z.boolean(),
|
||||
nodePack: z.string().min(1).nullish(),
|
||||
classification: zClassification,
|
||||
});
|
||||
export type InvocationTemplate = z.infer<typeof zInvocationTemplate>;
|
||||
// #endregion
|
||||
|
@ -83,6 +83,7 @@ export const parseSchema = (
|
||||
const description = schema.description ?? '';
|
||||
const version = schema.version;
|
||||
const nodePack = schema.node_pack;
|
||||
const classification = schema.classification;
|
||||
|
||||
const inputs = reduce(
|
||||
schema.properties,
|
||||
@ -245,6 +246,7 @@ export const parseSchema = (
|
||||
outputs,
|
||||
useCache,
|
||||
nodePack,
|
||||
classification,
|
||||
};
|
||||
|
||||
Object.assign(invocationsAccumulator, { [type]: invocation });
|
||||
|
Reference in New Issue
Block a user