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:
psychedelicious
2023-12-12 15:04:13 +11:00
parent 22ccaa4e9a
commit 43f2837117
9 changed files with 656 additions and 36 deletions

View File

@ -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;
};

View File

@ -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} />

View File

@ -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;
};

View File

@ -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',

View File

@ -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

View File

@ -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 });