mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Compare commits
5 Commits
Invoke-Upd
...
feat/ui/wo
Author | SHA1 | Date | |
---|---|---|---|
2c979d1b68 | |||
7b93b5e928 | |||
dc44debbab | |||
5ce2dc3a58 | |||
27fd9071ba |
@ -49,6 +49,7 @@
|
|||||||
"back": "Back",
|
"back": "Back",
|
||||||
"batch": "Batch Manager",
|
"batch": "Batch Manager",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
"clickToEdit": "Click to Edit",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"on": "On",
|
"on": "On",
|
||||||
"communityLabel": "Community",
|
"communityLabel": "Community",
|
||||||
@ -853,7 +854,7 @@
|
|||||||
"noConnectionData": "No connection data",
|
"noConnectionData": "No connection data",
|
||||||
"noConnectionInProgress": "No connection in progress",
|
"noConnectionInProgress": "No connection in progress",
|
||||||
"node": "Node",
|
"node": "Node",
|
||||||
"nodeOutputs": "Node Outputs",
|
"nodeOutputs": "Node Results",
|
||||||
"nodeSearch": "Search for nodes",
|
"nodeSearch": "Search for nodes",
|
||||||
"nodeTemplate": "Node Template",
|
"nodeTemplate": "Node Template",
|
||||||
"nodeType": "Node Type",
|
"nodeType": "Node Type",
|
||||||
@ -863,9 +864,9 @@
|
|||||||
"noMatchingNodes": "No matching nodes",
|
"noMatchingNodes": "No matching nodes",
|
||||||
"noNodeSelected": "No node selected",
|
"noNodeSelected": "No node selected",
|
||||||
"nodeOpacity": "Node Opacity",
|
"nodeOpacity": "Node Opacity",
|
||||||
"noOutputRecorded": "No outputs recorded",
|
"noOutputRecorded": "No results recorded",
|
||||||
"noOutputSchemaName": "No output schema name found in ref object",
|
"noOutputSchemaName": "No output schema name found in ref object",
|
||||||
"notes": "Notes",
|
"notes": "Node Notes",
|
||||||
"notesDescription": "Add notes about your workflow",
|
"notesDescription": "Add notes about your workflow",
|
||||||
"oNNXModelField": "ONNX Model",
|
"oNNXModelField": "ONNX Model",
|
||||||
"oNNXModelFieldDescription": "ONNX model field.",
|
"oNNXModelFieldDescription": "ONNX model field.",
|
||||||
@ -943,7 +944,12 @@
|
|||||||
"workflowValidation": "Workflow Validation Error",
|
"workflowValidation": "Workflow Validation Error",
|
||||||
"workflowVersion": "Version",
|
"workflowVersion": "Version",
|
||||||
"zoomInNodes": "Zoom In",
|
"zoomInNodes": "Zoom In",
|
||||||
"zoomOutNodes": "Zoom Out"
|
"zoomOutNodes": "Zoom Out",
|
||||||
|
"tabDetails": "Details",
|
||||||
|
"tabNotes": "Notes",
|
||||||
|
"tabResults": "Results",
|
||||||
|
"tabData": "Data",
|
||||||
|
"tabTemplate": "Template"
|
||||||
},
|
},
|
||||||
"parameters": {
|
"parameters": {
|
||||||
"aspectRatio": "Aspect Ratio",
|
"aspectRatio": "Aspect Ratio",
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { FormControl, FormLabel } from '@chakra-ui/react';
|
import { FormControl, FormLabel, Flex } from '@chakra-ui/react';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import IAITextarea from 'common/components/IAITextarea';
|
import IAITextarea from 'common/components/IAITextarea';
|
||||||
import { useNodeData } from 'features/nodes/hooks/useNodeData';
|
import { useNodeNotes } from 'features/nodes/hooks/useNodeNotes';
|
||||||
import { nodeNotesChanged } from 'features/nodes/store/nodesSlice';
|
import { nodeNotesChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { isInvocationNodeData } from 'features/nodes/types/types';
|
import { isNil } from 'lodash-es';
|
||||||
import { ChangeEvent, memo, useCallback } from 'react';
|
import { ChangeEvent, memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const NotesTextarea = ({ nodeId }: { nodeId: string }) => {
|
const NotesTextarea = ({ nodeId }: { nodeId: string }) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const data = useNodeData(nodeId);
|
const notes = useNodeNotes(nodeId);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const handleNotesChanged = useCallback(
|
const handleNotesChanged = useCallback(
|
||||||
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
@ -17,16 +17,17 @@ const NotesTextarea = ({ nodeId }: { nodeId: string }) => {
|
|||||||
},
|
},
|
||||||
[dispatch, nodeId]
|
[dispatch, nodeId]
|
||||||
);
|
);
|
||||||
if (!isInvocationNodeData(data)) {
|
if (isNil(notes)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<FormControl>
|
<FormControl as={Flex} sx={{ flexDir: 'column', h: 'full' }}>
|
||||||
<FormLabel>{t('nodes.notes')}</FormLabel>
|
<FormLabel>{t('nodes.notes')}</FormLabel>
|
||||||
<IAITextarea
|
<IAITextarea
|
||||||
value={data?.notes}
|
value={notes}
|
||||||
onChange={handleNotesChanged}
|
onChange={handleNotesChanged}
|
||||||
rows={10}
|
resize="none"
|
||||||
|
h="full"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
|
@ -47,8 +47,8 @@ const FieldHandle = (props: FieldHandleProps) => {
|
|||||||
isConnectionStartField,
|
isConnectionStartField,
|
||||||
connectionError,
|
connectionError,
|
||||||
} = props;
|
} = props;
|
||||||
const { name, type } = fieldTemplate;
|
const { name, type, originalType } = fieldTemplate;
|
||||||
const { color: typeColor, title } = FIELDS[type];
|
const { color: typeColor } = FIELDS[type];
|
||||||
|
|
||||||
const styles: CSSProperties = useMemo(() => {
|
const styles: CSSProperties = useMemo(() => {
|
||||||
const isCollectionType = COLLECTION_TYPES.includes(type);
|
const isCollectionType = COLLECTION_TYPES.includes(type);
|
||||||
@ -102,13 +102,18 @@ const FieldHandle = (props: FieldHandleProps) => {
|
|||||||
|
|
||||||
const tooltip = useMemo(() => {
|
const tooltip = useMemo(() => {
|
||||||
if (isConnectionInProgress && isConnectionStartField) {
|
if (isConnectionInProgress && isConnectionStartField) {
|
||||||
return title;
|
return originalType;
|
||||||
}
|
}
|
||||||
if (isConnectionInProgress && connectionError) {
|
if (isConnectionInProgress && connectionError) {
|
||||||
return connectionError ?? title;
|
return connectionError ?? originalType;
|
||||||
}
|
}
|
||||||
return title;
|
return originalType;
|
||||||
}, [connectionError, isConnectionInProgress, isConnectionStartField, title]);
|
}, [
|
||||||
|
connectionError,
|
||||||
|
isConnectionInProgress,
|
||||||
|
isConnectionStartField,
|
||||||
|
originalType,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Flex, Text } from '@chakra-ui/react';
|
import { Flex, Text } from '@chakra-ui/react';
|
||||||
import { useFieldData } from 'features/nodes/hooks/useFieldData';
|
import { useFieldData } from 'features/nodes/hooks/useFieldData';
|
||||||
import { useFieldTemplate } from 'features/nodes/hooks/useFieldTemplate';
|
import { useFieldTemplate } from 'features/nodes/hooks/useFieldTemplate';
|
||||||
import { FIELDS } from 'features/nodes/types/constants';
|
|
||||||
import {
|
import {
|
||||||
isInputFieldTemplate,
|
isInputFieldTemplate,
|
||||||
isInputFieldValue,
|
isInputFieldValue,
|
||||||
@ -9,7 +8,6 @@ import {
|
|||||||
import { startCase } from 'lodash-es';
|
import { startCase } from 'lodash-es';
|
||||||
import { memo, useMemo } from 'react';
|
import { memo, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
@ -49,7 +47,7 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => {
|
|||||||
{fieldTemplate.description}
|
{fieldTemplate.description}
|
||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
{fieldTemplate && <Text>Type: {FIELDS[fieldTemplate.type].title}</Text>}
|
{fieldTemplate && <Text>Type: {fieldTemplate.originalType}</Text>}
|
||||||
{isInputTemplate && <Text>Input: {startCase(fieldTemplate.input)}</Text>}
|
{isInputTemplate && <Text>Input: {startCase(fieldTemplate.input)}</Text>}
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -28,6 +28,10 @@ const NodeTitle = ({ nodeId, title }: Props) => {
|
|||||||
const [localTitle, setLocalTitle] = useState('');
|
const [localTitle, setLocalTitle] = useState('');
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
async (newTitle: string) => {
|
async (newTitle: string) => {
|
||||||
|
if (!newTitle.trim()) {
|
||||||
|
setLocalTitle(label || templateTitle || t('nodes.problemSettingTitle'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
dispatch(nodeLabelChanged({ nodeId, label: newTitle }));
|
dispatch(nodeLabelChanged({ nodeId, label: newTitle }));
|
||||||
setLocalTitle(
|
setLocalTitle(
|
||||||
label || title || templateTitle || t('nodes.problemSettingTitle')
|
label || title || templateTitle || t('nodes.problemSettingTitle')
|
||||||
|
@ -22,9 +22,8 @@ import { memo } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaSync } from 'react-icons/fa';
|
import { FaSync } from 'react-icons/fa';
|
||||||
import { Node } from 'reactflow';
|
import { Node } from 'reactflow';
|
||||||
import NotesTextarea from '../../flow/nodes/Invocation/NotesTextarea';
|
|
||||||
import ScrollableContent from '../ScrollableContent';
|
import ScrollableContent from '../ScrollableContent';
|
||||||
import EditableNodeTitle from './details/EditableNodeTitle';
|
import InputFields from './details/InputFields';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
stateSelector,
|
stateSelector,
|
||||||
@ -82,42 +81,23 @@ const Content = (props: {
|
|||||||
sx={{
|
sx={{
|
||||||
flexDir: 'column',
|
flexDir: 'column',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
p: 1,
|
|
||||||
gap: 2,
|
gap: 2,
|
||||||
w: 'full',
|
w: 'full',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EditableNodeTitle nodeId={props.node.data.id} />
|
<FormControl>
|
||||||
<HStack>
|
<FormLabel>Type</FormLabel>
|
||||||
<FormControl>
|
<Text fontSize="sm" fontWeight={600}>
|
||||||
<FormLabel>Node Type</FormLabel>
|
{props.template.title} ({props.template.type})
|
||||||
<Text fontSize="sm" fontWeight={600}>
|
</Text>
|
||||||
{props.template.title}
|
</FormControl>
|
||||||
</Text>
|
<FormControl>
|
||||||
</FormControl>
|
<FormLabel>Description</FormLabel>
|
||||||
<Flex
|
<Text fontSize="sm" fontWeight={600}>
|
||||||
flexDir="row"
|
{props.template.description}
|
||||||
alignItems="center"
|
</Text>
|
||||||
justifyContent="space-between"
|
</FormControl>
|
||||||
w="full"
|
<InputFields nodeId={props.node.id} />
|
||||||
>
|
|
||||||
<FormControl isInvalid={needsUpdate}>
|
|
||||||
<FormLabel>Node Version</FormLabel>
|
|
||||||
<Text fontSize="sm" fontWeight={600}>
|
|
||||||
{props.node.data.version}
|
|
||||||
</Text>
|
|
||||||
</FormControl>
|
|
||||||
{needsUpdate && (
|
|
||||||
<IAIIconButton
|
|
||||||
aria-label={t('nodes.updateNode')}
|
|
||||||
tooltip={t('nodes.updateNode')}
|
|
||||||
icon={<FaSync />}
|
|
||||||
onClick={updateNode}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
</HStack>
|
|
||||||
<NotesTextarea nodeId={props.node.data.id} />
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</ScrollableContent>
|
</ScrollableContent>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -0,0 +1,43 @@
|
|||||||
|
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 { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||||
|
import { isInvocationNode } from 'features/nodes/types/types';
|
||||||
|
import { memo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import NotesTextarea from '../../flow/nodes/Invocation/NotesTextarea';
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
stateSelector,
|
||||||
|
({ nodes }) => {
|
||||||
|
const lastSelectedNodeId =
|
||||||
|
nodes.selectedNodes[nodes.selectedNodes.length - 1];
|
||||||
|
|
||||||
|
const lastSelectedNode = nodes.nodes.find(
|
||||||
|
(node) => node.id === lastSelectedNodeId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isInvocationNode(lastSelectedNode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastSelectedNode.id;
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
const InspectorNotesTab = () => {
|
||||||
|
const nodeId = useAppSelector(selector);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (!nodeId) {
|
||||||
|
return (
|
||||||
|
<IAINoContentFallback label={t('nodes.noNodeSelected')} icon={null} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <NotesTextarea nodeId={nodeId} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(InspectorNotesTab);
|
@ -6,13 +6,41 @@ import {
|
|||||||
TabPanels,
|
TabPanels,
|
||||||
Tabs,
|
Tabs,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
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 { isInvocationNode } from 'features/nodes/types/types';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import InspectorDataTab from './InspectorDataTab';
|
import InspectorDataTab from './InspectorDataTab';
|
||||||
import InspectorOutputsTab from './InspectorOutputsTab';
|
|
||||||
import InspectorTemplateTab from './InspectorTemplateTab';
|
|
||||||
import InspectorDetailsTab from './InspectorDetailsTab';
|
import InspectorDetailsTab from './InspectorDetailsTab';
|
||||||
|
import InspectorNotesTab from './InspectorNotesTab';
|
||||||
|
import InspectorResultsTab from './InspectorResultsTab';
|
||||||
|
import InspectorTemplateTab from './InspectorTemplateTab';
|
||||||
|
import EditableNodeTitle from './details/EditableNodeTitle';
|
||||||
|
|
||||||
|
const selector = createSelector(
|
||||||
|
stateSelector,
|
||||||
|
({ nodes }) => {
|
||||||
|
const lastSelectedNodeId =
|
||||||
|
nodes.selectedNodes[nodes.selectedNodes.length - 1];
|
||||||
|
|
||||||
|
const lastSelectedNode = nodes.nodes.find(
|
||||||
|
(node) => node.id === lastSelectedNodeId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isInvocationNode(lastSelectedNode)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastSelectedNode.id;
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
);
|
||||||
const InspectorPanel = () => {
|
const InspectorPanel = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const nodeId = useAppSelector(selector);
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
layerStyle="first"
|
layerStyle="first"
|
||||||
@ -25,15 +53,17 @@ const InspectorPanel = () => {
|
|||||||
gap: 2,
|
gap: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<EditableNodeTitle nodeId={nodeId} />
|
||||||
<Tabs
|
<Tabs
|
||||||
variant="line"
|
variant="line"
|
||||||
sx={{ display: 'flex', flexDir: 'column', w: 'full', h: 'full' }}
|
sx={{ display: 'flex', flexDir: 'column', w: 'full', h: 'full' }}
|
||||||
>
|
>
|
||||||
<TabList>
|
<TabList>
|
||||||
<Tab>Details</Tab>
|
<Tab>{t('nodes.tabDetails')}</Tab>
|
||||||
<Tab>Outputs</Tab>
|
<Tab>{t('nodes.tabNotes')}</Tab>
|
||||||
<Tab>Data</Tab>
|
<Tab>{t('nodes.tabResults')}</Tab>
|
||||||
<Tab>Template</Tab>
|
<Tab>{t('nodes.tabData')}</Tab>
|
||||||
|
<Tab>{t('nodes.tabTemplate')}</Tab>
|
||||||
</TabList>
|
</TabList>
|
||||||
|
|
||||||
<TabPanels>
|
<TabPanels>
|
||||||
@ -41,7 +71,10 @@ const InspectorPanel = () => {
|
|||||||
<InspectorDetailsTab />
|
<InspectorDetailsTab />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<InspectorOutputsTab />
|
<InspectorNotesTab />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
<InspectorResultsTab />
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel>
|
<TabPanel>
|
||||||
<InspectorDataTab />
|
<InspectorDataTab />
|
||||||
|
@ -39,7 +39,7 @@ const selector = createSelector(
|
|||||||
defaultSelectorOptions
|
defaultSelectorOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
const InspectorOutputsTab = () => {
|
const InspectorResultsTab = () => {
|
||||||
const { node, template, nes } = useAppSelector(selector);
|
const { node, template, nes } = useAppSelector(selector);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@ -91,6 +91,6 @@ const InspectorOutputsTab = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(InspectorOutputsTab);
|
export default memo(InspectorResultsTab);
|
||||||
|
|
||||||
const getKey = (result: AnyResult, i: number) => `${result.type}-${i}`;
|
const getKey = (result: AnyResult, i: number) => `${result.type}-${i}`;
|
@ -3,20 +3,89 @@ import {
|
|||||||
EditableInput,
|
EditableInput,
|
||||||
EditablePreview,
|
EditablePreview,
|
||||||
Flex,
|
Flex,
|
||||||
|
Text,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import { useNodeLabel } from 'features/nodes/hooks/useNodeLabel';
|
import { useNodeLabel } from 'features/nodes/hooks/useNodeLabel';
|
||||||
import { useNodeTemplateTitle } from 'features/nodes/hooks/useNodeTemplateTitle';
|
import { useNodeTemplateTitle } from 'features/nodes/hooks/useNodeTemplateTitle';
|
||||||
|
import { useNodeVersion } from 'features/nodes/hooks/useNodeVersion';
|
||||||
import { nodeLabelChanged } from 'features/nodes/store/nodesSlice';
|
import { nodeLabelChanged } from 'features/nodes/store/nodesSlice';
|
||||||
import { memo, useCallback, useEffect, useState } from 'react';
|
import { memo, useCallback, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { FaSync } from 'react-icons/fa';
|
||||||
|
|
||||||
type Props = {
|
type EditableNodeTitleProps = {
|
||||||
nodeId: string;
|
nodeId?: string;
|
||||||
title?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const EditableNodeTitle = ({ nodeId, title }: Props) => {
|
const EditableNodeTitle = (props: EditableNodeTitleProps) => {
|
||||||
|
if (!props.nodeId) {
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
px: 1,
|
||||||
|
color: 'base.700',
|
||||||
|
_dark: { color: 'base.200' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
No node selected
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex
|
||||||
|
sx={{
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
px: 1,
|
||||||
|
color: 'base.700',
|
||||||
|
_dark: { color: 'base.200' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<EditableTitle nodeId={props.nodeId} />
|
||||||
|
<Version nodeId={props.nodeId} />
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type VersionProps = {
|
||||||
|
nodeId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Version = memo(({ nodeId }: VersionProps) => {
|
||||||
|
const { version, needsUpdate, updateNode } = useNodeVersion(nodeId);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex alignItems="center" gap={1}>
|
||||||
|
<Text variant={needsUpdate ? 'error' : 'subtext'} fontWeight={600}>
|
||||||
|
v{version}
|
||||||
|
</Text>
|
||||||
|
{needsUpdate && (
|
||||||
|
<IAIIconButton
|
||||||
|
size="sm"
|
||||||
|
aria-label={t('nodes.updateNode')}
|
||||||
|
tooltip={t('nodes.updateNode')}
|
||||||
|
icon={<FaSync />}
|
||||||
|
variant="link"
|
||||||
|
onClick={updateNode}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Version.displayName = 'Version';
|
||||||
|
|
||||||
|
type EditableTitleProps = {
|
||||||
|
nodeId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EditableTitle = memo(({ nodeId }: EditableTitleProps) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const label = useNodeLabel(nodeId);
|
const label = useNodeLabel(nodeId);
|
||||||
const templateTitle = useNodeTemplateTitle(nodeId);
|
const templateTitle = useNodeTemplateTitle(nodeId);
|
||||||
@ -25,12 +94,14 @@ const EditableNodeTitle = ({ nodeId, title }: Props) => {
|
|||||||
const [localTitle, setLocalTitle] = useState('');
|
const [localTitle, setLocalTitle] = useState('');
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
async (newTitle: string) => {
|
async (newTitle: string) => {
|
||||||
|
if (!newTitle.trim()) {
|
||||||
|
setLocalTitle(label || templateTitle || t('nodes.problemSettingTitle'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
dispatch(nodeLabelChanged({ nodeId, label: newTitle }));
|
dispatch(nodeLabelChanged({ nodeId, label: newTitle }));
|
||||||
setLocalTitle(
|
setLocalTitle(label || templateTitle || t('nodes.problemSettingTitle'));
|
||||||
label || title || templateTitle || t('nodes.problemSettingTitle')
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
[dispatch, nodeId, title, templateTitle, label, t]
|
[dispatch, nodeId, templateTitle, label, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChange = useCallback((newTitle: string) => {
|
const handleChange = useCallback((newTitle: string) => {
|
||||||
@ -39,36 +110,28 @@ const EditableNodeTitle = ({ nodeId, title }: Props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Another component may change the title; sync local title with global state
|
// Another component may change the title; sync local title with global state
|
||||||
setLocalTitle(
|
setLocalTitle(label || templateTitle || t('nodes.problemSettingTitle'));
|
||||||
label || title || templateTitle || t('nodes.problemSettingTitle')
|
}, [label, templateTitle, t]);
|
||||||
);
|
|
||||||
}, [label, templateTitle, title, t]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Editable
|
||||||
sx={{
|
as={Flex}
|
||||||
w: 'full',
|
value={localTitle}
|
||||||
h: 'full',
|
onChange={handleChange}
|
||||||
alignItems: 'center',
|
onSubmit={handleSubmit}
|
||||||
justifyContent: 'center',
|
w="full"
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Editable
|
<EditablePreview p={0} fontWeight={600} noOfLines={1} />
|
||||||
as={Flex}
|
<EditableInput
|
||||||
value={localTitle}
|
p={0}
|
||||||
onChange={handleChange}
|
className="nodrag"
|
||||||
onSubmit={handleSubmit}
|
fontWeight={700}
|
||||||
w="full"
|
_focusVisible={{ boxShadow: 'none' }}
|
||||||
fontWeight={600}
|
/>
|
||||||
>
|
</Editable>
|
||||||
<EditablePreview noOfLines={1} />
|
|
||||||
<EditableInput
|
|
||||||
className="nodrag"
|
|
||||||
_focusVisible={{ boxShadow: 'none' }}
|
|
||||||
/>
|
|
||||||
</Editable>
|
|
||||||
</Flex>
|
|
||||||
);
|
);
|
||||||
};
|
});
|
||||||
|
|
||||||
|
EditableTitle.displayName = 'EditableTitle';
|
||||||
|
|
||||||
export default memo(EditableNodeTitle);
|
export default memo(EditableNodeTitle);
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
import { FormControl, FormLabel, Text } from '@chakra-ui/react';
|
||||||
|
import { useNodeInputFields } from 'features/nodes/hooks/useNodeInputFields';
|
||||||
|
import { memo } from 'react';
|
||||||
|
|
||||||
|
type Props = { nodeId: string };
|
||||||
|
const InputFields = ({ nodeId }: Props) => {
|
||||||
|
const inputs = useNodeInputFields(nodeId);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{inputs?.map(({ fieldData, fieldTemplate }) => (
|
||||||
|
<FormControl key={fieldData.id}>
|
||||||
|
<FormLabel>{fieldData.label || fieldTemplate.title}</FormLabel>
|
||||||
|
<Text>{fieldData.type}</Text>
|
||||||
|
</FormControl>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(InputFields);
|
@ -0,0 +1,56 @@
|
|||||||
|
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 { useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
InputFieldTemplate,
|
||||||
|
InputFieldValue,
|
||||||
|
isInvocationNode,
|
||||||
|
} from '../types/types';
|
||||||
|
|
||||||
|
export const useNodeInputFields = (
|
||||||
|
nodeId: string
|
||||||
|
): { fieldData: InputFieldValue; fieldTemplate: InputFieldTemplate }[] => {
|
||||||
|
const selector = useMemo(
|
||||||
|
() =>
|
||||||
|
createSelector(
|
||||||
|
stateSelector,
|
||||||
|
({ nodes }) => {
|
||||||
|
const node = nodes.nodes.find((node) => node.id === nodeId);
|
||||||
|
if (!isInvocationNode(node)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = nodes.nodeTemplates[node.data.type];
|
||||||
|
|
||||||
|
if (!template) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const inputs = Object.values(node.data.inputs).reduce<
|
||||||
|
{
|
||||||
|
fieldData: InputFieldValue;
|
||||||
|
fieldTemplate: InputFieldTemplate;
|
||||||
|
}[]
|
||||||
|
>((acc, fieldData) => {
|
||||||
|
const fieldTemplate = template.inputs[fieldData.name];
|
||||||
|
if (fieldTemplate) {
|
||||||
|
acc.push({
|
||||||
|
fieldData,
|
||||||
|
fieldTemplate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return inputs;
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
),
|
||||||
|
[nodeId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const inputs = useAppSelector(selector);
|
||||||
|
return inputs;
|
||||||
|
};
|
@ -0,0 +1,28 @@
|
|||||||
|
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 { useMemo } from 'react';
|
||||||
|
import { isInvocationNode } from '../types/types';
|
||||||
|
|
||||||
|
export const useNodeNotes = (nodeId: string) => {
|
||||||
|
const selector = useMemo(
|
||||||
|
() =>
|
||||||
|
createSelector(
|
||||||
|
stateSelector,
|
||||||
|
({ nodes }) => {
|
||||||
|
const node = nodes.nodes.find((node) => node.id === nodeId);
|
||||||
|
if (!isInvocationNode(node)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return node.data.notes;
|
||||||
|
},
|
||||||
|
defaultSelectorOptions
|
||||||
|
),
|
||||||
|
[nodeId]
|
||||||
|
);
|
||||||
|
|
||||||
|
const nodeNotes = useAppSelector(selector);
|
||||||
|
|
||||||
|
return nodeNotes;
|
||||||
|
};
|
@ -1,10 +1,12 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { useAppToaster } from 'app/components/Toaster';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import { satisfies } from 'compare-versions';
|
import { satisfies } from 'compare-versions';
|
||||||
import { cloneDeep, defaultsDeep } from 'lodash-es';
|
import { cloneDeep, defaultsDeep } from 'lodash-es';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Node } from 'reactflow';
|
import { Node } from 'reactflow';
|
||||||
import { AnyInvocationType } from 'services/events/types';
|
import { AnyInvocationType } from 'services/events/types';
|
||||||
import { nodeReplaced } from '../store/nodesSlice';
|
import { nodeReplaced } from '../store/nodesSlice';
|
||||||
@ -16,8 +18,6 @@ import {
|
|||||||
isInvocationNode,
|
isInvocationNode,
|
||||||
zParsedSemver,
|
zParsedSemver,
|
||||||
} from '../types/types';
|
} from '../types/types';
|
||||||
import { useAppToaster } from 'app/components/Toaster';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
export const getNeedsUpdate = (
|
export const getNeedsUpdate = (
|
||||||
node?: Node<NodeData>,
|
node?: Node<NodeData>,
|
||||||
@ -115,5 +115,17 @@ export const useNodeVersion = (nodeId: string) => {
|
|||||||
dispatch(nodeReplaced({ nodeId: updatedNode.id, node: updatedNode }));
|
dispatch(nodeReplaced({ nodeId: updatedNode.id, node: updatedNode }));
|
||||||
}, [dispatch, node, nodeTemplate, t, toast]);
|
}, [dispatch, node, nodeTemplate, t, toast]);
|
||||||
|
|
||||||
return { needsUpdate, mayUpdate, updateNode: _updateNode };
|
const version = useMemo(() => {
|
||||||
|
if (!isInvocationNode(node)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return node.data.version;
|
||||||
|
}, [node]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
needsUpdate,
|
||||||
|
mayUpdate,
|
||||||
|
updateNode: _updateNode,
|
||||||
|
version,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
@ -156,6 +156,11 @@ export const FIELDS: Record<FieldType, FieldUIConfig> = {
|
|||||||
description: 'Any field type is accepted.',
|
description: 'Any field type is accepted.',
|
||||||
title: 'Any',
|
title: 'Any',
|
||||||
},
|
},
|
||||||
|
Unknown: {
|
||||||
|
color: 'gray.500',
|
||||||
|
description: 'Unknown field type is accepted.',
|
||||||
|
title: 'Unknown',
|
||||||
|
},
|
||||||
MetadataField: {
|
MetadataField: {
|
||||||
color: 'gray.500',
|
color: 'gray.500',
|
||||||
description: 'A metadata dict.',
|
description: 'A metadata dict.',
|
||||||
|
@ -133,6 +133,7 @@ export const zFieldType = z.enum([
|
|||||||
'UNetField',
|
'UNetField',
|
||||||
'VaeField',
|
'VaeField',
|
||||||
'VaeModelField',
|
'VaeModelField',
|
||||||
|
'Unknown',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type FieldType = z.infer<typeof zFieldType>;
|
export type FieldType = z.infer<typeof zFieldType>;
|
||||||
@ -190,6 +191,7 @@ export type OutputFieldTemplate = {
|
|||||||
type: FieldType;
|
type: FieldType;
|
||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
originalType: string; // used for custom types
|
||||||
} & _OutputField;
|
} & _OutputField;
|
||||||
|
|
||||||
export const zInputFieldValueBase = zFieldValueBase.extend({
|
export const zInputFieldValueBase = zFieldValueBase.extend({
|
||||||
@ -789,6 +791,11 @@ export const zAnyInputFieldValue = zInputFieldValueBase.extend({
|
|||||||
value: z.any().optional(),
|
value: z.any().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const zUnknownInputFieldValue = zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('Unknown'),
|
||||||
|
value: z.any().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
export const zInputFieldValue = z.discriminatedUnion('type', [
|
export const zInputFieldValue = z.discriminatedUnion('type', [
|
||||||
zAnyInputFieldValue,
|
zAnyInputFieldValue,
|
||||||
zBoardInputFieldValue,
|
zBoardInputFieldValue,
|
||||||
@ -846,6 +853,7 @@ export const zInputFieldValue = z.discriminatedUnion('type', [
|
|||||||
zMetadataItemPolymorphicInputFieldValue,
|
zMetadataItemPolymorphicInputFieldValue,
|
||||||
zMetadataInputFieldValue,
|
zMetadataInputFieldValue,
|
||||||
zMetadataCollectionInputFieldValue,
|
zMetadataCollectionInputFieldValue,
|
||||||
|
zUnknownInputFieldValue,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export type InputFieldValue = z.infer<typeof zInputFieldValue>;
|
export type InputFieldValue = z.infer<typeof zInputFieldValue>;
|
||||||
@ -856,6 +864,7 @@ export type InputFieldTemplateBase = {
|
|||||||
description: string;
|
description: string;
|
||||||
required: boolean;
|
required: boolean;
|
||||||
fieldKind: 'input';
|
fieldKind: 'input';
|
||||||
|
originalType: string; // used for custom types
|
||||||
} & _InputField;
|
} & _InputField;
|
||||||
|
|
||||||
export type AnyInputFieldTemplate = InputFieldTemplateBase & {
|
export type AnyInputFieldTemplate = InputFieldTemplateBase & {
|
||||||
@ -863,6 +872,11 @@ export type AnyInputFieldTemplate = InputFieldTemplateBase & {
|
|||||||
default: undefined;
|
default: undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type UnknownInputFieldTemplate = InputFieldTemplateBase & {
|
||||||
|
type: 'Unknown';
|
||||||
|
default: undefined;
|
||||||
|
};
|
||||||
|
|
||||||
export type IntegerInputFieldTemplate = InputFieldTemplateBase & {
|
export type IntegerInputFieldTemplate = InputFieldTemplateBase & {
|
||||||
type: 'integer';
|
type: 'integer';
|
||||||
default: number;
|
default: number;
|
||||||
@ -1259,7 +1273,8 @@ export type InputFieldTemplate =
|
|||||||
| MetadataItemCollectionInputFieldTemplate
|
| MetadataItemCollectionInputFieldTemplate
|
||||||
| MetadataInputFieldTemplate
|
| MetadataInputFieldTemplate
|
||||||
| MetadataItemPolymorphicInputFieldTemplate
|
| MetadataItemPolymorphicInputFieldTemplate
|
||||||
| MetadataCollectionInputFieldTemplate;
|
| MetadataCollectionInputFieldTemplate
|
||||||
|
| UnknownInputFieldTemplate;
|
||||||
|
|
||||||
export const isInputFieldValue = (
|
export const isInputFieldValue = (
|
||||||
field?: InputFieldValue | OutputFieldValue
|
field?: InputFieldValue | OutputFieldValue
|
||||||
|
@ -81,6 +81,7 @@ import {
|
|||||||
T2IAdapterModelInputFieldTemplate,
|
T2IAdapterModelInputFieldTemplate,
|
||||||
T2IAdapterPolymorphicInputFieldTemplate,
|
T2IAdapterPolymorphicInputFieldTemplate,
|
||||||
UNetInputFieldTemplate,
|
UNetInputFieldTemplate,
|
||||||
|
UnknownInputFieldTemplate,
|
||||||
VaeInputFieldTemplate,
|
VaeInputFieldTemplate,
|
||||||
VaeModelInputFieldTemplate,
|
VaeModelInputFieldTemplate,
|
||||||
isArraySchemaObject,
|
isArraySchemaObject,
|
||||||
@ -981,6 +982,18 @@ const buildSchedulerInputFieldTemplate = ({
|
|||||||
return template;
|
return template;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildUnknownInputFieldTemplate = ({
|
||||||
|
baseField,
|
||||||
|
}: BuildInputFieldArg): UnknownInputFieldTemplate => {
|
||||||
|
const template: UnknownInputFieldTemplate = {
|
||||||
|
...baseField,
|
||||||
|
type: 'Unknown',
|
||||||
|
default: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
return template;
|
||||||
|
};
|
||||||
|
|
||||||
export const getFieldType = (
|
export const getFieldType = (
|
||||||
schemaObject: OpenAPIV3_1SchemaOrRef
|
schemaObject: OpenAPIV3_1SchemaOrRef
|
||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
@ -1145,13 +1158,9 @@ const TEMPLATE_BUILDER_MAP: {
|
|||||||
UNetField: buildUNetInputFieldTemplate,
|
UNetField: buildUNetInputFieldTemplate,
|
||||||
VaeField: buildVaeInputFieldTemplate,
|
VaeField: buildVaeInputFieldTemplate,
|
||||||
VaeModelField: buildVaeModelInputFieldTemplate,
|
VaeModelField: buildVaeModelInputFieldTemplate,
|
||||||
|
Unknown: buildUnknownInputFieldTemplate,
|
||||||
};
|
};
|
||||||
|
|
||||||
const isTemplatedFieldType = (
|
|
||||||
fieldType: string | undefined
|
|
||||||
): fieldType is keyof typeof TEMPLATE_BUILDER_MAP =>
|
|
||||||
Boolean(fieldType && fieldType in TEMPLATE_BUILDER_MAP);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds an input field from an invocation schema property.
|
* Builds an input field from an invocation schema property.
|
||||||
* @param fieldSchema The schema object
|
* @param fieldSchema The schema object
|
||||||
@ -1161,7 +1170,8 @@ export const buildInputFieldTemplate = (
|
|||||||
nodeSchema: InvocationSchemaObject,
|
nodeSchema: InvocationSchemaObject,
|
||||||
fieldSchema: InvocationFieldSchema,
|
fieldSchema: InvocationFieldSchema,
|
||||||
name: string,
|
name: string,
|
||||||
fieldType: FieldType
|
fieldType: FieldType,
|
||||||
|
originalType: string
|
||||||
) => {
|
) => {
|
||||||
const {
|
const {
|
||||||
input,
|
input,
|
||||||
@ -1183,6 +1193,7 @@ export const buildInputFieldTemplate = (
|
|||||||
ui_order,
|
ui_order,
|
||||||
ui_choice_labels,
|
ui_choice_labels,
|
||||||
item_default,
|
item_default,
|
||||||
|
originalType,
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseField = {
|
const baseField = {
|
||||||
@ -1193,10 +1204,6 @@ export const buildInputFieldTemplate = (
|
|||||||
...extra,
|
...extra,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isTemplatedFieldType(fieldType)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const builder = TEMPLATE_BUILDER_MAP[fieldType];
|
const builder = TEMPLATE_BUILDER_MAP[fieldType];
|
||||||
|
|
||||||
if (!builder) {
|
if (!builder) {
|
||||||
|
@ -60,6 +60,7 @@ const FIELD_VALUE_FALLBACK_MAP: {
|
|||||||
UNetField: undefined,
|
UNetField: undefined,
|
||||||
VaeField: undefined,
|
VaeField: undefined,
|
||||||
VaeModelField: undefined,
|
VaeModelField: undefined,
|
||||||
|
Unknown: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const buildInputFieldValue = (
|
export const buildInputFieldValue = (
|
||||||
|
@ -4,6 +4,7 @@ import { reduce, startCase } from 'lodash-es';
|
|||||||
import { OpenAPIV3_1 } from 'openapi-types';
|
import { OpenAPIV3_1 } from 'openapi-types';
|
||||||
import { AnyInvocationType } from 'services/events/types';
|
import { AnyInvocationType } from 'services/events/types';
|
||||||
import {
|
import {
|
||||||
|
FieldType,
|
||||||
InputFieldTemplate,
|
InputFieldTemplate,
|
||||||
InvocationSchemaObject,
|
InvocationSchemaObject,
|
||||||
InvocationTemplate,
|
InvocationTemplate,
|
||||||
@ -103,7 +104,7 @@ export const parseSchema = (
|
|||||||
return inputsAccumulator;
|
return inputsAccumulator;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldType = property.ui_type ?? getFieldType(property);
|
let fieldType = property.ui_type ?? getFieldType(property);
|
||||||
|
|
||||||
if (!fieldType) {
|
if (!fieldType) {
|
||||||
logger('nodes').warn(
|
logger('nodes').warn(
|
||||||
@ -118,6 +119,9 @@ export const parseSchema = (
|
|||||||
return inputsAccumulator;
|
return inputsAccumulator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stash this for custom types
|
||||||
|
const originalType = fieldType;
|
||||||
|
|
||||||
if (fieldType === 'WorkflowField') {
|
if (fieldType === 'WorkflowField') {
|
||||||
withWorkflow = true;
|
withWorkflow = true;
|
||||||
return inputsAccumulator;
|
return inputsAccumulator;
|
||||||
@ -137,23 +141,24 @@ export const parseSchema = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!isFieldType(fieldType)) {
|
if (!isFieldType(fieldType)) {
|
||||||
logger('nodes').warn(
|
logger('nodes').debug(
|
||||||
{
|
{
|
||||||
node: type,
|
node: type,
|
||||||
fieldName: propertyName,
|
fieldName: propertyName,
|
||||||
fieldType,
|
fieldType,
|
||||||
field: parseify(property),
|
field: parseify(property),
|
||||||
},
|
},
|
||||||
`Skipping unknown input field type: ${fieldType}`
|
`Fallback handling for unknown input field type: ${fieldType}`
|
||||||
);
|
);
|
||||||
return inputsAccumulator;
|
fieldType = 'Unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
const field = buildInputFieldTemplate(
|
const field = buildInputFieldTemplate(
|
||||||
schema,
|
schema,
|
||||||
property,
|
property,
|
||||||
propertyName,
|
propertyName,
|
||||||
fieldType
|
fieldType as FieldType, // we have already checked that fieldType is a valid FieldType, and forced it to be Unknown if not
|
||||||
|
originalType
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!field) {
|
if (!field) {
|
||||||
@ -220,26 +225,43 @@ export const parseSchema = (
|
|||||||
return outputsAccumulator;
|
return outputsAccumulator;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fieldType = property.ui_type ?? getFieldType(property);
|
let fieldType = property.ui_type ?? getFieldType(property);
|
||||||
|
|
||||||
if (!isFieldType(fieldType)) {
|
if (!fieldType) {
|
||||||
logger('nodes').warn(
|
logger('nodes').warn(
|
||||||
{ fieldName: propertyName, fieldType, field: parseify(property) },
|
{
|
||||||
'Skipping unknown output field type'
|
node: type,
|
||||||
|
fieldName: propertyName,
|
||||||
|
fieldType,
|
||||||
|
field: parseify(property),
|
||||||
|
},
|
||||||
|
'Missing output field type'
|
||||||
);
|
);
|
||||||
return outputsAccumulator;
|
return outputsAccumulator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stash for custom types
|
||||||
|
const originalType = fieldType;
|
||||||
|
|
||||||
|
if (!isFieldType(fieldType)) {
|
||||||
|
logger('nodes').debug(
|
||||||
|
{ fieldName: propertyName, fieldType, field: parseify(property) },
|
||||||
|
`Fallback handling for unknown input field type: ${fieldType}`
|
||||||
|
);
|
||||||
|
fieldType = 'Unknown';
|
||||||
|
}
|
||||||
|
|
||||||
outputsAccumulator[propertyName] = {
|
outputsAccumulator[propertyName] = {
|
||||||
fieldKind: 'output',
|
fieldKind: 'output',
|
||||||
name: propertyName,
|
name: propertyName,
|
||||||
title:
|
title:
|
||||||
property.title ?? (propertyName ? startCase(propertyName) : ''),
|
property.title ?? (propertyName ? startCase(propertyName) : ''),
|
||||||
description: property.description ?? '',
|
description: property.description ?? '',
|
||||||
type: fieldType,
|
type: fieldType as FieldType,
|
||||||
ui_hidden: property.ui_hidden ?? false,
|
ui_hidden: property.ui_hidden ?? false,
|
||||||
ui_type: property.ui_type,
|
ui_type: property.ui_type,
|
||||||
ui_order: property.ui_order,
|
ui_order: property.ui_order,
|
||||||
|
originalType,
|
||||||
};
|
};
|
||||||
|
|
||||||
return outputsAccumulator;
|
return outputsAccumulator;
|
||||||
|
Reference in New Issue
Block a user