mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): ux improvements & redesign
This is a squash merge of a bajillion messy small commits created while iterating on the UI component library and redesign.
This commit is contained in:
committed by
Kent Keirsey
parent
a47d91f0e7
commit
f0b102d830
@ -1,16 +1,18 @@
|
||||
import 'reactflow/dist/style.css';
|
||||
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import TopPanel from 'features/nodes/components/flow/panels/TopPanel/TopPanel';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MdDeviceHub } from 'react-icons/md';
|
||||
import 'reactflow/dist/style.css';
|
||||
|
||||
import AddNodePopover from './flow/AddNodePopover/AddNodePopover';
|
||||
import { Flow } from './flow/Flow';
|
||||
import BottomLeftPanel from './flow/panels/BottomLeftPanel/BottomLeftPanel';
|
||||
import MinimapPanel from './flow/panels/MinimapPanel/MinimapPanel';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import TopPanel from 'features/nodes/components/flow/panels/TopPanel/TopPanel';
|
||||
|
||||
const NodeEditor = () => {
|
||||
const isReady = useAppSelector((state) => state.nodes.isReady);
|
||||
|
@ -1,15 +1,22 @@
|
||||
import {
|
||||
Flex,
|
||||
Popover,
|
||||
PopoverAnchor,
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
} from '@chakra-ui/react';
|
||||
import 'reactflow/dist/style.css';
|
||||
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
||||
import type { SelectInstance } from 'chakra-react-select';
|
||||
import {
|
||||
InvPopover,
|
||||
InvPopoverAnchor,
|
||||
InvPopoverBody,
|
||||
InvPopoverContent,
|
||||
} from 'common/components/InvPopover/wrapper';
|
||||
import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
||||
import type {
|
||||
InvSelectOnChange,
|
||||
InvSelectOption,
|
||||
} from 'common/components/InvSelect/types';
|
||||
import { useBuildNode } from 'features/nodes/hooks/useBuildNode';
|
||||
import {
|
||||
addNodePopoverClosed,
|
||||
@ -17,43 +24,47 @@ import {
|
||||
nodeAdded,
|
||||
} from 'features/nodes/store/nodesSlice';
|
||||
import { validateSourceAndTargetTypes } from 'features/nodes/store/util/validateSourceAndTargetTypes';
|
||||
import { filter, map, some } from 'lodash-es';
|
||||
import { filter, map, memoize, some } from 'lodash-es';
|
||||
import type { KeyboardEventHandler } from 'react';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { HotkeyCallback } from 'react-hotkeys-hook/dist/types';
|
||||
import type { HotkeyCallback } from 'react-hotkeys-hook/dist/types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import 'reactflow/dist/style.css';
|
||||
import { AddNodePopoverSelectItem } from './AddNodePopoverSelectItem';
|
||||
import type { FilterOptionOption } from 'react-select/dist/declarations/src/filters';
|
||||
|
||||
type NodeTemplate = {
|
||||
label: string;
|
||||
value: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
};
|
||||
const createRegex = memoize(
|
||||
(inputValue: string) =>
|
||||
new RegExp(
|
||||
inputValue
|
||||
.trim()
|
||||
.replace(/[-[\]{}()*+!<=:?./\\^$|#,]/g, '')
|
||||
.split(' ')
|
||||
.join('.*'),
|
||||
'gi'
|
||||
)
|
||||
);
|
||||
|
||||
const selectFilter = (value: string, item: NodeTemplate) => {
|
||||
const regex = new RegExp(
|
||||
value
|
||||
.trim()
|
||||
.replace(/[-[\]{}()*+!<=:?./\\^$|#,]/g, '')
|
||||
.split(' ')
|
||||
.join('.*'),
|
||||
'gi'
|
||||
);
|
||||
return (
|
||||
regex.test(item.label) ||
|
||||
regex.test(item.description) ||
|
||||
item.tags.some((tag) => regex.test(tag))
|
||||
);
|
||||
};
|
||||
const filterOption = memoize(
|
||||
(option: FilterOptionOption<InvSelectOption>, inputValue: string) => {
|
||||
if (!inputValue) {
|
||||
return true;
|
||||
}
|
||||
const regex = createRegex(inputValue);
|
||||
return (
|
||||
regex.test(option.label) ||
|
||||
regex.test(option.data.description ?? '') ||
|
||||
(option.data.tags ?? []).some((tag) => regex.test(tag))
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
const AddNodePopover = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const buildInvocation = useBuildNode();
|
||||
const toaster = useAppToaster();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const selectRef = useRef<SelectInstance<InvSelectOption> | null>(null);
|
||||
const fieldFilter = useAppSelector(
|
||||
(state) => state.nodes.connectionStartFieldType
|
||||
);
|
||||
@ -79,25 +90,28 @@ const AddNodePopover = () => {
|
||||
})
|
||||
: map(nodes.nodeTemplates);
|
||||
|
||||
const data: NodeTemplate[] = map(filteredNodeTemplates, (template) => {
|
||||
return {
|
||||
label: template.title,
|
||||
value: template.type,
|
||||
description: template.description,
|
||||
tags: template.tags,
|
||||
};
|
||||
});
|
||||
const options: InvSelectOption[] = map(
|
||||
filteredNodeTemplates,
|
||||
(template) => {
|
||||
return {
|
||||
label: template.title,
|
||||
value: template.type,
|
||||
description: template.description,
|
||||
tags: template.tags,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
//We only want these nodes if we're not filtered
|
||||
if (fieldFilter === null) {
|
||||
data.push({
|
||||
options.push({
|
||||
label: t('nodes.currentImage'),
|
||||
value: 'current_image',
|
||||
description: t('nodes.currentImageDescription'),
|
||||
tags: ['progress'],
|
||||
});
|
||||
|
||||
data.push({
|
||||
options.push({
|
||||
label: t('nodes.notes'),
|
||||
value: 'notes',
|
||||
description: t('nodes.notesDescription'),
|
||||
@ -105,14 +119,13 @@ const AddNodePopover = () => {
|
||||
});
|
||||
}
|
||||
|
||||
data.sort((a, b) => a.label.localeCompare(b.label));
|
||||
options.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
return { data };
|
||||
return { options };
|
||||
});
|
||||
|
||||
const { data } = useAppSelector(selector);
|
||||
const { options } = useAppSelector(selector);
|
||||
const isOpen = useAppSelector((state) => state.nodes.isAddNodePopoverOpen);
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const addNode = useCallback(
|
||||
(nodeType: string) => {
|
||||
@ -133,13 +146,12 @@ const AddNodePopover = () => {
|
||||
[dispatch, buildInvocation, toaster, t]
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(v: string | null) => {
|
||||
const onChange = useCallback<InvSelectOnChange>(
|
||||
(v) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
|
||||
addNode(v);
|
||||
addNode(v.value);
|
||||
},
|
||||
[addNode]
|
||||
);
|
||||
@ -156,9 +168,9 @@ const AddNodePopover = () => {
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
onOpen();
|
||||
setTimeout(() => {
|
||||
inputRef.current?.focus();
|
||||
}, 0);
|
||||
flushSync(() => {
|
||||
selectRef.current?.inputRef?.focus();
|
||||
});
|
||||
},
|
||||
[onOpen]
|
||||
);
|
||||
@ -169,10 +181,19 @@ const AddNodePopover = () => {
|
||||
|
||||
useHotkeys(['shift+a', 'space'], handleHotkeyOpen);
|
||||
useHotkeys(['escape'], handleHotkeyClose);
|
||||
const onKeyDown: KeyboardEventHandler = useCallback(
|
||||
(e) => {
|
||||
if (e.key === 'Escape') {
|
||||
onClose();
|
||||
}
|
||||
},
|
||||
[onClose]
|
||||
);
|
||||
|
||||
const noOptionsMessage = useCallback(() => t('nodes.noMatchingNodes'), [t]);
|
||||
|
||||
return (
|
||||
<Popover
|
||||
initialFocusRef={inputRef}
|
||||
<InvPopover
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
placement="bottom"
|
||||
@ -181,51 +202,41 @@ const AddNodePopover = () => {
|
||||
closeOnBlur={true}
|
||||
returnFocusOnClose={true}
|
||||
>
|
||||
<PopoverAnchor>
|
||||
<InvPopoverAnchor>
|
||||
<Flex
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '15%',
|
||||
insetInlineStart: '50%',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
position="absolute"
|
||||
top="15%"
|
||||
insetInlineStart="50%"
|
||||
pointerEvents="none"
|
||||
/>
|
||||
</PopoverAnchor>
|
||||
<PopoverContent
|
||||
sx={{
|
||||
p: 0,
|
||||
top: -1,
|
||||
shadow: 'dark-lg',
|
||||
borderColor: 'accent.300',
|
||||
borderWidth: '2px',
|
||||
borderStyle: 'solid',
|
||||
_dark: { borderColor: 'accent.400' },
|
||||
}}
|
||||
</InvPopoverAnchor>
|
||||
<InvPopoverContent
|
||||
p={0}
|
||||
top={-1}
|
||||
shadow="dark-lg"
|
||||
borderColor="blue.400"
|
||||
borderWidth="2px"
|
||||
borderStyle="solid"
|
||||
>
|
||||
<PopoverBody sx={{ p: 0 }}>
|
||||
<IAIMantineSearchableSelect
|
||||
inputRef={inputRef}
|
||||
selectOnBlur={false}
|
||||
placeholder={t('nodes.nodeSearch')}
|
||||
<InvPopoverBody w="32rem" p={0}>
|
||||
<InvSelect
|
||||
menuIsOpen={isOpen}
|
||||
selectRef={selectRef}
|
||||
value={null}
|
||||
data={data}
|
||||
maxDropdownHeight={400}
|
||||
nothingFound={t('nodes.noMatchingNodes')}
|
||||
itemComponent={AddNodePopoverSelectItem}
|
||||
filter={selectFilter}
|
||||
onChange={handleChange}
|
||||
hoverOnSearchChange={true}
|
||||
onDropdownClose={onClose}
|
||||
placeholder={t('nodes.nodeSearch')}
|
||||
options={options}
|
||||
noOptionsMessage={noOptionsMessage}
|
||||
filterOption={filterOption}
|
||||
onChange={onChange}
|
||||
onMenuClose={onClose}
|
||||
onKeyDown={onKeyDown}
|
||||
sx={{
|
||||
width: '32rem',
|
||||
input: {
|
||||
padding: '0.5rem',
|
||||
},
|
||||
width: 'full',
|
||||
}}
|
||||
/>
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</InvPopoverBody>
|
||||
</InvPopoverContent>
|
||||
</InvPopover>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,29 +0,0 @@
|
||||
import { Text } from '@chakra-ui/react';
|
||||
import { forwardRef } from 'react';
|
||||
import 'reactflow/dist/style.css';
|
||||
|
||||
interface ItemProps extends React.ComponentPropsWithoutRef<'div'> {
|
||||
value: string;
|
||||
label: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const AddNodePopoverSelectItem = forwardRef<HTMLDivElement, ItemProps>(
|
||||
({ label, description, ...others }: ItemProps, ref) => {
|
||||
return (
|
||||
<div ref={ref} {...others}>
|
||||
<div>
|
||||
<Text fontWeight={600}>{label}</Text>
|
||||
<Text
|
||||
size="xs"
|
||||
sx={{ color: 'base.600', _dark: { color: 'base.500' } }}
|
||||
>
|
||||
{description}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
AddNodePopoverSelectItem.displayName = 'AddNodePopoverSelectItem';
|
@ -23,26 +23,27 @@ import {
|
||||
} from 'features/nodes/store/nodesSlice';
|
||||
import { $flow } from 'features/nodes/store/reactFlowInstance';
|
||||
import { bumpGlobalMenuCloseTrigger } from 'features/ui/store/uiSlice';
|
||||
import { MouseEvent, useCallback, useRef } from 'react';
|
||||
import type { MouseEvent } from 'react';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import {
|
||||
Background,
|
||||
import type {
|
||||
OnConnect,
|
||||
OnConnectEnd,
|
||||
OnConnectStart,
|
||||
OnEdgeUpdateFunc,
|
||||
OnEdgesChange,
|
||||
OnEdgesDelete,
|
||||
OnEdgeUpdateFunc,
|
||||
OnInit,
|
||||
OnMoveEnd,
|
||||
OnNodesChange,
|
||||
OnNodesDelete,
|
||||
OnSelectionChangeFunc,
|
||||
ProOptions,
|
||||
ReactFlow,
|
||||
ReactFlowProps,
|
||||
XYPosition,
|
||||
} from 'reactflow';
|
||||
import { Background, ReactFlow } from 'reactflow';
|
||||
|
||||
import CustomConnectionLine from './connectionLines/CustomConnectionLine';
|
||||
import InvocationCollapsedEdge from './edges/InvocationCollapsedEdge';
|
||||
import InvocationDefaultEdge from './edges/InvocationDefaultEdge';
|
||||
@ -272,6 +273,7 @@ export const Flow = () => {
|
||||
onPaneClick={handlePaneClick}
|
||||
deleteKeyCode={DELETE_KEYS}
|
||||
selectionMode={selectionMode}
|
||||
onlyRenderVisibleElements
|
||||
>
|
||||
<Background />
|
||||
</ReactFlow>
|
||||
|
@ -4,7 +4,8 @@ import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
|
||||
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
|
||||
import { memo } from 'react';
|
||||
import { ConnectionLineComponentProps, getBezierPath } from 'reactflow';
|
||||
import type { ConnectionLineComponentProps } from 'reactflow';
|
||||
import { getBezierPath } from 'reactflow';
|
||||
|
||||
const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
|
||||
const { shouldAnimateEdges, connectionStartFieldType, shouldColorEdges } =
|
||||
|
@ -2,12 +2,9 @@ import { Badge, Flex } from '@chakra-ui/react';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
|
||||
import { memo, useMemo } from 'react';
|
||||
import {
|
||||
BaseEdge,
|
||||
EdgeLabelRenderer,
|
||||
EdgeProps,
|
||||
getBezierPath,
|
||||
} from 'reactflow';
|
||||
import type { EdgeProps } from 'reactflow';
|
||||
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow';
|
||||
|
||||
import { makeEdgeSelector } from './util/makeEdgeSelector';
|
||||
|
||||
const InvocationCollapsedEdge = ({
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { BaseEdge, EdgeProps, getBezierPath } from 'reactflow';
|
||||
import type { EdgeProps } from 'reactflow';
|
||||
import { BaseEdge, getBezierPath } from 'reactflow';
|
||||
|
||||
import { makeEdgeSelector } from './util/makeEdgeSelector';
|
||||
|
||||
const InvocationDefaultEdge = ({
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
|
||||
import { FIELD_COLORS } from 'features/nodes/types/constants';
|
||||
import { FieldType } from 'features/nodes/types/field';
|
||||
import type { FieldType } from 'features/nodes/types/field';
|
||||
|
||||
export const getFieldColor = (fieldType: FieldType | null): string => {
|
||||
if (!fieldType) {
|
||||
|
@ -2,6 +2,7 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
|
||||
import { getFieldColor } from './getEdgeColor';
|
||||
|
||||
export const makeEdgeSelector = (
|
||||
|
@ -1,16 +1,18 @@
|
||||
import { Flex, Image, Text } from '@chakra-ui/react';
|
||||
import { Flex, Image } from '@chakra-ui/react';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import { InvText } from 'common/components/InvText/wrapper';
|
||||
import NextPrevImageButtons from 'features/gallery/components/NextPrevImageButtons';
|
||||
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
|
||||
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
||||
import { motion } from 'framer-motion';
|
||||
import { PropsWithChildren, memo, useCallback, useState } from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import type { NodeProps } from 'reactflow';
|
||||
|
||||
const selector = createMemoizedSelector(
|
||||
stateSelector,
|
||||
@ -95,16 +97,15 @@ const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => {
|
||||
h: 8,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
<InvText
|
||||
sx={{
|
||||
fontSize: 'sm',
|
||||
fontWeight: 600,
|
||||
color: 'base.700',
|
||||
_dark: { color: 'base.200' },
|
||||
fontWeight: 'semibold',
|
||||
color: 'base.200',
|
||||
}}
|
||||
>
|
||||
{t('nodes.currentImage')}
|
||||
</Text>
|
||||
</InvText>
|
||||
</Flex>
|
||||
<Flex
|
||||
layerStyle="nodeBody"
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { Flex, Grid, GridItem } from '@chakra-ui/react';
|
||||
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
|
||||
import { useAnyOrDirectInputFieldNames } from 'features/nodes/hooks/useAnyOrDirectInputFieldNames';
|
||||
import { useConnectionInputFieldNames } from 'features/nodes/hooks/useConnectionInputFieldNames';
|
||||
import { useOutputFieldNames } from 'features/nodes/hooks/useOutputFieldNames';
|
||||
import { useWithFooter } from 'features/nodes/hooks/useWithFooter';
|
||||
import { memo } from 'react';
|
||||
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
|
||||
import InvocationNodeFooter from './InvocationNodeFooter';
|
||||
import InvocationNodeHeader from './InvocationNodeHeader';
|
||||
|
||||
import InputField from './fields/InputField';
|
||||
import OutputField from './fields/OutputField';
|
||||
import { useWithFooter } from 'features/nodes/hooks/useWithFooter';
|
||||
import InvocationNodeFooter from './InvocationNodeFooter';
|
||||
import InvocationNodeHeader from './InvocationNodeHeader';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { Icon, Tooltip } from '@chakra-ui/react';
|
||||
import { Icon } from '@chakra-ui/react';
|
||||
import { InvTooltip } from 'common/components/InvTooltip/InvTooltip';
|
||||
import { useNodeClassification } from 'features/nodes/hooks/useNodeClassification';
|
||||
import type { Classification } from 'features/nodes/types/common';
|
||||
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 {
|
||||
@ -18,7 +19,7 @@ const InvocationNodeClassificationIcon = ({ nodeId }: Props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
<InvTooltip
|
||||
label={<ClassificationTooltipContent classification={classification} />}
|
||||
placement="top"
|
||||
shouldWrapChildren
|
||||
@ -31,7 +32,7 @@ const InvocationNodeClassificationIcon = ({ nodeId }: Props) => {
|
||||
color: 'base.400',
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</InvTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { useColorModeValue } from '@chakra-ui/react';
|
||||
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
|
||||
import { useNodeData } from 'features/nodes/hooks/useNodeData';
|
||||
import { isInvocationNodeData } from 'features/nodes/types/invocation';
|
||||
import { map } from 'lodash-es';
|
||||
import { CSSProperties, memo, useMemo } from 'react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
|
||||
interface Props {
|
||||
@ -12,8 +12,7 @@ interface Props {
|
||||
|
||||
const InvocationNodeCollapsedHandles = ({ nodeId }: Props) => {
|
||||
const data = useNodeData(nodeId);
|
||||
const { base400, base600 } = useChakraThemeTokens();
|
||||
const backgroundColor = useColorModeValue(base400, base600);
|
||||
const { base600 } = useChakraThemeTokens();
|
||||
|
||||
const dummyHandleStyles: CSSProperties = useMemo(
|
||||
() => ({
|
||||
@ -21,10 +20,10 @@ const InvocationNodeCollapsedHandles = ({ nodeId }: Props) => {
|
||||
borderRadius: '3px',
|
||||
width: '1rem',
|
||||
height: '1rem',
|
||||
backgroundColor,
|
||||
backgroundColor: base600,
|
||||
zIndex: -1,
|
||||
}),
|
||||
[backgroundColor]
|
||||
[base600]
|
||||
);
|
||||
|
||||
if (!isInvocationNodeData(data)) {
|
||||
|
@ -3,6 +3,7 @@ import { useHasImageOutput } from 'features/nodes/hooks/useHasImageOutput';
|
||||
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import { memo } from 'react';
|
||||
|
||||
import SaveToGalleryCheckbox from './SaveToGalleryCheckbox';
|
||||
import UseCacheCheckbox from './UseCacheCheckbox';
|
||||
|
||||
@ -22,7 +23,7 @@ const InvocationNodeFooter = ({ nodeId }: Props) => {
|
||||
borderBottomRadius: 'base',
|
||||
px: 2,
|
||||
py: 0,
|
||||
h: 6,
|
||||
h: 8,
|
||||
justifyContent: 'space-between',
|
||||
}}
|
||||
>
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import NodeCollapseButton from 'features/nodes/components/flow/nodes/common/NodeCollapseButton';
|
||||
import NodeTitle from 'features/nodes/components/flow/nodes/common/NodeTitle';
|
||||
import InvocationNodeClassificationIcon from 'features/nodes/components/flow/nodes/Invocation/InvocationNodeClassificationIcon';
|
||||
import { memo } from 'react';
|
||||
|
||||
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;
|
||||
@ -26,9 +27,7 @@ const InvocationNodeHeader = ({ nodeId, isOpen }: Props) => {
|
||||
justifyContent: 'space-between',
|
||||
h: 8,
|
||||
textAlign: 'center',
|
||||
fontWeight: 500,
|
||||
color: 'base.700',
|
||||
_dark: { color: 'base.200' },
|
||||
color: 'base.200',
|
||||
}}
|
||||
>
|
||||
<NodeCollapseButton nodeId={nodeId} isOpen={isOpen} />
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { Flex, Icon, Text, Tooltip } from '@chakra-ui/react';
|
||||
import { Flex, Icon } from '@chakra-ui/react';
|
||||
import { InvText } from 'common/components/InvText/wrapper';
|
||||
import { InvTooltip } from 'common/components/InvTooltip/InvTooltip';
|
||||
import { compare } from 'compare-versions';
|
||||
import { useNodeData } from 'features/nodes/hooks/useNodeData';
|
||||
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
|
||||
import { useNodeNeedsUpdate } from 'features/nodes/hooks/useNodeNeedsUpdate';
|
||||
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
|
||||
import { isInvocationNodeData } from 'features/nodes/types/invocation';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -16,7 +18,7 @@ const InvocationNodeInfoIcon = ({ nodeId }: Props) => {
|
||||
const needsUpdate = useNodeNeedsUpdate(nodeId);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
<InvTooltip
|
||||
label={<TooltipContent nodeId={nodeId} />}
|
||||
placement="top"
|
||||
shouldWrapChildren
|
||||
@ -30,7 +32,7 @@ const InvocationNodeInfoIcon = ({ nodeId }: Props) => {
|
||||
color: needsUpdate ? 'error.400' : 'base.400',
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</InvTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@ -64,62 +66,66 @@ const TooltipContent = memo(({ nodeId }: { nodeId: string }) => {
|
||||
|
||||
if (!data.version) {
|
||||
return (
|
||||
<Text as="span" sx={{ color: 'error.500' }}>
|
||||
<InvText as="span" sx={{ color: 'error.500' }}>
|
||||
{t('nodes.versionUnknown')}
|
||||
</Text>
|
||||
</InvText>
|
||||
);
|
||||
}
|
||||
|
||||
if (!nodeTemplate.version) {
|
||||
return (
|
||||
<Text as="span" sx={{ color: 'error.500' }}>
|
||||
<InvText as="span" sx={{ color: 'error.500' }}>
|
||||
{t('nodes.version')} {data.version} ({t('nodes.unknownTemplate')})
|
||||
</Text>
|
||||
</InvText>
|
||||
);
|
||||
}
|
||||
|
||||
if (compare(data.version, nodeTemplate.version, '<')) {
|
||||
return (
|
||||
<Text as="span" sx={{ color: 'error.500' }}>
|
||||
<InvText as="span" sx={{ color: 'error.500' }}>
|
||||
{t('nodes.version')} {data.version} ({t('nodes.updateNode')})
|
||||
</Text>
|
||||
</InvText>
|
||||
);
|
||||
}
|
||||
|
||||
if (compare(data.version, nodeTemplate.version, '>')) {
|
||||
return (
|
||||
<Text as="span" sx={{ color: 'error.500' }}>
|
||||
<InvText as="span" sx={{ color: 'error.500' }}>
|
||||
{t('nodes.version')} {data.version} ({t('nodes.updateApp')})
|
||||
</Text>
|
||||
</InvText>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Text as="span">
|
||||
<InvText as="span">
|
||||
{t('nodes.version')} {data.version}
|
||||
</Text>
|
||||
</InvText>
|
||||
);
|
||||
}, [data, nodeTemplate, t]);
|
||||
|
||||
if (!isInvocationNodeData(data)) {
|
||||
return <Text sx={{ fontWeight: 600 }}>{t('nodes.unknownNode')}</Text>;
|
||||
return (
|
||||
<InvText sx={{ fontWeight: 'semibold' }}>
|
||||
{t('nodes.unknownNode')}
|
||||
</InvText>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex sx={{ flexDir: 'column' }}>
|
||||
<Text as="span" sx={{ fontWeight: 600 }}>
|
||||
<InvText as="span" sx={{ fontWeight: 'semibold' }}>
|
||||
{title}
|
||||
</Text>
|
||||
</InvText>
|
||||
{nodeTemplate?.nodePack && (
|
||||
<Text opacity={0.7}>
|
||||
<InvText opacity={0.7}>
|
||||
{t('nodes.nodePack')}: {nodeTemplate.nodePack}
|
||||
</Text>
|
||||
</InvText>
|
||||
)}
|
||||
<Text sx={{ opacity: 0.7, fontStyle: 'oblique 5deg' }}>
|
||||
<InvText sx={{ opacity: 0.7, fontStyle: 'oblique 5deg' }}>
|
||||
{nodeTemplate?.description}
|
||||
</Text>
|
||||
</InvText>
|
||||
{versionComponent}
|
||||
{data?.notes && <Text>{data.notes}</Text>}
|
||||
{data?.notes && <InvText>{data.notes}</InvText>}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
@ -1,20 +1,12 @@
|
||||
import {
|
||||
Badge,
|
||||
CircularProgress,
|
||||
Flex,
|
||||
Icon,
|
||||
Image,
|
||||
Text,
|
||||
Tooltip,
|
||||
} from '@chakra-ui/react';
|
||||
import { Badge, CircularProgress, Flex, Icon, Image } from '@chakra-ui/react';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { InvText } from 'common/components/InvText/wrapper';
|
||||
import { InvTooltip } from 'common/components/InvTooltip/InvTooltip';
|
||||
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
||||
import {
|
||||
NodeExecutionState,
|
||||
zNodeStatus,
|
||||
} from 'features/nodes/types/invocation';
|
||||
import type { NodeExecutionState } from 'features/nodes/types/invocation';
|
||||
import { zNodeStatus } from 'features/nodes/types/invocation';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaCheck, FaEllipsisH, FaExclamation } from 'react-icons/fa';
|
||||
@ -49,7 +41,7 @@ const InvocationNodeStatusIndicator = ({ nodeId }: Props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
<InvTooltip
|
||||
label={<TooltipLabel nodeExecutionState={nodeExecutionState} />}
|
||||
placement="top"
|
||||
>
|
||||
@ -64,7 +56,7 @@ const InvocationNodeStatusIndicator = ({ nodeId }: Props) => {
|
||||
>
|
||||
<StatusIcon nodeExecutionState={nodeExecutionState} />
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</InvTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
@ -78,7 +70,7 @@ const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => {
|
||||
const { status, progress, progressImage } = nodeExecutionState;
|
||||
const { t } = useTranslation();
|
||||
if (status === zNodeStatus.enum.PENDING) {
|
||||
return <Text>{t('queue.pending')}</Text>;
|
||||
return <InvText>{t('queue.pending')}</InvText>;
|
||||
}
|
||||
if (status === zNodeStatus.enum.IN_PROGRESS) {
|
||||
if (progressImage) {
|
||||
@ -102,21 +94,21 @@ const TooltipLabel = memo(({ nodeExecutionState }: TooltipLabelProps) => {
|
||||
|
||||
if (progress !== null) {
|
||||
return (
|
||||
<Text>
|
||||
<InvText>
|
||||
{t('nodes.executionStateInProgress')} ({Math.round(progress * 100)}%)
|
||||
</Text>
|
||||
</InvText>
|
||||
);
|
||||
}
|
||||
|
||||
return <Text>{t('nodes.executionStateInProgress')}</Text>;
|
||||
return <InvText>{t('nodes.executionStateInProgress')}</InvText>;
|
||||
}
|
||||
|
||||
if (status === zNodeStatus.enum.COMPLETED) {
|
||||
return <Text>{t('nodes.executionStateCompleted')}</Text>;
|
||||
return <InvText>{t('nodes.executionStateCompleted')}</InvText>;
|
||||
}
|
||||
|
||||
if (status === zNodeStatus.enum.FAILED) {
|
||||
return <Text>{t('nodes.executionStateError')}</Text>;
|
||||
return <InvText>{t('nodes.executionStateError')}</InvText>;
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -136,8 +128,7 @@ const StatusIcon = memo((props: StatusIconProps) => {
|
||||
as={FaEllipsisH}
|
||||
sx={{
|
||||
boxSize: iconBoxSize,
|
||||
color: 'base.600',
|
||||
_dark: { color: 'base.300' },
|
||||
color: 'base.300',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@ -167,8 +158,7 @@ const StatusIcon = memo((props: StatusIconProps) => {
|
||||
as={FaCheck}
|
||||
sx={{
|
||||
boxSize: iconBoxSize,
|
||||
color: 'ok.600',
|
||||
_dark: { color: 'ok.300' },
|
||||
color: 'ok.300',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@ -179,8 +169,7 @@ const StatusIcon = memo((props: StatusIconProps) => {
|
||||
as={FaExclamation}
|
||||
sx={{
|
||||
boxSize: iconBoxSize,
|
||||
color: 'error.600',
|
||||
_dark: { color: 'error.300' },
|
||||
color: 'error.300',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { Flex, Text } from '@chakra-ui/react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { InvText } from 'common/components/InvText/wrapper';
|
||||
import NodeCollapseButton from 'features/nodes/components/flow/nodes/common/NodeCollapseButton';
|
||||
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
|
||||
import { useNodePack } from 'features/nodes/hooks/useNodePack';
|
||||
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import NodeCollapseButton from 'features/nodes/components/flow/nodes/common/NodeCollapseButton';
|
||||
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
@ -33,22 +34,21 @@ const InvocationNodeUnknownFallback = ({
|
||||
borderBottomRadius: isOpen ? 0 : 'base',
|
||||
alignItems: 'center',
|
||||
h: 8,
|
||||
fontWeight: 600,
|
||||
fontWeight: 'semibold',
|
||||
fontSize: 'sm',
|
||||
}}
|
||||
>
|
||||
<NodeCollapseButton nodeId={nodeId} isOpen={isOpen} />
|
||||
<Text
|
||||
<InvText
|
||||
sx={{
|
||||
w: 'full',
|
||||
textAlign: 'center',
|
||||
pe: 8,
|
||||
color: 'error.500',
|
||||
_dark: { color: 'error.300' },
|
||||
color: 'error.300',
|
||||
}}
|
||||
>
|
||||
{label ? `${label} (${type})` : type}
|
||||
</Text>
|
||||
</InvText>
|
||||
</Flex>
|
||||
{isOpen && (
|
||||
<Flex
|
||||
@ -65,19 +65,19 @@ const InvocationNodeUnknownFallback = ({
|
||||
}}
|
||||
>
|
||||
<Flex gap={2} flexDir="column">
|
||||
<Text as="span">
|
||||
<InvText as="span">
|
||||
{t('nodes.unknownNodeType')}:{' '}
|
||||
<Text as="span" fontWeight={600}>
|
||||
<InvText as="span" fontWeight="semibold">
|
||||
{type}
|
||||
</Text>
|
||||
</Text>
|
||||
</InvText>
|
||||
</InvText>
|
||||
{nodePack && (
|
||||
<Text as="span">
|
||||
<InvText as="span">
|
||||
{t('nodes.nodePack')}:{' '}
|
||||
<Text as="span" fontWeight={600}>
|
||||
<InvText as="span" fontWeight="semibold">
|
||||
{nodePack}
|
||||
</Text>
|
||||
</Text>
|
||||
</InvText>
|
||||
</InvText>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
@ -2,9 +2,10 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import InvocationNode from 'features/nodes/components/flow/nodes/Invocation/InvocationNode';
|
||||
import { InvocationNodeData } from 'features/nodes/types/invocation';
|
||||
import type { InvocationNodeData } from 'features/nodes/types/invocation';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import type { NodeProps } from 'reactflow';
|
||||
|
||||
import InvocationNodeUnknownFallback from './InvocationNodeUnknownFallback';
|
||||
|
||||
const InvocationNodeWrapper = (props: NodeProps<InvocationNodeData>) => {
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { FormControl, FormLabel } from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAITextarea from 'common/components/IAITextarea';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvTextarea } from 'common/components/InvTextarea/InvTextarea';
|
||||
import { useNodeData } from 'features/nodes/hooks/useNodeData';
|
||||
import { nodeNotesChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { isInvocationNodeData } from 'features/nodes/types/invocation';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const NotesTextarea = ({ nodeId }: { nodeId: string }) => {
|
||||
@ -21,14 +22,14 @@ const NotesTextarea = ({ nodeId }: { nodeId: string }) => {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<FormControl>
|
||||
<FormLabel>{t('nodes.notes')}</FormLabel>
|
||||
<IAITextarea
|
||||
<InvControl label={t('nodes.notes')} orientation="vertical" h="full">
|
||||
<InvTextarea
|
||||
value={data?.notes}
|
||||
onChange={handleNotesChanged}
|
||||
rows={10}
|
||||
resize="none"
|
||||
/>
|
||||
</FormControl>
|
||||
</InvControl>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { Checkbox, Flex, FormControl, FormLabel } from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { InvCheckbox } from 'common/components/InvCheckbox/wrapper';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { useHasImageOutput } from 'features/nodes/hooks/useHasImageOutput';
|
||||
import { useIsIntermediate } from 'features/nodes/hooks/useIsIntermediate';
|
||||
import { nodeIsIntermediateChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const SaveToGalleryCheckbox = ({ nodeId }: { nodeId: string }) => {
|
||||
@ -28,17 +30,9 @@ const SaveToGalleryCheckbox = ({ nodeId }: { nodeId: string }) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<FormControl as={Flex} sx={{ alignItems: 'center', gap: 2, w: 'auto' }}>
|
||||
<FormLabel sx={{ fontSize: 'xs', mb: '1px' }}>
|
||||
{t('hotkeys.saveToGallery.title')}
|
||||
</FormLabel>
|
||||
<Checkbox
|
||||
className="nopan"
|
||||
size="sm"
|
||||
onChange={handleChange}
|
||||
isChecked={!isIntermediate}
|
||||
/>
|
||||
</FormControl>
|
||||
<InvControl label={t('hotkeys.saveToGallery.title')} className="nopan">
|
||||
<InvCheckbox onChange={handleChange} isChecked={!isIntermediate} />
|
||||
</InvControl>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { Checkbox, Flex, FormControl, FormLabel } from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { InvCheckbox } from 'common/components/InvCheckbox/wrapper';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { useUseCache } from 'features/nodes/hooks/useUseCache';
|
||||
import { nodeUseCacheChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const UseCacheCheckbox = ({ nodeId }: { nodeId: string }) => {
|
||||
@ -21,17 +23,13 @@ const UseCacheCheckbox = ({ nodeId }: { nodeId: string }) => {
|
||||
);
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<FormControl as={Flex} sx={{ alignItems: 'center', gap: 2, w: 'auto' }}>
|
||||
<FormLabel sx={{ fontSize: 'xs', mb: '1px' }}>
|
||||
{t('invocationCache.useCache')}
|
||||
</FormLabel>
|
||||
<Checkbox
|
||||
<InvControl label={t('invocationCache.useCache')}>
|
||||
<InvCheckbox
|
||||
className="nopan"
|
||||
size="sm"
|
||||
onChange={handleChange}
|
||||
isChecked={useCache}
|
||||
/>
|
||||
</FormControl>
|
||||
</InvControl>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -3,19 +3,21 @@ import {
|
||||
EditableInput,
|
||||
EditablePreview,
|
||||
Flex,
|
||||
Tooltip,
|
||||
forwardRef,
|
||||
useEditableControls,
|
||||
} from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { InvTooltip } from 'common/components/InvTooltip/InvTooltip';
|
||||
import { useFieldLabel } from 'features/nodes/hooks/useFieldLabel';
|
||||
import { useFieldTemplateTitle } from 'features/nodes/hooks/useFieldTemplateTitle';
|
||||
import { fieldLabelChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { MouseEvent, memo, useCallback, useEffect, useState } from 'react';
|
||||
import FieldTooltipContent from './FieldTooltipContent';
|
||||
import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
|
||||
import type { MouseEvent } from 'react';
|
||||
import { memo, useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import FieldTooltipContent from './FieldTooltipContent';
|
||||
|
||||
interface Props {
|
||||
nodeId: string;
|
||||
fieldName: string;
|
||||
@ -62,7 +64,7 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
|
||||
}, [label, fieldTemplateTitle, t]);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
<InvTooltip
|
||||
label={
|
||||
withTooltip ? (
|
||||
<FieldTooltipContent
|
||||
@ -73,8 +75,6 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
|
||||
) : undefined
|
||||
}
|
||||
openDelay={HANDLE_TOOLTIP_OPEN_DELAY}
|
||||
placement="top"
|
||||
hasArrow
|
||||
>
|
||||
<Flex
|
||||
ref={ref}
|
||||
@ -101,10 +101,10 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
|
||||
<EditablePreview
|
||||
sx={{
|
||||
p: 0,
|
||||
fontWeight: isMissingInput ? 600 : 400,
|
||||
fontWeight: isMissingInput ? 'bold' : 'normal',
|
||||
textAlign: 'left',
|
||||
_hover: {
|
||||
fontWeight: '600 !important',
|
||||
fontWeight: 'semibold !important',
|
||||
},
|
||||
}}
|
||||
noOfLines={1}
|
||||
@ -114,11 +114,8 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
|
||||
sx={{
|
||||
p: 0,
|
||||
w: 'full',
|
||||
fontWeight: 600,
|
||||
color: 'base.900',
|
||||
_dark: {
|
||||
color: 'base.100',
|
||||
},
|
||||
fontWeight: 'semibold',
|
||||
color: 'base.100',
|
||||
_focusVisible: {
|
||||
p: 0,
|
||||
textAlign: 'left',
|
||||
@ -129,7 +126,7 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
|
||||
<EditableControls />
|
||||
</Editable>
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
</InvTooltip>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { MenuGroup, MenuItem, MenuList } from '@chakra-ui/react';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
IAIContextMenu,
|
||||
IAIContextMenuProps,
|
||||
} from 'common/components/IAIContextMenu';
|
||||
import type { InvContextMenuProps } from 'common/components/InvContextMenu/InvContextMenu';
|
||||
import { InvContextMenu } from 'common/components/InvContextMenu/InvContextMenu';
|
||||
import { InvMenuItem } from 'common/components/InvMenu/InvMenuItem';
|
||||
import { InvMenuList } from 'common/components/InvMenu/InvMenuList';
|
||||
import { InvMenuGroup } from 'common/components/InvMenu/wrapper';
|
||||
import { useFieldInputKind } from 'features/nodes/hooks/useFieldInputKind';
|
||||
import { useFieldLabel } from 'features/nodes/hooks/useFieldLabel';
|
||||
import { useFieldTemplateTitle } from 'features/nodes/hooks/useFieldTemplateTitle';
|
||||
@ -13,16 +13,16 @@ import {
|
||||
workflowExposedFieldAdded,
|
||||
workflowExposedFieldRemoved,
|
||||
} from 'features/nodes/store/workflowSlice';
|
||||
import { MouseEvent, ReactNode, memo, useCallback, useMemo } from 'react';
|
||||
import type { MouseEvent, ReactNode } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaMinus, FaPlus } from 'react-icons/fa';
|
||||
import { menuListMotionProps } from 'theme/components/menu';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
fieldName: string;
|
||||
kind: 'input' | 'output';
|
||||
children: IAIContextMenuProps<HTMLDivElement>['children'];
|
||||
children: InvContextMenuProps<HTMLDivElement>['children'];
|
||||
};
|
||||
|
||||
const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
||||
@ -69,24 +69,24 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
||||
const menuItems: ReactNode[] = [];
|
||||
if (mayExpose && !isExposed) {
|
||||
menuItems.push(
|
||||
<MenuItem
|
||||
<InvMenuItem
|
||||
key={`${nodeId}.${fieldName}.expose-field`}
|
||||
icon={<FaPlus />}
|
||||
onClick={handleExposeField}
|
||||
>
|
||||
{t('nodes.addLinearView')}
|
||||
</MenuItem>
|
||||
</InvMenuItem>
|
||||
);
|
||||
}
|
||||
if (mayExpose && isExposed) {
|
||||
menuItems.push(
|
||||
<MenuItem
|
||||
<InvMenuItem
|
||||
key={`${nodeId}.${fieldName}.unexpose-field`}
|
||||
icon={<FaMinus />}
|
||||
onClick={handleUnexposeField}
|
||||
>
|
||||
{t('nodes.removeLinearView')}
|
||||
</MenuItem>
|
||||
</InvMenuItem>
|
||||
);
|
||||
}
|
||||
return menuItems;
|
||||
@ -103,35 +103,22 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
||||
const renderMenuFunc = useCallback(
|
||||
() =>
|
||||
!menuItems.length ? null : (
|
||||
<MenuList
|
||||
<InvMenuList
|
||||
sx={{ visibility: 'visible !important' }}
|
||||
motionProps={menuListMotionProps}
|
||||
onContextMenu={skipEvent}
|
||||
>
|
||||
<MenuGroup
|
||||
<InvMenuGroup
|
||||
title={label || fieldTemplateTitle || t('nodes.unknownField')}
|
||||
>
|
||||
{menuItems}
|
||||
</MenuGroup>
|
||||
</MenuList>
|
||||
</InvMenuGroup>
|
||||
</InvMenuList>
|
||||
),
|
||||
[fieldTemplateTitle, label, menuItems, skipEvent, t]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIContextMenu<HTMLDivElement>
|
||||
menuProps={{
|
||||
size: 'sm',
|
||||
isLazy: true,
|
||||
}}
|
||||
menuButtonProps={{
|
||||
bg: 'transparent',
|
||||
_hover: { bg: 'transparent' },
|
||||
}}
|
||||
renderMenu={renderMenuFunc}
|
||||
>
|
||||
{children}
|
||||
</IAIContextMenu>
|
||||
<InvContextMenu renderMenu={renderMenuFunc}>{children}</InvContextMenu>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,17 +1,19 @@
|
||||
import { Tooltip } from '@chakra-ui/react';
|
||||
import { InvTooltip } from 'common/components/InvTooltip/InvTooltip';
|
||||
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
|
||||
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
|
||||
import { useFieldTypeName } from 'features/nodes/hooks/usePrettyFieldType';
|
||||
import {
|
||||
HANDLE_TOOLTIP_OPEN_DELAY,
|
||||
MODEL_TYPES,
|
||||
} from 'features/nodes/types/constants';
|
||||
import {
|
||||
import type {
|
||||
FieldInputTemplate,
|
||||
FieldOutputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { CSSProperties, memo, useMemo } from 'react';
|
||||
import { Handle, HandleType, Position } from 'reactflow';
|
||||
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import type { HandleType } from 'reactflow';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
|
||||
export const handleBaseStyles: CSSProperties = {
|
||||
position: 'absolute',
|
||||
@ -104,10 +106,9 @@ const FieldHandle = (props: FieldHandleProps) => {
|
||||
}, [connectionError, fieldTypeName, isConnectionInProgress]);
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
<InvTooltip
|
||||
label={tooltip}
|
||||
placement={handleType === 'target' ? 'start' : 'end'}
|
||||
hasArrow
|
||||
openDelay={HANDLE_TOOLTIP_OPEN_DELAY}
|
||||
>
|
||||
<Handle
|
||||
@ -116,7 +117,7 @@ const FieldHandle = (props: FieldHandleProps) => {
|
||||
position={handleType === 'target' ? Position.Left : Position.Right}
|
||||
style={styles}
|
||||
/>
|
||||
</Tooltip>
|
||||
</InvTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Flex, Text, forwardRef } from '@chakra-ui/react';
|
||||
import { Flex, forwardRef } from '@chakra-ui/react';
|
||||
import { InvText } from 'common/components/InvText/wrapper';
|
||||
import { useFieldLabel } from 'features/nodes/hooks/useFieldLabel';
|
||||
import { useFieldTemplateTitle } from 'features/nodes/hooks/useFieldTemplateTitle';
|
||||
import { memo } from 'react';
|
||||
@ -28,9 +29,9 @@ const FieldTitle = forwardRef((props: Props, ref) => {
|
||||
w: 'full',
|
||||
}}
|
||||
>
|
||||
<Text sx={{ fontWeight: isMissingInput ? 600 : 400 }}>
|
||||
<InvText sx={{ fontWeight: isMissingInput ? 'bold' : 'normal' }}>
|
||||
{label || fieldTemplateTitle}
|
||||
</Text>
|
||||
</InvText>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Flex, Text } from '@chakra-ui/react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { InvText } from 'common/components/InvText/wrapper';
|
||||
import { useFieldInstance } from 'features/nodes/hooks/useFieldData';
|
||||
import { useFieldTemplate } from 'features/nodes/hooks/useFieldTemplate';
|
||||
import { useFieldTypeName } from 'features/nodes/hooks/usePrettyFieldType';
|
||||
@ -43,21 +44,21 @@ const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => {
|
||||
|
||||
return (
|
||||
<Flex sx={{ flexDir: 'column' }}>
|
||||
<Text sx={{ fontWeight: 600 }}>{fieldTitle}</Text>
|
||||
<InvText sx={{ fontWeight: 'semibold' }}>{fieldTitle}</InvText>
|
||||
{fieldTemplate && (
|
||||
<Text sx={{ opacity: 0.7, fontStyle: 'oblique 5deg' }}>
|
||||
<InvText sx={{ opacity: 0.7, fontStyle: 'oblique 5deg' }}>
|
||||
{fieldTemplate.description}
|
||||
</Text>
|
||||
</InvText>
|
||||
)}
|
||||
{fieldTypeName && (
|
||||
<Text>
|
||||
<InvText>
|
||||
{t('parameters.type')}: {fieldTypeName}
|
||||
</Text>
|
||||
</InvText>
|
||||
)}
|
||||
{isInputTemplate && (
|
||||
<Text>
|
||||
<InvText>
|
||||
{t('common.input')}: {startCase(fieldTemplate.input)}
|
||||
</Text>
|
||||
</InvText>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
|
@ -1,10 +1,14 @@
|
||||
import { Box, Flex, FormControl, FormLabel } from '@chakra-ui/react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvLabel } from 'common/components/InvControl/InvLabel';
|
||||
import { useConnectionState } from 'features/nodes/hooks/useConnectionState';
|
||||
import { useDoesInputHaveValue } from 'features/nodes/hooks/useDoesInputHaveValue';
|
||||
import { useFieldInputInstance } from 'features/nodes/hooks/useFieldInputInstance';
|
||||
import { useFieldInputTemplate } from 'features/nodes/hooks/useFieldInputTemplate';
|
||||
import { PropsWithChildren, memo, useMemo } from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import EditableFieldTitle from './EditableFieldTitle';
|
||||
import FieldContextMenu from './FieldContextMenu';
|
||||
import FieldHandle from './FieldHandle';
|
||||
@ -50,78 +54,67 @@ const InputField = ({ nodeId, fieldName }: Props) => {
|
||||
if (!fieldTemplate || !fieldInstance) {
|
||||
return (
|
||||
<InputFieldWrapper shouldDim={shouldDim}>
|
||||
<FormControl
|
||||
sx={{
|
||||
alignItems: 'stretch',
|
||||
justifyContent: 'space-between',
|
||||
gap: 2,
|
||||
h: 'full',
|
||||
w: 'full',
|
||||
}}
|
||||
<InvControl
|
||||
alignItems="stretch"
|
||||
justifyContent="space-between"
|
||||
flexDir="column"
|
||||
gap={2}
|
||||
h="full"
|
||||
w="full"
|
||||
>
|
||||
<FormLabel
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
mb: 0,
|
||||
px: 1,
|
||||
gap: 2,
|
||||
h: 'full',
|
||||
fontWeight: 600,
|
||||
color: 'error.400',
|
||||
_dark: { color: 'error.300' },
|
||||
}}
|
||||
<InvLabel
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
mb={0}
|
||||
px={1}
|
||||
gap={2}
|
||||
h="full"
|
||||
>
|
||||
{t('nodes.unknownInput', {
|
||||
name: fieldInstance?.label ?? fieldTemplate?.title ?? fieldName,
|
||||
})}
|
||||
</FormLabel>
|
||||
</FormControl>
|
||||
</InvLabel>
|
||||
</InvControl>
|
||||
</InputFieldWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<InputFieldWrapper shouldDim={shouldDim}>
|
||||
<FormControl
|
||||
isInvalid={isMissingInput}
|
||||
isDisabled={isConnected}
|
||||
sx={{
|
||||
alignItems: 'stretch',
|
||||
justifyContent: 'space-between',
|
||||
ps: fieldTemplate.input === 'direct' ? 0 : 2,
|
||||
gap: 2,
|
||||
h: 'full',
|
||||
w: 'full',
|
||||
}}
|
||||
>
|
||||
<FieldContextMenu nodeId={nodeId} fieldName={fieldName} kind="input">
|
||||
{(ref) => (
|
||||
<FormLabel
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
mb: 0,
|
||||
px: 1,
|
||||
gap: 2,
|
||||
h: 'full',
|
||||
}}
|
||||
<FieldContextMenu nodeId={nodeId} fieldName={fieldName} kind="input">
|
||||
{(ref) => (
|
||||
<InvControl
|
||||
isInvalid={isMissingInput}
|
||||
isDisabled={isConnected}
|
||||
ps={fieldTemplate.input === 'direct' ? 0 : 2}
|
||||
alignItems="stretch"
|
||||
justifyContent="space-between"
|
||||
flexDir="column"
|
||||
gap={2}
|
||||
h="full"
|
||||
w="full"
|
||||
>
|
||||
<InvLabel
|
||||
ref={ref}
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
mb={0}
|
||||
px={1}
|
||||
gap={2}
|
||||
h="full"
|
||||
>
|
||||
<EditableFieldTitle
|
||||
ref={ref}
|
||||
nodeId={nodeId}
|
||||
fieldName={fieldName}
|
||||
kind="input"
|
||||
isMissingInput={isMissingInput}
|
||||
withTooltip
|
||||
/>
|
||||
</FormLabel>
|
||||
)}
|
||||
</FieldContextMenu>
|
||||
<Box>
|
||||
<InputFieldRenderer nodeId={nodeId} fieldName={fieldName} />
|
||||
</Box>
|
||||
</FormControl>
|
||||
</InvLabel>
|
||||
<InputFieldRenderer nodeId={nodeId} fieldName={fieldName} />
|
||||
</InvControl>
|
||||
)}
|
||||
</FieldContextMenu>
|
||||
|
||||
{fieldTemplate.input !== 'direct' && (
|
||||
<FieldHandle
|
||||
@ -146,17 +139,15 @@ const InputFieldWrapper = memo(
|
||||
({ shouldDim, children }: InputFieldWrapperProps) => {
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
position: 'relative',
|
||||
minH: 8,
|
||||
py: 0.5,
|
||||
alignItems: 'center',
|
||||
opacity: shouldDim ? 0.5 : 1,
|
||||
transitionProperty: 'opacity',
|
||||
transitionDuration: '0.1s',
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
}}
|
||||
position="relative"
|
||||
minH={8}
|
||||
py={0.5}
|
||||
alignItems="center"
|
||||
opacity={shouldDim ? 0.5 : 1}
|
||||
transitionProperty="opacity"
|
||||
transitionDuration="0.1s"
|
||||
w="full"
|
||||
h="full"
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Box, Text } from '@chakra-ui/react';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import { InvText } from 'common/components/InvText/wrapper';
|
||||
import { useFieldInstance } from 'features/nodes/hooks/useFieldData';
|
||||
import { useFieldTemplate } from 'features/nodes/hooks/useFieldTemplate';
|
||||
import {
|
||||
@ -14,22 +15,22 @@ import {
|
||||
isEnumFieldInputTemplate,
|
||||
isFloatFieldInputInstance,
|
||||
isFloatFieldInputTemplate,
|
||||
isIPAdapterModelFieldInputInstance,
|
||||
isIPAdapterModelFieldInputTemplate,
|
||||
isImageFieldInputInstance,
|
||||
isImageFieldInputTemplate,
|
||||
isIntegerFieldInputInstance,
|
||||
isIntegerFieldInputTemplate,
|
||||
isIPAdapterModelFieldInputInstance,
|
||||
isIPAdapterModelFieldInputTemplate,
|
||||
isLoRAModelFieldInputInstance,
|
||||
isLoRAModelFieldInputTemplate,
|
||||
isMainModelFieldInputInstance,
|
||||
isMainModelFieldInputTemplate,
|
||||
isSchedulerFieldInputInstance,
|
||||
isSchedulerFieldInputTemplate,
|
||||
isSDXLMainModelFieldInputInstance,
|
||||
isSDXLMainModelFieldInputTemplate,
|
||||
isSDXLRefinerModelFieldInputInstance,
|
||||
isSDXLRefinerModelFieldInputTemplate,
|
||||
isSchedulerFieldInputInstance,
|
||||
isSchedulerFieldInputTemplate,
|
||||
isStringFieldInputInstance,
|
||||
isStringFieldInputTemplate,
|
||||
isT2IAdapterModelFieldInputInstance,
|
||||
@ -39,19 +40,20 @@ import {
|
||||
} from 'features/nodes/types/field';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import BoardFieldInputComponent from './inputs/BoardFieldInputComponent';
|
||||
import BooleanFieldInputComponent from './inputs/BooleanFieldInputComponent';
|
||||
import ColorFieldInputComponent from './inputs/ColorFieldInputComponent';
|
||||
import ControlNetModelFieldInputComponent from './inputs/ControlNetModelFieldInputComponent';
|
||||
import EnumFieldInputComponent from './inputs/EnumFieldInputComponent';
|
||||
import IPAdapterModelFieldInputComponent from './inputs/IPAdapterModelFieldInputComponent';
|
||||
import ImageFieldInputComponent from './inputs/ImageFieldInputComponent';
|
||||
import IPAdapterModelFieldInputComponent from './inputs/IPAdapterModelFieldInputComponent';
|
||||
import LoRAModelFieldInputComponent from './inputs/LoRAModelFieldInputComponent';
|
||||
import MainModelFieldInputComponent from './inputs/MainModelFieldInputComponent';
|
||||
import NumberFieldInputComponent from './inputs/NumberFieldInputComponent';
|
||||
import RefinerModelFieldInputComponent from './inputs/RefinerModelFieldInputComponent';
|
||||
import SDXLMainModelFieldInputComponent from './inputs/SDXLMainModelFieldInputComponent';
|
||||
import SchedulerFieldInputComponent from './inputs/SchedulerFieldInputComponent';
|
||||
import SDXLMainModelFieldInputComponent from './inputs/SDXLMainModelFieldInputComponent';
|
||||
import StringFieldInputComponent from './inputs/StringFieldInputComponent';
|
||||
import T2IAdapterModelFieldInputComponent from './inputs/T2IAdapterModelFieldInputComponent';
|
||||
import VAEModelFieldInputComponent from './inputs/VAEModelFieldInputComponent';
|
||||
@ -290,16 +292,15 @@ const InputFieldRenderer = ({ nodeId, fieldName }: InputFieldProps) => {
|
||||
|
||||
return (
|
||||
<Box p={1}>
|
||||
<Text
|
||||
<InvText
|
||||
sx={{
|
||||
fontSize: 'sm',
|
||||
fontWeight: 600,
|
||||
color: 'error.400',
|
||||
_dark: { color: 'error.300' },
|
||||
fontWeight: 'semibold',
|
||||
color: 'error.300',
|
||||
}}
|
||||
>
|
||||
{t('nodes.unknownFieldType', { type: fieldInstance?.type.name })}
|
||||
</Text>
|
||||
</InvText>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
@ -1,23 +1,18 @@
|
||||
import {
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Icon,
|
||||
Spacer,
|
||||
Tooltip,
|
||||
} from '@chakra-ui/react';
|
||||
import { Flex, Icon, Spacer } from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
|
||||
import { InvTooltip } from 'common/components/InvTooltip/InvTooltip';
|
||||
import NodeSelectionOverlay from 'common/components/NodeSelectionOverlay';
|
||||
import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
|
||||
import { workflowExposedFieldRemoved } from 'features/nodes/store/workflowSlice';
|
||||
import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaInfoCircle, FaTrash } from 'react-icons/fa';
|
||||
|
||||
import EditableFieldTitle from './EditableFieldTitle';
|
||||
import FieldTooltipContent from './FieldTooltipContent';
|
||||
import InputFieldRenderer from './InputFieldRenderer';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
@ -38,54 +33,44 @@ const LinearViewField = ({ nodeId, fieldName }: Props) => {
|
||||
onMouseEnter={handleMouseOver}
|
||||
onMouseLeave={handleMouseOut}
|
||||
layerStyle="second"
|
||||
sx={{
|
||||
position: 'relative',
|
||||
borderRadius: 'base',
|
||||
w: 'full',
|
||||
p: 2,
|
||||
}}
|
||||
position="relative"
|
||||
borderRadius="base"
|
||||
w="full"
|
||||
p={4}
|
||||
flexDir="column"
|
||||
>
|
||||
<FormControl as={Flex} sx={{ flexDir: 'column', gap: 1, flexShrink: 1 }}>
|
||||
<FormLabel
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
mb: 0,
|
||||
}}
|
||||
<Flex>
|
||||
<EditableFieldTitle
|
||||
nodeId={nodeId}
|
||||
fieldName={fieldName}
|
||||
kind="input"
|
||||
/>
|
||||
<Spacer />
|
||||
<InvTooltip
|
||||
label={
|
||||
<FieldTooltipContent
|
||||
nodeId={nodeId}
|
||||
fieldName={fieldName}
|
||||
kind="input"
|
||||
/>
|
||||
}
|
||||
openDelay={HANDLE_TOOLTIP_OPEN_DELAY}
|
||||
placement="top"
|
||||
>
|
||||
<EditableFieldTitle
|
||||
nodeId={nodeId}
|
||||
fieldName={fieldName}
|
||||
kind="input"
|
||||
/>
|
||||
<Spacer />
|
||||
<Tooltip
|
||||
label={
|
||||
<FieldTooltipContent
|
||||
nodeId={nodeId}
|
||||
fieldName={fieldName}
|
||||
kind="input"
|
||||
/>
|
||||
}
|
||||
openDelay={HANDLE_TOOLTIP_OPEN_DELAY}
|
||||
placement="top"
|
||||
hasArrow
|
||||
>
|
||||
<Flex h="full" alignItems="center">
|
||||
<Icon as={FaInfoCircle} />
|
||||
</Flex>
|
||||
</Tooltip>
|
||||
<IAIIconButton
|
||||
aria-label={t('nodes.removeLinearView')}
|
||||
tooltip={t('nodes.removeLinearView')}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleRemoveField}
|
||||
icon={<FaTrash />}
|
||||
/>
|
||||
</FormLabel>
|
||||
<InputFieldRenderer nodeId={nodeId} fieldName={fieldName} />
|
||||
</FormControl>
|
||||
<Flex h="full" alignItems="center">
|
||||
<Icon as={FaInfoCircle} />
|
||||
</Flex>
|
||||
</InvTooltip>
|
||||
<InvIconButton
|
||||
aria-label={t('nodes.removeLinearView')}
|
||||
tooltip={t('nodes.removeLinearView')}
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleRemoveField}
|
||||
icon={<FaTrash />}
|
||||
/>
|
||||
</Flex>
|
||||
<InputFieldRenderer nodeId={nodeId} fieldName={fieldName} />
|
||||
<NodeSelectionOverlay isSelected={false} isHovered={isMouseOverNode} />
|
||||
</Flex>
|
||||
);
|
||||
|
@ -1,10 +1,15 @@
|
||||
import { Flex, FormControl, FormLabel, Tooltip } from '@chakra-ui/react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvLabel } from 'common/components/InvControl/InvLabel';
|
||||
import { InvTooltip } from 'common/components/InvTooltip/InvTooltip';
|
||||
import { useConnectionState } from 'features/nodes/hooks/useConnectionState';
|
||||
import { useFieldOutputInstance } from 'features/nodes/hooks/useFieldOutputInstance';
|
||||
import { useFieldOutputTemplate } from 'features/nodes/hooks/useFieldOutputTemplate';
|
||||
import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
|
||||
import { PropsWithChildren, memo } from 'react';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import FieldHandle from './FieldHandle';
|
||||
import FieldTooltipContent from './FieldTooltipContent';
|
||||
|
||||
@ -29,40 +34,34 @@ const OutputField = ({ nodeId, fieldName }: Props) => {
|
||||
if (!fieldTemplate || !fieldInstance) {
|
||||
return (
|
||||
<OutputFieldWrapper shouldDim={shouldDim}>
|
||||
<FormControl
|
||||
sx={{
|
||||
alignItems: 'stretch',
|
||||
justifyContent: 'space-between',
|
||||
gap: 2,
|
||||
h: 'full',
|
||||
w: 'full',
|
||||
}}
|
||||
<InvControl
|
||||
alignItems="stretch"
|
||||
justifyContent="space-between"
|
||||
gap={2}
|
||||
h="full"
|
||||
w="full"
|
||||
>
|
||||
<FormLabel
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
mb: 0,
|
||||
px: 1,
|
||||
gap: 2,
|
||||
h: 'full',
|
||||
fontWeight: 600,
|
||||
color: 'error.400',
|
||||
_dark: { color: 'error.300' },
|
||||
}}
|
||||
<InvLabel
|
||||
display="flex"
|
||||
alignItems="center"
|
||||
h="full"
|
||||
color="error.300"
|
||||
mb={0}
|
||||
px={1}
|
||||
gap={2}
|
||||
>
|
||||
{t('nodes.unknownOutput', {
|
||||
name: fieldTemplate?.title ?? fieldName,
|
||||
})}
|
||||
</FormLabel>
|
||||
</FormControl>
|
||||
</InvLabel>
|
||||
</InvControl>
|
||||
</OutputFieldWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<OutputFieldWrapper shouldDim={shouldDim}>
|
||||
<Tooltip
|
||||
<InvTooltip
|
||||
label={
|
||||
<FieldTooltipContent
|
||||
nodeId={nodeId}
|
||||
@ -73,14 +72,11 @@ const OutputField = ({ nodeId, fieldName }: Props) => {
|
||||
openDelay={HANDLE_TOOLTIP_OPEN_DELAY}
|
||||
placement="top"
|
||||
shouldWrapChildren
|
||||
hasArrow
|
||||
>
|
||||
<FormControl isDisabled={isConnected} pe={2}>
|
||||
<FormLabel sx={{ mb: 0, fontWeight: 500 }}>
|
||||
{fieldTemplate?.title}
|
||||
</FormLabel>
|
||||
</FormControl>
|
||||
</Tooltip>
|
||||
<InvControl isDisabled={isConnected} pe={2}>
|
||||
<InvLabel mb={0}>{fieldTemplate?.title}</InvLabel>
|
||||
</InvControl>
|
||||
</InvTooltip>
|
||||
<FieldHandle
|
||||
fieldTemplate={fieldTemplate}
|
||||
handleType="source"
|
||||
|
@ -1,63 +1,80 @@
|
||||
import { SelectItem } from '@mantine/core';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
||||
import type {
|
||||
InvSelectOnChange,
|
||||
InvSelectOption,
|
||||
} from 'common/components/InvSelect/types';
|
||||
import { fieldBoardValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
BoardFieldInputTemplate,
|
||||
import type {
|
||||
BoardFieldInputInstance,
|
||||
BoardFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useListAllBoardsQuery } from 'services/api/endpoints/boards';
|
||||
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
const BoardFieldInputComponent = (
|
||||
props: FieldComponentProps<BoardFieldInputInstance, BoardFieldInputTemplate>
|
||||
) => {
|
||||
const { nodeId, field } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { data, hasBoards } = useListAllBoardsQuery(undefined, {
|
||||
const { t } = useTranslation();
|
||||
const { options, hasBoards } = useListAllBoardsQuery(undefined, {
|
||||
selectFromResult: ({ data }) => {
|
||||
const boards: SelectItem[] = [
|
||||
const options: InvSelectOption[] = [
|
||||
{
|
||||
label: 'None',
|
||||
value: 'none',
|
||||
},
|
||||
];
|
||||
data?.forEach(({ board_id, board_name }) => {
|
||||
boards.push({
|
||||
].concat(
|
||||
(data ?? []).map(({ board_id, board_name }) => ({
|
||||
label: board_name,
|
||||
value: board_id,
|
||||
});
|
||||
});
|
||||
}))
|
||||
);
|
||||
return {
|
||||
data: boards,
|
||||
hasBoards: boards.length > 1,
|
||||
options,
|
||||
hasBoards: options.length > 1,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const handleChange = useCallback(
|
||||
(v: string | null) => {
|
||||
const onChange = useCallback<InvSelectOnChange>(
|
||||
(v) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
dispatch(
|
||||
fieldBoardValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: v && v !== 'none' ? { board_id: v } : undefined,
|
||||
value: v.value !== 'none' ? { board_id: v.value } : undefined,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, field.name, nodeId]
|
||||
);
|
||||
|
||||
const value = useMemo(
|
||||
() => options.find((o) => o.value === field.value?.board_id),
|
||||
[options, field.value]
|
||||
);
|
||||
|
||||
const noOptionsMessage = useCallback(() => t('boards.noMatching'), [t]);
|
||||
|
||||
return (
|
||||
<IAIMantineSearchableSelect
|
||||
className="nowheel nodrag"
|
||||
value={field.value?.board_id ?? 'none'}
|
||||
data={data}
|
||||
onChange={handleChange}
|
||||
disabled={!hasBoards}
|
||||
/>
|
||||
<InvControl className="nowheel nodrag" isDisabled={!hasBoards}>
|
||||
<InvSelect
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
placeholder={t('boards.selectBoard')}
|
||||
noOptionsMessage={noOptionsMessage}
|
||||
/>
|
||||
</InvControl>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { Switch } from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { fieldBooleanValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
import type {
|
||||
BooleanFieldInputInstance,
|
||||
BooleanFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
const BooleanFieldInputComponent = (
|
||||
props: FieldComponentProps<
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { fieldColorValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
ColorFieldInputTemplate,
|
||||
import type {
|
||||
ColorFieldInputInstance,
|
||||
ColorFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { RgbaColor, RgbaColorPicker } from 'react-colorful';
|
||||
import type { RgbaColor } from 'react-colorful';
|
||||
import { RgbaColorPicker } from 'react-colorful';
|
||||
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
const ColorFieldInputComponent = (
|
||||
props: FieldComponentProps<ColorFieldInputInstance, ColorFieldInputTemplate>
|
||||
|
@ -1,99 +1,67 @@
|
||||
import { SelectItem } from '@mantine/core';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
||||
import { useGroupedModelInvSelect } from 'common/components/InvSelect/useGroupedModelInvSelect';
|
||||
import { InvTooltip } from 'common/components/InvTooltip/InvTooltip';
|
||||
import { fieldControlNetModelValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
ControlNetModelFieldInputTemplate,
|
||||
import type {
|
||||
ControlNetModelFieldInputInstance,
|
||||
ControlNetModelFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||
import { modelIdToControlNetModelParam } from 'features/parameters/util/modelIdToControlNetModelParam';
|
||||
import { forEach } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import type { ControlNetModelConfigEntity } from 'services/api/endpoints/models';
|
||||
import { useGetControlNetModelsQuery } from 'services/api/endpoints/models';
|
||||
|
||||
const ControlNetModelFieldInputComponent = (
|
||||
props: FieldComponentProps<
|
||||
ControlNetModelFieldInputInstance,
|
||||
ControlNetModelFieldInputTemplate
|
||||
>
|
||||
) => {
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
type Props = FieldComponentProps<
|
||||
ControlNetModelFieldInputInstance,
|
||||
ControlNetModelFieldInputTemplate
|
||||
>;
|
||||
|
||||
const ControlNetModelFieldInputComponent = (props: Props) => {
|
||||
const { nodeId, field } = props;
|
||||
const controlNetModel = field.value;
|
||||
const dispatch = useAppDispatch();
|
||||
const { data, isLoading } = useGetControlNetModelsQuery();
|
||||
|
||||
const { data: controlNetModels } = useGetControlNetModelsQuery();
|
||||
|
||||
// grab the full model entity from the RTK Query cache
|
||||
const selectedModel = useMemo(
|
||||
() =>
|
||||
controlNetModels?.entities[
|
||||
`${controlNetModel?.base_model}/controlnet/${controlNetModel?.model_name}`
|
||||
] ?? null,
|
||||
[
|
||||
controlNetModel?.base_model,
|
||||
controlNetModel?.model_name,
|
||||
controlNetModels?.entities,
|
||||
]
|
||||
);
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (!controlNetModels) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data: SelectItem[] = [];
|
||||
|
||||
forEach(controlNetModels.entities, (model, id) => {
|
||||
if (!model) {
|
||||
const _onChange = useCallback(
|
||||
(value: ControlNetModelConfigEntity | null) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.push({
|
||||
value: id,
|
||||
label: model.model_name,
|
||||
group: MODEL_TYPE_MAP[model.base_model],
|
||||
});
|
||||
});
|
||||
|
||||
return data;
|
||||
}, [controlNetModels]);
|
||||
|
||||
const handleValueChanged = useCallback(
|
||||
(v: string | null) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newControlNetModel = modelIdToControlNetModelParam(v);
|
||||
|
||||
if (!newControlNetModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
fieldControlNetModelValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: newControlNetModel,
|
||||
value,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, field.name, nodeId]
|
||||
);
|
||||
|
||||
const { options, value, onChange, placeholder, noOptionsMessage } =
|
||||
useGroupedModelInvSelect({
|
||||
modelEntities: data,
|
||||
onChange: _onChange,
|
||||
selectedModel: field.value
|
||||
? { ...field.value, model_type: 'controlnet' }
|
||||
: undefined,
|
||||
isLoading,
|
||||
});
|
||||
|
||||
return (
|
||||
<IAIMantineSelect
|
||||
className="nowheel nodrag"
|
||||
tooltip={selectedModel?.description}
|
||||
value={selectedModel?.id ?? null}
|
||||
placeholder="Pick one"
|
||||
error={!selectedModel}
|
||||
data={data}
|
||||
onChange={handleValueChanged}
|
||||
sx={{ width: '100%' }}
|
||||
/>
|
||||
<InvTooltip label={value?.description}>
|
||||
<InvControl className="nowheel nodrag" isInvalid={!value}>
|
||||
<InvSelect
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
noOptionsMessage={noOptionsMessage}
|
||||
/>
|
||||
</InvControl>
|
||||
</InvTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
import { Select } from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { fieldEnumModelValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
import type {
|
||||
EnumFieldInputInstance,
|
||||
EnumFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
const EnumFieldInputComponent = (
|
||||
props: FieldComponentProps<EnumFieldInputInstance, EnumFieldInputTemplate>
|
||||
|
@ -1,18 +1,19 @@
|
||||
import { SelectItem } from '@mantine/core';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
||||
import { useGroupedModelInvSelect } from 'common/components/InvSelect/useGroupedModelInvSelect';
|
||||
import { InvTooltip } from 'common/components/InvTooltip/InvTooltip';
|
||||
import { fieldIPAdapterModelValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
IPAdapterModelFieldInputTemplate,
|
||||
import type {
|
||||
IPAdapterModelFieldInputInstance,
|
||||
IPAdapterModelFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||
import { modelIdToIPAdapterModelParam } from 'features/parameters/util/modelIdToIPAdapterModelParams';
|
||||
import { forEach } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import type { IPAdapterModelConfigEntity } from 'services/api/endpoints/models';
|
||||
import { useGetIPAdapterModelsQuery } from 'services/api/endpoints/models';
|
||||
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
const IPAdapterModelFieldInputComponent = (
|
||||
props: FieldComponentProps<
|
||||
IPAdapterModelFieldInputInstance,
|
||||
@ -20,80 +21,45 @@ const IPAdapterModelFieldInputComponent = (
|
||||
>
|
||||
) => {
|
||||
const { nodeId, field } = props;
|
||||
const ipAdapterModel = field.value;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { data: ipAdapterModels } = useGetIPAdapterModelsQuery();
|
||||
|
||||
// grab the full model entity from the RTK Query cache
|
||||
const selectedModel = useMemo(
|
||||
() =>
|
||||
ipAdapterModels?.entities[
|
||||
`${ipAdapterModel?.base_model}/ip_adapter/${ipAdapterModel?.model_name}`
|
||||
] ?? null,
|
||||
[
|
||||
ipAdapterModel?.base_model,
|
||||
ipAdapterModel?.model_name,
|
||||
ipAdapterModels?.entities,
|
||||
]
|
||||
);
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (!ipAdapterModels) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data: SelectItem[] = [];
|
||||
|
||||
forEach(ipAdapterModels.entities, (model, id) => {
|
||||
if (!model) {
|
||||
const _onChange = useCallback(
|
||||
(value: IPAdapterModelConfigEntity | null) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.push({
|
||||
value: id,
|
||||
label: model.model_name,
|
||||
group: MODEL_TYPE_MAP[model.base_model],
|
||||
});
|
||||
});
|
||||
|
||||
return data;
|
||||
}, [ipAdapterModels]);
|
||||
|
||||
const handleValueChanged = useCallback(
|
||||
(v: string | null) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newIPAdapterModel = modelIdToIPAdapterModelParam(v);
|
||||
|
||||
if (!newIPAdapterModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
fieldIPAdapterModelValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: newIPAdapterModel,
|
||||
value,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, field.name, nodeId]
|
||||
);
|
||||
|
||||
const { options, value, onChange } = useGroupedModelInvSelect({
|
||||
modelEntities: ipAdapterModels,
|
||||
onChange: _onChange,
|
||||
selectedModel: field.value
|
||||
? { ...field.value, model_type: 'ip_adapter' }
|
||||
: undefined,
|
||||
});
|
||||
|
||||
return (
|
||||
<IAIMantineSelect
|
||||
className="nowheel nodrag"
|
||||
tooltip={selectedModel?.description}
|
||||
value={selectedModel?.id ?? null}
|
||||
placeholder="Pick one"
|
||||
error={!selectedModel}
|
||||
data={data}
|
||||
onChange={handleValueChanged}
|
||||
sx={{ width: '100%' }}
|
||||
/>
|
||||
<InvTooltip label={value?.description}>
|
||||
<InvControl className="nowheel nodrag" isInvalid={!value}>
|
||||
<InvSelect
|
||||
value={value}
|
||||
placeholder="Pick one"
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
sx={{ width: '100%' }}
|
||||
/>
|
||||
</InvControl>
|
||||
</InvTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,23 +1,25 @@
|
||||
import { Flex, Text } from '@chakra-ui/react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { skipToken } from '@reduxjs/toolkit/query';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import IAIDndImageIcon from 'common/components/IAIDndImageIcon';
|
||||
import {
|
||||
import { InvText } from 'common/components/InvText/wrapper';
|
||||
import type {
|
||||
TypesafeDraggableData,
|
||||
TypesafeDroppableData,
|
||||
} from 'features/dnd/types';
|
||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
import type {
|
||||
ImageFieldInputInstance,
|
||||
ImageFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { memo, useCallback, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaUndo } from 'react-icons/fa';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import { PostUploadAction } from 'services/api/types';
|
||||
import type { PostUploadAction } from 'services/api/types';
|
||||
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
const ImageFieldInputComponent = (
|
||||
props: FieldComponentProps<ImageFieldInputInstance, ImageFieldInputTemplate>
|
||||
@ -108,9 +110,9 @@ export default memo(ImageFieldInputComponent);
|
||||
const UploadElement = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Text fontSize={16} fontWeight={600}>
|
||||
<InvText fontSize={16} fontWeight="semibold">
|
||||
{t('gallery.dropOrUpload')}
|
||||
</Text>
|
||||
</InvText>
|
||||
);
|
||||
});
|
||||
|
||||
@ -119,9 +121,9 @@ UploadElement.displayName = 'UploadElement';
|
||||
const DropLabel = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Text fontSize={16} fontWeight={600}>
|
||||
<InvText fontSize={16} fontWeight="semibold">
|
||||
{t('gallery.drop')}
|
||||
</Text>
|
||||
</InvText>
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -1,123 +1,67 @@
|
||||
import { Flex, Text } from '@chakra-ui/react';
|
||||
import { SelectItem } from '@mantine/core';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
||||
import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
||||
import { useGroupedModelInvSelect } from 'common/components/InvSelect/useGroupedModelInvSelect';
|
||||
import { fieldLoRAModelValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
LoRAModelFieldInputTemplate,
|
||||
import type {
|
||||
LoRAModelFieldInputInstance,
|
||||
LoRAModelFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||
import { modelIdToLoRAModelParam } from 'features/parameters/util/modelIdToLoRAModelParam';
|
||||
import { forEach } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import type { LoRAModelConfigEntity } from 'services/api/endpoints/models';
|
||||
import { useGetLoRAModelsQuery } from 'services/api/endpoints/models';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const LoRAModelFieldInputComponent = (
|
||||
props: FieldComponentProps<
|
||||
LoRAModelFieldInputInstance,
|
||||
LoRAModelFieldInputTemplate
|
||||
>
|
||||
) => {
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
type Props = FieldComponentProps<
|
||||
LoRAModelFieldInputInstance,
|
||||
LoRAModelFieldInputTemplate
|
||||
>;
|
||||
|
||||
const LoRAModelFieldInputComponent = (props: Props) => {
|
||||
const { nodeId, field } = props;
|
||||
const lora = field.value;
|
||||
const dispatch = useAppDispatch();
|
||||
const { data: loraModels } = useGetLoRAModelsQuery();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (!loraModels) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data: SelectItem[] = [];
|
||||
|
||||
forEach(loraModels.entities, (lora, id) => {
|
||||
if (!lora) {
|
||||
const { data, isLoading } = useGetLoRAModelsQuery();
|
||||
const _onChange = useCallback(
|
||||
(value: LoRAModelConfigEntity | null) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.push({
|
||||
value: id,
|
||||
label: lora.model_name,
|
||||
group: MODEL_TYPE_MAP[lora.base_model],
|
||||
});
|
||||
});
|
||||
|
||||
return data.sort((a, b) => (a.disabled && !b.disabled ? 1 : -1));
|
||||
}, [loraModels]);
|
||||
|
||||
const selectedLoRAModel = useMemo(
|
||||
() =>
|
||||
loraModels?.entities[`${lora?.base_model}/lora/${lora?.model_name}`] ??
|
||||
null,
|
||||
[loraModels?.entities, lora?.base_model, lora?.model_name]
|
||||
);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(v: string | null) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newLoRAModel = modelIdToLoRAModelParam(v);
|
||||
|
||||
if (!newLoRAModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
fieldLoRAModelValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: newLoRAModel,
|
||||
value,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, field.name, nodeId]
|
||||
);
|
||||
|
||||
const filterFunc = useCallback(
|
||||
(value: string, item: SelectItem) =>
|
||||
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
|
||||
item.value.toLowerCase().includes(value.toLowerCase().trim()),
|
||||
[]
|
||||
);
|
||||
|
||||
if (loraModels?.ids.length === 0) {
|
||||
return (
|
||||
<Flex sx={{ justifyContent: 'center', p: 2 }}>
|
||||
<Text sx={{ fontSize: 'sm', color: 'base.500', _dark: 'base.700' }}>
|
||||
{t('models.noLoRAsLoaded')}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
const { options, value, onChange, placeholder, noOptionsMessage } =
|
||||
useGroupedModelInvSelect({
|
||||
modelEntities: data,
|
||||
onChange: _onChange,
|
||||
selectedModel: field.value
|
||||
? { ...field.value, model_type: 'lora' }
|
||||
: undefined,
|
||||
isLoading,
|
||||
});
|
||||
|
||||
return (
|
||||
<IAIMantineSearchableSelect
|
||||
<InvControl
|
||||
className="nowheel nodrag"
|
||||
value={selectedLoRAModel?.id ?? null}
|
||||
placeholder={
|
||||
data.length > 0 ? t('models.selectLoRA') : t('models.noLoRAsAvailable')
|
||||
}
|
||||
data={data}
|
||||
nothingFound={t('models.noMatchingLoRAs')}
|
||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||
disabled={data.length === 0}
|
||||
filter={filterFunc}
|
||||
error={!selectedLoRAModel}
|
||||
onChange={handleChange}
|
||||
sx={{
|
||||
width: '100%',
|
||||
'.mantine-Select-dropdown': {
|
||||
width: '16rem !important',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
isInvalid={!value}
|
||||
isDisabled={!options.length}
|
||||
>
|
||||
<InvSelect
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
noOptionsMessage={noOptionsMessage}
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</InvControl>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,150 +1,68 @@
|
||||
import { Flex, Text } from '@chakra-ui/react';
|
||||
import { SelectItem } from '@mantine/core';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
||||
import { useGroupedModelInvSelect } from 'common/components/InvSelect/useGroupedModelInvSelect';
|
||||
import { SyncModelsIconButton } from 'features/modelManager/components/SyncModels/SyncModelsIconButton';
|
||||
import { fieldMainModelValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
MainModelFieldInputTemplate,
|
||||
import type {
|
||||
MainModelFieldInputInstance,
|
||||
MainModelFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||
import { modelIdToMainModelParam } from 'features/parameters/util/modelIdToMainModelParam';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import SyncModelsButton from 'features/modelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton';
|
||||
import { forEach } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { NON_SDXL_MAIN_MODELS } from 'services/api/constants';
|
||||
import {
|
||||
useGetMainModelsQuery,
|
||||
useGetOnnxModelsQuery,
|
||||
} from 'services/api/endpoints/models';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type { MainModelConfigEntity } from 'services/api/endpoints/models';
|
||||
import { useGetMainModelsQuery } from 'services/api/endpoints/models';
|
||||
|
||||
const MainModelFieldInputComponent = (
|
||||
props: FieldComponentProps<
|
||||
MainModelFieldInputInstance,
|
||||
MainModelFieldInputTemplate
|
||||
>
|
||||
) => {
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
type Props = FieldComponentProps<
|
||||
MainModelFieldInputInstance,
|
||||
MainModelFieldInputTemplate
|
||||
>;
|
||||
|
||||
const MainModelFieldInputComponent = (props: Props) => {
|
||||
const { nodeId, field } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled;
|
||||
const { t } = useTranslation();
|
||||
const { data: onnxModels, isLoading: isLoadingOnnxModels } =
|
||||
useGetOnnxModelsQuery(NON_SDXL_MAIN_MODELS);
|
||||
const { data: mainModels, isLoading: isLoadingMainModels } =
|
||||
useGetMainModelsQuery(NON_SDXL_MAIN_MODELS);
|
||||
|
||||
const isLoadingModels = useMemo(
|
||||
() => isLoadingOnnxModels || isLoadingMainModels,
|
||||
[isLoadingOnnxModels, isLoadingMainModels]
|
||||
);
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (!mainModels) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data: SelectItem[] = [];
|
||||
|
||||
forEach(mainModels.entities, (model, id) => {
|
||||
if (!model) {
|
||||
const { data, isLoading } = useGetMainModelsQuery(NON_SDXL_MAIN_MODELS);
|
||||
const _onChange = useCallback(
|
||||
(value: MainModelConfigEntity | null) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.push({
|
||||
value: id,
|
||||
label: model.model_name,
|
||||
group: MODEL_TYPE_MAP[model.base_model],
|
||||
});
|
||||
});
|
||||
|
||||
if (onnxModels) {
|
||||
forEach(onnxModels.entities, (model, id) => {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.push({
|
||||
value: id,
|
||||
label: model.model_name,
|
||||
group: MODEL_TYPE_MAP[model.base_model],
|
||||
});
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}, [mainModels, onnxModels]);
|
||||
|
||||
// grab the full model entity from the RTK Query cache
|
||||
// TODO: maybe we should just store the full model entity in state?
|
||||
const selectedModel = useMemo(
|
||||
() =>
|
||||
(mainModels?.entities[
|
||||
`${field.value?.base_model}/main/${field.value?.model_name}`
|
||||
] ||
|
||||
onnxModels?.entities[
|
||||
`${field.value?.base_model}/onnx/${field.value?.model_name}`
|
||||
]) ??
|
||||
null,
|
||||
[
|
||||
field.value?.base_model,
|
||||
field.value?.model_name,
|
||||
mainModels?.entities,
|
||||
onnxModels?.entities,
|
||||
]
|
||||
);
|
||||
|
||||
const handleChangeModel = useCallback(
|
||||
(v: string | null) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newModel = modelIdToMainModelParam(v);
|
||||
|
||||
if (!newModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
fieldMainModelValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: newModel,
|
||||
value,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, field.name, nodeId]
|
||||
);
|
||||
const { options, value, onChange, placeholder, noOptionsMessage } =
|
||||
useGroupedModelInvSelect({
|
||||
modelEntities: data,
|
||||
onChange: _onChange,
|
||||
isLoading,
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex sx={{ w: 'full', alignItems: 'center', gap: 2 }}>
|
||||
{isLoadingModels ? (
|
||||
<Text variant="subtext">Loading...</Text>
|
||||
) : (
|
||||
<IAIMantineSearchableSelect
|
||||
className="nowheel nodrag"
|
||||
tooltip={selectedModel?.description}
|
||||
value={selectedModel?.id}
|
||||
placeholder={
|
||||
data.length > 0
|
||||
? t('models.selectModel')
|
||||
: t('models.noModelsAvailable')
|
||||
}
|
||||
data={data}
|
||||
error={!selectedModel}
|
||||
disabled={data.length === 0}
|
||||
onChange={handleChangeModel}
|
||||
sx={{
|
||||
width: '100%',
|
||||
'.mantine-Select-dropdown': {
|
||||
width: '16rem !important',
|
||||
},
|
||||
}}
|
||||
<InvControl
|
||||
className="nowheel nodrag"
|
||||
isDisabled={!options.length}
|
||||
isInvalid={!value}
|
||||
>
|
||||
<InvSelect
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
noOptionsMessage={noOptionsMessage}
|
||||
/>
|
||||
)}
|
||||
{isSyncModelEnabled && <SyncModelsButton className="nodrag" iconMode />}
|
||||
</InvControl>
|
||||
<SyncModelsIconButton className="nodrag" />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -1,21 +1,17 @@
|
||||
import {
|
||||
NumberDecrementStepper,
|
||||
NumberIncrementStepper,
|
||||
NumberInput,
|
||||
NumberInputField,
|
||||
NumberInputStepper,
|
||||
} from '@chakra-ui/react';
|
||||
import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { numberStringRegex } from 'common/components/IAINumberInput';
|
||||
import { InvNumberInput } from 'common/components/InvNumberInput/InvNumberInput';
|
||||
import { fieldNumberValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
import type {
|
||||
FloatFieldInputInstance,
|
||||
FloatFieldInputTemplate,
|
||||
IntegerFieldInputInstance,
|
||||
IntegerFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
const NumberFieldInputComponent = (
|
||||
props: FieldComponentProps<
|
||||
@ -25,54 +21,53 @@ const NumberFieldInputComponent = (
|
||||
) => {
|
||||
const { nodeId, field, fieldTemplate } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const [valueAsString, setValueAsString] = useState<string>(
|
||||
String(field.value)
|
||||
);
|
||||
const isIntegerField = useMemo(
|
||||
() => fieldTemplate.type.name === 'IntegerField',
|
||||
[fieldTemplate.type]
|
||||
);
|
||||
|
||||
const handleValueChanged = useCallback(
|
||||
(v: string) => {
|
||||
setValueAsString(v);
|
||||
// This allows negatives and decimals e.g. '-123', `.5`, `-0.2`, etc.
|
||||
if (!v.match(numberStringRegex)) {
|
||||
// Cast the value to number. Floor it if it should be an integer.
|
||||
dispatch(
|
||||
fieldNumberValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: isIntegerField ? Math.floor(Number(v)) : Number(v),
|
||||
})
|
||||
);
|
||||
}
|
||||
(v: number) => {
|
||||
dispatch(
|
||||
fieldNumberValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: isIntegerField ? Math.floor(Number(v)) : Number(v),
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, field.name, isIntegerField, nodeId]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!valueAsString.match(numberStringRegex) &&
|
||||
field.value !== Number(valueAsString)
|
||||
) {
|
||||
setValueAsString(String(field.value));
|
||||
const min = useMemo(() => {
|
||||
if (!isNil(fieldTemplate.minimum)) {
|
||||
return fieldTemplate.minimum;
|
||||
}
|
||||
}, [field.value, valueAsString]);
|
||||
if (!isNil(fieldTemplate.exclusiveMinimum)) {
|
||||
return fieldTemplate.exclusiveMinimum + 0.01;
|
||||
}
|
||||
return;
|
||||
}, [fieldTemplate.exclusiveMinimum, fieldTemplate.minimum]);
|
||||
|
||||
const max = useMemo(() => {
|
||||
if (!isNil(fieldTemplate.maximum)) {
|
||||
return fieldTemplate.maximum;
|
||||
}
|
||||
if (!isNil(fieldTemplate.exclusiveMaximum)) {
|
||||
return fieldTemplate.exclusiveMaximum - 0.01;
|
||||
}
|
||||
return;
|
||||
}, [fieldTemplate.exclusiveMaximum, fieldTemplate.maximum]);
|
||||
|
||||
return (
|
||||
<NumberInput
|
||||
<InvNumberInput
|
||||
onChange={handleValueChanged}
|
||||
value={valueAsString}
|
||||
value={field.value}
|
||||
min={min ?? NUMPY_RAND_MIN}
|
||||
max={max ?? NUMPY_RAND_MAX}
|
||||
step={isIntegerField ? 1 : 0.1}
|
||||
precision={isIntegerField ? 0 : 3}
|
||||
>
|
||||
<NumberInputField className="nodrag" />
|
||||
<NumberInputStepper>
|
||||
<NumberIncrementStepper />
|
||||
<NumberDecrementStepper />
|
||||
</NumberInputStepper>
|
||||
</NumberInput>
|
||||
className="nodrag"
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,121 +1,68 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { SelectItem } from '@mantine/core';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
||||
import { useGroupedModelInvSelect } from 'common/components/InvSelect/useGroupedModelInvSelect';
|
||||
import { SyncModelsIconButton } from 'features/modelManager/components/SyncModels/SyncModelsIconButton';
|
||||
import { fieldRefinerModelValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
SDXLRefinerModelFieldInputTemplate,
|
||||
import type {
|
||||
SDXLRefinerModelFieldInputInstance,
|
||||
SDXLRefinerModelFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||
import { modelIdToMainModelParam } from 'features/parameters/util/modelIdToMainModelParam';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import SyncModelsButton from 'features/modelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton';
|
||||
import { forEach } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { REFINER_BASE_MODELS } from 'services/api/constants';
|
||||
import type { MainModelConfigEntity } from 'services/api/endpoints/models';
|
||||
import { useGetMainModelsQuery } from 'services/api/endpoints/models';
|
||||
|
||||
const RefinerModelFieldInputComponent = (
|
||||
props: FieldComponentProps<
|
||||
SDXLRefinerModelFieldInputInstance,
|
||||
SDXLRefinerModelFieldInputTemplate
|
||||
>
|
||||
) => {
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
type Props = FieldComponentProps<
|
||||
SDXLRefinerModelFieldInputInstance,
|
||||
SDXLRefinerModelFieldInputTemplate
|
||||
>;
|
||||
|
||||
const RefinerModelFieldInputComponent = (props: Props) => {
|
||||
const { nodeId, field } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled;
|
||||
const { data: refinerModels, isLoading } =
|
||||
useGetMainModelsQuery(REFINER_BASE_MODELS);
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (!refinerModels) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data: SelectItem[] = [];
|
||||
|
||||
forEach(refinerModels.entities, (model, id) => {
|
||||
if (!model) {
|
||||
const { data, isLoading } = useGetMainModelsQuery(REFINER_BASE_MODELS);
|
||||
const _onChange = useCallback(
|
||||
(value: MainModelConfigEntity | null) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.push({
|
||||
value: id,
|
||||
label: model.model_name,
|
||||
group: MODEL_TYPE_MAP[model.base_model],
|
||||
});
|
||||
});
|
||||
|
||||
return data;
|
||||
}, [refinerModels]);
|
||||
|
||||
// grab the full model entity from the RTK Query cache
|
||||
// TODO: maybe we should just store the full model entity in state?
|
||||
const selectedModel = useMemo(
|
||||
() =>
|
||||
refinerModels?.entities[
|
||||
`${field.value?.base_model}/main/${field.value?.model_name}`
|
||||
] ?? null,
|
||||
[field.value?.base_model, field.value?.model_name, refinerModels?.entities]
|
||||
);
|
||||
|
||||
const handleChangeModel = useCallback(
|
||||
(v: string | null) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newModel = modelIdToMainModelParam(v);
|
||||
|
||||
if (!newModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
fieldRefinerModelValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: newModel,
|
||||
value,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, field.name, nodeId]
|
||||
);
|
||||
const { options, value, onChange, placeholder, noOptionsMessage } =
|
||||
useGroupedModelInvSelect({
|
||||
modelEntities: data,
|
||||
onChange: _onChange,
|
||||
isLoading,
|
||||
});
|
||||
|
||||
return isLoading ? (
|
||||
<IAIMantineSearchableSelect
|
||||
label={t('modelManager.model')}
|
||||
placeholder={t('models.loading')}
|
||||
disabled={true}
|
||||
data={[]}
|
||||
/>
|
||||
) : (
|
||||
<Flex w="100%" alignItems="center" gap={2}>
|
||||
<IAIMantineSearchableSelect
|
||||
return (
|
||||
<Flex sx={{ w: 'full', alignItems: 'center', gap: 2 }}>
|
||||
<InvControl
|
||||
className="nowheel nodrag"
|
||||
tooltip={selectedModel?.description}
|
||||
value={selectedModel?.id}
|
||||
placeholder={
|
||||
data.length > 0
|
||||
? t('models.selectModel')
|
||||
: t('models.noModelsAvailable')
|
||||
}
|
||||
data={data}
|
||||
error={!selectedModel}
|
||||
disabled={data.length === 0}
|
||||
onChange={handleChangeModel}
|
||||
sx={{
|
||||
width: '100%',
|
||||
'.mantine-Select-dropdown': {
|
||||
width: '16rem !important',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{isSyncModelEnabled && <SyncModelsButton className="nodrag" iconMode />}
|
||||
isDisabled={!options.length}
|
||||
isInvalid={!value}
|
||||
>
|
||||
<InvSelect
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
noOptionsMessage={noOptionsMessage}
|
||||
/>
|
||||
</InvControl>
|
||||
<SyncModelsIconButton className="nodrag" />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -1,148 +1,68 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { SelectItem } from '@mantine/core';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
||||
import { useGroupedModelInvSelect } from 'common/components/InvSelect/useGroupedModelInvSelect';
|
||||
import { SyncModelsIconButton } from 'features/modelManager/components/SyncModels/SyncModelsIconButton';
|
||||
import { fieldMainModelValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
SDXLMainModelFieldInputTemplate,
|
||||
import type {
|
||||
SDXLMainModelFieldInputInstance,
|
||||
SDXLMainModelFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||
import { modelIdToMainModelParam } from 'features/parameters/util/modelIdToMainModelParam';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import SyncModelsButton from 'features/modelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton';
|
||||
import { forEach } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { SDXL_MAIN_MODELS } from 'services/api/constants';
|
||||
import {
|
||||
useGetMainModelsQuery,
|
||||
useGetOnnxModelsQuery,
|
||||
} from 'services/api/endpoints/models';
|
||||
import type { MainModelConfigEntity } from 'services/api/endpoints/models';
|
||||
import { useGetMainModelsQuery } from 'services/api/endpoints/models';
|
||||
|
||||
const SDXLMainModelFieldInputComponent = (
|
||||
props: FieldComponentProps<
|
||||
SDXLMainModelFieldInputInstance,
|
||||
SDXLMainModelFieldInputTemplate
|
||||
>
|
||||
) => {
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
type Props = FieldComponentProps<
|
||||
SDXLMainModelFieldInputInstance,
|
||||
SDXLMainModelFieldInputTemplate
|
||||
>;
|
||||
|
||||
const SDXLMainModelFieldInputComponent = (props: Props) => {
|
||||
const { nodeId, field } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const isSyncModelEnabled = useFeatureStatus('syncModels').isFeatureEnabled;
|
||||
|
||||
const { data: onnxModels } = useGetOnnxModelsQuery(SDXL_MAIN_MODELS);
|
||||
const { data: mainModels, isLoading } =
|
||||
useGetMainModelsQuery(SDXL_MAIN_MODELS);
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (!mainModels) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data: SelectItem[] = [];
|
||||
|
||||
forEach(mainModels.entities, (model, id) => {
|
||||
if (!model || model.base_model !== 'sdxl') {
|
||||
const { data, isLoading } = useGetMainModelsQuery(SDXL_MAIN_MODELS);
|
||||
const _onChange = useCallback(
|
||||
(value: MainModelConfigEntity | null) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.push({
|
||||
value: id,
|
||||
label: model.model_name,
|
||||
group: MODEL_TYPE_MAP[model.base_model],
|
||||
});
|
||||
});
|
||||
|
||||
if (onnxModels) {
|
||||
forEach(onnxModels.entities, (model, id) => {
|
||||
if (!model || model.base_model !== 'sdxl') {
|
||||
return;
|
||||
}
|
||||
|
||||
data.push({
|
||||
value: id,
|
||||
label: model.model_name,
|
||||
group: MODEL_TYPE_MAP[model.base_model],
|
||||
});
|
||||
});
|
||||
}
|
||||
return data;
|
||||
}, [mainModels, onnxModels]);
|
||||
|
||||
// grab the full model entity from the RTK Query cache
|
||||
// TODO: maybe we should just store the full model entity in state?
|
||||
const selectedModel = useMemo(
|
||||
() =>
|
||||
(mainModels?.entities[
|
||||
`${field.value?.base_model}/main/${field.value?.model_name}`
|
||||
] ||
|
||||
onnxModels?.entities[
|
||||
`${field.value?.base_model}/onnx/${field.value?.model_name}`
|
||||
]) ??
|
||||
null,
|
||||
[
|
||||
field.value?.base_model,
|
||||
field.value?.model_name,
|
||||
mainModels?.entities,
|
||||
onnxModels?.entities,
|
||||
]
|
||||
);
|
||||
|
||||
const handleChangeModel = useCallback(
|
||||
(v: string | null) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newModel = modelIdToMainModelParam(v);
|
||||
|
||||
if (!newModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
fieldMainModelValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: newModel,
|
||||
value,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, field.name, nodeId]
|
||||
);
|
||||
const { options, value, onChange, placeholder, noOptionsMessage } =
|
||||
useGroupedModelInvSelect({
|
||||
modelEntities: data,
|
||||
onChange: _onChange,
|
||||
isLoading,
|
||||
});
|
||||
|
||||
return isLoading ? (
|
||||
<IAIMantineSearchableSelect
|
||||
label={t('modelManager.model')}
|
||||
placeholder={t('models.loading')}
|
||||
disabled={true}
|
||||
data={[]}
|
||||
/>
|
||||
) : (
|
||||
<Flex w="100%" alignItems="center" gap={2}>
|
||||
<IAIMantineSearchableSelect
|
||||
return (
|
||||
<Flex sx={{ w: 'full', alignItems: 'center', gap: 2 }}>
|
||||
<InvControl
|
||||
className="nowheel nodrag"
|
||||
tooltip={selectedModel?.description}
|
||||
value={selectedModel?.id}
|
||||
placeholder={
|
||||
data.length > 0
|
||||
? t('models.selectModel')
|
||||
: t('models.noModelsAvailable')
|
||||
}
|
||||
data={data}
|
||||
error={!selectedModel}
|
||||
disabled={data.length === 0}
|
||||
onChange={handleChangeModel}
|
||||
sx={{
|
||||
width: '100%',
|
||||
'.mantine-Select-dropdown': {
|
||||
width: '16rem !important',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{isSyncModelEnabled && <SyncModelsButton className="nodrag" iconMode />}
|
||||
isDisabled={!options.length}
|
||||
isInvalid={!value}
|
||||
>
|
||||
<InvSelect
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
noOptionsMessage={noOptionsMessage}
|
||||
/>
|
||||
</InvControl>
|
||||
<SyncModelsIconButton className="nodrag" />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -1,67 +1,56 @@
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
||||
import type { InvSelectOnChange } from 'common/components/InvSelect/types';
|
||||
import { fieldSchedulerValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
import type {
|
||||
SchedulerFieldInputInstance,
|
||||
SchedulerFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { SCHEDULER_LABEL_MAP } from 'features/parameters/types/constants';
|
||||
import { ParameterScheduler } from 'features/parameters/types/parameterSchemas';
|
||||
import { map } from 'lodash-es';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { SCHEDULER_OPTIONS } from 'features/parameters/types/constants';
|
||||
import { isParameterScheduler } from 'features/parameters/types/parameterSchemas';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
|
||||
const selector = createMemoizedSelector([stateSelector], ({ ui }) => {
|
||||
const { favoriteSchedulers: enabledSchedulers } = ui;
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
const data = map(SCHEDULER_LABEL_MAP, (label, name) => ({
|
||||
value: name,
|
||||
label: label,
|
||||
group: enabledSchedulers.includes(name as ParameterScheduler)
|
||||
? 'Favorites'
|
||||
: undefined,
|
||||
})).sort((a, b) => a.label.localeCompare(b.label));
|
||||
type Props = FieldComponentProps<
|
||||
SchedulerFieldInputInstance,
|
||||
SchedulerFieldInputTemplate
|
||||
>;
|
||||
|
||||
return {
|
||||
data,
|
||||
};
|
||||
});
|
||||
|
||||
const SchedulerFieldInputComponent = (
|
||||
props: FieldComponentProps<
|
||||
SchedulerFieldInputInstance,
|
||||
SchedulerFieldInputTemplate
|
||||
>
|
||||
) => {
|
||||
const SchedulerFieldInputComponent = (props: Props) => {
|
||||
const { nodeId, field } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
const { data } = useAppSelector(selector);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(value: string | null) => {
|
||||
if (!value) {
|
||||
const onChange = useCallback<InvSelectOnChange>(
|
||||
(v) => {
|
||||
if (!isParameterScheduler(v?.value)) {
|
||||
return;
|
||||
}
|
||||
dispatch(
|
||||
fieldSchedulerValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: value as ParameterScheduler,
|
||||
value: v.value,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, field.name, nodeId]
|
||||
);
|
||||
|
||||
const value = useMemo(
|
||||
() => SCHEDULER_OPTIONS.find((o) => o.value === field?.value),
|
||||
[field?.value]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAIMantineSearchableSelect
|
||||
className="nowheel nodrag"
|
||||
value={field.value}
|
||||
data={data}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<InvControl className="nowheel nodrag">
|
||||
<InvSelect
|
||||
value={value}
|
||||
options={SCHEDULER_OPTIONS}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</InvControl>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,13 +1,15 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIInput from 'common/components/IAIInput';
|
||||
import IAITextarea from 'common/components/IAITextarea';
|
||||
import { InvInput } from 'common/components/InvInput/InvInput';
|
||||
import { InvTextarea } from 'common/components/InvTextarea/InvTextarea';
|
||||
import { fieldStringValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
import type {
|
||||
StringFieldInputInstance,
|
||||
StringFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
const StringFieldInputComponent = (
|
||||
props: FieldComponentProps<StringFieldInputInstance, StringFieldInputTemplate>
|
||||
@ -30,7 +32,7 @@ const StringFieldInputComponent = (
|
||||
|
||||
if (fieldTemplate.ui_component === 'textarea') {
|
||||
return (
|
||||
<IAITextarea
|
||||
<InvTextarea
|
||||
className="nodrag"
|
||||
onChange={handleValueChanged}
|
||||
value={field.value}
|
||||
@ -40,7 +42,7 @@ const StringFieldInputComponent = (
|
||||
);
|
||||
}
|
||||
|
||||
return <IAIInput onChange={handleValueChanged} value={field.value} />;
|
||||
return <InvInput onChange={handleValueChanged} value={field.value} />;
|
||||
};
|
||||
|
||||
export default memo(StringFieldInputComponent);
|
||||
|
@ -1,18 +1,19 @@
|
||||
import { SelectItem } from '@mantine/core';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
||||
import { useGroupedModelInvSelect } from 'common/components/InvSelect/useGroupedModelInvSelect';
|
||||
import { InvTooltip } from 'common/components/InvTooltip/InvTooltip';
|
||||
import { fieldT2IAdapterModelValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
import type {
|
||||
T2IAdapterModelFieldInputInstance,
|
||||
T2IAdapterModelFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||
import { modelIdToT2IAdapterModelParam } from 'features/parameters/util/modelIdToT2IAdapterModelParam';
|
||||
import { forEach } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import type { T2IAdapterModelConfigEntity } from 'services/api/endpoints/models';
|
||||
import { useGetT2IAdapterModelsQuery } from 'services/api/endpoints/models';
|
||||
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
const T2IAdapterModelFieldInputComponent = (
|
||||
props: FieldComponentProps<
|
||||
T2IAdapterModelFieldInputInstance,
|
||||
@ -20,80 +21,46 @@ const T2IAdapterModelFieldInputComponent = (
|
||||
>
|
||||
) => {
|
||||
const { nodeId, field } = props;
|
||||
const t2iAdapterModel = field.value;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const { data: t2iAdapterModels } = useGetT2IAdapterModelsQuery();
|
||||
|
||||
// grab the full model entity from the RTK Query cache
|
||||
const selectedModel = useMemo(
|
||||
() =>
|
||||
t2iAdapterModels?.entities[
|
||||
`${t2iAdapterModel?.base_model}/t2i_adapter/${t2iAdapterModel?.model_name}`
|
||||
] ?? null,
|
||||
[
|
||||
t2iAdapterModel?.base_model,
|
||||
t2iAdapterModel?.model_name,
|
||||
t2iAdapterModels?.entities,
|
||||
]
|
||||
);
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (!t2iAdapterModels) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data: SelectItem[] = [];
|
||||
|
||||
forEach(t2iAdapterModels.entities, (model, id) => {
|
||||
if (!model) {
|
||||
const _onChange = useCallback(
|
||||
(value: T2IAdapterModelConfigEntity | null) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.push({
|
||||
value: id,
|
||||
label: model.model_name,
|
||||
group: MODEL_TYPE_MAP[model.base_model],
|
||||
});
|
||||
});
|
||||
|
||||
return data;
|
||||
}, [t2iAdapterModels]);
|
||||
|
||||
const handleValueChanged = useCallback(
|
||||
(v: string | null) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newT2IAdapterModel = modelIdToT2IAdapterModelParam(v);
|
||||
|
||||
if (!newT2IAdapterModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
fieldT2IAdapterModelValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: newT2IAdapterModel,
|
||||
value,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, field.name, nodeId]
|
||||
);
|
||||
|
||||
const { options, value, onChange } = useGroupedModelInvSelect({
|
||||
modelEntities: t2iAdapterModels,
|
||||
onChange: _onChange,
|
||||
selectedModel: field.value
|
||||
? { ...field.value, model_type: 't2i_adapter' }
|
||||
: undefined,
|
||||
});
|
||||
|
||||
return (
|
||||
<IAIMantineSelect
|
||||
className="nowheel nodrag"
|
||||
tooltip={selectedModel?.description}
|
||||
value={selectedModel?.id ?? null}
|
||||
placeholder="Pick one"
|
||||
error={!selectedModel}
|
||||
data={data}
|
||||
onChange={handleValueChanged}
|
||||
sx={{ width: '100%' }}
|
||||
/>
|
||||
<InvTooltip label={value?.description}>
|
||||
<InvControl className="nowheel nodrag" isInvalid={!value}>
|
||||
<InvSelect
|
||||
value={value}
|
||||
placeholder="Pick one"
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
sx={{ width: '100%' }}
|
||||
/>
|
||||
</InvControl>
|
||||
</InvTooltip>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,107 +1,69 @@
|
||||
import { SelectItem } from '@mantine/core';
|
||||
import { Flex } from '@chakra-ui/layout';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
|
||||
import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectItemWithTooltip';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvSelect } from 'common/components/InvSelect/InvSelect';
|
||||
import { useGroupedModelInvSelect } from 'common/components/InvSelect/useGroupedModelInvSelect';
|
||||
import { SyncModelsIconButton } from 'features/modelManager/components/SyncModels/SyncModelsIconButton';
|
||||
import { fieldVaeModelValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
VAEModelFieldInputTemplate,
|
||||
import type {
|
||||
VAEModelFieldInputInstance,
|
||||
VAEModelFieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
import { FieldComponentProps } from './types';
|
||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||
import { modelIdToVAEModelParam } from 'features/parameters/util/modelIdToVAEModelParam';
|
||||
import { forEach } from 'lodash-es';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import type { VaeModelConfigEntity } from 'services/api/endpoints/models';
|
||||
import { useGetVaeModelsQuery } from 'services/api/endpoints/models';
|
||||
|
||||
const VAEModelFieldInputComponent = (
|
||||
props: FieldComponentProps<
|
||||
VAEModelFieldInputInstance,
|
||||
VAEModelFieldInputTemplate
|
||||
>
|
||||
) => {
|
||||
import type { FieldComponentProps } from './types';
|
||||
|
||||
type Props = FieldComponentProps<
|
||||
VAEModelFieldInputInstance,
|
||||
VAEModelFieldInputTemplate
|
||||
>;
|
||||
|
||||
const VAEModelFieldInputComponent = (props: Props) => {
|
||||
const { nodeId, field } = props;
|
||||
const vae = field.value;
|
||||
const dispatch = useAppDispatch();
|
||||
const { data: vaeModels } = useGetVaeModelsQuery();
|
||||
|
||||
const data = useMemo(() => {
|
||||
if (!vaeModels) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const data: SelectItem[] = [
|
||||
{
|
||||
value: 'default',
|
||||
label: 'Default',
|
||||
group: 'Default',
|
||||
},
|
||||
];
|
||||
|
||||
forEach(vaeModels.entities, (vae, id) => {
|
||||
if (!vae) {
|
||||
const { data, isLoading } = useGetVaeModelsQuery();
|
||||
const _onChange = useCallback(
|
||||
(value: VaeModelConfigEntity | null) => {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.push({
|
||||
value: id,
|
||||
label: vae.model_name,
|
||||
group: MODEL_TYPE_MAP[vae.base_model],
|
||||
});
|
||||
});
|
||||
|
||||
return data.sort((a, b) => (a.disabled && !b.disabled ? 1 : -1));
|
||||
}, [vaeModels]);
|
||||
|
||||
// grab the full model entity from the RTK Query cache
|
||||
const selectedVaeModel = useMemo(
|
||||
() =>
|
||||
vaeModels?.entities[`${vae?.base_model}/vae/${vae?.model_name}`] ?? null,
|
||||
[vaeModels?.entities, vae]
|
||||
);
|
||||
|
||||
const handleChangeModel = useCallback(
|
||||
(v: string | null) => {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newVaeModel = modelIdToVAEModelParam(v);
|
||||
|
||||
if (!newVaeModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(
|
||||
fieldVaeModelValueChanged({
|
||||
nodeId,
|
||||
fieldName: field.name,
|
||||
value: newVaeModel,
|
||||
value,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch, field.name, nodeId]
|
||||
);
|
||||
const { options, value, onChange, placeholder, noOptionsMessage } =
|
||||
useGroupedModelInvSelect({
|
||||
modelEntities: data,
|
||||
onChange: _onChange,
|
||||
selectedModel: field.value ? { ...field.value, model_type: 'vae' } : null,
|
||||
isLoading,
|
||||
});
|
||||
|
||||
return (
|
||||
<IAIMantineSearchableSelect
|
||||
className="nowheel nodrag"
|
||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||
tooltip={selectedVaeModel?.description}
|
||||
value={selectedVaeModel?.id ?? 'default'}
|
||||
placeholder="Default"
|
||||
data={data}
|
||||
onChange={handleChangeModel}
|
||||
disabled={data.length === 0}
|
||||
error={!selectedVaeModel}
|
||||
clearable
|
||||
sx={{
|
||||
width: '100%',
|
||||
'.mantine-Select-dropdown': {
|
||||
width: '16rem !important',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Flex sx={{ w: 'full', alignItems: 'center', gap: 2 }}>
|
||||
<InvControl
|
||||
className="nowheel nodrag"
|
||||
isDisabled={!options.length}
|
||||
isInvalid={!value}
|
||||
>
|
||||
<InvSelect
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
options={options}
|
||||
onChange={onChange}
|
||||
noOptionsMessage={noOptionsMessage}
|
||||
/>
|
||||
</InvControl>
|
||||
<SyncModelsIconButton className="nodrag" />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {
|
||||
import type {
|
||||
FieldInputInstance,
|
||||
FieldInputTemplate,
|
||||
} from 'features/nodes/types/field';
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAITextarea from 'common/components/IAITextarea';
|
||||
import { notesNodeValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { NotesNodeData } from 'features/nodes/types/invocation';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { NodeProps } from 'reactflow';
|
||||
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
|
||||
import { InvTextarea } from 'common/components/InvTextarea/InvTextarea';
|
||||
import NodeCollapseButton from 'features/nodes/components/flow/nodes/common/NodeCollapseButton';
|
||||
import NodeTitle from 'features/nodes/components/flow/nodes/common/NodeTitle';
|
||||
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
|
||||
import { notesNodeValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import type { NotesNodeData } from 'features/nodes/types/invocation';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import type { NodeProps } from 'reactflow';
|
||||
|
||||
const NotesNode = (props: NodeProps<NotesNodeData>) => {
|
||||
const { id: nodeId, data, selected } = props;
|
||||
@ -55,7 +56,7 @@ const NotesNode = (props: NodeProps<NotesNodeData>) => {
|
||||
className="nopan"
|
||||
sx={{ flexDir: 'column', w: 'full', h: 'full' }}
|
||||
>
|
||||
<IAITextarea
|
||||
<InvTextarea
|
||||
value={notes}
|
||||
onChange={handleChange}
|
||||
rows={8}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ChevronUpIcon } from '@chakra-ui/icons';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
|
||||
import { nodeIsOpenChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useUpdateNodeInternals } from 'reactflow';
|
||||
@ -20,7 +20,7 @@ const NodeCollapseButton = ({ nodeId, isOpen }: Props) => {
|
||||
}, [dispatch, isOpen, nodeId, updateNodeInternals]);
|
||||
|
||||
return (
|
||||
<IAIIconButton
|
||||
<InvIconButton
|
||||
className="nodrag"
|
||||
onClick={handleClick}
|
||||
aria-label="Minimize"
|
||||
@ -29,14 +29,8 @@ const NodeCollapseButton = ({ nodeId, isOpen }: Props) => {
|
||||
w: 8,
|
||||
h: 8,
|
||||
color: 'base.500',
|
||||
_dark: {
|
||||
color: 'base.500',
|
||||
},
|
||||
_hover: {
|
||||
color: 'base.700',
|
||||
_dark: {
|
||||
color: 'base.300',
|
||||
},
|
||||
color: 'base.300',
|
||||
},
|
||||
}}
|
||||
variant="link"
|
||||
|
@ -11,7 +11,8 @@ import { useNodeLabel } from 'features/nodes/hooks/useNodeLabel';
|
||||
import { useNodeTemplateTitle } from 'features/nodes/hooks/useNodeTemplateTitle';
|
||||
import { nodeLabelChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
|
||||
import { MouseEvent, memo, useCallback, useEffect, useState } from 'react';
|
||||
import type { MouseEvent } from 'react';
|
||||
import { memo, useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
type Props = {
|
||||
@ -83,7 +84,7 @@ const NodeTitle = ({ nodeId, title }: Props) => {
|
||||
fontSize="sm"
|
||||
sx={{
|
||||
p: 0,
|
||||
fontWeight: 700,
|
||||
fontWeight: 'bold',
|
||||
_focusVisible: {
|
||||
p: 0,
|
||||
boxShadow: 'none',
|
||||
|
@ -1,9 +1,5 @@
|
||||
import {
|
||||
Box,
|
||||
ChakraProps,
|
||||
useColorModeValue,
|
||||
useToken,
|
||||
} from '@chakra-ui/react';
|
||||
import type { ChakraProps } from '@chakra-ui/react';
|
||||
import { Box, useToken } from '@chakra-ui/react';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
@ -16,13 +12,8 @@ import {
|
||||
} from 'features/nodes/types/constants';
|
||||
import { zNodeStatus } from 'features/nodes/types/invocation';
|
||||
import { bumpGlobalMenuCloseTrigger } from 'features/ui/store/uiSlice';
|
||||
import {
|
||||
MouseEvent,
|
||||
PropsWithChildren,
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import type { MouseEvent, PropsWithChildren } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
|
||||
type NodeWrapperProps = PropsWithChildren & {
|
||||
nodeId: string;
|
||||
@ -48,21 +39,14 @@ const NodeWrapper = (props: NodeWrapperProps) => {
|
||||
|
||||
const isInProgress = useAppSelector(selectIsInProgress);
|
||||
|
||||
const [nodeInProgressLight, nodeInProgressDark, shadowsXl, shadowsBase] =
|
||||
useToken('shadows', [
|
||||
'nodeInProgress.light',
|
||||
'nodeInProgress.dark',
|
||||
'shadows.xl',
|
||||
'shadows.base',
|
||||
]);
|
||||
const [nodeInProgress, shadowsXl, shadowsBase] = useToken('shadows', [
|
||||
'nodeInProgress',
|
||||
'shadows.xl',
|
||||
'shadows.base',
|
||||
]);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const inProgressShadow = useColorModeValue(
|
||||
nodeInProgressLight,
|
||||
nodeInProgressDark
|
||||
);
|
||||
|
||||
const opacity = useAppSelector((state) => state.nodes.nodeOpacity);
|
||||
|
||||
const handleClick = useCallback(
|
||||
@ -117,7 +101,7 @@ const NodeWrapper = (props: NodeWrapperProps) => {
|
||||
transitionProperty: 'common',
|
||||
transitionDuration: '0.1s',
|
||||
opacity: 0.7,
|
||||
shadow: isInProgress ? inProgressShadow : undefined,
|
||||
shadow: isInProgress ? nodeInProgress : undefined,
|
||||
zIndex: -1,
|
||||
}}
|
||||
/>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
|
||||
import NodeOpacitySlider from './NodeOpacitySlider';
|
||||
import ViewportControls from './ViewportControls';
|
||||
|
||||
|
@ -1,11 +1,6 @@
|
||||
import {
|
||||
Flex,
|
||||
Slider,
|
||||
SliderFilledTrack,
|
||||
SliderThumb,
|
||||
SliderTrack,
|
||||
} from '@chakra-ui/react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { InvSlider } from 'common/components/InvSlider/InvSlider';
|
||||
import { nodeOpacityChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -24,7 +19,7 @@ export default function NodeOpacitySlider() {
|
||||
|
||||
return (
|
||||
<Flex alignItems="center">
|
||||
<Slider
|
||||
<InvSlider
|
||||
aria-label={t('nodes.nodeOpacity')}
|
||||
value={nodeOpacity}
|
||||
min={0.5}
|
||||
@ -34,12 +29,7 @@ export default function NodeOpacitySlider() {
|
||||
orientation="vertical"
|
||||
defaultValue={30}
|
||||
h="calc(100% - 0.5rem)"
|
||||
>
|
||||
<SliderTrack>
|
||||
<SliderFilledTrack />
|
||||
</SliderTrack>
|
||||
<SliderThumb />
|
||||
</Slider>
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ButtonGroup } from '@chakra-ui/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { InvButtonGroup } from 'common/components/InvButtonGroup/InvButtonGroup';
|
||||
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
|
||||
import {
|
||||
// shouldShowFieldTypeLegendChanged,
|
||||
shouldShowMinimapPanelChanged,
|
||||
@ -47,40 +47,40 @@ const ViewportControls = () => {
|
||||
}, [shouldShowMinimapPanel, dispatch]);
|
||||
|
||||
return (
|
||||
<ButtonGroup isAttached orientation="vertical">
|
||||
<IAIIconButton
|
||||
<InvButtonGroup orientation="vertical">
|
||||
<InvIconButton
|
||||
tooltip={t('nodes.zoomInNodes')}
|
||||
aria-label={t('nodes.zoomInNodes')}
|
||||
onClick={handleClickedZoomIn}
|
||||
icon={<FaMagnifyingGlassPlus />}
|
||||
/>
|
||||
<IAIIconButton
|
||||
<InvIconButton
|
||||
tooltip={t('nodes.zoomOutNodes')}
|
||||
aria-label={t('nodes.zoomOutNodes')}
|
||||
onClick={handleClickedZoomOut}
|
||||
icon={<FaMagnifyingGlassMinus />}
|
||||
/>
|
||||
<IAIIconButton
|
||||
<InvIconButton
|
||||
tooltip={t('nodes.fitViewportNodes')}
|
||||
aria-label={t('nodes.fitViewportNodes')}
|
||||
onClick={handleClickedFitView}
|
||||
icon={<FaExpand />}
|
||||
/>
|
||||
{/* <Tooltip
|
||||
{/* <InvTooltip
|
||||
label={
|
||||
shouldShowFieldTypeLegend
|
||||
? t('nodes.hideLegendNodes')
|
||||
: t('nodes.showLegendNodes')
|
||||
}
|
||||
>
|
||||
<IAIIconButton
|
||||
<InvIconButton
|
||||
aria-label="Toggle field type legend"
|
||||
isChecked={shouldShowFieldTypeLegend}
|
||||
onClick={handleClickedToggleFieldTypeLegend}
|
||||
icon={<FaInfo />}
|
||||
/>
|
||||
</Tooltip> */}
|
||||
<IAIIconButton
|
||||
</InvTooltip> */}
|
||||
<InvIconButton
|
||||
tooltip={
|
||||
shouldShowMinimapPanel
|
||||
? t('nodes.hideMinimapnodes')
|
||||
@ -95,7 +95,7 @@ const ViewportControls = () => {
|
||||
onClick={handleClickedToggleMiniMapPanel}
|
||||
icon={<FaMapMarkerAlt />}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</InvButtonGroup>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Flex, chakra, useColorModeValue } from '@chakra-ui/react';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { chakra, Flex } from '@chakra-ui/react';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { memo } from 'react';
|
||||
import { MiniMap } from 'reactflow';
|
||||
@ -11,16 +11,6 @@ const MinimapPanel = () => {
|
||||
(state: RootState) => state.nodes.shouldShowMinimapPanel
|
||||
);
|
||||
|
||||
const nodeColor = useColorModeValue(
|
||||
'var(--invokeai-colors-accent-300)',
|
||||
'var(--invokeai-colors-accent-600)'
|
||||
);
|
||||
|
||||
const maskColor = useColorModeValue(
|
||||
'var(--invokeai-colors-blackAlpha-300)',
|
||||
'var(--invokeai-colors-blackAlpha-600)'
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex sx={{ gap: 2, position: 'absolute', bottom: 2, insetInlineEnd: 2 }}>
|
||||
{shouldShowMinimapPanel && (
|
||||
@ -30,17 +20,14 @@ const MinimapPanel = () => {
|
||||
nodeBorderRadius={15}
|
||||
sx={{
|
||||
m: '0 !important',
|
||||
backgroundColor: 'base.200 !important',
|
||||
borderRadius: 'base',
|
||||
_dark: {
|
||||
backgroundColor: 'base.500 !important',
|
||||
},
|
||||
backgroundColor: 'base.500 !important',
|
||||
svg: {
|
||||
borderRadius: 'inherit',
|
||||
},
|
||||
}}
|
||||
nodeColor={nodeColor}
|
||||
maskColor={maskColor}
|
||||
nodeColor="var(--invokeai-colors-blue-600)"
|
||||
maskColor="var(--invokeai-colors-blackAlpha-600)"
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
|
@ -1,43 +0,0 @@
|
||||
import { Flex } from '@chakra-ui/layout';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { useGetNodesNeedUpdate } from 'features/nodes/hooks/useGetNodesNeedUpdate';
|
||||
import { updateAllNodesRequested } from 'features/nodes/store/actions';
|
||||
import { addNodePopoverOpened } from 'features/nodes/store/nodesSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaExclamationTriangle, FaPlus } from 'react-icons/fa';
|
||||
|
||||
const TopLeftPanel = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
const nodesNeedUpdate = useGetNodesNeedUpdate();
|
||||
const handleOpenAddNodePopover = useCallback(() => {
|
||||
dispatch(addNodePopoverOpened());
|
||||
}, [dispatch]);
|
||||
const handleClickUpdateNodes = useCallback(() => {
|
||||
dispatch(updateAllNodesRequested());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Flex sx={{ gap: 2, position: 'absolute', top: 2, insetInlineStart: 2 }}>
|
||||
<IAIIconButton
|
||||
tooltip={t('nodes.addNodeToolTip')}
|
||||
aria-label={t('nodes.addNode')}
|
||||
icon={<FaPlus />}
|
||||
onClick={handleOpenAddNodePopover}
|
||||
/>
|
||||
{nodesNeedUpdate && (
|
||||
<IAIButton
|
||||
leftIcon={<FaExclamationTriangle />}
|
||||
onClick={handleClickUpdateNodes}
|
||||
>
|
||||
{t('nodes.updateAllNodes')}
|
||||
</IAIButton>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(TopLeftPanel);
|
@ -1,5 +1,5 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { InvIconButton } from 'common/components/InvIconButton/InvIconButton';
|
||||
import { addNodePopoverOpened } from 'features/nodes/store/nodesSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -13,7 +13,7 @@ const AddNodeButton = () => {
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<IAIIconButton
|
||||
<InvIconButton
|
||||
tooltip={t('nodes.addNodeToolTip')}
|
||||
aria-label={t('nodes.addNode')}
|
||||
icon={<FaPlus />}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import { InvButton } from 'common/components/InvButton/InvButton';
|
||||
import { useGetNodesNeedUpdate } from 'features/nodes/hooks/useGetNodesNeedUpdate';
|
||||
import { updateAllNodesRequested } from 'features/nodes/store/actions';
|
||||
import { memo, useCallback } from 'react';
|
||||
@ -19,13 +19,13 @@ const UpdateNodesButton = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<IAIButton
|
||||
<InvButton
|
||||
leftIcon={<FaExclamationTriangle />}
|
||||
onClick={handleClickUpdateNodes}
|
||||
pointerEvents="auto"
|
||||
>
|
||||
{t('nodes.updateAllNodes')}
|
||||
</IAIButton>
|
||||
</InvButton>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Text } from '@chakra-ui/layout';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { InvText } from 'common/components/InvText/wrapper';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const TopCenterPanel = () => {
|
||||
const { t } = useTranslation();
|
||||
@ -11,19 +11,26 @@ const TopCenterPanel = () => {
|
||||
const isWorkflowLibraryEnabled =
|
||||
useFeatureStatus('workflowLibrary').isFeatureEnabled;
|
||||
|
||||
const displayName = useMemo(() => {
|
||||
let _displayName = name || t('workflows.unnamedWorkflow');
|
||||
if (isTouched && isWorkflowLibraryEnabled) {
|
||||
_displayName += ` (${t('common.unsaved')})`;
|
||||
}
|
||||
return _displayName;
|
||||
}, [t, name, isTouched, isWorkflowLibraryEnabled]);
|
||||
|
||||
return (
|
||||
<Text
|
||||
<InvText
|
||||
m={2}
|
||||
fontSize="lg"
|
||||
userSelect="none"
|
||||
noOfLines={1}
|
||||
wordBreak="break-all"
|
||||
fontWeight={600}
|
||||
fontWeight="semibold"
|
||||
opacity={0.8}
|
||||
>
|
||||
{name || t('workflows.unnamedWorkflow')}
|
||||
{isTouched && isWorkflowLibraryEnabled ? ` (${t('common.unsaved')})` : ''}
|
||||
</Text>
|
||||
{displayName}
|
||||
</InvText>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import { InvButton } from 'common/components/InvButton/InvButton';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaSyncAlt } from 'react-icons/fa';
|
||||
@ -14,14 +14,14 @@ const ReloadNodeTemplatesButton = () => {
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<IAIButton
|
||||
<InvButton
|
||||
leftIcon={<FaSyncAlt />}
|
||||
tooltip={t('nodes.reloadNodeTemplates')}
|
||||
aria-label={t('nodes.reloadNodeTemplates')}
|
||||
onClick={handleReloadSchema}
|
||||
>
|
||||
{t('nodes.reloadNodeTemplates')}
|
||||
</IAIButton>
|
||||
</InvButton>
|
||||
);
|
||||
};
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import WorkflowLibraryButton from 'features/workflowLibrary/components/WorkflowLibraryButton';
|
||||
import { memo } from 'react';
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import WorkflowLibraryButton from 'features/workflowLibrary/components/WorkflowLibraryButton';
|
||||
import WorkflowLibraryMenu from 'features/workflowLibrary/components/WorkflowLibraryMenu/WorkflowLibraryMenu';
|
||||
import { memo } from 'react';
|
||||
|
||||
const TopRightPanel = () => {
|
||||
const isWorkflowLibraryEnabled =
|
||||
|
@ -1,21 +1,18 @@
|
||||
import {
|
||||
Divider,
|
||||
Flex,
|
||||
FormLabelProps,
|
||||
Heading,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalCloseButton,
|
||||
ModalContent,
|
||||
ModalHeader,
|
||||
ModalOverlay,
|
||||
useDisclosure,
|
||||
} from '@chakra-ui/react';
|
||||
import { Divider, Flex, Heading, useDisclosure } from '@chakra-ui/react';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import ReloadNodeTemplatesButton from 'features/nodes/components/flow/panels/TopCenterPanel/ReloadSchemaButton';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import {
|
||||
InvModal,
|
||||
InvModalBody,
|
||||
InvModalCloseButton,
|
||||
InvModalContent,
|
||||
InvModalHeader,
|
||||
InvModalOverlay,
|
||||
} from 'common/components/InvModal/wrapper';
|
||||
import { InvSwitch } from 'common/components/InvSwitch/wrapper';
|
||||
import ReloadNodeTemplatesButton from 'features/nodes/components/flow/panels/TopRightPanel/ReloadSchemaButton';
|
||||
import {
|
||||
selectionModeChanged,
|
||||
shouldAnimateEdgesChanged,
|
||||
@ -23,14 +20,11 @@ import {
|
||||
shouldSnapToGridChanged,
|
||||
shouldValidateGraphChanged,
|
||||
} from 'features/nodes/store/nodesSlice';
|
||||
import { ChangeEvent, ReactNode, memo, useCallback } from 'react';
|
||||
import type { ChangeEvent, ReactNode } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SelectionMode } from 'reactflow';
|
||||
|
||||
const formLabelProps: FormLabelProps = {
|
||||
fontWeight: 600,
|
||||
};
|
||||
|
||||
const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
|
||||
const {
|
||||
shouldAnimateEdges,
|
||||
@ -104,12 +98,12 @@ const WorkflowEditorSettings = ({ children }: Props) => {
|
||||
<>
|
||||
{children({ onOpen })}
|
||||
|
||||
<Modal isOpen={isOpen} onClose={onClose} size="2xl" isCentered>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>{t('nodes.workflowSettings')}</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>
|
||||
<InvModal isOpen={isOpen} onClose={onClose} size="2xl" isCentered>
|
||||
<InvModalOverlay />
|
||||
<InvModalContent>
|
||||
<InvModalHeader>{t('nodes.workflowSettings')}</InvModalHeader>
|
||||
<InvModalCloseButton />
|
||||
<InvModalBody>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
@ -118,52 +112,62 @@ const WorkflowEditorSettings = ({ children }: Props) => {
|
||||
}}
|
||||
>
|
||||
<Heading size="sm">{t('parameters.general')}</Heading>
|
||||
<IAISwitch
|
||||
formLabelProps={formLabelProps}
|
||||
onChange={handleChangeShouldAnimate}
|
||||
isChecked={shouldAnimateEdges}
|
||||
<InvControl
|
||||
label={t('nodes.animatedEdges')}
|
||||
helperText={t('nodes.animatedEdgesHelp')}
|
||||
/>
|
||||
>
|
||||
<InvSwitch
|
||||
onChange={handleChangeShouldAnimate}
|
||||
isChecked={shouldAnimateEdges}
|
||||
/>
|
||||
</InvControl>
|
||||
<Divider />
|
||||
<IAISwitch
|
||||
formLabelProps={formLabelProps}
|
||||
isChecked={shouldSnapToGrid}
|
||||
onChange={handleChangeShouldSnap}
|
||||
<InvControl
|
||||
label={t('nodes.snapToGrid')}
|
||||
helperText={t('nodes.snapToGridHelp')}
|
||||
/>
|
||||
>
|
||||
<InvSwitch
|
||||
isChecked={shouldSnapToGrid}
|
||||
onChange={handleChangeShouldSnap}
|
||||
/>
|
||||
</InvControl>
|
||||
<Divider />
|
||||
<IAISwitch
|
||||
formLabelProps={formLabelProps}
|
||||
isChecked={shouldColorEdges}
|
||||
onChange={handleChangeShouldColor}
|
||||
<InvControl
|
||||
label={t('nodes.colorCodeEdges')}
|
||||
helperText={t('nodes.colorCodeEdgesHelp')}
|
||||
/>
|
||||
>
|
||||
<InvSwitch
|
||||
isChecked={shouldColorEdges}
|
||||
onChange={handleChangeShouldColor}
|
||||
/>
|
||||
</InvControl>
|
||||
<Divider />
|
||||
<IAISwitch
|
||||
formLabelProps={formLabelProps}
|
||||
isChecked={selectionModeIsChecked}
|
||||
onChange={handleChangeSelectionMode}
|
||||
<InvControl
|
||||
label={t('nodes.fullyContainNodes')}
|
||||
helperText={t('nodes.fullyContainNodesHelp')}
|
||||
/>
|
||||
>
|
||||
<InvSwitch
|
||||
isChecked={selectionModeIsChecked}
|
||||
onChange={handleChangeSelectionMode}
|
||||
/>
|
||||
</InvControl>
|
||||
<Heading size="sm" pt={4}>
|
||||
{t('common.advanced')}
|
||||
</Heading>
|
||||
<IAISwitch
|
||||
formLabelProps={formLabelProps}
|
||||
isChecked={shouldValidateGraph}
|
||||
onChange={handleChangeShouldValidate}
|
||||
<InvControl
|
||||
label={t('nodes.validateConnections')}
|
||||
helperText={t('nodes.validateConnectionsHelp')}
|
||||
/>
|
||||
>
|
||||
<InvSwitch
|
||||
isChecked={shouldValidateGraph}
|
||||
onChange={handleChangeShouldValidate}
|
||||
/>
|
||||
</InvControl>
|
||||
<ReloadNodeTemplatesButton />
|
||||
</Flex>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</InvModalBody>
|
||||
</InvModalContent>
|
||||
</InvModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -1,15 +1,13 @@
|
||||
import 'reactflow/dist/style.css';
|
||||
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import ParamIterations from 'features/parameters/components/Parameters/Core/ParamIterations';
|
||||
import QueueControls from 'features/queue/components/QueueControls';
|
||||
import ResizeHandle from 'features/ui/components/tabs/ResizeHandle';
|
||||
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
|
||||
import { memo, useCallback, useRef, useState } from 'react';
|
||||
import {
|
||||
ImperativePanelGroupHandle,
|
||||
Panel,
|
||||
PanelGroup,
|
||||
} from 'react-resizable-panels';
|
||||
import 'reactflow/dist/style.css';
|
||||
import type { ImperativePanelGroupHandle } from 'react-resizable-panels';
|
||||
import { Panel, PanelGroup } from 'react-resizable-panels';
|
||||
|
||||
import InspectorPanel from './inspector/InspectorPanel';
|
||||
import WorkflowPanel from './workflow/WorkflowPanel';
|
||||
|
||||
@ -28,20 +26,6 @@ const NodeEditorPanelGroup = () => {
|
||||
return (
|
||||
<Flex sx={{ flexDir: 'column', gap: 2, height: '100%', width: '100%' }}>
|
||||
<QueueControls />
|
||||
<Flex
|
||||
layerStyle="first"
|
||||
sx={{
|
||||
w: 'full',
|
||||
position: 'relative',
|
||||
borderRadius: 'base',
|
||||
p: 2,
|
||||
pb: 3,
|
||||
gap: 2,
|
||||
flexDir: 'column',
|
||||
}}
|
||||
>
|
||||
<ParamIterations asSlider />
|
||||
</Flex>
|
||||
<PanelGroup
|
||||
ref={panelGroupRef}
|
||||
id="workflow-panel-group"
|
||||
|
@ -1,50 +0,0 @@
|
||||
import { Box, Flex, StyleProps } from '@chakra-ui/react';
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
import { PropsWithChildren, memo } from 'react';
|
||||
|
||||
type Props = PropsWithChildren & {
|
||||
maxHeight?: StyleProps['maxHeight'];
|
||||
};
|
||||
|
||||
const ScrollableContent = ({ children, maxHeight }: Props) => {
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
maxHeight,
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
}}
|
||||
>
|
||||
<OverlayScrollbarsComponent
|
||||
defer
|
||||
style={{ height: '100%', width: '100%' }}
|
||||
options={{
|
||||
scrollbars: {
|
||||
visibility: 'auto',
|
||||
autoHide: 'scroll',
|
||||
autoHideDelay: 1300,
|
||||
theme: 'os-theme-dark',
|
||||
},
|
||||
overflow: {
|
||||
x: 'hidden',
|
||||
},
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</OverlayScrollbarsComponent>
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ScrollableContent);
|
@ -1,25 +1,21 @@
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
HStack,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
import { Box, Flex, HStack } from '@chakra-ui/react';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvText } from 'common/components/InvText/wrapper';
|
||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||
import NotesTextarea from 'features/nodes/components/flow/nodes/Invocation/NotesTextarea';
|
||||
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
|
||||
import {
|
||||
import type {
|
||||
InvocationNode,
|
||||
InvocationTemplate,
|
||||
isInvocationNode,
|
||||
} from 'features/nodes/types/invocation';
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
import { getNeedsUpdate } from 'features/nodes/util/node/nodeUpdate';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import EditableNodeTitle from './details/EditableNodeTitle';
|
||||
|
||||
const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
|
||||
@ -67,44 +63,28 @@ const Content = memo(({ node, template }: ContentProps) => {
|
||||
[node, template]
|
||||
);
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'relative',
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
}}
|
||||
>
|
||||
<Box position="relative" w="full" h="full">
|
||||
<ScrollableContent>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDir: 'column',
|
||||
position: 'relative',
|
||||
p: 1,
|
||||
gap: 2,
|
||||
w: 'full',
|
||||
}}
|
||||
flexDir="column"
|
||||
position="relative"
|
||||
w="full"
|
||||
h="full"
|
||||
p={1}
|
||||
gap={2}
|
||||
>
|
||||
<EditableNodeTitle nodeId={node.data.id} />
|
||||
<HStack>
|
||||
<FormControl>
|
||||
<FormLabel>{t('nodes.nodeType')}</FormLabel>
|
||||
<Text fontSize="sm" fontWeight={600}>
|
||||
<InvControl label={t('nodes.nodeType')}>
|
||||
<InvText fontSize="sm" fontWeight="semibold">
|
||||
{template.title}
|
||||
</Text>
|
||||
</FormControl>
|
||||
<Flex
|
||||
flexDir="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
w="full"
|
||||
>
|
||||
<FormControl isInvalid={needsUpdate}>
|
||||
<FormLabel>{t('nodes.nodeVersion')}</FormLabel>
|
||||
<Text fontSize="sm" fontWeight={600}>
|
||||
{node.data.version}
|
||||
</Text>
|
||||
</FormControl>
|
||||
</Flex>
|
||||
</InvText>
|
||||
</InvControl>
|
||||
<InvControl label={t('nodes.nodeVersion')} isInvalid={needsUpdate}>
|
||||
<InvText fontSize="sm" fontWeight="semibold">
|
||||
{node.data.version}
|
||||
</InvText>
|
||||
</InvControl>
|
||||
</HStack>
|
||||
<NotesTextarea nodeId={node.data.id} />
|
||||
</Flex>
|
||||
|
@ -3,13 +3,14 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
|
||||
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ImageOutput } from 'services/api/types';
|
||||
import { AnyResult } from 'services/events/types';
|
||||
import type { ImageOutput } from 'services/api/types';
|
||||
import type { AnyResult } from 'services/events/types';
|
||||
|
||||
import ImageOutputPreview from './outputs/ImageOutputPreview';
|
||||
|
||||
const selector = createMemoizedSelector(stateSelector, ({ nodes }) => {
|
||||
|
@ -7,11 +7,12 @@ import {
|
||||
Tabs,
|
||||
} from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import InspectorDataTab from './InspectorDataTab';
|
||||
import InspectorDetailsTab from './InspectorDetailsTab';
|
||||
import InspectorOutputsTab from './InspectorOutputsTab';
|
||||
import InspectorTemplateTab from './InspectorTemplateTab';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import InspectorDetailsTab from './InspectorDetailsTab';
|
||||
|
||||
const InspectorPanel = () => {
|
||||
const { t } = useTranslation();
|
||||
|
@ -45,21 +45,14 @@ const EditableNodeTitle = ({ nodeId, title }: Props) => {
|
||||
}, [label, templateTitle, title, t]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Flex w="full" alignItems="center" justifyContent="center">
|
||||
<Editable
|
||||
as={Flex}
|
||||
value={localTitle}
|
||||
onChange={handleChange}
|
||||
onSubmit={handleSubmit}
|
||||
w="full"
|
||||
fontWeight={600}
|
||||
fontWeight="semibold"
|
||||
>
|
||||
<EditablePreview noOfLines={1} />
|
||||
<EditableInput
|
||||
|
@ -1,7 +1,7 @@
|
||||
import IAIDndImage from 'common/components/IAIDndImage';
|
||||
import { memo } from 'react';
|
||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
||||
import { ImageOutput } from 'services/api/types';
|
||||
import type { ImageOutput } from 'services/api/types';
|
||||
|
||||
type Props = {
|
||||
output: ImageOutput;
|
||||
|
@ -1,10 +1,12 @@
|
||||
import { Flex, FormControl, FormLabel } from '@chakra-ui/react';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIInput from 'common/components/IAIInput';
|
||||
import IAITextarea from 'common/components/IAITextarea';
|
||||
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
|
||||
import { InvControl } from 'common/components/InvControl/InvControl';
|
||||
import { InvControlGroup } from 'common/components/InvControl/InvControlGroup';
|
||||
import { InvInput } from 'common/components/InvInput/InvInput';
|
||||
import { InvTextarea } from 'common/components/InvTextarea/InvTextarea';
|
||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||
import {
|
||||
workflowAuthorChanged,
|
||||
workflowContactChanged,
|
||||
@ -14,7 +16,8 @@ import {
|
||||
workflowTagsChanged,
|
||||
workflowVersionChanged,
|
||||
} from 'features/nodes/store/workflowSlice';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import type { ChangeEvent } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createMemoizedSelector(stateSelector, ({ workflow }) => {
|
||||
@ -92,53 +95,48 @@ const WorkflowGeneralTab = () => {
|
||||
h: 'full',
|
||||
}}
|
||||
>
|
||||
<Flex sx={{ gap: 2, w: 'full' }}>
|
||||
<IAIInput
|
||||
label={t('nodes.workflowName')}
|
||||
value={name}
|
||||
onChange={handleChangeName}
|
||||
/>
|
||||
<IAIInput
|
||||
label={t('nodes.workflowVersion')}
|
||||
value={version}
|
||||
onChange={handleChangeVersion}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex sx={{ gap: 2, w: 'full' }}>
|
||||
<IAIInput
|
||||
label={t('nodes.workflowAuthor')}
|
||||
value={author}
|
||||
onChange={handleChangeAuthor}
|
||||
/>
|
||||
<IAIInput
|
||||
label={t('nodes.workflowContact')}
|
||||
value={contact}
|
||||
onChange={handleChangeContact}
|
||||
/>
|
||||
</Flex>
|
||||
<IAIInput
|
||||
label={t('nodes.workflowTags')}
|
||||
value={tags}
|
||||
onChange={handleChangeTags}
|
||||
/>
|
||||
<FormControl as={Flex} sx={{ flexDir: 'column' }}>
|
||||
<FormLabel>{t('nodes.workflowDescription')}</FormLabel>
|
||||
<IAITextarea
|
||||
<InvControlGroup orientation="vertical">
|
||||
<Flex sx={{ gap: 2, w: 'full' }}>
|
||||
<InvControl label={t('nodes.workflowName')}>
|
||||
<InvInput value={name} onChange={handleChangeName} />
|
||||
</InvControl>
|
||||
<InvControl label={t('nodes.workflowVersion')}>
|
||||
<InvInput value={version} onChange={handleChangeVersion} />
|
||||
</InvControl>
|
||||
</Flex>
|
||||
<Flex sx={{ gap: 2, w: 'full' }}>
|
||||
<InvControl label={t('nodes.workflowAuthor')}>
|
||||
<InvInput value={author} onChange={handleChangeAuthor} />
|
||||
</InvControl>
|
||||
<InvControl label={t('nodes.workflowContact')}>
|
||||
<InvInput value={contact} onChange={handleChangeContact} />
|
||||
</InvControl>
|
||||
</Flex>
|
||||
<InvControl label={t('nodes.workflowTags')}>
|
||||
<InvInput value={tags} onChange={handleChangeTags} />
|
||||
</InvControl>
|
||||
</InvControlGroup>
|
||||
<InvControl
|
||||
label={t('nodes.workflowDescription')}
|
||||
orientation="vertical"
|
||||
>
|
||||
<InvTextarea
|
||||
onChange={handleChangeDescription}
|
||||
value={description}
|
||||
fontSize="sm"
|
||||
sx={{ resize: 'none' }}
|
||||
resize="none"
|
||||
rows={3}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl as={Flex} sx={{ flexDir: 'column', h: 'full' }}>
|
||||
<FormLabel>{t('nodes.workflowNotes')}</FormLabel>
|
||||
<IAITextarea
|
||||
</InvControl>
|
||||
<InvControl label={t('nodes.workflowNotes')} orientation="vertical">
|
||||
<InvTextarea
|
||||
onChange={handleChangeNotes}
|
||||
value={notes}
|
||||
fontSize="sm"
|
||||
sx={{ h: 'full', resize: 'none' }}
|
||||
resize="none"
|
||||
rows={10}
|
||||
/>
|
||||
</FormControl>
|
||||
</InvControl>
|
||||
</Flex>
|
||||
</ScrollableContent>
|
||||
);
|
||||
|
@ -3,8 +3,8 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
|
||||
import LinearViewField from 'features/nodes/components/flow/nodes/Invocation/fields/LinearViewField';
|
||||
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -7,10 +7,11 @@ import {
|
||||
Tabs,
|
||||
} from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import WorkflowGeneralTab from './WorkflowGeneralTab';
|
||||
import WorkflowJSONTab from './WorkflowJSONTab';
|
||||
import WorkflowLinearTab from './WorkflowLinearTab';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const WorkflowPanel = () => {
|
||||
const { t } = useTranslation();
|
||||
|
@ -1,16 +1,20 @@
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { RootState } from 'app/store/store';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import {
|
||||
DRAG_HANDLE_CLASSNAME,
|
||||
NODE_WIDTH,
|
||||
} from 'features/nodes/types/constants';
|
||||
import { AnyNode, InvocationTemplate } from 'features/nodes/types/invocation';
|
||||
import type {
|
||||
AnyNode,
|
||||
InvocationTemplate,
|
||||
} from 'features/nodes/types/invocation';
|
||||
import { buildCurrentImageNode } from 'features/nodes/util/node/buildCurrentImageNode';
|
||||
import { buildInvocationNode } from 'features/nodes/util/node/buildInvocationNode';
|
||||
import { buildNotesNode } from 'features/nodes/util/node/buildNotesNode';
|
||||
import { useCallback } from 'react';
|
||||
import { Node, useReactFlow } from 'reactflow';
|
||||
import type { Node } from 'reactflow';
|
||||
import { useReactFlow } from 'reactflow';
|
||||
|
||||
const templatesSelector = createMemoizedSelector(
|
||||
[(state: RootState) => state.nodes],
|
||||
|
@ -3,6 +3,7 @@ import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { makeConnectionErrorSelector } from 'features/nodes/store/util/makeIsConnectionValidSelector';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useFieldType } from './useFieldType.ts';
|
||||
|
||||
const selectIsConnectionInProgress = createMemoizedSelector(
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { useWorkflow } from 'features/nodes/hooks/useWorkflow';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const useDownloadWorkflow = () => {
|
||||
const workflow = useWorkflow();
|
||||
const downloadWorkflow = useCallback(() => {
|
||||
const blob = new Blob([JSON.stringify(workflow, null, 2)]);
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = `${workflow.name || 'My Workflow'}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
}, [workflow]);
|
||||
|
||||
return downloadWorkflow;
|
||||
};
|
@ -1,10 +1,11 @@
|
||||
// TODO: enable this at some point
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useCallback } from 'react';
|
||||
import { Connection, Node, useReactFlow } from 'reactflow';
|
||||
import { validateSourceAndTargetTypes } from 'features/nodes/store/util/validateSourceAndTargetTypes';
|
||||
import { getIsGraphAcyclic } from 'features/nodes/store/util/getIsGraphAcyclic';
|
||||
import { InvocationNodeData } from 'features/nodes/types/invocation';
|
||||
import { validateSourceAndTargetTypes } from 'features/nodes/store/util/validateSourceAndTargetTypes';
|
||||
import type { InvocationNodeData } from 'features/nodes/types/invocation';
|
||||
import { useCallback } from 'react';
|
||||
import type { Connection, Node } from 'reactflow';
|
||||
import { useReactFlow } from 'reactflow';
|
||||
|
||||
/**
|
||||
* NOTE: The logic here must be duplicated in `invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts`
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { InvocationTemplate } from 'features/nodes/types/invocation';
|
||||
import type { InvocationTemplate } from 'features/nodes/types/invocation';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export const useNodeTemplateByType = (type: string) => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FieldType } from 'features/nodes/types/field';
|
||||
import type { FieldType } from 'features/nodes/types/field';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const useFieldTypeName = (fieldType?: FieldType): string => {
|
||||
const { t } = useTranslation();
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { useHasImageOutput } from './useHasImageOutput';
|
||||
|
||||
export const useWithFooter = (nodeId: string) => {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createAction, isAnyOf } from '@reduxjs/toolkit';
|
||||
import { WorkflowV2 } from 'features/nodes/types/workflow';
|
||||
import { Graph } from 'services/api/types';
|
||||
import type { WorkflowV2 } from 'features/nodes/types/workflow';
|
||||
import type { Graph } from 'services/api/types';
|
||||
|
||||
export const textToImageGraphBuilt = createAction<Graph>(
|
||||
'nodes/textToImageGraphBuilt'
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { NodesState } from './types';
|
||||
import type { NodesState } from './types';
|
||||
|
||||
/**
|
||||
* Nodes slice persist denylist
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { createSlice, isAnyOf, PayloadAction } from '@reduxjs/toolkit';
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice, isAnyOf } from '@reduxjs/toolkit';
|
||||
import { workflowLoaded } from 'features/nodes/store/actions';
|
||||
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
|
||||
import {
|
||||
import type {
|
||||
BoardFieldValue,
|
||||
BooleanFieldValue,
|
||||
ColorFieldValue,
|
||||
@ -20,6 +21,8 @@ import {
|
||||
StringFieldValue,
|
||||
T2IAdapterModelFieldValue,
|
||||
VAEModelFieldValue,
|
||||
} from 'features/nodes/types/field';
|
||||
import {
|
||||
zBoardFieldValue,
|
||||
zBooleanFieldValue,
|
||||
zColorFieldValue,
|
||||
@ -37,34 +40,38 @@ import {
|
||||
zT2IAdapterModelFieldValue,
|
||||
zVAEModelFieldValue,
|
||||
} from 'features/nodes/types/field';
|
||||
import {
|
||||
import type {
|
||||
AnyNode,
|
||||
InvocationTemplate,
|
||||
NodeExecutionState,
|
||||
} from 'features/nodes/types/invocation';
|
||||
import {
|
||||
isInvocationNode,
|
||||
isNotesNode,
|
||||
NodeExecutionState,
|
||||
zNodeStatus,
|
||||
} from 'features/nodes/types/invocation';
|
||||
import { cloneDeep, forEach } from 'lodash-es';
|
||||
import {
|
||||
addEdge,
|
||||
applyEdgeChanges,
|
||||
applyNodeChanges,
|
||||
import type {
|
||||
Connection,
|
||||
Edge,
|
||||
EdgeChange,
|
||||
EdgeRemoveChange,
|
||||
getConnectedEdges,
|
||||
getIncomers,
|
||||
getOutgoers,
|
||||
Node,
|
||||
NodeChange,
|
||||
OnConnectStartParams,
|
||||
SelectionMode,
|
||||
updateEdge,
|
||||
Viewport,
|
||||
XYPosition,
|
||||
} from 'reactflow';
|
||||
import {
|
||||
addEdge,
|
||||
applyEdgeChanges,
|
||||
applyNodeChanges,
|
||||
getConnectedEdges,
|
||||
getIncomers,
|
||||
getOutgoers,
|
||||
SelectionMode,
|
||||
updateEdge,
|
||||
} from 'reactflow';
|
||||
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
|
||||
import {
|
||||
appSocketGeneratorProgress,
|
||||
@ -74,8 +81,9 @@ import {
|
||||
appSocketQueueItemStatusChanged,
|
||||
} from 'services/events/actions';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { z } from 'zod';
|
||||
import { NodesState } from './types';
|
||||
import type { z } from 'zod';
|
||||
|
||||
import type { NodesState } from './types';
|
||||
import { findConnectionToValidHandle } from './util/findConnectionToValidHandle';
|
||||
import { findUnoccupiedPosition } from './util/findUnoccupiedPosition';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { atom } from 'nanostores';
|
||||
import { ReactFlowInstance } from 'reactflow';
|
||||
import type { ReactFlowInstance } from 'reactflow';
|
||||
|
||||
export const $flow = atom<ReactFlowInstance | null>(null);
|
||||
|
@ -1,17 +1,17 @@
|
||||
import {
|
||||
OnConnectStartParams,
|
||||
SelectionMode,
|
||||
Viewport,
|
||||
XYPosition,
|
||||
} from 'reactflow';
|
||||
import { FieldIdentifier, FieldType } from 'features/nodes/types/field';
|
||||
import {
|
||||
import type { FieldIdentifier, FieldType } from 'features/nodes/types/field';
|
||||
import type {
|
||||
AnyNode,
|
||||
InvocationNodeEdge,
|
||||
InvocationTemplate,
|
||||
NodeExecutionState,
|
||||
} from 'features/nodes/types/invocation';
|
||||
import { WorkflowV2 } from 'features/nodes/types/workflow';
|
||||
import type { WorkflowV2 } from 'features/nodes/types/workflow';
|
||||
import type {
|
||||
OnConnectStartParams,
|
||||
SelectionMode,
|
||||
Viewport,
|
||||
XYPosition,
|
||||
} from 'reactflow';
|
||||
|
||||
export type NodesState = {
|
||||
nodes: AnyNode[];
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { Connection, Edge, HandleType, Node } from 'reactflow';
|
||||
import {
|
||||
import type {
|
||||
FieldInputInstance,
|
||||
FieldOutputInstance,
|
||||
FieldType,
|
||||
} from 'features/nodes/types/field';
|
||||
import type { Connection, Edge, HandleType, Node } from 'reactflow';
|
||||
|
||||
import { getIsGraphAcyclic } from './getIsGraphAcyclic';
|
||||
import { validateSourceAndTargetTypes } from './validateSourceAndTargetTypes';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Node } from 'reactflow';
|
||||
import type { Node } from 'reactflow';
|
||||
|
||||
export const findUnoccupiedPosition = (nodes: Node[], x: number, y: number) => {
|
||||
let newX = x;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import graphlib from '@dagrejs/graphlib';
|
||||
import { Edge, Node } from 'reactflow';
|
||||
import type { Edge, Node } from 'reactflow';
|
||||
|
||||
export const getIsGraphAcyclic = (
|
||||
source: string,
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { FieldType } from 'features/nodes/types/field';
|
||||
import type { FieldType } from 'features/nodes/types/field';
|
||||
import i18n from 'i18next';
|
||||
import { HandleType } from 'reactflow';
|
||||
import type { HandleType } from 'reactflow';
|
||||
|
||||
import { getIsGraphAcyclic } from './getIsGraphAcyclic';
|
||||
import { validateSourceAndTargetTypes } from './validateSourceAndTargetTypes';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { FieldType } from 'features/nodes/types/field';
|
||||
import type { FieldType } from 'features/nodes/types/field';
|
||||
import { isEqual } from 'lodash-es';
|
||||
|
||||
/**
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { workflowLoaded } from 'features/nodes/store/actions';
|
||||
import {
|
||||
isAnyNodeOrEdgeMutation,
|
||||
nodeEditorReset,
|
||||
nodesDeleted,
|
||||
isAnyNodeOrEdgeMutation,
|
||||
} from 'features/nodes/store/nodesSlice';
|
||||
import { WorkflowsState as WorkflowState } from 'features/nodes/store/types';
|
||||
import { FieldIdentifier } from 'features/nodes/types/field';
|
||||
import type { WorkflowsState as WorkflowState } from 'features/nodes/store/types';
|
||||
import type { FieldIdentifier } from 'features/nodes/types/field';
|
||||
import { cloneDeep, isEqual, uniqBy } from 'lodash-es';
|
||||
|
||||
export const initialWorkflowState: WorkflowState = {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Node } from 'reactflow';
|
||||
import type { Node } from 'reactflow';
|
||||
|
||||
/**
|
||||
* How long to wait before showing a tooltip when hovering a field handle.
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
zBoardField,
|
||||
zColorField,
|
||||
zControlNetModelField,
|
||||
zIPAdapterModelField,
|
||||
zImageField,
|
||||
zIPAdapterModelField,
|
||||
zLoRAModelField,
|
||||
zMainOrONNXModelField,
|
||||
zSchedulerField,
|
||||
@ -699,6 +700,10 @@ export const isControlNetModelFieldInputTemplate = (
|
||||
val: unknown
|
||||
): val is ControlNetModelFieldInputTemplate =>
|
||||
zControlNetModelFieldInputTemplate.safeParse(val).success;
|
||||
export const isControlNetModelFieldValue = (
|
||||
v: unknown
|
||||
): v is ControlNetModelFieldValue =>
|
||||
zControlNetModelFieldValue.safeParse(v).success;
|
||||
// #endregion
|
||||
|
||||
// #region IPAdapterModelField
|
||||
@ -747,6 +752,10 @@ export const isIPAdapterModelFieldInputTemplate = (
|
||||
val: unknown
|
||||
): val is IPAdapterModelFieldInputTemplate =>
|
||||
zIPAdapterModelFieldInputTemplate.safeParse(val).success;
|
||||
export const isIPAdapterModelFieldValue = (
|
||||
val: unknown
|
||||
): val is IPAdapterModelFieldValue =>
|
||||
zIPAdapterModelFieldValue.safeParse(val).success;
|
||||
// #endregion
|
||||
|
||||
// #region T2IAdapterField
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Edge, Node } from 'reactflow';
|
||||
import type { Edge, Node } from 'reactflow';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { zClassification, zProgressImage } from './common';
|
||||
import {
|
||||
zFieldInputInstance,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
zControlField,
|
||||
zIPAdapterField,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { OpenAPIV3_1 } from 'openapi-types';
|
||||
import {
|
||||
import type { OpenAPIV3_1 } from 'openapi-types';
|
||||
import type {
|
||||
InputFieldJSONSchemaExtra,
|
||||
InvocationJSONSchemaExtra,
|
||||
OutputFieldJSONSchemaExtra,
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { FieldType, StatefulFieldType } from 'features/nodes/types/field';
|
||||
import { FieldTypeV1 } from './workflowV1';
|
||||
import type { FieldType, StatefulFieldType } from 'features/nodes/types/field';
|
||||
|
||||
import type { FieldTypeV1 } from './workflowV1';
|
||||
|
||||
/**
|
||||
* Mapping of V1 field type strings to their *stateful* V2 field type counterparts.
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { zFieldIdentifier } from './field';
|
||||
import { zInvocationNodeData, zNotesNodeData } from './invocation';
|
||||
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import { selectValidControlNets } from 'features/controlAdapters/store/controlAdaptersSlice';
|
||||
import { omit } from 'lodash-es';
|
||||
import {
|
||||
import type {
|
||||
CollectInvocation,
|
||||
ControlField,
|
||||
ControlNetInvocation,
|
||||
CoreMetadataInvocation,
|
||||
NonNullableGraph,
|
||||
} from 'services/api/types';
|
||||
|
||||
import {
|
||||
CANVAS_COHERENCE_DENOISE_LATENTS,
|
||||
CONTROL_NET_COLLECT,
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { RootState } from 'app/store/store';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||
import {
|
||||
import type {
|
||||
DenoiseLatentsInvocation,
|
||||
ESRGANInvocation,
|
||||
Edge,
|
||||
ESRGANInvocation,
|
||||
LatentsToImageInvocation,
|
||||
NoiseInvocation,
|
||||
NonNullableGraph,
|
||||
} from 'services/api/types';
|
||||
|
||||
import {
|
||||
DENOISE_LATENTS,
|
||||
DENOISE_LATENTS_HRF,
|
||||
@ -113,15 +114,15 @@ export const addHrfToGraph = (
|
||||
): void => {
|
||||
// Double check hrf is enabled.
|
||||
if (
|
||||
!state.generation.hrfEnabled ||
|
||||
state.config.disabledSDFeatures.includes('hrf') ||
|
||||
state.generation.model?.model_type === 'onnx' // TODO: ONNX support
|
||||
!state.hrf.hrfEnabled ||
|
||||
state.config.disabledSDFeatures.includes('hrf')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const log = logger('txt2img');
|
||||
|
||||
const { vae, hrfStrength, hrfEnabled, hrfMethod } = state.generation;
|
||||
const { vae } = state.generation;
|
||||
const { hrfStrength, hrfEnabled, hrfMethod } = state.hrf;
|
||||
const isAutoVae = !vae;
|
||||
const width = state.generation.width;
|
||||
const height = state.generation.height;
|
||||
@ -309,7 +310,7 @@ export const addHrfToGraph = (
|
||||
cfg_scale: originalDenoiseLatentsNode?.cfg_scale,
|
||||
scheduler: originalDenoiseLatentsNode?.scheduler,
|
||||
steps: originalDenoiseLatentsNode?.steps,
|
||||
denoising_start: 1 - state.generation.hrfStrength,
|
||||
denoising_start: 1 - hrfStrength,
|
||||
denoising_end: 1,
|
||||
};
|
||||
graph.edges.push(
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user