mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
chore(ui): cleanup (#5084)
## What type of PR is this? (check all applicable) - [ ] Refactor - [ ] Feature - [ ] Bug Fix - [x] Optimization - [ ] Documentation Update - [ ] Community Node Submission ## Description Bit of a cleanup. [chore(ui): delete unused files](5eaea9dd64
) [feat(ui): add eslint rule react/jsx-no-bind](3a0ec635c9
) This rule enforces no arrow functions in component props. In practice, it means all functions passed as component props must be wrapped in `useCallback()`. This is a performance optimization to prevent unnecessary rerenders. The rule is added and all violations have been fixed, whew! [chore(ui): move useCopyImageToClipboard to common/hooks/](f2d26a3a3c
) [chore(ui): move MM components & store to features/](bb52861896
) Somehow they had ended up in `features/ui/tabs` which isn't right ## QA Instructions, Screenshots, Recordings UI should still work. It builds successfully, and I tested things out - looks good to me.
This commit is contained in:
commit
7fcb8959fb
@ -24,6 +24,7 @@ module.exports = {
|
|||||||
root: true,
|
root: true,
|
||||||
rules: {
|
rules: {
|
||||||
curly: 'error',
|
curly: 'error',
|
||||||
|
'react/jsx-no-bind': ['error', { allowBind: true }],
|
||||||
'react/jsx-curly-brace-presence': [
|
'react/jsx-curly-brace-presence': [
|
||||||
'error',
|
'error',
|
||||||
{ props: 'never', children: 'never' },
|
{ props: 'never', children: 'never' },
|
||||||
|
@ -19,7 +19,7 @@ import sdxlReducer from 'features/sdxl/store/sdxlSlice';
|
|||||||
import configReducer from 'features/system/store/configSlice';
|
import configReducer from 'features/system/store/configSlice';
|
||||||
import systemReducer from 'features/system/store/systemSlice';
|
import systemReducer from 'features/system/store/systemSlice';
|
||||||
import queueReducer from 'features/queue/store/queueSlice';
|
import queueReducer from 'features/queue/store/queueSlice';
|
||||||
import modelmanagerReducer from 'features/ui/components/tabs/ModelManager/store/modelManagerSlice';
|
import modelmanagerReducer from 'features/modelManager/store/modelManagerSlice';
|
||||||
import hotkeysReducer from 'features/ui/store/hotkeysSlice';
|
import hotkeysReducer from 'features/ui/store/hotkeysSlice';
|
||||||
import uiReducer from 'features/ui/store/uiSlice';
|
import uiReducer from 'features/ui/store/uiSlice';
|
||||||
import dynamicMiddlewares from 'redux-dynamic-middlewares';
|
import dynamicMiddlewares from 'redux-dynamic-middlewares';
|
||||||
|
@ -8,7 +8,14 @@ import {
|
|||||||
forwardRef,
|
forwardRef,
|
||||||
useDisclosure,
|
useDisclosure,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { cloneElement, memo, ReactElement, ReactNode, useRef } from 'react';
|
import {
|
||||||
|
cloneElement,
|
||||||
|
memo,
|
||||||
|
ReactElement,
|
||||||
|
ReactNode,
|
||||||
|
useCallback,
|
||||||
|
useRef,
|
||||||
|
} from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import IAIButton from './IAIButton';
|
import IAIButton from './IAIButton';
|
||||||
|
|
||||||
@ -38,15 +45,15 @@ const IAIAlertDialog = forwardRef((props: Props, ref) => {
|
|||||||
const { isOpen, onOpen, onClose } = useDisclosure();
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
const cancelRef = useRef<HTMLButtonElement | null>(null);
|
const cancelRef = useRef<HTMLButtonElement | null>(null);
|
||||||
|
|
||||||
const handleAccept = () => {
|
const handleAccept = useCallback(() => {
|
||||||
acceptCallback();
|
acceptCallback();
|
||||||
onClose();
|
onClose();
|
||||||
};
|
}, [acceptCallback, onClose]);
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = useCallback(() => {
|
||||||
cancelCallback && cancelCallback();
|
cancelCallback && cancelCallback();
|
||||||
onClose();
|
onClose();
|
||||||
};
|
}, [cancelCallback, onClose]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,43 +0,0 @@
|
|||||||
import { Box, Flex, Icon } from '@chakra-ui/react';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { FaExclamation } from 'react-icons/fa';
|
|
||||||
|
|
||||||
const IAIErrorLoadingImageFallback = () => {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'relative',
|
|
||||||
height: 'full',
|
|
||||||
width: 'full',
|
|
||||||
'::before': {
|
|
||||||
content: "''",
|
|
||||||
display: 'block',
|
|
||||||
pt: '100%',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
top: 0,
|
|
||||||
insetInlineStart: 0,
|
|
||||||
height: 'full',
|
|
||||||
width: 'full',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
borderRadius: 'base',
|
|
||||||
bg: 'base.100',
|
|
||||||
color: 'base.500',
|
|
||||||
_dark: {
|
|
||||||
color: 'base.700',
|
|
||||||
bg: 'base.850',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon as={FaExclamation} boxSize={16} opacity={0.7} />
|
|
||||||
</Flex>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(IAIErrorLoadingImageFallback);
|
|
@ -1,8 +0,0 @@
|
|||||||
import { chakra } from '@chakra-ui/react';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Chakra-enabled <form />
|
|
||||||
*/
|
|
||||||
const IAIForm = chakra.form;
|
|
||||||
|
|
||||||
export default IAIForm;
|
|
@ -1,15 +0,0 @@
|
|||||||
import { FormErrorMessage, FormErrorMessageProps } from '@chakra-ui/react';
|
|
||||||
import { ReactNode } from 'react';
|
|
||||||
|
|
||||||
type IAIFormErrorMessageProps = FormErrorMessageProps & {
|
|
||||||
children: ReactNode | string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function IAIFormErrorMessage(props: IAIFormErrorMessageProps) {
|
|
||||||
const { children, ...rest } = props;
|
|
||||||
return (
|
|
||||||
<FormErrorMessage color="error.400" {...rest}>
|
|
||||||
{children}
|
|
||||||
</FormErrorMessage>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
import { FormHelperText, FormHelperTextProps } from '@chakra-ui/react';
|
|
||||||
import { ReactNode } from 'react';
|
|
||||||
|
|
||||||
type IAIFormHelperTextProps = FormHelperTextProps & {
|
|
||||||
children: ReactNode | string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function IAIFormHelperText(props: IAIFormHelperTextProps) {
|
|
||||||
const { children, ...rest } = props;
|
|
||||||
return (
|
|
||||||
<FormHelperText margin={0} color="base.400" {...rest}>
|
|
||||||
{children}
|
|
||||||
</FormHelperText>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
import { Flex, useColorMode } from '@chakra-ui/react';
|
|
||||||
import { ReactElement } from 'react';
|
|
||||||
import { mode } from 'theme/util/mode';
|
|
||||||
|
|
||||||
export function IAIFormItemWrapper({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: ReactElement | ReactElement[];
|
|
||||||
}) {
|
|
||||||
const { colorMode } = useColorMode();
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
flexDirection: 'column',
|
|
||||||
padding: 4,
|
|
||||||
rowGap: 4,
|
|
||||||
borderRadius: 'base',
|
|
||||||
width: 'full',
|
|
||||||
bg: mode('base.100', 'base.900')(colorMode),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
import {
|
|
||||||
Checkbox,
|
|
||||||
CheckboxProps,
|
|
||||||
FormControl,
|
|
||||||
FormControlProps,
|
|
||||||
FormLabel,
|
|
||||||
} from '@chakra-ui/react';
|
|
||||||
import { memo, ReactNode } from 'react';
|
|
||||||
|
|
||||||
type IAIFullCheckboxProps = CheckboxProps & {
|
|
||||||
label: string | ReactNode;
|
|
||||||
formControlProps?: FormControlProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
const IAIFullCheckbox = (props: IAIFullCheckboxProps) => {
|
|
||||||
const { label, formControlProps, ...rest } = props;
|
|
||||||
return (
|
|
||||||
<FormControl {...formControlProps}>
|
|
||||||
<FormLabel>{label}</FormLabel>
|
|
||||||
<Checkbox colorScheme="accent" {...rest} />
|
|
||||||
</FormControl>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(IAIFullCheckbox);
|
|
@ -1,6 +1,7 @@
|
|||||||
import { useColorMode } from '@chakra-ui/react';
|
import { useColorMode } from '@chakra-ui/react';
|
||||||
import { TextInput, TextInputProps } from '@mantine/core';
|
import { TextInput, TextInputProps } from '@mantine/core';
|
||||||
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
|
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
|
||||||
|
import { useCallback } from 'react';
|
||||||
import { mode } from 'theme/util/mode';
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
type IAIMantineTextInputProps = TextInputProps;
|
type IAIMantineTextInputProps = TextInputProps;
|
||||||
@ -20,9 +21,8 @@ export default function IAIMantineTextInput(props: IAIMantineTextInputProps) {
|
|||||||
} = useChakraThemeTokens();
|
} = useChakraThemeTokens();
|
||||||
const { colorMode } = useColorMode();
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
return (
|
const stylesFunc = useCallback(
|
||||||
<TextInput
|
() => ({
|
||||||
styles={() => ({
|
|
||||||
input: {
|
input: {
|
||||||
color: mode(base900, base100)(colorMode),
|
color: mode(base900, base100)(colorMode),
|
||||||
backgroundColor: mode(base50, base900)(colorMode),
|
backgroundColor: mode(base50, base900)(colorMode),
|
||||||
@ -35,11 +35,23 @@ export default function IAIMantineTextInput(props: IAIMantineTextInputProps) {
|
|||||||
},
|
},
|
||||||
label: {
|
label: {
|
||||||
color: mode(base700, base300)(colorMode),
|
color: mode(base700, base300)(colorMode),
|
||||||
fontWeight: 'normal',
|
fontWeight: 'normal' as const,
|
||||||
marginBottom: 4,
|
marginBottom: 4,
|
||||||
},
|
},
|
||||||
})}
|
}),
|
||||||
{...rest}
|
[
|
||||||
/>
|
accent300,
|
||||||
|
accent500,
|
||||||
|
base100,
|
||||||
|
base200,
|
||||||
|
base300,
|
||||||
|
base50,
|
||||||
|
base700,
|
||||||
|
base800,
|
||||||
|
base900,
|
||||||
|
colorMode,
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return <TextInput styles={stylesFunc} {...rest} />;
|
||||||
}
|
}
|
||||||
|
@ -98,20 +98,24 @@ const IAINumberInput = forwardRef((props: Props, ref) => {
|
|||||||
}
|
}
|
||||||
}, [value, valueAsString]);
|
}, [value, valueAsString]);
|
||||||
|
|
||||||
const handleOnChange = (v: string) => {
|
const handleOnChange = useCallback(
|
||||||
|
(v: string) => {
|
||||||
setValueAsString(v);
|
setValueAsString(v);
|
||||||
// This allows negatives and decimals e.g. '-123', `.5`, `-0.2`, etc.
|
// This allows negatives and decimals e.g. '-123', `.5`, `-0.2`, etc.
|
||||||
if (!v.match(numberStringRegex)) {
|
if (!v.match(numberStringRegex)) {
|
||||||
// Cast the value to number. Floor it if it should be an integer.
|
// Cast the value to number. Floor it if it should be an integer.
|
||||||
onChange(isInteger ? Math.floor(Number(v)) : Number(v));
|
onChange(isInteger ? Math.floor(Number(v)) : Number(v));
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[isInteger, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clicking the steppers allows the value to go outside bounds; we need to
|
* Clicking the steppers allows the value to go outside bounds; we need to
|
||||||
* clamp it on blur and floor it if needed.
|
* clamp it on blur and floor it if needed.
|
||||||
*/
|
*/
|
||||||
const handleBlur = (e: FocusEvent<HTMLInputElement>) => {
|
const handleBlur = useCallback(
|
||||||
|
(e: FocusEvent<HTMLInputElement>) => {
|
||||||
const clamped = clamp(
|
const clamped = clamp(
|
||||||
isInteger ? Math.floor(Number(e.target.value)) : Number(e.target.value),
|
isInteger ? Math.floor(Number(e.target.value)) : Number(e.target.value),
|
||||||
min,
|
min,
|
||||||
@ -119,7 +123,9 @@ const IAINumberInput = forwardRef((props: Props, ref) => {
|
|||||||
);
|
);
|
||||||
setValueAsString(String(clamped));
|
setValueAsString(String(clamped));
|
||||||
onChange(clamped);
|
onChange(clamped);
|
||||||
};
|
},
|
||||||
|
[isInteger, max, min, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: KeyboardEvent<HTMLInputElement>) => {
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipProps,
|
TooltipProps,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { memo, MouseEvent } from 'react';
|
import { memo, MouseEvent, useCallback } from 'react';
|
||||||
import IAIOption from './IAIOption';
|
import IAIOption from './IAIOption';
|
||||||
|
|
||||||
type IAISelectProps = SelectProps & {
|
type IAISelectProps = SelectProps & {
|
||||||
@ -33,15 +33,16 @@ const IAISelect = (props: IAISelectProps) => {
|
|||||||
spaceEvenly,
|
spaceEvenly,
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
return (
|
const handleClick = useCallback((e: MouseEvent<HTMLDivElement>) => {
|
||||||
<FormControl
|
|
||||||
isDisabled={isDisabled}
|
|
||||||
onClick={(e: MouseEvent<HTMLDivElement>) => {
|
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.nativeEvent.stopImmediatePropagation();
|
e.nativeEvent.stopImmediatePropagation();
|
||||||
e.nativeEvent.stopPropagation();
|
e.nativeEvent.stopPropagation();
|
||||||
e.nativeEvent.cancelBubble = true;
|
e.nativeEvent.cancelBubble = true;
|
||||||
}}
|
}, []);
|
||||||
|
return (
|
||||||
|
<FormControl
|
||||||
|
isDisabled={isDisabled}
|
||||||
|
onClick={handleClick}
|
||||||
sx={
|
sx={
|
||||||
horizontal
|
horizontal
|
||||||
? {
|
? {
|
||||||
|
@ -186,6 +186,13 @@ const IAISlider = forwardRef((props: IAIFullSliderProps, ref) => {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleMouseEnter = useCallback(() => setShowTooltip(true), []);
|
||||||
|
const handleMouseLeave = useCallback(() => setShowTooltip(false), []);
|
||||||
|
const handleStepperClick = useCallback(
|
||||||
|
() => onChange(Number(localInputValue)),
|
||||||
|
[localInputValue, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl
|
<FormControl
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@ -219,8 +226,8 @@ const IAISlider = forwardRef((props: IAIFullSliderProps, ref) => {
|
|||||||
max={max}
|
max={max}
|
||||||
step={step}
|
step={step}
|
||||||
onChange={handleSliderChange}
|
onChange={handleSliderChange}
|
||||||
onMouseEnter={() => setShowTooltip(true)}
|
onMouseEnter={handleMouseEnter}
|
||||||
onMouseLeave={() => setShowTooltip(false)}
|
onMouseLeave={handleMouseLeave}
|
||||||
focusThumbOnChange={false}
|
focusThumbOnChange={false}
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
{...rest}
|
{...rest}
|
||||||
@ -332,12 +339,8 @@ const IAISlider = forwardRef((props: IAIFullSliderProps, ref) => {
|
|||||||
{...sliderNumberInputFieldProps}
|
{...sliderNumberInputFieldProps}
|
||||||
/>
|
/>
|
||||||
<NumberInputStepper {...sliderNumberInputStepperProps}>
|
<NumberInputStepper {...sliderNumberInputStepperProps}>
|
||||||
<NumberIncrementStepper
|
<NumberIncrementStepper onClick={handleStepperClick} />
|
||||||
onClick={() => onChange(Number(localInputValue))}
|
<NumberDecrementStepper onClick={handleStepperClick} />
|
||||||
/>
|
|
||||||
<NumberDecrementStepper
|
|
||||||
onClick={() => onChange(Number(localInputValue))}
|
|
||||||
/>
|
|
||||||
</NumberInputStepper>
|
</NumberInputStepper>
|
||||||
</NumberInput>
|
</NumberInput>
|
||||||
)}
|
)}
|
||||||
|
@ -146,16 +146,15 @@ const ImageUploader = (props: ImageUploaderProps) => {
|
|||||||
};
|
};
|
||||||
}, [inputRef]);
|
}, [inputRef]);
|
||||||
|
|
||||||
return (
|
const handleKeyDown = useCallback((e: KeyboardEvent) => {
|
||||||
<Box
|
|
||||||
{...getRootProps({ style: {} })}
|
|
||||||
onKeyDown={(e: KeyboardEvent) => {
|
|
||||||
// Bail out if user hits spacebar - do not open the uploader
|
// Bail out if user hits spacebar - do not open the uploader
|
||||||
if (e.key === ' ') {
|
if (e.key === ' ') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}}
|
}, []);
|
||||||
>
|
|
||||||
|
return (
|
||||||
|
<Box {...getRootProps({ style: {} })} onKeyDown={handleKeyDown}>
|
||||||
<input {...getInputProps()} />
|
<input {...getInputProps()} />
|
||||||
{children}
|
{children}
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
import { Flex, Icon } from '@chakra-ui/react';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { FaImage } from 'react-icons/fa';
|
|
||||||
|
|
||||||
const SelectImagePlaceholder = () => {
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
w: 'full',
|
|
||||||
h: 'full',
|
|
||||||
// bg: 'base.800',
|
|
||||||
borderRadius: 'base',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
aspectRatio: '1/1',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icon color="base.400" boxSize={32} as={FaImage}></Icon>
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(SelectImagePlaceholder);
|
|
@ -1,24 +0,0 @@
|
|||||||
import { useBreakpoint } from '@chakra-ui/react';
|
|
||||||
|
|
||||||
export default function useResolution():
|
|
||||||
| 'mobile'
|
|
||||||
| 'tablet'
|
|
||||||
| 'desktop'
|
|
||||||
| 'unknown' {
|
|
||||||
const breakpointValue = useBreakpoint();
|
|
||||||
|
|
||||||
const mobileResolutions = ['base', 'sm'];
|
|
||||||
const tabletResolutions = ['md', 'lg'];
|
|
||||||
const desktopResolutions = ['xl', '2xl'];
|
|
||||||
|
|
||||||
if (mobileResolutions.includes(breakpointValue)) {
|
|
||||||
return 'mobile';
|
|
||||||
}
|
|
||||||
if (tabletResolutions.includes(breakpointValue)) {
|
|
||||||
return 'tablet';
|
|
||||||
}
|
|
||||||
if (desktopResolutions.includes(breakpointValue)) {
|
|
||||||
return 'desktop';
|
|
||||||
}
|
|
||||||
return 'unknown';
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
import dateFormat from 'dateformat';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a `now` timestamp with 1s precision, formatted as ISO datetime.
|
|
||||||
*/
|
|
||||||
export const getTimestamp = () =>
|
|
||||||
dateFormat(new Date(), `yyyy-mm-dd'T'HH:MM:ss:lo`);
|
|
@ -1,71 +0,0 @@
|
|||||||
// TODO: Restore variations
|
|
||||||
// Support code from v2.3 in here.
|
|
||||||
|
|
||||||
// export const stringToSeedWeights = (
|
|
||||||
// string: string
|
|
||||||
// ): InvokeAI.SeedWeights | boolean => {
|
|
||||||
// const stringPairs = string.split(',');
|
|
||||||
// const arrPairs = stringPairs.map((p) => p.split(':'));
|
|
||||||
// const pairs = arrPairs.map((p: Array<string>): InvokeAI.SeedWeightPair => {
|
|
||||||
// return { seed: Number(p[0]), weight: Number(p[1]) };
|
|
||||||
// });
|
|
||||||
|
|
||||||
// if (!validateSeedWeights(pairs)) {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return pairs;
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export const validateSeedWeights = (
|
|
||||||
// seedWeights: InvokeAI.SeedWeights | string
|
|
||||||
// ): boolean => {
|
|
||||||
// return typeof seedWeights === 'string'
|
|
||||||
// ? Boolean(stringToSeedWeights(seedWeights))
|
|
||||||
// : Boolean(
|
|
||||||
// seedWeights.length &&
|
|
||||||
// !seedWeights.some((pair: InvokeAI.SeedWeightPair) => {
|
|
||||||
// const { seed, weight } = pair;
|
|
||||||
// const isSeedValid = !isNaN(parseInt(seed.toString(), 10));
|
|
||||||
// const isWeightValid =
|
|
||||||
// !isNaN(parseInt(weight.toString(), 10)) &&
|
|
||||||
// weight >= 0 &&
|
|
||||||
// weight <= 1;
|
|
||||||
// return !(isSeedValid && isWeightValid);
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export const seedWeightsToString = (
|
|
||||||
// seedWeights: InvokeAI.SeedWeights
|
|
||||||
// ): string => {
|
|
||||||
// return seedWeights.reduce((acc, pair, i, arr) => {
|
|
||||||
// const { seed, weight } = pair;
|
|
||||||
// acc += `${seed}:${weight}`;
|
|
||||||
// if (i !== arr.length - 1) {
|
|
||||||
// acc += ',';
|
|
||||||
// }
|
|
||||||
// return acc;
|
|
||||||
// }, '');
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export const seedWeightsToArray = (
|
|
||||||
// seedWeights: InvokeAI.SeedWeights
|
|
||||||
// ): Array<Array<number>> => {
|
|
||||||
// return seedWeights.map((pair: InvokeAI.SeedWeightPair) => [
|
|
||||||
// pair.seed,
|
|
||||||
// pair.weight,
|
|
||||||
// ]);
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export const stringToSeedWeightsArray = (
|
|
||||||
// string: string
|
|
||||||
// ): Array<Array<number>> => {
|
|
||||||
// const stringPairs = string.split(',');
|
|
||||||
// const arrPairs = stringPairs.map((p) => p.split(':'));
|
|
||||||
// return arrPairs.map(
|
|
||||||
// (p: Array<string>): Array<number> => [parseInt(p[0], 10), parseFloat(p[1])]
|
|
||||||
// );
|
|
||||||
// };
|
|
||||||
|
|
||||||
export default {};
|
|
@ -5,17 +5,22 @@ import { clearCanvasHistory } from 'features/canvas/store/canvasSlice';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaTrash } from 'react-icons/fa';
|
import { FaTrash } from 'react-icons/fa';
|
||||||
import { isStagingSelector } from '../store/canvasSelectors';
|
import { isStagingSelector } from '../store/canvasSelectors';
|
||||||
import { memo } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
|
||||||
const ClearCanvasHistoryButtonModal = () => {
|
const ClearCanvasHistoryButtonModal = () => {
|
||||||
const isStaging = useAppSelector(isStagingSelector);
|
const isStaging = useAppSelector(isStagingSelector);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const acceptCallback = useCallback(
|
||||||
|
() => dispatch(clearCanvasHistory()),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIAlertDialog
|
<IAIAlertDialog
|
||||||
title={t('unifiedCanvas.clearCanvasHistory')}
|
title={t('unifiedCanvas.clearCanvasHistory')}
|
||||||
acceptCallback={() => dispatch(clearCanvasHistory())}
|
acceptCallback={acceptCallback}
|
||||||
acceptButtonText={t('unifiedCanvas.clearHistory')}
|
acceptButtonText={t('unifiedCanvas.clearHistory')}
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
<IAIButton size="sm" leftIcon={<FaTrash />} isDisabled={isStaging}>
|
<IAIButton size="sm" leftIcon={<FaTrash />} isDisabled={isStaging}>
|
||||||
|
@ -20,7 +20,8 @@ import {
|
|||||||
} from 'features/canvas/store/canvasSlice';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { memo } from 'react';
|
import { ChangeEvent, memo, useCallback } from 'react';
|
||||||
|
import { RgbaColor } from 'react-colorful';
|
||||||
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -95,18 +96,35 @@ const IAICanvasMaskOptions = () => {
|
|||||||
[isMaskEnabled]
|
[isMaskEnabled]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleToggleMaskLayer = () => {
|
const handleToggleMaskLayer = useCallback(() => {
|
||||||
dispatch(setLayer(layer === 'mask' ? 'base' : 'mask'));
|
dispatch(setLayer(layer === 'mask' ? 'base' : 'mask'));
|
||||||
};
|
}, [dispatch, layer]);
|
||||||
|
|
||||||
const handleClearMask = () => dispatch(clearMask());
|
const handleClearMask = useCallback(() => {
|
||||||
|
dispatch(clearMask());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleToggleEnableMask = () =>
|
const handleToggleEnableMask = useCallback(() => {
|
||||||
dispatch(setIsMaskEnabled(!isMaskEnabled));
|
dispatch(setIsMaskEnabled(!isMaskEnabled));
|
||||||
|
}, [dispatch, isMaskEnabled]);
|
||||||
|
|
||||||
const handleSaveMask = async () => {
|
const handleSaveMask = useCallback(async () => {
|
||||||
dispatch(canvasMaskSavedToGallery());
|
dispatch(canvasMaskSavedToGallery());
|
||||||
};
|
}, [dispatch]);
|
||||||
|
|
||||||
|
const handleChangePreserveMaskedArea = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
dispatch(setShouldPreserveMaskedArea(e.target.checked));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeMaskColor = useCallback(
|
||||||
|
(newColor: RgbaColor) => {
|
||||||
|
dispatch(setMaskColor(newColor));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
@ -131,15 +149,10 @@ const IAICanvasMaskOptions = () => {
|
|||||||
<IAISimpleCheckbox
|
<IAISimpleCheckbox
|
||||||
label={t('unifiedCanvas.preserveMaskedArea')}
|
label={t('unifiedCanvas.preserveMaskedArea')}
|
||||||
isChecked={shouldPreserveMaskedArea}
|
isChecked={shouldPreserveMaskedArea}
|
||||||
onChange={(e) =>
|
onChange={handleChangePreserveMaskedArea}
|
||||||
dispatch(setShouldPreserveMaskedArea(e.target.checked))
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<Box sx={{ paddingTop: 2, paddingBottom: 2 }}>
|
<Box sx={{ paddingTop: 2, paddingBottom: 2 }}>
|
||||||
<IAIColorPicker
|
<IAIColorPicker color={maskColor} onChange={handleChangeMaskColor} />
|
||||||
color={maskColor}
|
|
||||||
onChange={(newColor) => dispatch(setMaskColor(newColor))}
|
|
||||||
/>
|
|
||||||
</Box>
|
</Box>
|
||||||
<IAIButton size="sm" leftIcon={<FaSave />} onClick={handleSaveMask}>
|
<IAIButton size="sm" leftIcon={<FaSave />} onClick={handleSaveMask}>
|
||||||
Save Mask
|
Save Mask
|
||||||
|
@ -10,6 +10,7 @@ import { redo } from 'features/canvas/store/canvasSlice';
|
|||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
const canvasRedoSelector = createSelector(
|
const canvasRedoSelector = createSelector(
|
||||||
[stateSelector, activeTabNameSelector],
|
[stateSelector, activeTabNameSelector],
|
||||||
@ -34,9 +35,9 @@ export default function IAICanvasRedoButton() {
|
|||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleRedo = () => {
|
const handleRedo = useCallback(() => {
|
||||||
dispatch(redo());
|
dispatch(redo());
|
||||||
};
|
}, [dispatch]);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
['meta+shift+z', 'ctrl+shift+z', 'control+y', 'meta+y'],
|
['meta+shift+z', 'ctrl+shift+z', 'control+y', 'meta+y'],
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
} from 'features/canvas/store/canvasSlice';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
|
|
||||||
import { ChangeEvent, memo } from 'react';
|
import { ChangeEvent, memo, useCallback } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaWrench } from 'react-icons/fa';
|
import { FaWrench } from 'react-icons/fa';
|
||||||
@ -86,8 +86,52 @@ const IAICanvasSettingsButtonPopover = () => {
|
|||||||
[shouldSnapToGrid]
|
[shouldSnapToGrid]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChangeShouldSnapToGrid = (e: ChangeEvent<HTMLInputElement>) =>
|
const handleChangeShouldSnapToGrid = useCallback(
|
||||||
dispatch(setShouldSnapToGrid(e.target.checked));
|
(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
dispatch(setShouldSnapToGrid(e.target.checked)),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeShouldShowIntermediates = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
dispatch(setShouldShowIntermediates(e.target.checked)),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
const handleChangeShouldShowGrid = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
dispatch(setShouldShowGrid(e.target.checked)),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
const handleChangeShouldDarkenOutsideBoundingBox = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked)),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
const handleChangeShouldAutoSave = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
dispatch(setShouldAutoSave(e.target.checked)),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
const handleChangeShouldCropToBoundingBoxOnSave = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked)),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
const handleChangeShouldRestrictStrokesToBox = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
dispatch(setShouldRestrictStrokesToBox(e.target.checked)),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
const handleChangeShouldShowCanvasDebugInfo = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
dispatch(setShouldShowCanvasDebugInfo(e.target.checked)),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
const handleChangeShouldAntialias = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
dispatch(setShouldAntialias(e.target.checked)),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
@ -104,14 +148,12 @@ const IAICanvasSettingsButtonPopover = () => {
|
|||||||
<IAISimpleCheckbox
|
<IAISimpleCheckbox
|
||||||
label={t('unifiedCanvas.showIntermediates')}
|
label={t('unifiedCanvas.showIntermediates')}
|
||||||
isChecked={shouldShowIntermediates}
|
isChecked={shouldShowIntermediates}
|
||||||
onChange={(e) =>
|
onChange={handleChangeShouldShowIntermediates}
|
||||||
dispatch(setShouldShowIntermediates(e.target.checked))
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<IAISimpleCheckbox
|
<IAISimpleCheckbox
|
||||||
label={t('unifiedCanvas.showGrid')}
|
label={t('unifiedCanvas.showGrid')}
|
||||||
isChecked={shouldShowGrid}
|
isChecked={shouldShowGrid}
|
||||||
onChange={(e) => dispatch(setShouldShowGrid(e.target.checked))}
|
onChange={handleChangeShouldShowGrid}
|
||||||
/>
|
/>
|
||||||
<IAISimpleCheckbox
|
<IAISimpleCheckbox
|
||||||
label={t('unifiedCanvas.snapToGrid')}
|
label={t('unifiedCanvas.snapToGrid')}
|
||||||
@ -121,41 +163,33 @@ const IAICanvasSettingsButtonPopover = () => {
|
|||||||
<IAISimpleCheckbox
|
<IAISimpleCheckbox
|
||||||
label={t('unifiedCanvas.darkenOutsideSelection')}
|
label={t('unifiedCanvas.darkenOutsideSelection')}
|
||||||
isChecked={shouldDarkenOutsideBoundingBox}
|
isChecked={shouldDarkenOutsideBoundingBox}
|
||||||
onChange={(e) =>
|
onChange={handleChangeShouldDarkenOutsideBoundingBox}
|
||||||
dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked))
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<IAISimpleCheckbox
|
<IAISimpleCheckbox
|
||||||
label={t('unifiedCanvas.autoSaveToGallery')}
|
label={t('unifiedCanvas.autoSaveToGallery')}
|
||||||
isChecked={shouldAutoSave}
|
isChecked={shouldAutoSave}
|
||||||
onChange={(e) => dispatch(setShouldAutoSave(e.target.checked))}
|
onChange={handleChangeShouldAutoSave}
|
||||||
/>
|
/>
|
||||||
<IAISimpleCheckbox
|
<IAISimpleCheckbox
|
||||||
label={t('unifiedCanvas.saveBoxRegionOnly')}
|
label={t('unifiedCanvas.saveBoxRegionOnly')}
|
||||||
isChecked={shouldCropToBoundingBoxOnSave}
|
isChecked={shouldCropToBoundingBoxOnSave}
|
||||||
onChange={(e) =>
|
onChange={handleChangeShouldCropToBoundingBoxOnSave}
|
||||||
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked))
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<IAISimpleCheckbox
|
<IAISimpleCheckbox
|
||||||
label={t('unifiedCanvas.limitStrokesToBox')}
|
label={t('unifiedCanvas.limitStrokesToBox')}
|
||||||
isChecked={shouldRestrictStrokesToBox}
|
isChecked={shouldRestrictStrokesToBox}
|
||||||
onChange={(e) =>
|
onChange={handleChangeShouldRestrictStrokesToBox}
|
||||||
dispatch(setShouldRestrictStrokesToBox(e.target.checked))
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<IAISimpleCheckbox
|
<IAISimpleCheckbox
|
||||||
label={t('unifiedCanvas.showCanvasDebugInfo')}
|
label={t('unifiedCanvas.showCanvasDebugInfo')}
|
||||||
isChecked={shouldShowCanvasDebugInfo}
|
isChecked={shouldShowCanvasDebugInfo}
|
||||||
onChange={(e) =>
|
onChange={handleChangeShouldShowCanvasDebugInfo}
|
||||||
dispatch(setShouldShowCanvasDebugInfo(e.target.checked))
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<IAISimpleCheckbox
|
<IAISimpleCheckbox
|
||||||
label={t('unifiedCanvas.antialiasing')}
|
label={t('unifiedCanvas.antialiasing')}
|
||||||
isChecked={shouldAntialias}
|
isChecked={shouldAntialias}
|
||||||
onChange={(e) => dispatch(setShouldAntialias(e.target.checked))}
|
onChange={handleChangeShouldAntialias}
|
||||||
/>
|
/>
|
||||||
<ClearCanvasHistoryButtonModal />
|
<ClearCanvasHistoryButtonModal />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -15,7 +15,8 @@ import {
|
|||||||
setTool,
|
setTool,
|
||||||
} from 'features/canvas/store/canvasSlice';
|
} from 'features/canvas/store/canvasSlice';
|
||||||
import { clamp, isEqual } from 'lodash-es';
|
import { clamp, isEqual } from 'lodash-es';
|
||||||
import { memo } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
import { RgbaColor } from 'react-colorful';
|
||||||
|
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -172,11 +173,33 @@ const IAICanvasToolChooserOptions = () => {
|
|||||||
[brushColor]
|
[brushColor]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelectBrushTool = () => dispatch(setTool('brush'));
|
const handleSelectBrushTool = useCallback(() => {
|
||||||
const handleSelectEraserTool = () => dispatch(setTool('eraser'));
|
dispatch(setTool('brush'));
|
||||||
const handleSelectColorPickerTool = () => dispatch(setTool('colorPicker'));
|
}, [dispatch]);
|
||||||
const handleFillRect = () => dispatch(addFillRect());
|
const handleSelectEraserTool = useCallback(() => {
|
||||||
const handleEraseBoundingBox = () => dispatch(addEraseRect());
|
dispatch(setTool('eraser'));
|
||||||
|
}, [dispatch]);
|
||||||
|
const handleSelectColorPickerTool = useCallback(() => {
|
||||||
|
dispatch(setTool('colorPicker'));
|
||||||
|
}, [dispatch]);
|
||||||
|
const handleFillRect = useCallback(() => {
|
||||||
|
dispatch(addFillRect());
|
||||||
|
}, [dispatch]);
|
||||||
|
const handleEraseBoundingBox = useCallback(() => {
|
||||||
|
dispatch(addEraseRect());
|
||||||
|
}, [dispatch]);
|
||||||
|
const handleChangeBrushSize = useCallback(
|
||||||
|
(newSize: number) => {
|
||||||
|
dispatch(setBrushSize(newSize));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
const handleChangeBrushColor = useCallback(
|
||||||
|
(newColor: RgbaColor) => {
|
||||||
|
dispatch(setBrushColor(newColor));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonGroup isAttached>
|
<ButtonGroup isAttached>
|
||||||
@ -233,7 +256,7 @@ const IAICanvasToolChooserOptions = () => {
|
|||||||
label={t('unifiedCanvas.brushSize')}
|
label={t('unifiedCanvas.brushSize')}
|
||||||
value={brushSize}
|
value={brushSize}
|
||||||
withInput
|
withInput
|
||||||
onChange={(newSize) => dispatch(setBrushSize(newSize))}
|
onChange={handleChangeBrushSize}
|
||||||
sliderNumberInputProps={{ max: 500 }}
|
sliderNumberInputProps={{ max: 500 }}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -247,7 +270,7 @@ const IAICanvasToolChooserOptions = () => {
|
|||||||
<IAIColorPicker
|
<IAIColorPicker
|
||||||
withNumberInput={true}
|
withNumberInput={true}
|
||||||
color={brushColor}
|
color={brushColor}
|
||||||
onChange={(newColor) => dispatch(setBrushColor(newColor))}
|
onChange={handleChangeBrushColor}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -25,9 +25,9 @@ import {
|
|||||||
LAYER_NAMES_DICT,
|
LAYER_NAMES_DICT,
|
||||||
} from 'features/canvas/store/canvasTypes';
|
} from 'features/canvas/store/canvasTypes';
|
||||||
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
|
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
|
||||||
import { useCopyImageToClipboard } from 'features/ui/hooks/useCopyImageToClipboard';
|
import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { memo } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useHotkeys } from 'react-hotkeys-hook';
|
import { useHotkeys } from 'react-hotkeys-hook';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
@ -151,7 +151,9 @@ const IAICanvasToolbar = () => {
|
|||||||
[canvasBaseLayer]
|
[canvasBaseLayer]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelectMoveTool = () => dispatch(setTool('move'));
|
const handleSelectMoveTool = useCallback(() => {
|
||||||
|
dispatch(setTool('move'));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleClickResetCanvasView = useSingleAndDoubleClick(
|
const handleClickResetCanvasView = useSingleAndDoubleClick(
|
||||||
() => handleResetCanvasView(false),
|
() => handleResetCanvasView(false),
|
||||||
@ -174,36 +176,39 @@ const IAICanvasToolbar = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResetCanvas = () => {
|
const handleResetCanvas = useCallback(() => {
|
||||||
dispatch(resetCanvas());
|
dispatch(resetCanvas());
|
||||||
};
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleMergeVisible = () => {
|
const handleMergeVisible = useCallback(() => {
|
||||||
dispatch(canvasMerged());
|
dispatch(canvasMerged());
|
||||||
};
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleSaveToGallery = () => {
|
const handleSaveToGallery = useCallback(() => {
|
||||||
dispatch(canvasSavedToGallery());
|
dispatch(canvasSavedToGallery());
|
||||||
};
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleCopyImageToClipboard = () => {
|
const handleCopyImageToClipboard = useCallback(() => {
|
||||||
if (!isClipboardAPIAvailable) {
|
if (!isClipboardAPIAvailable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dispatch(canvasCopiedToClipboard());
|
dispatch(canvasCopiedToClipboard());
|
||||||
};
|
}, [dispatch, isClipboardAPIAvailable]);
|
||||||
|
|
||||||
const handleDownloadAsImage = () => {
|
const handleDownloadAsImage = useCallback(() => {
|
||||||
dispatch(canvasDownloadedAsImage());
|
dispatch(canvasDownloadedAsImage());
|
||||||
};
|
}, [dispatch]);
|
||||||
|
|
||||||
const handleChangeLayer = (v: string) => {
|
const handleChangeLayer = useCallback(
|
||||||
|
(v: string) => {
|
||||||
const newLayer = v as CanvasLayer;
|
const newLayer = v as CanvasLayer;
|
||||||
dispatch(setLayer(newLayer));
|
dispatch(setLayer(newLayer));
|
||||||
if (newLayer === 'mask' && !isMaskEnabled) {
|
if (newLayer === 'mask' && !isMaskEnabled) {
|
||||||
dispatch(setIsMaskEnabled(true));
|
dispatch(setIsMaskEnabled(true));
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[dispatch, isMaskEnabled]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
|
@ -10,6 +10,7 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
|||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
const canvasUndoSelector = createSelector(
|
const canvasUndoSelector = createSelector(
|
||||||
[stateSelector, activeTabNameSelector],
|
[stateSelector, activeTabNameSelector],
|
||||||
@ -35,9 +36,9 @@ export default function IAICanvasUndoButton() {
|
|||||||
|
|
||||||
const { canUndo, activeTabName } = useAppSelector(canvasUndoSelector);
|
const { canUndo, activeTabName } = useAppSelector(canvasUndoSelector);
|
||||||
|
|
||||||
const handleUndo = () => {
|
const handleUndo = useCallback(() => {
|
||||||
dispatch(undo());
|
dispatch(undo());
|
||||||
};
|
}, [dispatch]);
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
['meta+z', 'ctrl+z'],
|
['meta+z', 'ctrl+z'],
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
import Konva from 'konva';
|
|
||||||
import { IRect } from 'konva/lib/types';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a Konva node to a dataURL
|
|
||||||
* @param node - The Konva node to convert to a dataURL
|
|
||||||
* @param boundingBox - The bounding box to crop to
|
|
||||||
* @returns A dataURL of the node cropped to the bounding box
|
|
||||||
*/
|
|
||||||
export const konvaNodeToDataURL = (
|
|
||||||
node: Konva.Node,
|
|
||||||
boundingBox: IRect
|
|
||||||
): string => {
|
|
||||||
// get a dataURL of the bbox'd region
|
|
||||||
return node.toDataURL(boundingBox);
|
|
||||||
};
|
|
@ -87,6 +87,11 @@ const ChangeBoardModal = () => {
|
|||||||
selectedBoard,
|
selectedBoard,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const handleSetSelectedBoard = useCallback(
|
||||||
|
(v: string | null) => setSelectedBoard(v),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const cancelRef = useRef<HTMLButtonElement>(null);
|
const cancelRef = useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -113,7 +118,7 @@ const ChangeBoardModal = () => {
|
|||||||
isFetching ? t('boards.loading') : t('boards.selectBoard')
|
isFetching ? t('boards.loading') : t('boards.selectBoard')
|
||||||
}
|
}
|
||||||
disabled={isFetching}
|
disabled={isFetching}
|
||||||
onChange={(v) => setSelectedBoard(v)}
|
onChange={handleSetSelectedBoard}
|
||||||
value={selectedBoard}
|
value={selectedBoard}
|
||||||
data={data}
|
data={data}
|
||||||
/>
|
/>
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
|
||||||
import IAIButton from 'common/components/IAIButton';
|
|
||||||
import { useIsReadyToEnqueue } from 'common/hooks/useIsReadyToEnqueue';
|
|
||||||
import { memo, useCallback } from 'react';
|
|
||||||
import { useControlAdapterControlImage } from '../hooks/useControlAdapterControlImage';
|
|
||||||
import { controlAdapterImageProcessed } from '../store/actions';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
id: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const ControlAdapterPreprocessButton = ({ id }: Props) => {
|
|
||||||
const controlImage = useControlAdapterControlImage(id);
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const isReady = useIsReadyToEnqueue();
|
|
||||||
|
|
||||||
const handleProcess = useCallback(() => {
|
|
||||||
dispatch(
|
|
||||||
controlAdapterImageProcessed({
|
|
||||||
id,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [id, dispatch]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IAIButton
|
|
||||||
size="sm"
|
|
||||||
onClick={handleProcess}
|
|
||||||
isDisabled={Boolean(!controlImage) || !isReady}
|
|
||||||
>
|
|
||||||
Preprocess
|
|
||||||
</IAIButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(ControlAdapterPreprocessButton);
|
|
@ -14,9 +14,9 @@ import IAIMantineSelectItemWithTooltip from 'common/components/IAIMantineSelectI
|
|||||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||||
import { forEach } from 'lodash-es';
|
import { forEach } from 'lodash-es';
|
||||||
import { PropsWithChildren, memo, useCallback, useMemo, useRef } from 'react';
|
import { PropsWithChildren, memo, useCallback, useMemo, useRef } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useGetTextualInversionModelsQuery } from 'services/api/endpoints/models';
|
import { useGetTextualInversionModelsQuery } from 'services/api/endpoints/models';
|
||||||
import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants';
|
import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type Props = PropsWithChildren & {
|
type Props = PropsWithChildren & {
|
||||||
onSelect: (v: string) => void;
|
onSelect: (v: string) => void;
|
||||||
@ -78,6 +78,13 @@ const ParamEmbeddingPopover = (props: Props) => {
|
|||||||
[onSelect]
|
[onSelect]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const filterFunc = useCallback(
|
||||||
|
(value: string, item: SelectItem) =>
|
||||||
|
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
|
||||||
|
item.value.toLowerCase().includes(value.toLowerCase().trim()),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover
|
<Popover
|
||||||
initialFocusRef={inputRef}
|
initialFocusRef={inputRef}
|
||||||
@ -127,12 +134,7 @@ const ParamEmbeddingPopover = (props: Props) => {
|
|||||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||||
disabled={data.length === 0}
|
disabled={data.length === 0}
|
||||||
onDropdownClose={onClose}
|
onDropdownClose={onClose}
|
||||||
filter={(value, item: SelectItem) =>
|
filter={filterFunc}
|
||||||
item.label
|
|
||||||
?.toLowerCase()
|
|
||||||
.includes(value.toLowerCase().trim()) ||
|
|
||||||
item.value.toLowerCase().includes(value.toLowerCase().trim())
|
|
||||||
}
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -60,6 +60,13 @@ const BoardAutoAddSelect = () => {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const filterFunc = useCallback(
|
||||||
|
(value: string, item: SelectItem) =>
|
||||||
|
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
|
||||||
|
item.value.toLowerCase().includes(value.toLowerCase().trim()),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
label={t('boards.autoAddBoard')}
|
label={t('boards.autoAddBoard')}
|
||||||
@ -71,10 +78,7 @@ const BoardAutoAddSelect = () => {
|
|||||||
nothingFound={t('boards.noMatching')}
|
nothingFound={t('boards.noMatching')}
|
||||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||||
disabled={!hasBoards || autoAssignBoardOnClick}
|
disabled={!hasBoards || autoAssignBoardOnClick}
|
||||||
filter={(value, item: SelectItem) =>
|
filter={filterFunc}
|
||||||
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
|
|
||||||
item.value.toLowerCase().includes(value.toLowerCase().trim())
|
|
||||||
}
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -90,14 +90,8 @@ const BoardContextMenu = ({
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
const renderMenuFunc = useCallback(
|
||||||
<IAIContextMenu<HTMLDivElement>
|
() => (
|
||||||
menuProps={{ size: 'sm', isLazy: true }}
|
|
||||||
menuButtonProps={{
|
|
||||||
bg: 'transparent',
|
|
||||||
_hover: { bg: 'transparent' },
|
|
||||||
}}
|
|
||||||
renderMenu={() => (
|
|
||||||
<MenuList
|
<MenuList
|
||||||
sx={{ visibility: 'visible !important' }}
|
sx={{ visibility: 'visible !important' }}
|
||||||
motionProps={menuListMotionProps}
|
motionProps={menuListMotionProps}
|
||||||
@ -112,10 +106,7 @@ const BoardContextMenu = ({
|
|||||||
{t('boards.menuItemAutoAdd')}
|
{t('boards.menuItemAutoAdd')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{isBulkDownloadEnabled && (
|
{isBulkDownloadEnabled && (
|
||||||
<MenuItem
|
<MenuItem icon={<FaDownload />} onClickCapture={handleBulkDownload}>
|
||||||
icon={<FaDownload />}
|
|
||||||
onClickCapture={handleBulkDownload}
|
|
||||||
>
|
|
||||||
{t('boards.downloadBoard')}
|
{t('boards.downloadBoard')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
@ -128,7 +119,29 @@ const BoardContextMenu = ({
|
|||||||
)}
|
)}
|
||||||
</MenuGroup>
|
</MenuGroup>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
)}
|
),
|
||||||
|
[
|
||||||
|
autoAssignBoardOnClick,
|
||||||
|
board,
|
||||||
|
boardName,
|
||||||
|
handleBulkDownload,
|
||||||
|
handleSetAutoAdd,
|
||||||
|
isAutoAdd,
|
||||||
|
isBulkDownloadEnabled,
|
||||||
|
setBoardToDelete,
|
||||||
|
skipEvent,
|
||||||
|
t,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIContextMenu<HTMLDivElement>
|
||||||
|
menuProps={{ size: 'sm', isLazy: true }}
|
||||||
|
menuButtonProps={{
|
||||||
|
bg: 'transparent',
|
||||||
|
_hover: { bg: 'transparent' },
|
||||||
|
}}
|
||||||
|
renderMenu={renderMenuFunc}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</IAIContextMenu>
|
</IAIContextMenu>
|
||||||
|
@ -1,108 +0,0 @@
|
|||||||
import { As, Badge, Flex } from '@chakra-ui/react';
|
|
||||||
import IAIDroppable from 'common/components/IAIDroppable';
|
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
|
||||||
import { TypesafeDroppableData } from 'features/dnd/types';
|
|
||||||
import { BoardId } from 'features/gallery/store/types';
|
|
||||||
import { ReactNode, memo } from 'react';
|
|
||||||
import BoardContextMenu from '../BoardContextMenu';
|
|
||||||
|
|
||||||
type GenericBoardProps = {
|
|
||||||
board_id: BoardId;
|
|
||||||
droppableData?: TypesafeDroppableData;
|
|
||||||
onClick: () => void;
|
|
||||||
isSelected: boolean;
|
|
||||||
icon: As;
|
|
||||||
label: string;
|
|
||||||
dropLabel?: ReactNode;
|
|
||||||
badgeCount?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const formatBadgeCount = (count: number) =>
|
|
||||||
Intl.NumberFormat('en-US', {
|
|
||||||
notation: 'compact',
|
|
||||||
maximumFractionDigits: 1,
|
|
||||||
}).format(count);
|
|
||||||
|
|
||||||
const GenericBoard = (props: GenericBoardProps) => {
|
|
||||||
const {
|
|
||||||
board_id,
|
|
||||||
droppableData,
|
|
||||||
onClick,
|
|
||||||
isSelected,
|
|
||||||
icon,
|
|
||||||
label,
|
|
||||||
badgeCount,
|
|
||||||
dropLabel,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BoardContextMenu board_id={board_id}>
|
|
||||||
{(ref) => (
|
|
||||||
<Flex
|
|
||||||
ref={ref}
|
|
||||||
sx={{
|
|
||||||
flexDir: 'column',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
cursor: 'pointer',
|
|
||||||
w: 'full',
|
|
||||||
h: 'full',
|
|
||||||
borderRadius: 'base',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Flex
|
|
||||||
onClick={onClick}
|
|
||||||
sx={{
|
|
||||||
position: 'relative',
|
|
||||||
justifyContent: 'center',
|
|
||||||
alignItems: 'center',
|
|
||||||
borderRadius: 'base',
|
|
||||||
w: 'full',
|
|
||||||
aspectRatio: '1/1',
|
|
||||||
overflow: 'hidden',
|
|
||||||
shadow: isSelected ? 'selected.light' : undefined,
|
|
||||||
_dark: { shadow: isSelected ? 'selected.dark' : undefined },
|
|
||||||
flexShrink: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<IAINoContentFallback
|
|
||||||
boxSize={8}
|
|
||||||
icon={icon}
|
|
||||||
sx={{
|
|
||||||
border: '2px solid var(--invokeai-colors-base-200)',
|
|
||||||
_dark: { border: '2px solid var(--invokeai-colors-base-800)' },
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
position: 'absolute',
|
|
||||||
insetInlineEnd: 0,
|
|
||||||
top: 0,
|
|
||||||
p: 1,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{badgeCount !== undefined && (
|
|
||||||
<Badge variant="solid">{formatBadgeCount(badgeCount)}</Badge>
|
|
||||||
)}
|
|
||||||
</Flex>
|
|
||||||
<IAIDroppable data={droppableData} dropLabel={dropLabel} />
|
|
||||||
</Flex>
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
h: 'full',
|
|
||||||
alignItems: 'center',
|
|
||||||
fontWeight: isSelected ? 600 : undefined,
|
|
||||||
fontSize: 'sm',
|
|
||||||
color: isSelected ? 'base.900' : 'base.700',
|
|
||||||
_dark: { color: isSelected ? 'base.50' : 'base.200' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</Flex>
|
|
||||||
</Flex>
|
|
||||||
)}
|
|
||||||
</BoardContextMenu>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(GenericBoard);
|
|
@ -1,53 +0,0 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { stateSelector } from 'app/store/store';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
|
||||||
import IAIButton from 'common/components/IAIButton';
|
|
||||||
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
|
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
|
||||||
import { useBoardName } from 'services/api/hooks/useBoardName';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
board_id: 'images' | 'assets' | 'no_board';
|
|
||||||
};
|
|
||||||
|
|
||||||
const SystemBoardButton = ({ board_id }: Props) => {
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const selector = useMemo(
|
|
||||||
() =>
|
|
||||||
createSelector(
|
|
||||||
[stateSelector],
|
|
||||||
({ gallery }) => {
|
|
||||||
const { selectedBoardId } = gallery;
|
|
||||||
return { isSelected: selectedBoardId === board_id };
|
|
||||||
},
|
|
||||||
defaultSelectorOptions
|
|
||||||
),
|
|
||||||
[board_id]
|
|
||||||
);
|
|
||||||
|
|
||||||
const { isSelected } = useAppSelector(selector);
|
|
||||||
|
|
||||||
const boardName = useBoardName(board_id);
|
|
||||||
|
|
||||||
const handleClick = useCallback(() => {
|
|
||||||
dispatch(boardIdSelected({ boardId: board_id }));
|
|
||||||
}, [board_id, dispatch]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IAIButton
|
|
||||||
onClick={handleClick}
|
|
||||||
size="sm"
|
|
||||||
isChecked={isSelected}
|
|
||||||
sx={{
|
|
||||||
flexGrow: 1,
|
|
||||||
borderRadius: 'base',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{boardName}
|
|
||||||
</IAIButton>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(SystemBoardButton);
|
|
@ -1,22 +0,0 @@
|
|||||||
import { Flex } from '@chakra-ui/react';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { FaEyeSlash } from 'react-icons/fa';
|
|
||||||
|
|
||||||
const CurrentImageHidden = () => {
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
w: 'full',
|
|
||||||
h: 'full',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
position: 'absolute',
|
|
||||||
color: 'base.400',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FaEyeSlash fontSize="25vh" />
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(CurrentImageHidden);
|
|
@ -61,6 +61,12 @@ const GallerySettingsPopover = () => {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleChangeAutoAssignBoardOnClick = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
dispatch(autoAssignBoardOnClickChanged(e.target.checked)),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIPopover
|
<IAIPopover
|
||||||
triggerComponent={
|
triggerComponent={
|
||||||
@ -91,9 +97,7 @@ const GallerySettingsPopover = () => {
|
|||||||
<IAISimpleCheckbox
|
<IAISimpleCheckbox
|
||||||
label={t('gallery.autoAssignBoardOnClick')}
|
label={t('gallery.autoAssignBoardOnClick')}
|
||||||
isChecked={autoAssignBoardOnClick}
|
isChecked={autoAssignBoardOnClick}
|
||||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
onChange={handleChangeAutoAssignBoardOnClick}
|
||||||
dispatch(autoAssignBoardOnClickChanged(e.target.checked))
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
<BoardAutoAddSelect />
|
<BoardAutoAddSelect />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -35,14 +35,7 @@ const ImageContextMenu = ({ imageDTO, children }: Props) => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
const renderMenuFunc = useCallback(() => {
|
||||||
<IAIContextMenu<HTMLDivElement>
|
|
||||||
menuProps={{ size: 'sm', isLazy: true }}
|
|
||||||
menuButtonProps={{
|
|
||||||
bg: 'transparent',
|
|
||||||
_hover: { bg: 'transparent' },
|
|
||||||
}}
|
|
||||||
renderMenu={() => {
|
|
||||||
if (!imageDTO) {
|
if (!imageDTO) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -68,7 +61,16 @@ const ImageContextMenu = ({ imageDTO, children }: Props) => {
|
|||||||
<SingleSelectionMenuItems imageDTO={imageDTO} />
|
<SingleSelectionMenuItems imageDTO={imageDTO} />
|
||||||
</MenuList>
|
</MenuList>
|
||||||
);
|
);
|
||||||
|
}, [imageDTO, selectionCount, skipEvent]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIContextMenu<HTMLDivElement>
|
||||||
|
menuProps={{ size: 'sm', isLazy: true }}
|
||||||
|
menuButtonProps={{
|
||||||
|
bg: 'transparent',
|
||||||
|
_hover: { bg: 'transparent' },
|
||||||
}}
|
}}
|
||||||
|
renderMenu={renderMenuFunc}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</IAIContextMenu>
|
</IAIContextMenu>
|
||||||
|
@ -13,7 +13,7 @@ import { workflowLoadRequested } from 'features/nodes/store/actions';
|
|||||||
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
import { useRecallParameters } from 'features/parameters/hooks/useRecallParameters';
|
||||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
import { useCopyImageToClipboard } from 'features/ui/hooks/useCopyImageToClipboard';
|
import { useCopyImageToClipboard } from 'common/hooks/useCopyImageToClipboard';
|
||||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { flushSync } from 'react-dom';
|
import { flushSync } from 'react-dom';
|
||||||
|
@ -1,27 +0,0 @@
|
|||||||
import { Flex, Spinner, SpinnerProps } from '@chakra-ui/react';
|
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
type ImageFallbackSpinnerProps = SpinnerProps;
|
|
||||||
|
|
||||||
const ImageFallbackSpinner = (props: ImageFallbackSpinnerProps) => {
|
|
||||||
const { size = 'xl', ...rest } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
w: 'full',
|
|
||||||
h: 'full',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
position: 'absolute',
|
|
||||||
color: 'base.400',
|
|
||||||
minH: 36,
|
|
||||||
minW: 36,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Spinner size={size} {...rest} />
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(ImageFallbackSpinner);
|
|
@ -20,6 +20,7 @@ import { useBoardTotal } from 'services/api/hooks/useBoardTotal';
|
|||||||
import GalleryImage from './GalleryImage';
|
import GalleryImage from './GalleryImage';
|
||||||
import ImageGridItemContainer from './ImageGridItemContainer';
|
import ImageGridItemContainer from './ImageGridItemContainer';
|
||||||
import ImageGridListContainer from './ImageGridListContainer';
|
import ImageGridListContainer from './ImageGridListContainer';
|
||||||
|
import { EntityId } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
const overlayScrollbarsConfig: UseOverlayScrollbarsParams = {
|
const overlayScrollbarsConfig: UseOverlayScrollbarsParams = {
|
||||||
defer: true,
|
defer: true,
|
||||||
@ -71,6 +72,13 @@ const GalleryImageGrid = () => {
|
|||||||
});
|
});
|
||||||
}, [areMoreAvailable, listImages, queryArgs, currentData?.ids.length]);
|
}, [areMoreAvailable, listImages, queryArgs, currentData?.ids.length]);
|
||||||
|
|
||||||
|
const itemContentFunc = useCallback(
|
||||||
|
(index: number, imageName: EntityId) => (
|
||||||
|
<GalleryImage key={imageName} imageName={imageName as string} />
|
||||||
|
),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Initialize the gallery's custom scrollbar
|
// Initialize the gallery's custom scrollbar
|
||||||
const { current: root } = rootRef;
|
const { current: root } = rootRef;
|
||||||
@ -131,9 +139,7 @@ const GalleryImageGrid = () => {
|
|||||||
List: ImageGridListContainer,
|
List: ImageGridListContainer,
|
||||||
}}
|
}}
|
||||||
scrollerRef={setScroller}
|
scrollerRef={setScroller}
|
||||||
itemContent={(index, imageName) => (
|
itemContent={itemContentFunc}
|
||||||
<GalleryImage key={imageName} imageName={imageName as string} />
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<IAIButton
|
<IAIButton
|
||||||
|
@ -279,7 +279,7 @@ const ImageMetadataActions = (props: Props) => {
|
|||||||
key={index}
|
key={index}
|
||||||
label="LoRA"
|
label="LoRA"
|
||||||
value={`${lora.lora.model_name} - ${lora.weight}`}
|
value={`${lora.lora.model_name} - ${lora.weight}`}
|
||||||
onClick={() => handleRecallLoRA(lora)}
|
onClick={handleRecallLoRA.bind(null, lora)}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -289,7 +289,7 @@ const ImageMetadataActions = (props: Props) => {
|
|||||||
key={index}
|
key={index}
|
||||||
label="ControlNet"
|
label="ControlNet"
|
||||||
value={`${controlnet.control_model?.model_name} - ${controlnet.control_weight}`}
|
value={`${controlnet.control_model?.model_name} - ${controlnet.control_weight}`}
|
||||||
onClick={() => handleRecallControlNet(controlnet)}
|
onClick={handleRecallControlNet.bind(null, controlnet)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{validIPAdapters.map((ipAdapter, index) => (
|
{validIPAdapters.map((ipAdapter, index) => (
|
||||||
@ -297,7 +297,7 @@ const ImageMetadataActions = (props: Props) => {
|
|||||||
key={index}
|
key={index}
|
||||||
label="IP Adapter"
|
label="IP Adapter"
|
||||||
value={`${ipAdapter.ip_adapter_model?.model_name} - ${ipAdapter.weight}`}
|
value={`${ipAdapter.ip_adapter_model?.model_name} - ${ipAdapter.weight}`}
|
||||||
onClick={() => handleRecallIPAdapter(ipAdapter)}
|
onClick={handleRecallIPAdapter.bind(null, ipAdapter)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{validT2IAdapters.map((t2iAdapter, index) => (
|
{validT2IAdapters.map((t2iAdapter, index) => (
|
||||||
@ -305,7 +305,7 @@ const ImageMetadataActions = (props: Props) => {
|
|||||||
key={index}
|
key={index}
|
||||||
label="T2I Adapter"
|
label="T2I Adapter"
|
||||||
value={`${t2iAdapter.t2i_adapter_model?.model_name} - ${t2iAdapter.weight}`}
|
value={`${t2iAdapter.t2i_adapter_model?.model_name} - ${t2iAdapter.weight}`}
|
||||||
onClick={() => handleRecallT2IAdapter(t2iAdapter)}
|
onClick={handleRecallT2IAdapter.bind(null, t2iAdapter)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ExternalLinkIcon } from '@chakra-ui/icons';
|
import { ExternalLinkIcon } from '@chakra-ui/icons';
|
||||||
import { Flex, IconButton, Link, Text, Tooltip } from '@chakra-ui/react';
|
import { Flex, IconButton, Link, Text, Tooltip } from '@chakra-ui/react';
|
||||||
import { memo } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaCopy } from 'react-icons/fa';
|
import { FaCopy } from 'react-icons/fa';
|
||||||
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
import { IoArrowUndoCircleOutline } from 'react-icons/io5';
|
||||||
@ -27,6 +27,11 @@ const ImageMetadataItem = ({
|
|||||||
}: MetadataItemProps) => {
|
}: MetadataItemProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleCopy = useCallback(
|
||||||
|
() => navigator.clipboard.writeText(value.toString()),
|
||||||
|
[value]
|
||||||
|
);
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -53,7 +58,7 @@ const ImageMetadataItem = ({
|
|||||||
size="xs"
|
size="xs"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
fontSize={14}
|
fontSize={14}
|
||||||
onClick={() => navigator.clipboard.writeText(value.toString())}
|
onClick={handleCopy}
|
||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
@ -76,6 +76,13 @@ const ParamLoRASelect = () => {
|
|||||||
[dispatch, loraModels?.entities]
|
[dispatch, loraModels?.entities]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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) {
|
if (loraModels?.ids.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Flex sx={{ justifyContent: 'center', p: 2 }}>
|
<Flex sx={{ justifyContent: 'center', p: 2 }}>
|
||||||
@ -94,10 +101,7 @@ const ParamLoRASelect = () => {
|
|||||||
nothingFound="No matching LoRAs"
|
nothingFound="No matching LoRAs"
|
||||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||||
disabled={data.length === 0}
|
disabled={data.length === 0}
|
||||||
filter={(value, item: SelectItem) =>
|
filter={filterFunc}
|
||||||
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
|
|
||||||
item.value.toLowerCase().includes(value.toLowerCase().trim())
|
|
||||||
}
|
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
data-testid="add-lora"
|
data-testid="add-lora"
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ButtonGroup, Flex } from '@chakra-ui/react';
|
import { ButtonGroup, Flex } from '@chakra-ui/react';
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
import { useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import AdvancedAddModels from './AdvancedAddModels';
|
import AdvancedAddModels from './AdvancedAddModels';
|
||||||
import SimpleAddModels from './SimpleAddModels';
|
import SimpleAddModels from './SimpleAddModels';
|
||||||
|
|
||||||
@ -8,6 +8,11 @@ export default function AddModels() {
|
|||||||
const [addModelMode, setAddModelMode] = useState<'simple' | 'advanced'>(
|
const [addModelMode, setAddModelMode] = useState<'simple' | 'advanced'>(
|
||||||
'simple'
|
'simple'
|
||||||
);
|
);
|
||||||
|
const handleAddModelSimple = useCallback(() => setAddModelMode('simple'), []);
|
||||||
|
const handleAddModelAdvanced = useCallback(
|
||||||
|
() => setAddModelMode('advanced'),
|
||||||
|
[]
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<Flex
|
<Flex
|
||||||
flexDirection="column"
|
flexDirection="column"
|
||||||
@ -20,14 +25,14 @@ export default function AddModels() {
|
|||||||
<IAIButton
|
<IAIButton
|
||||||
size="sm"
|
size="sm"
|
||||||
isChecked={addModelMode == 'simple'}
|
isChecked={addModelMode == 'simple'}
|
||||||
onClick={() => setAddModelMode('simple')}
|
onClick={handleAddModelSimple}
|
||||||
>
|
>
|
||||||
Simple
|
Simple
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
<IAIButton
|
<IAIButton
|
||||||
size="sm"
|
size="sm"
|
||||||
isChecked={addModelMode == 'advanced'}
|
isChecked={addModelMode == 'advanced'}
|
||||||
onClick={() => setAddModelMode('advanced')}
|
onClick={handleAddModelAdvanced}
|
||||||
>
|
>
|
||||||
Advanced
|
Advanced
|
||||||
</IAIButton>
|
</IAIButton>
|
@ -6,7 +6,7 @@ import IAIMantineTextInput from 'common/components/IAIMantineInput';
|
|||||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { makeToast } from 'features/system/util/makeToast';
|
import { makeToast } from 'features/system/util/makeToast';
|
||||||
import { useState } from 'react';
|
import { FocusEventHandler, useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useAddMainModelsMutation } from 'services/api/endpoints/models';
|
import { useAddMainModelsMutation } from 'services/api/endpoints/models';
|
||||||
import { CheckpointModelConfig } from 'services/api/types';
|
import { CheckpointModelConfig } from 'services/api/types';
|
||||||
@ -83,6 +83,27 @@ export default function AdvancedAddCheckpoint(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleBlurModelLocation: FocusEventHandler<HTMLInputElement> =
|
||||||
|
useCallback(
|
||||||
|
(e) => {
|
||||||
|
if (advancedAddCheckpointForm.values['model_name'] === '') {
|
||||||
|
const modelName = getModelName(e.currentTarget.value);
|
||||||
|
if (modelName) {
|
||||||
|
advancedAddCheckpointForm.setFieldValue(
|
||||||
|
'model_name',
|
||||||
|
modelName as string
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[advancedAddCheckpointForm]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeUseCustomConfig = useCallback(
|
||||||
|
() => setUseCustomConfig((prev) => !prev),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={advancedAddCheckpointForm.onSubmit((v) =>
|
onSubmit={advancedAddCheckpointForm.onSubmit((v) =>
|
||||||
@ -104,17 +125,7 @@ export default function AdvancedAddCheckpoint(
|
|||||||
label={t('modelManager.modelLocation')}
|
label={t('modelManager.modelLocation')}
|
||||||
required
|
required
|
||||||
{...advancedAddCheckpointForm.getInputProps('path')}
|
{...advancedAddCheckpointForm.getInputProps('path')}
|
||||||
onBlur={(e) => {
|
onBlur={handleBlurModelLocation}
|
||||||
if (advancedAddCheckpointForm.values['model_name'] === '') {
|
|
||||||
const modelName = getModelName(e.currentTarget.value);
|
|
||||||
if (modelName) {
|
|
||||||
advancedAddCheckpointForm.setFieldValue(
|
|
||||||
'model_name',
|
|
||||||
modelName as string
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<IAIMantineTextInput
|
<IAIMantineTextInput
|
||||||
label={t('modelManager.description')}
|
label={t('modelManager.description')}
|
||||||
@ -144,7 +155,7 @@ export default function AdvancedAddCheckpoint(
|
|||||||
)}
|
)}
|
||||||
<IAISimpleCheckbox
|
<IAISimpleCheckbox
|
||||||
isChecked={useCustomConfig}
|
isChecked={useCustomConfig}
|
||||||
onChange={() => setUseCustomConfig(!useCustomConfig)}
|
onChange={handleChangeUseCustomConfig}
|
||||||
label={t('modelManager.useCustomConfig')}
|
label={t('modelManager.useCustomConfig')}
|
||||||
/>
|
/>
|
||||||
<IAIButton mt={2} type="submit">
|
<IAIButton mt={2} type="submit">
|
@ -12,6 +12,7 @@ import { setAdvancedAddScanModel } from '../../store/modelManagerSlice';
|
|||||||
import BaseModelSelect from '../shared/BaseModelSelect';
|
import BaseModelSelect from '../shared/BaseModelSelect';
|
||||||
import ModelVariantSelect from '../shared/ModelVariantSelect';
|
import ModelVariantSelect from '../shared/ModelVariantSelect';
|
||||||
import { getModelName } from './util';
|
import { getModelName } from './util';
|
||||||
|
import { FocusEventHandler, useCallback } from 'react';
|
||||||
|
|
||||||
type AdvancedAddDiffusersProps = {
|
type AdvancedAddDiffusersProps = {
|
||||||
model_path?: string;
|
model_path?: string;
|
||||||
@ -74,6 +75,22 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleBlurModelLocation: FocusEventHandler<HTMLInputElement> =
|
||||||
|
useCallback(
|
||||||
|
(e) => {
|
||||||
|
if (advancedAddDiffusersForm.values['model_name'] === '') {
|
||||||
|
const modelName = getModelName(e.currentTarget.value, false);
|
||||||
|
if (modelName) {
|
||||||
|
advancedAddDiffusersForm.setFieldValue(
|
||||||
|
'model_name',
|
||||||
|
modelName as string
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[advancedAddDiffusersForm]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
onSubmit={advancedAddDiffusersForm.onSubmit((v) =>
|
onSubmit={advancedAddDiffusersForm.onSubmit((v) =>
|
||||||
@ -96,17 +113,7 @@ export default function AdvancedAddDiffusers(props: AdvancedAddDiffusersProps) {
|
|||||||
label={t('modelManager.modelLocation')}
|
label={t('modelManager.modelLocation')}
|
||||||
placeholder={t('modelManager.modelLocationValidationMsg')}
|
placeholder={t('modelManager.modelLocationValidationMsg')}
|
||||||
{...advancedAddDiffusersForm.getInputProps('path')}
|
{...advancedAddDiffusersForm.getInputProps('path')}
|
||||||
onBlur={(e) => {
|
onBlur={handleBlurModelLocation}
|
||||||
if (advancedAddDiffusersForm.values['model_name'] === '') {
|
|
||||||
const modelName = getModelName(e.currentTarget.value, false);
|
|
||||||
if (modelName) {
|
|
||||||
advancedAddDiffusersForm.setFieldValue(
|
|
||||||
'model_name',
|
|
||||||
modelName as string
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<IAIMantineTextInput
|
<IAIMantineTextInput
|
||||||
label={t('modelManager.description')}
|
label={t('modelManager.description')}
|
@ -1,7 +1,7 @@
|
|||||||
import { Flex } from '@chakra-ui/react';
|
import { Flex } from '@chakra-ui/react';
|
||||||
import { SelectItem } from '@mantine/core';
|
import { SelectItem } from '@mantine/core';
|
||||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||||
import { useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import AdvancedAddCheckpoint from './AdvancedAddCheckpoint';
|
import AdvancedAddCheckpoint from './AdvancedAddCheckpoint';
|
||||||
import AdvancedAddDiffusers from './AdvancedAddDiffusers';
|
import AdvancedAddDiffusers from './AdvancedAddDiffusers';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -18,6 +18,12 @@ export default function AdvancedAddModels() {
|
|||||||
useState<ManualAddMode>('diffusers');
|
useState<ManualAddMode>('diffusers');
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const handleChange = useCallback((v: string | null) => {
|
||||||
|
if (!v) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setAdvancedAddMode(v as ManualAddMode);
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDirection="column" gap={4} width="100%">
|
<Flex flexDirection="column" gap={4} width="100%">
|
||||||
@ -25,12 +31,7 @@ export default function AdvancedAddModels() {
|
|||||||
label={t('modelManager.modelType')}
|
label={t('modelManager.modelType')}
|
||||||
value={advancedAddMode}
|
value={advancedAddMode}
|
||||||
data={advancedAddModeData}
|
data={advancedAddModeData}
|
||||||
onChange={(v) => {
|
onChange={handleChange}
|
||||||
if (!v) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setAdvancedAddMode(v as ManualAddMode);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Flex
|
<Flex
|
@ -92,6 +92,11 @@ export default function FoundModelsList() {
|
|||||||
setNameFilter(e.target.value);
|
setNameFilter(e.target.value);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleClickSetAdvanced = useCallback(
|
||||||
|
(model: string) => dispatch(setAdvancedAddScanModel(model)),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
const renderModels = ({
|
const renderModels = ({
|
||||||
models,
|
models,
|
||||||
showActions = true,
|
showActions = true,
|
||||||
@ -140,7 +145,7 @@ export default function FoundModelsList() {
|
|||||||
{t('modelManager.quickAdd')}
|
{t('modelManager.quickAdd')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
<IAIButton
|
<IAIButton
|
||||||
onClick={() => dispatch(setAdvancedAddScanModel(model))}
|
onClick={handleClickSetAdvanced.bind(null, model)}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
>
|
>
|
||||||
{t('modelManager.advanced')}
|
{t('modelManager.advanced')}
|
@ -4,7 +4,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { FaTimes } from 'react-icons/fa';
|
import { FaTimes } from 'react-icons/fa';
|
||||||
import { setAdvancedAddScanModel } from '../../store/modelManagerSlice';
|
import { setAdvancedAddScanModel } from '../../store/modelManagerSlice';
|
||||||
import AdvancedAddCheckpoint from './AdvancedAddCheckpoint';
|
import AdvancedAddCheckpoint from './AdvancedAddCheckpoint';
|
||||||
@ -35,6 +35,23 @@ export default function ScanAdvancedAddModels() {
|
|||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleClickSetAdvanced = useCallback(
|
||||||
|
() => dispatch(setAdvancedAddScanModel(null)),
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeAddMode = useCallback((v: string | null) => {
|
||||||
|
if (!v) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setAdvancedAddMode(v as ManualAddMode);
|
||||||
|
if (v === 'checkpoint') {
|
||||||
|
setIsCheckpoint(true);
|
||||||
|
} else {
|
||||||
|
setIsCheckpoint(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (!advancedAddScanModel) {
|
if (!advancedAddScanModel) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -68,7 +85,7 @@ export default function ScanAdvancedAddModels() {
|
|||||||
<IAIIconButton
|
<IAIIconButton
|
||||||
icon={<FaTimes />}
|
icon={<FaTimes />}
|
||||||
aria-label={t('modelManager.closeAdvanced')}
|
aria-label={t('modelManager.closeAdvanced')}
|
||||||
onClick={() => dispatch(setAdvancedAddScanModel(null))}
|
onClick={handleClickSetAdvanced}
|
||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -76,17 +93,7 @@ export default function ScanAdvancedAddModels() {
|
|||||||
label={t('modelManager.modelType')}
|
label={t('modelManager.modelType')}
|
||||||
value={advancedAddMode}
|
value={advancedAddMode}
|
||||||
data={advancedAddModeData}
|
data={advancedAddModeData}
|
||||||
onChange={(v) => {
|
onChange={handleChangeAddMode}
|
||||||
if (!v) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setAdvancedAddMode(v as ManualAddMode);
|
|
||||||
if (v === 'checkpoint') {
|
|
||||||
setIsCheckpoint(true);
|
|
||||||
} else {
|
|
||||||
setIsCheckpoint(false);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
{isCheckpoint ? (
|
{isCheckpoint ? (
|
||||||
<AdvancedAddCheckpoint
|
<AdvancedAddCheckpoint
|
@ -42,9 +42,14 @@ function SearchFolderForm() {
|
|||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
const scanAgainHandler = () => {
|
const scanAgainHandler = useCallback(() => {
|
||||||
refetchFoundModels();
|
refetchFoundModels();
|
||||||
};
|
}, [refetchFoundModels]);
|
||||||
|
|
||||||
|
const handleClickClearCheckpointFolder = useCallback(() => {
|
||||||
|
dispatch(setSearchFolder(null));
|
||||||
|
dispatch(setAdvancedAddScanModel(null));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
@ -123,10 +128,7 @@ function SearchFolderForm() {
|
|||||||
tooltip={t('modelManager.clearCheckpointFolder')}
|
tooltip={t('modelManager.clearCheckpointFolder')}
|
||||||
icon={<FaTrash />}
|
icon={<FaTrash />}
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => {
|
onClick={handleClickClearCheckpointFolder}
|
||||||
dispatch(setSearchFolder(null));
|
|
||||||
dispatch(setAdvancedAddScanModel(null));
|
|
||||||
}}
|
|
||||||
isDisabled={!searchFolder}
|
isDisabled={!searchFolder}
|
||||||
colorScheme="red"
|
colorScheme="red"
|
||||||
/>
|
/>
|
@ -1,6 +1,6 @@
|
|||||||
import { ButtonGroup, Flex } from '@chakra-ui/react';
|
import { ButtonGroup, Flex } from '@chakra-ui/react';
|
||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
import { useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import AddModels from './AddModelsPanel/AddModels';
|
import AddModels from './AddModelsPanel/AddModels';
|
||||||
import ScanModels from './AddModelsPanel/ScanModels';
|
import ScanModels from './AddModelsPanel/ScanModels';
|
||||||
@ -11,11 +11,14 @@ export default function ImportModelsPanel() {
|
|||||||
const [addModelTab, setAddModelTab] = useState<AddModelTabs>('add');
|
const [addModelTab, setAddModelTab] = useState<AddModelTabs>('add');
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleClickAddTab = useCallback(() => setAddModelTab('add'), []);
|
||||||
|
const handleClickScanTab = useCallback(() => setAddModelTab('scan'), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDirection="column" gap={4}>
|
<Flex flexDirection="column" gap={4}>
|
||||||
<ButtonGroup isAttached>
|
<ButtonGroup isAttached>
|
||||||
<IAIButton
|
<IAIButton
|
||||||
onClick={() => setAddModelTab('add')}
|
onClick={handleClickAddTab}
|
||||||
isChecked={addModelTab == 'add'}
|
isChecked={addModelTab == 'add'}
|
||||||
size="sm"
|
size="sm"
|
||||||
width="100%"
|
width="100%"
|
||||||
@ -23,7 +26,7 @@ export default function ImportModelsPanel() {
|
|||||||
{t('modelManager.addModel')}
|
{t('modelManager.addModel')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
<IAIButton
|
<IAIButton
|
||||||
onClick={() => setAddModelTab('scan')}
|
onClick={handleClickScanTab}
|
||||||
isChecked={addModelTab == 'scan'}
|
isChecked={addModelTab == 'scan'}
|
||||||
size="sm"
|
size="sm"
|
||||||
width="100%"
|
width="100%"
|
@ -9,7 +9,7 @@ import IAISlider from 'common/components/IAISlider';
|
|||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { makeToast } from 'features/system/util/makeToast';
|
import { makeToast } from 'features/system/util/makeToast';
|
||||||
import { pickBy } from 'lodash-es';
|
import { pickBy } from 'lodash-es';
|
||||||
import { useMemo, useState } from 'react';
|
import { ChangeEvent, useCallback, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ALL_BASE_MODELS } from 'services/api/constants';
|
import { ALL_BASE_MODELS } from 'services/api/constants';
|
||||||
import {
|
import {
|
||||||
@ -94,13 +94,58 @@ export default function MergeModelsPanel() {
|
|||||||
modelsMap[baseModel as keyof typeof modelsMap]
|
modelsMap[baseModel as keyof typeof modelsMap]
|
||||||
).filter((model) => model !== modelOne && model !== modelTwo);
|
).filter((model) => model !== modelOne && model !== modelTwo);
|
||||||
|
|
||||||
const handleBaseModelChange = (v: string) => {
|
const handleBaseModelChange = useCallback((v: string) => {
|
||||||
setBaseModel(v as BaseModelType);
|
setBaseModel(v as BaseModelType);
|
||||||
setModelOne(null);
|
setModelOne(null);
|
||||||
setModelTwo(null);
|
setModelTwo(null);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const mergeModelsHandler = () => {
|
const handleChangeModelOne = useCallback((v: string) => {
|
||||||
|
setModelOne(v);
|
||||||
|
}, []);
|
||||||
|
const handleChangeModelTwo = useCallback((v: string) => {
|
||||||
|
setModelTwo(v);
|
||||||
|
}, []);
|
||||||
|
const handleChangeModelThree = useCallback((v: string) => {
|
||||||
|
if (!v) {
|
||||||
|
setModelThree(null);
|
||||||
|
setModelMergeInterp('add_difference');
|
||||||
|
} else {
|
||||||
|
setModelThree(v);
|
||||||
|
setModelMergeInterp('weighted_sum');
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
const handleChangeMergedModelName = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) => setMergedModelName(e.target.value),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const handleChangeModelMergeAlpha = useCallback(
|
||||||
|
(v: number) => setModelMergeAlpha(v),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const handleResetModelMergeAlpha = useCallback(
|
||||||
|
() => setModelMergeAlpha(0.5),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const handleChangeMergeInterp = useCallback(
|
||||||
|
(v: MergeInterpolationMethods) => setModelMergeInterp(v),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const handleChangeMergeSaveLocType = useCallback(
|
||||||
|
(v: 'root' | 'custom') => setModelMergeSaveLocType(v),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const handleChangeMergeCustomSaveLoc = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) =>
|
||||||
|
setModelMergeCustomSaveLoc(e.target.value),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
const handleChangeModelMergeForce = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) => setModelMergeForce(e.target.checked),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const mergeModelsHandler = useCallback(() => {
|
||||||
const models_names: string[] = [];
|
const models_names: string[] = [];
|
||||||
|
|
||||||
let modelsToMerge: (string | null)[] = [modelOne, modelTwo, modelThree];
|
let modelsToMerge: (string | null)[] = [modelOne, modelTwo, modelThree];
|
||||||
@ -150,7 +195,21 @@ export default function MergeModelsPanel() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
}, [
|
||||||
|
baseModel,
|
||||||
|
dispatch,
|
||||||
|
mergeModels,
|
||||||
|
mergedModelName,
|
||||||
|
modelMergeAlpha,
|
||||||
|
modelMergeCustomSaveLoc,
|
||||||
|
modelMergeForce,
|
||||||
|
modelMergeInterp,
|
||||||
|
modelMergeSaveLocType,
|
||||||
|
modelOne,
|
||||||
|
modelThree,
|
||||||
|
modelTwo,
|
||||||
|
t,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex flexDirection="column" rowGap={4}>
|
<Flex flexDirection="column" rowGap={4}>
|
||||||
@ -180,7 +239,7 @@ export default function MergeModelsPanel() {
|
|||||||
value={modelOne}
|
value={modelOne}
|
||||||
placeholder={t('modelManager.selectModel')}
|
placeholder={t('modelManager.selectModel')}
|
||||||
data={modelOneList}
|
data={modelOneList}
|
||||||
onChange={(v) => setModelOne(v)}
|
onChange={handleChangeModelOne}
|
||||||
/>
|
/>
|
||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
label={t('modelManager.modelTwo')}
|
label={t('modelManager.modelTwo')}
|
||||||
@ -188,7 +247,7 @@ export default function MergeModelsPanel() {
|
|||||||
placeholder={t('modelManager.selectModel')}
|
placeholder={t('modelManager.selectModel')}
|
||||||
value={modelTwo}
|
value={modelTwo}
|
||||||
data={modelTwoList}
|
data={modelTwoList}
|
||||||
onChange={(v) => setModelTwo(v)}
|
onChange={handleChangeModelTwo}
|
||||||
/>
|
/>
|
||||||
<IAIMantineSearchableSelect
|
<IAIMantineSearchableSelect
|
||||||
label={t('modelManager.modelThree')}
|
label={t('modelManager.modelThree')}
|
||||||
@ -196,22 +255,14 @@ export default function MergeModelsPanel() {
|
|||||||
w="100%"
|
w="100%"
|
||||||
placeholder={t('modelManager.selectModel')}
|
placeholder={t('modelManager.selectModel')}
|
||||||
clearable
|
clearable
|
||||||
onChange={(v) => {
|
onChange={handleChangeModelThree}
|
||||||
if (!v) {
|
|
||||||
setModelThree(null);
|
|
||||||
setModelMergeInterp('add_difference');
|
|
||||||
} else {
|
|
||||||
setModelThree(v);
|
|
||||||
setModelMergeInterp('weighted_sum');
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<IAIInput
|
<IAIInput
|
||||||
label={t('modelManager.mergedModelName')}
|
label={t('modelManager.mergedModelName')}
|
||||||
value={mergedModelName}
|
value={mergedModelName}
|
||||||
onChange={(e) => setMergedModelName(e.target.value)}
|
onChange={handleChangeMergedModelName}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Flex
|
<Flex
|
||||||
@ -232,10 +283,10 @@ export default function MergeModelsPanel() {
|
|||||||
max={0.99}
|
max={0.99}
|
||||||
step={0.01}
|
step={0.01}
|
||||||
value={modelMergeAlpha}
|
value={modelMergeAlpha}
|
||||||
onChange={(v) => setModelMergeAlpha(v)}
|
onChange={handleChangeModelMergeAlpha}
|
||||||
withInput
|
withInput
|
||||||
withReset
|
withReset
|
||||||
handleReset={() => setModelMergeAlpha(0.5)}
|
handleReset={handleResetModelMergeAlpha}
|
||||||
withSliderMarks
|
withSliderMarks
|
||||||
/>
|
/>
|
||||||
<Text variant="subtext" fontSize="sm">
|
<Text variant="subtext" fontSize="sm">
|
||||||
@ -257,10 +308,7 @@ export default function MergeModelsPanel() {
|
|||||||
<Text fontWeight={500} fontSize="sm" variant="subtext">
|
<Text fontWeight={500} fontSize="sm" variant="subtext">
|
||||||
{t('modelManager.interpolationType')}
|
{t('modelManager.interpolationType')}
|
||||||
</Text>
|
</Text>
|
||||||
<RadioGroup
|
<RadioGroup value={modelMergeInterp} onChange={handleChangeMergeInterp}>
|
||||||
value={modelMergeInterp}
|
|
||||||
onChange={(v: MergeInterpolationMethods) => setModelMergeInterp(v)}
|
|
||||||
>
|
|
||||||
<Flex columnGap={4}>
|
<Flex columnGap={4}>
|
||||||
{modelThree === null ? (
|
{modelThree === null ? (
|
||||||
<>
|
<>
|
||||||
@ -305,7 +353,7 @@ export default function MergeModelsPanel() {
|
|||||||
</Text>
|
</Text>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
value={modelMergeSaveLocType}
|
value={modelMergeSaveLocType}
|
||||||
onChange={(v: 'root' | 'custom') => setModelMergeSaveLocType(v)}
|
onChange={handleChangeMergeSaveLocType}
|
||||||
>
|
>
|
||||||
<Flex columnGap={4}>
|
<Flex columnGap={4}>
|
||||||
<Radio value="root">
|
<Radio value="root">
|
||||||
@ -323,7 +371,7 @@ export default function MergeModelsPanel() {
|
|||||||
<IAIInput
|
<IAIInput
|
||||||
label={t('modelManager.mergedModelCustomSaveLocation')}
|
label={t('modelManager.mergedModelCustomSaveLocation')}
|
||||||
value={modelMergeCustomSaveLoc}
|
value={modelMergeCustomSaveLoc}
|
||||||
onChange={(e) => setModelMergeCustomSaveLoc(e.target.value)}
|
onChange={handleChangeMergeCustomSaveLoc}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -331,7 +379,7 @@ export default function MergeModelsPanel() {
|
|||||||
<IAISimpleCheckbox
|
<IAISimpleCheckbox
|
||||||
label={t('modelManager.ignoreMismatch')}
|
label={t('modelManager.ignoreMismatch')}
|
||||||
isChecked={modelMergeForce}
|
isChecked={modelMergeForce}
|
||||||
onChange={(e) => setModelMergeForce(e.target.checked)}
|
onChange={handleChangeModelMergeForce}
|
||||||
fontWeight="500"
|
fontWeight="500"
|
||||||
/>
|
/>
|
||||||
|
|
@ -59,6 +59,11 @@ export default function CheckpointModelEdit(props: CheckpointModelEditProps) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleChangeUseCustomConfig = useCallback(
|
||||||
|
() => setUseCustomConfig((prev) => !prev),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
const editModelFormSubmitHandler = useCallback(
|
const editModelFormSubmitHandler = useCallback(
|
||||||
(values: CheckpointModelConfig) => {
|
(values: CheckpointModelConfig) => {
|
||||||
const responseBody = {
|
const responseBody = {
|
||||||
@ -181,7 +186,7 @@ export default function CheckpointModelEdit(props: CheckpointModelEditProps) {
|
|||||||
)}
|
)}
|
||||||
<IAISimpleCheckbox
|
<IAISimpleCheckbox
|
||||||
isChecked={useCustomConfig}
|
isChecked={useCustomConfig}
|
||||||
onChange={() => setUseCustomConfig(!useCustomConfig)}
|
onChange={handleChangeUseCustomConfig}
|
||||||
label="Use Custom Config"
|
label="Use Custom Config"
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
@ -14,7 +14,7 @@ import IAIAlertDialog from 'common/components/IAIAlertDialog';
|
|||||||
import IAIButton from 'common/components/IAIButton';
|
import IAIButton from 'common/components/IAIButton';
|
||||||
import IAIInput from 'common/components/IAIInput';
|
import IAIInput from 'common/components/IAIInput';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { useEffect, useState } from 'react';
|
import { ChangeEvent, useCallback, useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { useConvertMainModelsMutation } from 'services/api/endpoints/models';
|
import { useConvertMainModelsMutation } from 'services/api/endpoints/models';
|
||||||
@ -42,11 +42,21 @@ export default function ModelConvert(props: ModelConvertProps) {
|
|||||||
setSaveLocation('InvokeAIRoot');
|
setSaveLocation('InvokeAIRoot');
|
||||||
}, [model]);
|
}, [model]);
|
||||||
|
|
||||||
const modelConvertCancelHandler = () => {
|
const modelConvertCancelHandler = useCallback(() => {
|
||||||
setSaveLocation('InvokeAIRoot');
|
setSaveLocation('InvokeAIRoot');
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const modelConvertHandler = () => {
|
const handleChangeSaveLocation = useCallback((v: string) => {
|
||||||
|
setSaveLocation(v as SaveLocation);
|
||||||
|
}, []);
|
||||||
|
const handleChangeCustomSaveLocation = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setCustomSaveLocation(e.target.value);
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const modelConvertHandler = useCallback(() => {
|
||||||
const queryArg = {
|
const queryArg = {
|
||||||
base_model: model.base_model,
|
base_model: model.base_model,
|
||||||
model_name: model.model_name,
|
model_name: model.model_name,
|
||||||
@ -101,7 +111,15 @@ export default function ModelConvert(props: ModelConvertProps) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
};
|
}, [
|
||||||
|
convertModel,
|
||||||
|
customSaveLocation,
|
||||||
|
dispatch,
|
||||||
|
model.base_model,
|
||||||
|
model.model_name,
|
||||||
|
saveLocation,
|
||||||
|
t,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIAlertDialog
|
<IAIAlertDialog
|
||||||
@ -137,10 +155,7 @@ export default function ModelConvert(props: ModelConvertProps) {
|
|||||||
<Text fontWeight="600">
|
<Text fontWeight="600">
|
||||||
{t('modelManager.convertToDiffusersSaveLocation')}
|
{t('modelManager.convertToDiffusersSaveLocation')}
|
||||||
</Text>
|
</Text>
|
||||||
<RadioGroup
|
<RadioGroup value={saveLocation} onChange={handleChangeSaveLocation}>
|
||||||
value={saveLocation}
|
|
||||||
onChange={(v) => setSaveLocation(v as SaveLocation)}
|
|
||||||
>
|
|
||||||
<Flex gap={4}>
|
<Flex gap={4}>
|
||||||
<Radio value="InvokeAIRoot">
|
<Radio value="InvokeAIRoot">
|
||||||
<Tooltip label="Save converted model in the InvokeAI root folder">
|
<Tooltip label="Save converted model in the InvokeAI root folder">
|
||||||
@ -162,9 +177,7 @@ export default function ModelConvert(props: ModelConvertProps) {
|
|||||||
</Text>
|
</Text>
|
||||||
<IAIInput
|
<IAIInput
|
||||||
value={customSaveLocation}
|
value={customSaveLocation}
|
||||||
onChange={(e) => {
|
onChange={handleChangeCustomSaveLocation}
|
||||||
setCustomSaveLocation(e.target.value);
|
|
||||||
}}
|
|
||||||
width="full"
|
width="full"
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
@ -100,7 +100,7 @@ const ModelList = (props: ModelListProps) => {
|
|||||||
<Flex flexDirection="column" gap={4} paddingInlineEnd={4}>
|
<Flex flexDirection="column" gap={4} paddingInlineEnd={4}>
|
||||||
<ButtonGroup isAttached>
|
<ButtonGroup isAttached>
|
||||||
<IAIButton
|
<IAIButton
|
||||||
onClick={() => setModelFormatFilter('all')}
|
onClick={setModelFormatFilter.bind(null, 'all')}
|
||||||
isChecked={modelFormatFilter === 'all'}
|
isChecked={modelFormatFilter === 'all'}
|
||||||
size="sm"
|
size="sm"
|
||||||
>
|
>
|
||||||
@ -108,35 +108,35 @@ const ModelList = (props: ModelListProps) => {
|
|||||||
</IAIButton>
|
</IAIButton>
|
||||||
<IAIButton
|
<IAIButton
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setModelFormatFilter('diffusers')}
|
onClick={setModelFormatFilter.bind(null, 'diffusers')}
|
||||||
isChecked={modelFormatFilter === 'diffusers'}
|
isChecked={modelFormatFilter === 'diffusers'}
|
||||||
>
|
>
|
||||||
{t('modelManager.diffusersModels')}
|
{t('modelManager.diffusersModels')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
<IAIButton
|
<IAIButton
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setModelFormatFilter('checkpoint')}
|
onClick={setModelFormatFilter.bind(null, 'checkpoint')}
|
||||||
isChecked={modelFormatFilter === 'checkpoint'}
|
isChecked={modelFormatFilter === 'checkpoint'}
|
||||||
>
|
>
|
||||||
{t('modelManager.checkpointModels')}
|
{t('modelManager.checkpointModels')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
<IAIButton
|
<IAIButton
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setModelFormatFilter('onnx')}
|
onClick={setModelFormatFilter.bind(null, 'onnx')}
|
||||||
isChecked={modelFormatFilter === 'onnx'}
|
isChecked={modelFormatFilter === 'onnx'}
|
||||||
>
|
>
|
||||||
{t('modelManager.onnxModels')}
|
{t('modelManager.onnxModels')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
<IAIButton
|
<IAIButton
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setModelFormatFilter('olive')}
|
onClick={setModelFormatFilter.bind(null, 'olive')}
|
||||||
isChecked={modelFormatFilter === 'olive'}
|
isChecked={modelFormatFilter === 'olive'}
|
||||||
>
|
>
|
||||||
{t('modelManager.oliveModels')}
|
{t('modelManager.oliveModels')}
|
||||||
</IAIButton>
|
</IAIButton>
|
||||||
<IAIButton
|
<IAIButton
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={() => setModelFormatFilter('lora')}
|
onClick={setModelFormatFilter.bind(null, 'lora')}
|
||||||
isChecked={modelFormatFilter === 'lora'}
|
isChecked={modelFormatFilter === 'lora'}
|
||||||
>
|
>
|
||||||
{t('modelManager.loraModels')}
|
{t('modelManager.loraModels')}
|
@ -4,6 +4,7 @@ import IAIButton from 'common/components/IAIButton';
|
|||||||
import IAIIconButton from 'common/components/IAIIconButton';
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { makeToast } from 'features/system/util/makeToast';
|
import { makeToast } from 'features/system/util/makeToast';
|
||||||
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { FaSync } from 'react-icons/fa';
|
import { FaSync } from 'react-icons/fa';
|
||||||
import { useSyncModelsMutation } from 'services/api/endpoints/models';
|
import { useSyncModelsMutation } from 'services/api/endpoints/models';
|
||||||
@ -19,7 +20,7 @@ export default function SyncModelsButton(props: SyncModelsButtonProps) {
|
|||||||
|
|
||||||
const [syncModels, { isLoading }] = useSyncModelsMutation();
|
const [syncModels, { isLoading }] = useSyncModelsMutation();
|
||||||
|
|
||||||
const syncModelsHandler = () => {
|
const syncModelsHandler = useCallback(() => {
|
||||||
syncModels()
|
syncModels()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.then((_) => {
|
.then((_) => {
|
||||||
@ -44,7 +45,7 @@ export default function SyncModelsButton(props: SyncModelsButtonProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
}, [dispatch, syncModels, t]);
|
||||||
|
|
||||||
return !iconMode ? (
|
return !iconMode ? (
|
||||||
<IAIButton
|
<IAIButton
|
@ -1,4 +1,4 @@
|
|||||||
import { useState, PropsWithChildren, memo } from 'react';
|
import { useState, PropsWithChildren, memo, useCallback } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { Flex, Image, Text } from '@chakra-ui/react';
|
import { Flex, Image, Text } from '@chakra-ui/react';
|
||||||
@ -59,13 +59,13 @@ export default memo(CurrentImageNode);
|
|||||||
const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => {
|
const Wrapper = (props: PropsWithChildren<{ nodeProps: NodeProps }>) => {
|
||||||
const [isHovering, setIsHovering] = useState(false);
|
const [isHovering, setIsHovering] = useState(false);
|
||||||
|
|
||||||
const handleMouseEnter = () => {
|
const handleMouseEnter = useCallback(() => {
|
||||||
setIsHovering(true);
|
setIsHovering(true);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
const handleMouseLeave = () => {
|
const handleMouseLeave = useCallback(() => {
|
||||||
setIsHovering(false);
|
setIsHovering(false);
|
||||||
};
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeWrapper
|
<NodeWrapper
|
||||||
|
@ -104,17 +104,8 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
|||||||
nodeId,
|
nodeId,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
const renderMenuFunc = useCallback(
|
||||||
<IAIContextMenu<HTMLDivElement>
|
() =>
|
||||||
menuProps={{
|
|
||||||
size: 'sm',
|
|
||||||
isLazy: true,
|
|
||||||
}}
|
|
||||||
menuButtonProps={{
|
|
||||||
bg: 'transparent',
|
|
||||||
_hover: { bg: 'transparent' },
|
|
||||||
}}
|
|
||||||
renderMenu={() =>
|
|
||||||
!menuItems.length ? null : (
|
!menuItems.length ? null : (
|
||||||
<MenuList
|
<MenuList
|
||||||
sx={{ visibility: 'visible !important' }}
|
sx={{ visibility: 'visible !important' }}
|
||||||
@ -127,8 +118,21 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
|||||||
{menuItems}
|
{menuItems}
|
||||||
</MenuGroup>
|
</MenuGroup>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
)
|
),
|
||||||
}
|
[fieldTemplateTitle, label, menuItems, skipEvent, t]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IAIContextMenu<HTMLDivElement>
|
||||||
|
menuProps={{
|
||||||
|
size: 'sm',
|
||||||
|
isLazy: true,
|
||||||
|
}}
|
||||||
|
menuButtonProps={{
|
||||||
|
bg: 'transparent',
|
||||||
|
_hover: { bg: 'transparent' },
|
||||||
|
}}
|
||||||
|
renderMenu={renderMenuFunc}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</IAIContextMenu>
|
</IAIContextMenu>
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import {
|
|
||||||
ClipInputFieldTemplate,
|
|
||||||
ClipInputFieldValue,
|
|
||||||
FieldComponentProps,
|
|
||||||
} from 'features/nodes/types/types';
|
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
const ClipInputFieldComponent = (
|
|
||||||
_props: FieldComponentProps<ClipInputFieldValue, ClipInputFieldTemplate>
|
|
||||||
) => {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(ClipInputFieldComponent);
|
|
@ -1,17 +0,0 @@
|
|||||||
import {
|
|
||||||
CollectionInputFieldTemplate,
|
|
||||||
CollectionInputFieldValue,
|
|
||||||
FieldComponentProps,
|
|
||||||
} from 'features/nodes/types/types';
|
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
const CollectionInputFieldComponent = (
|
|
||||||
_props: FieldComponentProps<
|
|
||||||
CollectionInputFieldValue,
|
|
||||||
CollectionInputFieldTemplate
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(CollectionInputFieldComponent);
|
|
@ -1,17 +0,0 @@
|
|||||||
import {
|
|
||||||
CollectionItemInputFieldTemplate,
|
|
||||||
CollectionItemInputFieldValue,
|
|
||||||
FieldComponentProps,
|
|
||||||
} from 'features/nodes/types/types';
|
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
const CollectionItemInputFieldComponent = (
|
|
||||||
_props: FieldComponentProps<
|
|
||||||
CollectionItemInputFieldValue,
|
|
||||||
CollectionItemInputFieldTemplate
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(CollectionItemInputFieldComponent);
|
|
@ -1,17 +0,0 @@
|
|||||||
import {
|
|
||||||
ConditioningInputFieldTemplate,
|
|
||||||
ConditioningInputFieldValue,
|
|
||||||
FieldComponentProps,
|
|
||||||
} from 'features/nodes/types/types';
|
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
const ConditioningInputFieldComponent = (
|
|
||||||
_props: FieldComponentProps<
|
|
||||||
ConditioningInputFieldValue,
|
|
||||||
ConditioningInputFieldTemplate
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(ConditioningInputFieldComponent);
|
|
@ -1,19 +0,0 @@
|
|||||||
import {
|
|
||||||
ControlInputFieldTemplate,
|
|
||||||
ControlInputFieldValue,
|
|
||||||
ControlPolymorphicInputFieldTemplate,
|
|
||||||
ControlPolymorphicInputFieldValue,
|
|
||||||
FieldComponentProps,
|
|
||||||
} from 'features/nodes/types/types';
|
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
const ControlInputFieldComponent = (
|
|
||||||
_props: FieldComponentProps<
|
|
||||||
ControlInputFieldValue | ControlPolymorphicInputFieldValue,
|
|
||||||
ControlInputFieldTemplate | ControlPolymorphicInputFieldTemplate
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(ControlInputFieldComponent);
|
|
@ -1,17 +0,0 @@
|
|||||||
import {
|
|
||||||
DenoiseMaskInputFieldTemplate,
|
|
||||||
DenoiseMaskInputFieldValue,
|
|
||||||
FieldComponentProps,
|
|
||||||
} from 'features/nodes/types/types';
|
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
const DenoiseMaskInputFieldComponent = (
|
|
||||||
_props: FieldComponentProps<
|
|
||||||
DenoiseMaskInputFieldValue,
|
|
||||||
DenoiseMaskInputFieldTemplate
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(DenoiseMaskInputFieldComponent);
|
|
@ -1,19 +0,0 @@
|
|||||||
import {
|
|
||||||
IPAdapterInputFieldTemplate,
|
|
||||||
IPAdapterInputFieldValue,
|
|
||||||
FieldComponentProps,
|
|
||||||
IPAdapterPolymorphicInputFieldValue,
|
|
||||||
IPAdapterPolymorphicInputFieldTemplate,
|
|
||||||
} from 'features/nodes/types/types';
|
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
const IPAdapterInputFieldComponent = (
|
|
||||||
_props: FieldComponentProps<
|
|
||||||
IPAdapterInputFieldValue | IPAdapterPolymorphicInputFieldValue,
|
|
||||||
IPAdapterInputFieldTemplate | IPAdapterPolymorphicInputFieldTemplate
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(IPAdapterInputFieldComponent);
|
|
@ -1,94 +0,0 @@
|
|||||||
import {
|
|
||||||
ImageCollectionInputFieldTemplate,
|
|
||||||
ImageCollectionInputFieldValue,
|
|
||||||
FieldComponentProps,
|
|
||||||
} from 'features/nodes/types/types';
|
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
import { Flex } from '@chakra-ui/react';
|
|
||||||
import IAIDndImage from 'common/components/IAIDndImage';
|
|
||||||
import IAIDropOverlay from 'common/components/IAIDropOverlay';
|
|
||||||
import { useDroppableTypesafe } from 'features/dnd/hooks/typesafeHooks';
|
|
||||||
import { NodesMultiImageDropData } from 'features/dnd/types';
|
|
||||||
import { isValidDrop } from 'features/dnd/util/isValidDrop';
|
|
||||||
import { useGetImageDTOQuery } from 'services/api/endpoints/images';
|
|
||||||
|
|
||||||
const ImageCollectionInputFieldComponent = (
|
|
||||||
props: FieldComponentProps<
|
|
||||||
ImageCollectionInputFieldValue,
|
|
||||||
ImageCollectionInputFieldTemplate
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
const { nodeId, field } = props;
|
|
||||||
|
|
||||||
// const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
// const handleDrop = useCallback(
|
|
||||||
// ({ image_name }: ImageDTO) => {
|
|
||||||
// dispatch(
|
|
||||||
// fieldValueChanged({
|
|
||||||
// nodeId,
|
|
||||||
// fieldName: field.name,
|
|
||||||
// value: uniqBy([...(field.value ?? []), { image_name }], 'image_name'),
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// [dispatch, field.name, field.value, nodeId]
|
|
||||||
// );
|
|
||||||
|
|
||||||
const droppableData: NodesMultiImageDropData = {
|
|
||||||
id: `node-${nodeId}-${field.name}`,
|
|
||||||
actionType: 'SET_MULTI_NODES_IMAGE',
|
|
||||||
context: { nodeId: nodeId, fieldName: field.name },
|
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
|
||||||
isOver,
|
|
||||||
setNodeRef: setDroppableRef,
|
|
||||||
active,
|
|
||||||
} = useDroppableTypesafe({
|
|
||||||
id: `node_${nodeId}`,
|
|
||||||
data: droppableData,
|
|
||||||
});
|
|
||||||
|
|
||||||
// const handleReset = useCallback(() => {
|
|
||||||
// dispatch(
|
|
||||||
// fieldValueChanged({
|
|
||||||
// nodeId,
|
|
||||||
// fieldName: field.name,
|
|
||||||
// value: undefined,
|
|
||||||
// })
|
|
||||||
// );
|
|
||||||
// }, [dispatch, field.name, nodeId]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
ref={setDroppableRef}
|
|
||||||
sx={{
|
|
||||||
w: 'full',
|
|
||||||
h: 'full',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
position: 'relative',
|
|
||||||
minH: '10rem',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{field.value?.map(({ image_name }) => (
|
|
||||||
<ImageSubField key={image_name} imageName={image_name} />
|
|
||||||
))}
|
|
||||||
{isValidDrop(droppableData, active) && <IAIDropOverlay isOver={isOver} />}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(ImageCollectionInputFieldComponent);
|
|
||||||
|
|
||||||
type ImageSubFieldProps = { imageName: string };
|
|
||||||
|
|
||||||
const ImageSubField = (props: ImageSubFieldProps) => {
|
|
||||||
const { currentData: image } = useGetImageDTOQuery(props.imageName);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IAIDndImage imageDTO={image} isDropDisabled={true} isDragDisabled={true} />
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,19 +0,0 @@
|
|||||||
import {
|
|
||||||
LatentsInputFieldTemplate,
|
|
||||||
LatentsInputFieldValue,
|
|
||||||
FieldComponentProps,
|
|
||||||
LatentsPolymorphicInputFieldValue,
|
|
||||||
LatentsPolymorphicInputFieldTemplate,
|
|
||||||
} from 'features/nodes/types/types';
|
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
const LatentsInputFieldComponent = (
|
|
||||||
_props: FieldComponentProps<
|
|
||||||
LatentsInputFieldValue | LatentsPolymorphicInputFieldValue,
|
|
||||||
LatentsInputFieldTemplate | LatentsPolymorphicInputFieldTemplate
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(LatentsInputFieldComponent);
|
|
@ -80,6 +80,13 @@ const LoRAModelInputFieldComponent = (
|
|||||||
[dispatch, field.name, nodeId]
|
[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) {
|
if (loraModels?.ids.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Flex sx={{ justifyContent: 'center', p: 2 }}>
|
<Flex sx={{ justifyContent: 'center', p: 2 }}>
|
||||||
@ -101,10 +108,7 @@ const LoRAModelInputFieldComponent = (
|
|||||||
nothingFound={t('models.noMatchingLoRAs')}
|
nothingFound={t('models.noMatchingLoRAs')}
|
||||||
itemComponent={IAIMantineSelectItemWithTooltip}
|
itemComponent={IAIMantineSelectItemWithTooltip}
|
||||||
disabled={data.length === 0}
|
disabled={data.length === 0}
|
||||||
filter={(value, item: SelectItem) =>
|
filter={filterFunc}
|
||||||
item.label?.toLowerCase().includes(value.toLowerCase().trim()) ||
|
|
||||||
item.value.toLowerCase().includes(value.toLowerCase().trim())
|
|
||||||
}
|
|
||||||
error={!selectedLoRAModel}
|
error={!selectedLoRAModel}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||||
import { modelIdToMainModelParam } from 'features/parameters/util/modelIdToMainModelParam';
|
import { modelIdToMainModelParam } from 'features/parameters/util/modelIdToMainModelParam';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
import SyncModelsButton from 'features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton';
|
import SyncModelsButton from 'features/modelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton';
|
||||||
import { forEach } from 'lodash-es';
|
import { forEach } from 'lodash-es';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { NON_SDXL_MAIN_MODELS } from 'services/api/constants';
|
import { NON_SDXL_MAIN_MODELS } from 'services/api/constants';
|
||||||
|
@ -19,7 +19,7 @@ import {
|
|||||||
IntegerPolymorphicInputFieldTemplate,
|
IntegerPolymorphicInputFieldTemplate,
|
||||||
IntegerPolymorphicInputFieldValue,
|
IntegerPolymorphicInputFieldValue,
|
||||||
} from 'features/nodes/types/types';
|
} from 'features/nodes/types/types';
|
||||||
import { memo, useEffect, useMemo, useState } from 'react';
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
const NumberInputFieldComponent = (
|
const NumberInputFieldComponent = (
|
||||||
props: FieldComponentProps<
|
props: FieldComponentProps<
|
||||||
@ -43,7 +43,8 @@ const NumberInputFieldComponent = (
|
|||||||
[fieldTemplate.type]
|
[fieldTemplate.type]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleValueChanged = (v: string) => {
|
const handleValueChanged = useCallback(
|
||||||
|
(v: string) => {
|
||||||
setValueAsString(v);
|
setValueAsString(v);
|
||||||
// This allows negatives and decimals e.g. '-123', `.5`, `-0.2`, etc.
|
// This allows negatives and decimals e.g. '-123', `.5`, `-0.2`, etc.
|
||||||
if (!v.match(numberStringRegex)) {
|
if (!v.match(numberStringRegex)) {
|
||||||
@ -56,7 +57,9 @@ const NumberInputFieldComponent = (
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[dispatch, field.name, isIntegerField, nodeId]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||||
import { modelIdToMainModelParam } from 'features/parameters/util/modelIdToMainModelParam';
|
import { modelIdToMainModelParam } from 'features/parameters/util/modelIdToMainModelParam';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
import SyncModelsButton from 'features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton';
|
import SyncModelsButton from 'features/modelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton';
|
||||||
import { forEach } from 'lodash-es';
|
import { forEach } from 'lodash-es';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
@ -11,7 +11,7 @@ import {
|
|||||||
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
import { MODEL_TYPE_MAP } from 'features/parameters/types/constants';
|
||||||
import { modelIdToMainModelParam } from 'features/parameters/util/modelIdToMainModelParam';
|
import { modelIdToMainModelParam } from 'features/parameters/util/modelIdToMainModelParam';
|
||||||
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
|
||||||
import SyncModelsButton from 'features/ui/components/tabs/ModelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton';
|
import SyncModelsButton from 'features/modelManager/subpanels/ModelManagerSettingsPanel/SyncModelsButton';
|
||||||
import { forEach } from 'lodash-es';
|
import { forEach } from 'lodash-es';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
import {
|
|
||||||
T2IAdapterInputFieldTemplate,
|
|
||||||
T2IAdapterInputFieldValue,
|
|
||||||
T2IAdapterPolymorphicInputFieldTemplate,
|
|
||||||
T2IAdapterPolymorphicInputFieldValue,
|
|
||||||
FieldComponentProps,
|
|
||||||
} from 'features/nodes/types/types';
|
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
const T2IAdapterInputFieldComponent = (
|
|
||||||
_props: FieldComponentProps<
|
|
||||||
T2IAdapterInputFieldValue | T2IAdapterPolymorphicInputFieldValue,
|
|
||||||
T2IAdapterInputFieldTemplate | T2IAdapterPolymorphicInputFieldTemplate
|
|
||||||
>
|
|
||||||
) => {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(T2IAdapterInputFieldComponent);
|
|
@ -1,14 +0,0 @@
|
|||||||
import {
|
|
||||||
UNetInputFieldTemplate,
|
|
||||||
UNetInputFieldValue,
|
|
||||||
FieldComponentProps,
|
|
||||||
} from 'features/nodes/types/types';
|
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
const UNetInputFieldComponent = (
|
|
||||||
_props: FieldComponentProps<UNetInputFieldValue, UNetInputFieldTemplate>
|
|
||||||
) => {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(UNetInputFieldComponent);
|
|
@ -1,14 +0,0 @@
|
|||||||
import {
|
|
||||||
VaeInputFieldTemplate,
|
|
||||||
VaeInputFieldValue,
|
|
||||||
FieldComponentProps,
|
|
||||||
} from 'features/nodes/types/types';
|
|
||||||
import { memo } from 'react';
|
|
||||||
|
|
||||||
const VaeInputFieldComponent = (
|
|
||||||
_props: FieldComponentProps<VaeInputFieldValue, VaeInputFieldTemplate>
|
|
||||||
) => {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(VaeInputFieldComponent);
|
|
@ -1,27 +0,0 @@
|
|||||||
import { NODE_MIN_WIDTH } from 'features/nodes/types/constants';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { NodeResizeControl, NodeResizerProps } from 'reactflow';
|
|
||||||
|
|
||||||
// this causes https://github.com/invoke-ai/InvokeAI/issues/4140
|
|
||||||
// not using it for now
|
|
||||||
|
|
||||||
const NodeResizer = (props: NodeResizerProps) => {
|
|
||||||
const { ...rest } = props;
|
|
||||||
return (
|
|
||||||
<NodeResizeControl
|
|
||||||
style={{
|
|
||||||
position: 'absolute',
|
|
||||||
border: 'none',
|
|
||||||
background: 'transparent',
|
|
||||||
width: 15,
|
|
||||||
height: 15,
|
|
||||||
bottom: 0,
|
|
||||||
right: 0,
|
|
||||||
}}
|
|
||||||
minWidth={NODE_MIN_WIDTH}
|
|
||||||
{...rest}
|
|
||||||
></NodeResizeControl>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(NodeResizer);
|
|
@ -1,78 +0,0 @@
|
|||||||
import { Box, Flex } from '@chakra-ui/react';
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { stateSelector } from 'app/store/store';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
|
||||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
|
||||||
import { InvocationTemplate, NodeData } from 'features/nodes/types/types';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import NotesTextarea from '../../flow/nodes/Invocation/NotesTextarea';
|
|
||||||
import NodeTitle from '../../flow/nodes/common/NodeTitle';
|
|
||||||
import ScrollableContent from '../ScrollableContent';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
const selector = createSelector(
|
|
||||||
stateSelector,
|
|
||||||
({ nodes }) => {
|
|
||||||
const lastSelectedNodeId =
|
|
||||||
nodes.selectedNodes[nodes.selectedNodes.length - 1];
|
|
||||||
|
|
||||||
const lastSelectedNode = nodes.nodes.find(
|
|
||||||
(node) => node.id === lastSelectedNodeId
|
|
||||||
);
|
|
||||||
|
|
||||||
const lastSelectedNodeTemplate = lastSelectedNode
|
|
||||||
? nodes.nodeTemplates[lastSelectedNode.data.type]
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
return {
|
|
||||||
data: lastSelectedNode?.data,
|
|
||||||
template: lastSelectedNodeTemplate,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
defaultSelectorOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
const InspectorDetailsTab = () => {
|
|
||||||
const { data, template } = useAppSelector(selector);
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
if (!template || !data) {
|
|
||||||
return (
|
|
||||||
<IAINoContentFallback label={t('nodes.noNodeSelected')} icon={null} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Content data={data} template={template} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(InspectorDetailsTab);
|
|
||||||
|
|
||||||
const Content = (props: { data: NodeData; template: InvocationTemplate }) => {
|
|
||||||
const { data } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
position: 'relative',
|
|
||||||
w: 'full',
|
|
||||||
h: 'full',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ScrollableContent>
|
|
||||||
<Flex
|
|
||||||
sx={{
|
|
||||||
flexDir: 'column',
|
|
||||||
position: 'relative',
|
|
||||||
p: 1,
|
|
||||||
gap: 2,
|
|
||||||
w: 'full',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<NodeTitle nodeId={data.id} />
|
|
||||||
<NotesTextarea nodeId={data.id} />
|
|
||||||
</Flex>
|
|
||||||
</ScrollableContent>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,51 +0,0 @@
|
|||||||
import { Box, Text } from '@chakra-ui/react';
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { stateSelector } from 'app/store/store';
|
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import IAITextarea from 'common/components/IAITextarea';
|
|
||||||
import { workflowNotesChanged } from 'features/nodes/store/nodesSlice';
|
|
||||||
import { ChangeEvent, memo, useCallback } from 'react';
|
|
||||||
|
|
||||||
const selector = createSelector(stateSelector, ({ nodes }) => {
|
|
||||||
const { notes } = nodes.workflow;
|
|
||||||
|
|
||||||
return {
|
|
||||||
notes,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const WorkflowNotesTab = () => {
|
|
||||||
const { notes } = useAppSelector(selector);
|
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
|
|
||||||
const handleChangeNotes = useCallback(
|
|
||||||
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
|
||||||
dispatch(workflowNotesChanged(e.target.value));
|
|
||||||
},
|
|
||||||
[dispatch]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box sx={{ pos: 'relative', h: 'full' }}>
|
|
||||||
<IAITextarea
|
|
||||||
onChange={handleChangeNotes}
|
|
||||||
value={notes}
|
|
||||||
fontSize="sm"
|
|
||||||
sx={{ h: 'full', resize: 'none' }}
|
|
||||||
/>
|
|
||||||
<Box sx={{ pos: 'absolute', bottom: 2, right: 2 }}>
|
|
||||||
<Text
|
|
||||||
sx={{
|
|
||||||
fontSize: 'xs',
|
|
||||||
opacity: 0.5,
|
|
||||||
userSelect: 'none',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{notes.length}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(WorkflowNotesTab);
|
|
@ -1,11 +0,0 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { stateSelector } from 'app/store/store';
|
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
|
||||||
import { AnyInvocationType } from 'services/events/types';
|
|
||||||
|
|
||||||
export const makeTemplateSelector = (type: AnyInvocationType) =>
|
|
||||||
createSelector(
|
|
||||||
stateSelector,
|
|
||||||
({ nodes }) => nodes.nodeTemplates[type],
|
|
||||||
defaultSelectorOptions
|
|
||||||
);
|
|
@ -1,21 +0,0 @@
|
|||||||
import { Flex } from '@chakra-ui/react';
|
|
||||||
import IAICollapse from 'common/components/IAICollapse';
|
|
||||||
import { memo } from 'react';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import ParamBoundingBoxHeight from './ParamBoundingBoxHeight';
|
|
||||||
import ParamBoundingBoxWidth from './ParamBoundingBoxWidth';
|
|
||||||
|
|
||||||
const ParamBoundingBoxCollapse = () => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<IAICollapse label={t('parameters.boundingBoxHeader')}>
|
|
||||||
<Flex sx={{ gap: 2, flexDirection: 'column' }}>
|
|
||||||
<ParamBoundingBoxWidth />
|
|
||||||
<ParamBoundingBoxHeight />
|
|
||||||
</Flex>
|
|
||||||
</IAICollapse>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(ParamBoundingBoxCollapse);
|
|
@ -6,7 +6,7 @@ import IAISlider from 'common/components/IAISlider';
|
|||||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||||
import { memo } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@ -36,7 +36,8 @@ const ParamBoundingBoxWidth = () => {
|
|||||||
? 1024
|
? 1024
|
||||||
: 512;
|
: 512;
|
||||||
|
|
||||||
const handleChangeHeight = (v: number) => {
|
const handleChangeHeight = useCallback(
|
||||||
|
(v: number) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
setBoundingBoxDimensions({
|
setBoundingBoxDimensions({
|
||||||
...boundingBoxDimensions,
|
...boundingBoxDimensions,
|
||||||
@ -52,9 +53,11 @@ const ParamBoundingBoxWidth = () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[aspectRatio, boundingBoxDimensions, dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
const handleResetHeight = () => {
|
const handleResetHeight = useCallback(() => {
|
||||||
dispatch(
|
dispatch(
|
||||||
setBoundingBoxDimensions({
|
setBoundingBoxDimensions({
|
||||||
...boundingBoxDimensions,
|
...boundingBoxDimensions,
|
||||||
@ -70,7 +73,7 @@ const ParamBoundingBoxWidth = () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}, [aspectRatio, boundingBoxDimensions, dispatch, initial]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAISlider
|
<IAISlider
|
||||||
|
@ -6,7 +6,7 @@ import IAISlider from 'common/components/IAISlider';
|
|||||||
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
import { roundToMultiple } from 'common/util/roundDownToMultiple';
|
||||||
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
import { isStagingSelector } from 'features/canvas/store/canvasSelectors';
|
||||||
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
|
||||||
import { memo } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
@ -36,7 +36,8 @@ const ParamBoundingBoxWidth = () => {
|
|||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleChangeWidth = (v: number) => {
|
const handleChangeWidth = useCallback(
|
||||||
|
(v: number) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
setBoundingBoxDimensions({
|
setBoundingBoxDimensions({
|
||||||
...boundingBoxDimensions,
|
...boundingBoxDimensions,
|
||||||
@ -52,9 +53,11 @@ const ParamBoundingBoxWidth = () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[aspectRatio, boundingBoxDimensions, dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
const handleResetWidth = () => {
|
const handleResetWidth = useCallback(() => {
|
||||||
dispatch(
|
dispatch(
|
||||||
setBoundingBoxDimensions({
|
setBoundingBoxDimensions({
|
||||||
...boundingBoxDimensions,
|
...boundingBoxDimensions,
|
||||||
@ -70,7 +73,7 @@ const ParamBoundingBoxWidth = () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
}, [aspectRatio, boundingBoxDimensions, dispatch, initial]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAISlider
|
<IAISlider
|
||||||
|
@ -6,7 +6,7 @@ import IAIMantineSelect from 'common/components/IAIMantineSelect';
|
|||||||
import { setCanvasCoherenceMode } from 'features/parameters/store/generationSlice';
|
import { setCanvasCoherenceMode } from 'features/parameters/store/generationSlice';
|
||||||
import { CanvasCoherenceModeParam } from 'features/parameters/types/parameterSchemas';
|
import { CanvasCoherenceModeParam } from 'features/parameters/types/parameterSchemas';
|
||||||
|
|
||||||
import { memo } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const coherenceModeSelectData: IAISelectDataType[] = [
|
const coherenceModeSelectData: IAISelectDataType[] = [
|
||||||
@ -22,13 +22,16 @@ const ParamCanvasCoherenceMode = () => {
|
|||||||
);
|
);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleCoherenceModeChange = (v: string | null) => {
|
const handleCoherenceModeChange = useCallback(
|
||||||
|
(v: string | null) => {
|
||||||
if (!v) {
|
if (!v) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(setCanvasCoherenceMode(v as CanvasCoherenceModeParam));
|
dispatch(setCanvasCoherenceMode(v as CanvasCoherenceModeParam));
|
||||||
};
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover feature="compositingCoherenceMode">
|
<IAIInformationalPopover feature="compositingCoherenceMode">
|
||||||
|
@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
import { setCanvasCoherenceSteps } from 'features/parameters/store/generationSlice';
|
import { setCanvasCoherenceSteps } from 'features/parameters/store/generationSlice';
|
||||||
import { memo } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const ParamCanvasCoherenceSteps = () => {
|
const ParamCanvasCoherenceSteps = () => {
|
||||||
@ -13,6 +13,17 @@ const ParamCanvasCoherenceSteps = () => {
|
|||||||
);
|
);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(v: number) => {
|
||||||
|
dispatch(setCanvasCoherenceSteps(v));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleReset = useCallback(() => {
|
||||||
|
dispatch(setCanvasCoherenceSteps(20));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover feature="compositingCoherenceSteps">
|
<IAIInformationalPopover feature="compositingCoherenceSteps">
|
||||||
<IAISlider
|
<IAISlider
|
||||||
@ -22,15 +33,11 @@ const ParamCanvasCoherenceSteps = () => {
|
|||||||
step={1}
|
step={1}
|
||||||
sliderNumberInputProps={{ max: 999 }}
|
sliderNumberInputProps={{ max: 999 }}
|
||||||
value={canvasCoherenceSteps}
|
value={canvasCoherenceSteps}
|
||||||
onChange={(v) => {
|
onChange={handleChange}
|
||||||
dispatch(setCanvasCoherenceSteps(v));
|
|
||||||
}}
|
|
||||||
withInput
|
withInput
|
||||||
withSliderMarks
|
withSliderMarks
|
||||||
withReset
|
withReset
|
||||||
handleReset={() => {
|
handleReset={handleReset}
|
||||||
dispatch(setCanvasCoherenceSteps(20));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</IAIInformationalPopover>
|
</IAIInformationalPopover>
|
||||||
);
|
);
|
||||||
|
@ -3,7 +3,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
|||||||
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
import IAIInformationalPopover from 'common/components/IAIInformationalPopover/IAIInformationalPopover';
|
||||||
import IAISlider from 'common/components/IAISlider';
|
import IAISlider from 'common/components/IAISlider';
|
||||||
import { setCanvasCoherenceStrength } from 'features/parameters/store/generationSlice';
|
import { setCanvasCoherenceStrength } from 'features/parameters/store/generationSlice';
|
||||||
import { memo } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const ParamCanvasCoherenceStrength = () => {
|
const ParamCanvasCoherenceStrength = () => {
|
||||||
@ -13,6 +13,16 @@ const ParamCanvasCoherenceStrength = () => {
|
|||||||
);
|
);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleChange = useCallback(
|
||||||
|
(v: number) => {
|
||||||
|
dispatch(setCanvasCoherenceStrength(v));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
const handleReset = useCallback(() => {
|
||||||
|
dispatch(setCanvasCoherenceStrength(0.3));
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IAIInformationalPopover feature="compositingStrength">
|
<IAIInformationalPopover feature="compositingStrength">
|
||||||
<IAISlider
|
<IAISlider
|
||||||
@ -22,15 +32,11 @@ const ParamCanvasCoherenceStrength = () => {
|
|||||||
step={0.01}
|
step={0.01}
|
||||||
sliderNumberInputProps={{ max: 999 }}
|
sliderNumberInputProps={{ max: 999 }}
|
||||||
value={canvasCoherenceStrength}
|
value={canvasCoherenceStrength}
|
||||||
onChange={(v) => {
|
onChange={handleChange}
|
||||||
dispatch(setCanvasCoherenceStrength(v));
|
|
||||||
}}
|
|
||||||
withInput
|
withInput
|
||||||
withSliderMarks
|
withSliderMarks
|
||||||
withReset
|
withReset
|
||||||
handleReset={() => {
|
handleReset={handleReset}
|
||||||
dispatch(setCanvasCoherenceStrength(0.3));
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</IAIInformationalPopover>
|
</IAIInformationalPopover>
|
||||||
);
|
);
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user