mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): add workflow saving/loading (wip)
Adds loading workflows with exhaustive validation via `zod`. There is a load button but no dedicated save/load UI yet. Also need to add versioning to the workflow format itself.
This commit is contained in:
parent
38b2dedc1d
commit
ce7172d78c
@ -114,7 +114,8 @@
|
|||||||
"use-debounce": "^9.0.4",
|
"use-debounce": "^9.0.4",
|
||||||
"use-image": "^1.1.1",
|
"use-image": "^1.1.1",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"zod": "^3.22.2"
|
"zod": "^3.22.2",
|
||||||
|
"zod-validation-error": "^1.5.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@chakra-ui/cli": "^2.4.0",
|
"@chakra-ui/cli": "^2.4.0",
|
||||||
|
@ -32,7 +32,7 @@ interface Props {
|
|||||||
const App = ({ config = DEFAULT_CONFIG, headerComponent }: Props) => {
|
const App = ({ config = DEFAULT_CONFIG, headerComponent }: Props) => {
|
||||||
const language = useAppSelector(languageSelector);
|
const language = useAppSelector(languageSelector);
|
||||||
|
|
||||||
const logger = useLogger();
|
const logger = useLogger('system');
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const handleReset = useCallback(() => {
|
const handleReset = useCallback(() => {
|
||||||
localStorage.clear();
|
localStorage.clear();
|
||||||
@ -46,7 +46,7 @@ const App = ({ config = DEFAULT_CONFIG, headerComponent }: Props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (size(config)) {
|
if (size(config)) {
|
||||||
logger.info({ namespace: 'App', config }, 'Received config');
|
logger.info({ config }, 'Received config');
|
||||||
dispatch(configChanged(config));
|
dispatch(configChanged(config));
|
||||||
}
|
}
|
||||||
}, [dispatch, config, logger]);
|
}, [dispatch, config, logger]);
|
||||||
|
@ -9,7 +9,7 @@ export const log = Roarr.child(BASE_CONTEXT);
|
|||||||
|
|
||||||
export const $logger = atom<Logger>(Roarr.child(BASE_CONTEXT));
|
export const $logger = atom<Logger>(Roarr.child(BASE_CONTEXT));
|
||||||
|
|
||||||
type LoggerNamespace =
|
export type LoggerNamespace =
|
||||||
| 'images'
|
| 'images'
|
||||||
| 'models'
|
| 'models'
|
||||||
| 'config'
|
| 'config'
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
import { useStore } from '@nanostores/react';
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { createLogWriter } from '@roarr/browser-log-writer';
|
import { createLogWriter } from '@roarr/browser-log-writer';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||||
import { isEqual } from 'lodash-es';
|
import { isEqual } from 'lodash-es';
|
||||||
import { useEffect } from 'react';
|
import { useEffect, useMemo } from 'react';
|
||||||
import { ROARR, Roarr } from 'roarr';
|
import { ROARR, Roarr } from 'roarr';
|
||||||
import { $logger, BASE_CONTEXT, LOG_LEVEL_MAP } from './logger';
|
import {
|
||||||
|
$logger,
|
||||||
|
BASE_CONTEXT,
|
||||||
|
LOG_LEVEL_MAP,
|
||||||
|
LoggerNamespace,
|
||||||
|
logger,
|
||||||
|
} from './logger';
|
||||||
|
|
||||||
const selector = createSelector(
|
const selector = createSelector(
|
||||||
systemSelector,
|
systemSelector,
|
||||||
@ -25,7 +30,7 @@ const selector = createSelector(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const useLogger = () => {
|
export const useLogger = (namespace: LoggerNamespace) => {
|
||||||
const { consoleLogLevel, shouldLogToConsole } = useAppSelector(selector);
|
const { consoleLogLevel, shouldLogToConsole } = useAppSelector(selector);
|
||||||
|
|
||||||
// The provided Roarr browser log writer uses localStorage to config logging to console
|
// The provided Roarr browser log writer uses localStorage to config logging to console
|
||||||
@ -57,7 +62,7 @@ export const useLogger = () => {
|
|||||||
$logger.set(Roarr.child(newContext));
|
$logger.set(Roarr.child(newContext));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const logger = useStore($logger);
|
const log = useMemo(() => logger(namespace), [namespace]);
|
||||||
|
|
||||||
return logger;
|
return log;
|
||||||
};
|
};
|
||||||
|
@ -5,6 +5,7 @@ import { modelsApi } from 'services/api/endpoints/models';
|
|||||||
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
|
import { receivedOpenAPISchema } from 'services/api/thunks/schema';
|
||||||
import { appSocketConnected, socketConnected } from 'services/events/actions';
|
import { appSocketConnected, socketConnected } from 'services/events/actions';
|
||||||
import { startAppListening } from '../..';
|
import { startAppListening } from '../..';
|
||||||
|
import { size } from 'lodash-es';
|
||||||
|
|
||||||
export const addSocketConnectedEventListener = () => {
|
export const addSocketConnectedEventListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
@ -18,7 +19,7 @@ export const addSocketConnectedEventListener = () => {
|
|||||||
|
|
||||||
const { disabledTabs } = config;
|
const { disabledTabs } = config;
|
||||||
|
|
||||||
if (!nodes.schema && !disabledTabs.includes('nodes')) {
|
if (!size(nodes.nodeTemplates) && !disabledTabs.includes('nodes')) {
|
||||||
dispatch(receivedOpenAPISchema());
|
dispatch(receivedOpenAPISchema());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
export const colorTokenToCssVar = (colorToken: string) =>
|
||||||
|
`var(--invokeai-colors-${colorToken.split('.').join('-')}`;
|
@ -8,10 +8,12 @@ type Props = {
|
|||||||
label: string;
|
label: string;
|
||||||
data: object | string;
|
data: object | string;
|
||||||
fileName?: string;
|
fileName?: string;
|
||||||
|
withDownload?: boolean;
|
||||||
|
withCopy?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DataViewer = (props: Props) => {
|
const DataViewer = (props: Props) => {
|
||||||
const { label, data, fileName } = props;
|
const { label, data, fileName, withDownload = true, withCopy = true } = props;
|
||||||
const dataString = useMemo(
|
const dataString = useMemo(
|
||||||
() => (isString(data) ? data : JSON.stringify(data, null, 2)),
|
() => (isString(data) ? data : JSON.stringify(data, null, 2)),
|
||||||
[data]
|
[data]
|
||||||
@ -70,24 +72,28 @@ const DataViewer = (props: Props) => {
|
|||||||
</OverlayScrollbarsComponent>
|
</OverlayScrollbarsComponent>
|
||||||
</Box>
|
</Box>
|
||||||
<Flex sx={{ position: 'absolute', top: 0, insetInlineEnd: 0, p: 2 }}>
|
<Flex sx={{ position: 'absolute', top: 0, insetInlineEnd: 0, p: 2 }}>
|
||||||
<Tooltip label={`Save ${label} JSON`}>
|
{withDownload && (
|
||||||
<IconButton
|
<Tooltip label={`Save ${label} JSON`}>
|
||||||
aria-label={`Save ${label} JSON`}
|
<IconButton
|
||||||
icon={<FaSave />}
|
aria-label={`Save ${label} JSON`}
|
||||||
variant="ghost"
|
icon={<FaSave />}
|
||||||
opacity={0.7}
|
variant="ghost"
|
||||||
onClick={handleSave}
|
opacity={0.7}
|
||||||
/>
|
onClick={handleSave}
|
||||||
</Tooltip>
|
/>
|
||||||
<Tooltip label={`Copy ${label} JSON`}>
|
</Tooltip>
|
||||||
<IconButton
|
)}
|
||||||
aria-label={`Copy ${label} JSON`}
|
{withCopy && (
|
||||||
icon={<FaCopy />}
|
<Tooltip label={`Copy ${label} JSON`}>
|
||||||
variant="ghost"
|
<IconButton
|
||||||
opacity={0.7}
|
aria-label={`Copy ${label} JSON`}
|
||||||
onClick={handleCopy}
|
icon={<FaCopy />}
|
||||||
/>
|
variant="ghost"
|
||||||
</Tooltip>
|
opacity={0.7}
|
||||||
|
onClick={handleCopy}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { ConnectionLineComponentProps, getBezierPath } from 'reactflow';
|
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
|
||||||
import { FIELDS, colorTokenToCssVar } from '../../../types/constants';
|
import { FIELDS } from 'features/nodes/types/constants';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
import { ConnectionLineComponentProps, getBezierPath } from 'reactflow';
|
||||||
|
|
||||||
const selector = createSelector(stateSelector, ({ nodes }) => {
|
const selector = createSelector(stateSelector, ({ nodes }) => {
|
||||||
const { shouldAnimateEdges, currentConnectionFieldType, shouldColorEdges } =
|
const { shouldAnimateEdges, currentConnectionFieldType, shouldColorEdges } =
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||||
import { FIELDS, colorTokenToCssVar } from 'features/nodes/types/constants';
|
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
|
||||||
|
import { FIELDS } from 'features/nodes/types/constants';
|
||||||
import { isInvocationNode } from 'features/nodes/types/types';
|
import { isInvocationNode } from 'features/nodes/types/types';
|
||||||
|
|
||||||
export const makeEdgeSelector = (
|
export const makeEdgeSelector = (
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { Tooltip } from '@chakra-ui/react';
|
import { Tooltip } from '@chakra-ui/react';
|
||||||
import { CSSProperties, memo, useMemo } from 'react';
|
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
|
||||||
import { Handle, HandleType, Position } from 'reactflow';
|
|
||||||
import {
|
import {
|
||||||
FIELDS,
|
FIELDS,
|
||||||
HANDLE_TOOLTIP_OPEN_DELAY,
|
HANDLE_TOOLTIP_OPEN_DELAY,
|
||||||
colorTokenToCssVar,
|
} from 'features/nodes/types/constants';
|
||||||
} from '../../../../../types/constants';
|
|
||||||
import {
|
import {
|
||||||
InputFieldTemplate,
|
InputFieldTemplate,
|
||||||
OutputFieldTemplate,
|
OutputFieldTemplate,
|
||||||
} from '../../../../../types/types';
|
} from 'features/nodes/types/types';
|
||||||
|
import { CSSProperties, memo, useMemo } from 'react';
|
||||||
|
import { Handle, HandleType, Position } from 'reactflow';
|
||||||
|
|
||||||
export const handleBaseStyles: CSSProperties = {
|
export const handleBaseStyles: CSSProperties = {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
import { FileButton } from '@mantine/core';
|
||||||
|
import IAIIconButton from 'common/components/IAIIconButton';
|
||||||
|
import { useLoadWorkflowFromFile } from 'features/nodes/hooks/useLoadWorkflowFromFile';
|
||||||
|
import { memo, useRef } from 'react';
|
||||||
|
import { FaUpload } from 'react-icons/fa';
|
||||||
|
|
||||||
|
const LoadWorkflowButton = () => {
|
||||||
|
const resetRef = useRef<() => void>(null);
|
||||||
|
const loadWorkflowFromFile = useLoadWorkflowFromFile();
|
||||||
|
return (
|
||||||
|
<FileButton
|
||||||
|
resetRef={resetRef}
|
||||||
|
accept="application/json"
|
||||||
|
onChange={loadWorkflowFromFile}
|
||||||
|
>
|
||||||
|
{(props) => (
|
||||||
|
<IAIIconButton
|
||||||
|
icon={<FaUpload />}
|
||||||
|
tooltip="Load Workflow"
|
||||||
|
aria-label="Load Workflow"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</FileButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(LoadWorkflowButton);
|
@ -6,6 +6,7 @@ import NodeEditorSettings from './NodeEditorSettings';
|
|||||||
import ClearGraphButton from './ClearGraphButton';
|
import ClearGraphButton from './ClearGraphButton';
|
||||||
import NodeInvokeButton from './NodeInvokeButton';
|
import NodeInvokeButton from './NodeInvokeButton';
|
||||||
import ReloadSchemaButton from './ReloadSchemaButton';
|
import ReloadSchemaButton from './ReloadSchemaButton';
|
||||||
|
import LoadWorkflowButton from './LoadWorkflowButton';
|
||||||
|
|
||||||
const TopCenterPanel = () => {
|
const TopCenterPanel = () => {
|
||||||
return (
|
return (
|
||||||
@ -16,6 +17,7 @@ const TopCenterPanel = () => {
|
|||||||
<ReloadSchemaButton />
|
<ReloadSchemaButton />
|
||||||
<ClearGraphButton />
|
<ClearGraphButton />
|
||||||
<NodeEditorSettings />
|
<NodeEditorSettings />
|
||||||
|
<LoadWorkflowButton />
|
||||||
</HStack>
|
</HStack>
|
||||||
</Panel>
|
</Panel>
|
||||||
);
|
);
|
||||||
|
@ -31,7 +31,7 @@ const WorkflowJSONTab = () => {
|
|||||||
h: 'full',
|
h: 'full',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DataViewer data={workflow} label="Workflow" fileName={workflow.name} />
|
<DataViewer data={workflow} label="Workflow" />
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,107 @@
|
|||||||
|
import { ListItem, Text, UnorderedList } from '@chakra-ui/react';
|
||||||
|
import { useLogger } from 'app/logging/useLogger';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import { parseify } from 'common/util/serialize';
|
||||||
|
import { workflowLoaded } from 'features/nodes/store/nodesSlice';
|
||||||
|
import { zWorkflow } from 'features/nodes/types/types';
|
||||||
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
|
import { makeToast } from 'features/system/util/makeToast';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { flushSync } from 'react-dom';
|
||||||
|
import { useReactFlow } from 'reactflow';
|
||||||
|
import { ZodError } from 'zod';
|
||||||
|
import { fromZodError, fromZodIssue } from 'zod-validation-error';
|
||||||
|
|
||||||
|
export const useLoadWorkflowFromFile = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const { fitView } = useReactFlow();
|
||||||
|
const logger = useLogger('nodes');
|
||||||
|
const loadWorkflowFromFile = useCallback(
|
||||||
|
(file: File | null) => {
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = async () => {
|
||||||
|
const rawJSON = reader.result;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsedJSON = JSON.parse(String(rawJSON));
|
||||||
|
const result = zWorkflow.safeParse(parsedJSON);
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
const message = fromZodError(result.error, {
|
||||||
|
prefix: 'Workflow Validation Error',
|
||||||
|
}).toString();
|
||||||
|
logger.error({ error: parseify(result.error) }, message);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
addToast(
|
||||||
|
makeToast({
|
||||||
|
title: 'Unable to Validate Workflow',
|
||||||
|
description: (
|
||||||
|
<WorkflowValidationErrorContent error={result.error} />
|
||||||
|
),
|
||||||
|
status: 'error',
|
||||||
|
duration: 5000,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(workflowLoaded(result.data));
|
||||||
|
flushSync(fitView);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
addToast(
|
||||||
|
makeToast({
|
||||||
|
title: 'Workflow Loaded',
|
||||||
|
status: 'success',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
reader.abort();
|
||||||
|
} catch (error) {
|
||||||
|
// file reader error
|
||||||
|
if (error) {
|
||||||
|
dispatch(
|
||||||
|
addToast(
|
||||||
|
makeToast({
|
||||||
|
title: 'Unable to Load Workflow',
|
||||||
|
status: 'error',
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsText(file);
|
||||||
|
},
|
||||||
|
[dispatch, fitView, logger]
|
||||||
|
);
|
||||||
|
|
||||||
|
return loadWorkflowFromFile;
|
||||||
|
};
|
||||||
|
|
||||||
|
const WorkflowValidationErrorContent = memo((props: { error: ZodError }) => {
|
||||||
|
if (props.error.issues[0]) {
|
||||||
|
return (
|
||||||
|
<Text>
|
||||||
|
{fromZodIssue(props.error.issues[0], { prefix: null }).toString()}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<UnorderedList>
|
||||||
|
{props.error.issues.map((issue, i) => (
|
||||||
|
<ListItem key={i}>
|
||||||
|
<Text>{fromZodIssue(issue, { prefix: null }).toString()}</Text>
|
||||||
|
</ListItem>
|
||||||
|
))}
|
||||||
|
</UnorderedList>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
WorkflowValidationErrorContent.displayName = 'WorkflowValidationErrorContent';
|
@ -4,7 +4,6 @@ import { NodesState } from './types';
|
|||||||
* Nodes slice persist denylist
|
* Nodes slice persist denylist
|
||||||
*/
|
*/
|
||||||
export const nodesPersistDenylist: (keyof NodesState)[] = [
|
export const nodesPersistDenylist: (keyof NodesState)[] = [
|
||||||
'schema',
|
|
||||||
'nodeTemplates',
|
'nodeTemplates',
|
||||||
'connectionStartParams',
|
'connectionStartParams',
|
||||||
'currentConnectionFieldType',
|
'currentConnectionFieldType',
|
||||||
|
@ -56,6 +56,8 @@ import {
|
|||||||
import { NodesState } from './types';
|
import { NodesState } from './types';
|
||||||
import { findUnoccupiedPosition } from './util/findUnoccupiedPosition';
|
import { findUnoccupiedPosition } from './util/findUnoccupiedPosition';
|
||||||
|
|
||||||
|
export const WORKFLOW_FORMAT_VERSION = '1.0.0';
|
||||||
|
|
||||||
const initialNodeExecutionState: Omit<NodeExecutionState, 'nodeId'> = {
|
const initialNodeExecutionState: Omit<NodeExecutionState, 'nodeId'> = {
|
||||||
status: NodeStatus.PENDING,
|
status: NodeStatus.PENDING,
|
||||||
error: null,
|
error: null,
|
||||||
@ -64,10 +66,23 @@ const initialNodeExecutionState: Omit<NodeExecutionState, 'nodeId'> = {
|
|||||||
outputs: [],
|
outputs: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const initialWorkflow = {
|
||||||
|
meta: {
|
||||||
|
version: WORKFLOW_FORMAT_VERSION,
|
||||||
|
},
|
||||||
|
name: '',
|
||||||
|
author: '',
|
||||||
|
description: '',
|
||||||
|
notes: '',
|
||||||
|
tags: '',
|
||||||
|
contact: '',
|
||||||
|
version: '',
|
||||||
|
exposedFields: [],
|
||||||
|
};
|
||||||
|
|
||||||
export const initialNodesState: NodesState = {
|
export const initialNodesState: NodesState = {
|
||||||
nodes: [],
|
nodes: [],
|
||||||
edges: [],
|
edges: [],
|
||||||
schema: null,
|
|
||||||
nodeTemplates: {},
|
nodeTemplates: {},
|
||||||
isReady: false,
|
isReady: false,
|
||||||
connectionStartParams: null,
|
connectionStartParams: null,
|
||||||
@ -82,16 +97,7 @@ export const initialNodesState: NodesState = {
|
|||||||
nodeOpacity: 1,
|
nodeOpacity: 1,
|
||||||
selectedNodes: [],
|
selectedNodes: [],
|
||||||
selectedEdges: [],
|
selectedEdges: [],
|
||||||
workflow: {
|
workflow: initialWorkflow,
|
||||||
name: '',
|
|
||||||
author: '',
|
|
||||||
description: '',
|
|
||||||
notes: '',
|
|
||||||
tags: '',
|
|
||||||
contact: '',
|
|
||||||
version: '',
|
|
||||||
exposedFields: [],
|
|
||||||
},
|
|
||||||
nodeExecutionStates: {},
|
nodeExecutionStates: {},
|
||||||
viewport: { x: 0, y: 0, zoom: 1 },
|
viewport: { x: 0, y: 0, zoom: 1 },
|
||||||
mouseOverField: null,
|
mouseOverField: null,
|
||||||
@ -570,15 +576,6 @@ const nodesSlice = createSlice({
|
|||||||
nodeOpacityChanged: (state, action: PayloadAction<number>) => {
|
nodeOpacityChanged: (state, action: PayloadAction<number>) => {
|
||||||
state.nodeOpacity = action.payload;
|
state.nodeOpacity = action.payload;
|
||||||
},
|
},
|
||||||
loadFileNodes: (
|
|
||||||
state,
|
|
||||||
action: PayloadAction<Node<InvocationNodeData>[]>
|
|
||||||
) => {
|
|
||||||
state.nodes = action.payload;
|
|
||||||
},
|
|
||||||
loadFileEdges: (state, action: PayloadAction<Edge[]>) => {
|
|
||||||
state.edges = action.payload;
|
|
||||||
},
|
|
||||||
workflowNameChanged: (state, action: PayloadAction<string>) => {
|
workflowNameChanged: (state, action: PayloadAction<string>) => {
|
||||||
state.workflow.name = action.payload;
|
state.workflow.name = action.payload;
|
||||||
},
|
},
|
||||||
@ -616,6 +613,9 @@ const nodesSlice = createSlice({
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
workflowReset: (state) => {
|
||||||
|
state.workflow = cloneDeep(initialWorkflow);
|
||||||
|
},
|
||||||
viewportChanged: (state, action: PayloadAction<Viewport>) => {
|
viewportChanged: (state, action: PayloadAction<Viewport>) => {
|
||||||
state.viewport = action.payload;
|
state.viewport = action.payload;
|
||||||
},
|
},
|
||||||
@ -726,9 +726,6 @@ const nodesSlice = createSlice({
|
|||||||
builder.addCase(receivedOpenAPISchema.pending, (state) => {
|
builder.addCase(receivedOpenAPISchema.pending, (state) => {
|
||||||
state.isReady = false;
|
state.isReady = false;
|
||||||
});
|
});
|
||||||
builder.addCase(receivedOpenAPISchema.fulfilled, (state, action) => {
|
|
||||||
state.schema = action.payload;
|
|
||||||
});
|
|
||||||
builder.addCase(appSocketInvocationStarted, (state, action) => {
|
builder.addCase(appSocketInvocationStarted, (state, action) => {
|
||||||
const { source_node_id } = action.payload.data;
|
const { source_node_id } = action.payload.data;
|
||||||
const node = state.nodeExecutionStates[source_node_id];
|
const node = state.nodeExecutionStates[source_node_id];
|
||||||
@ -792,8 +789,6 @@ export const {
|
|||||||
nodeTemplatesBuilt,
|
nodeTemplatesBuilt,
|
||||||
nodeEditorReset,
|
nodeEditorReset,
|
||||||
imageCollectionFieldValueChanged,
|
imageCollectionFieldValueChanged,
|
||||||
loadFileNodes,
|
|
||||||
loadFileEdges,
|
|
||||||
fieldStringValueChanged,
|
fieldStringValueChanged,
|
||||||
fieldNumberValueChanged,
|
fieldNumberValueChanged,
|
||||||
fieldBooleanValueChanged,
|
fieldBooleanValueChanged,
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { OpenAPIV3 } from 'openapi-types';
|
|
||||||
import { Edge, Node, OnConnectStartParams, Viewport } from 'reactflow';
|
import { Edge, Node, OnConnectStartParams, Viewport } from 'reactflow';
|
||||||
import {
|
import {
|
||||||
FieldIdentifier,
|
FieldIdentifier,
|
||||||
@ -13,7 +12,6 @@ import {
|
|||||||
export type NodesState = {
|
export type NodesState = {
|
||||||
nodes: Node<NodeData>[];
|
nodes: Node<NodeData>[];
|
||||||
edges: Edge<InvocationEdgeExtra>[];
|
edges: Edge<InvocationEdgeExtra>[];
|
||||||
schema: OpenAPIV3.Document | null;
|
|
||||||
nodeTemplates: Record<string, InvocationTemplate>;
|
nodeTemplates: Record<string, InvocationTemplate>;
|
||||||
connectionStartParams: OnConnectStartParams | null;
|
connectionStartParams: OnConnectStartParams | null;
|
||||||
currentConnectionFieldType: FieldType | null;
|
currentConnectionFieldType: FieldType | null;
|
||||||
|
@ -23,9 +23,6 @@ export const COLLECTION_TYPES: FieldType[] = [
|
|||||||
'ImageCollection',
|
'ImageCollection',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const colorTokenToCssVar = (colorToken: string) =>
|
|
||||||
`var(--invokeai-colors-${colorToken.split('.').join('-')}`;
|
|
||||||
|
|
||||||
export const FIELDS: Record<FieldType, FieldUIConfig> = {
|
export const FIELDS: Record<FieldType, FieldUIConfig> = {
|
||||||
integer: {
|
integer: {
|
||||||
title: 'Integer',
|
title: 'Integer',
|
||||||
|
@ -1,23 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
ControlNetModelParam,
|
|
||||||
LoRAModelParam,
|
|
||||||
MainModelParam,
|
|
||||||
OnnxModelParam,
|
|
||||||
SchedulerParam,
|
SchedulerParam,
|
||||||
VaeModelParam,
|
zBaseModel,
|
||||||
|
zMainOrOnnxModel,
|
||||||
|
zScheduler,
|
||||||
} from 'features/parameters/types/parameterSchemas';
|
} from 'features/parameters/types/parameterSchemas';
|
||||||
import { OpenAPIV3 } from 'openapi-types';
|
import { OpenAPIV3 } from 'openapi-types';
|
||||||
import { RgbaColor } from 'react-colorful';
|
import { RgbaColor } from 'react-colorful';
|
||||||
import { Edge, Node } from 'reactflow';
|
import { Node } from 'reactflow';
|
||||||
import { components } from 'services/api/schema';
|
import { Graph, ImageDTO, _InputField, _OutputField } from 'services/api/types';
|
||||||
import {
|
|
||||||
Graph,
|
|
||||||
GraphExecutionState,
|
|
||||||
ImageDTO,
|
|
||||||
ImageField,
|
|
||||||
_InputField,
|
|
||||||
_OutputField,
|
|
||||||
} from 'services/api/types';
|
|
||||||
import {
|
import {
|
||||||
AnyInvocationType,
|
AnyInvocationType,
|
||||||
AnyResult,
|
AnyResult,
|
||||||
@ -118,40 +108,6 @@ export type FieldType = z.infer<typeof zFieldType>;
|
|||||||
export const isFieldType = (value: unknown): value is FieldType =>
|
export const isFieldType = (value: unknown): value is FieldType =>
|
||||||
zFieldType.safeParse(value).success;
|
zFieldType.safeParse(value).success;
|
||||||
|
|
||||||
/**
|
|
||||||
* An input field is persisted across reloads as part of the user's local state.
|
|
||||||
*
|
|
||||||
* An input field has three properties:
|
|
||||||
* - `id` a unique identifier
|
|
||||||
* - `name` the name of the field, which comes from the python dataclass
|
|
||||||
* - `value` the field's value
|
|
||||||
*/
|
|
||||||
export type InputFieldValue =
|
|
||||||
| IntegerInputFieldValue
|
|
||||||
| SeedInputFieldValue
|
|
||||||
| FloatInputFieldValue
|
|
||||||
| StringInputFieldValue
|
|
||||||
| BooleanInputFieldValue
|
|
||||||
| ImageInputFieldValue
|
|
||||||
| LatentsInputFieldValue
|
|
||||||
| ConditioningInputFieldValue
|
|
||||||
| UNetInputFieldValue
|
|
||||||
| ClipInputFieldValue
|
|
||||||
| VaeInputFieldValue
|
|
||||||
| ControlInputFieldValue
|
|
||||||
| EnumInputFieldValue
|
|
||||||
| MainModelInputFieldValue
|
|
||||||
| SDXLMainModelInputFieldValue
|
|
||||||
| SDXLRefinerModelInputFieldValue
|
|
||||||
| VaeModelInputFieldValue
|
|
||||||
| LoRAModelInputFieldValue
|
|
||||||
| ControlNetModelInputFieldValue
|
|
||||||
| CollectionInputFieldValue
|
|
||||||
| CollectionItemInputFieldValue
|
|
||||||
| ColorInputFieldValue
|
|
||||||
| ImageCollectionInputFieldValue
|
|
||||||
| SchedulerInputFieldValue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An input field template is generated on each page load from the OpenAPI schema.
|
* An input field template is generated on each page load from the OpenAPI schema.
|
||||||
*
|
*
|
||||||
@ -183,6 +139,19 @@ export type InputFieldTemplate =
|
|||||||
| ImageCollectionInputFieldTemplate
|
| ImageCollectionInputFieldTemplate
|
||||||
| SchedulerInputFieldTemplate;
|
| SchedulerInputFieldTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the kind of input(s) this field may have.
|
||||||
|
*/
|
||||||
|
export const zInputKind = z.enum(['connection', 'direct', 'any']);
|
||||||
|
export type InputKind = z.infer<typeof zInputKind>;
|
||||||
|
|
||||||
|
export const zFieldValueBase = z.object({
|
||||||
|
id: z.string().trim().min(1),
|
||||||
|
name: z.string().trim().min(1),
|
||||||
|
type: zFieldType,
|
||||||
|
});
|
||||||
|
export type FieldValueBase = z.infer<typeof zFieldValueBase>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An output field is persisted across as part of the user's local state.
|
* An output field is persisted across as part of the user's local state.
|
||||||
*
|
*
|
||||||
@ -190,7 +159,11 @@ export type InputFieldTemplate =
|
|||||||
* - `id` a unique identifier
|
* - `id` a unique identifier
|
||||||
* - `name` the name of the field, which comes from the python dataclass
|
* - `name` the name of the field, which comes from the python dataclass
|
||||||
*/
|
*/
|
||||||
export type OutputFieldValue = FieldValueBase & { fieldKind: 'output' };
|
|
||||||
|
export const zOutputFieldValue = zFieldValueBase.extend({
|
||||||
|
fieldKind: z.literal('output'),
|
||||||
|
});
|
||||||
|
export type OutputFieldValue = z.infer<typeof zOutputFieldValue>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An output field template is generated on each page load from the OpenAPI schema.
|
* An output field template is generated on each page load from the OpenAPI schema.
|
||||||
@ -205,141 +178,307 @@ export type OutputFieldTemplate = {
|
|||||||
description: string;
|
description: string;
|
||||||
} & _OutputField;
|
} & _OutputField;
|
||||||
|
|
||||||
/**
|
export const zInputFieldValueBase = zFieldValueBase.extend({
|
||||||
* Indicates the kind of input(s) this field may have.
|
fieldKind: z.literal('input'),
|
||||||
*/
|
label: z.string(),
|
||||||
export type InputKind = 'connection' | 'direct' | 'any';
|
});
|
||||||
|
export type InputFieldValueBase = z.infer<typeof zInputFieldValueBase>;
|
||||||
|
|
||||||
export type FieldValueBase = {
|
export const zModelIdentifier = z.object({
|
||||||
id: string;
|
model_name: z.string().trim().min(1),
|
||||||
name: string;
|
base_model: zBaseModel,
|
||||||
type: FieldType;
|
});
|
||||||
};
|
|
||||||
|
|
||||||
export type InputFieldValueBase = FieldValueBase & {
|
export const zImageField = z.object({
|
||||||
fieldKind: 'input';
|
image_name: z.string().trim().min(1),
|
||||||
label: string;
|
});
|
||||||
};
|
export type ImageField = z.infer<typeof zImageField>;
|
||||||
|
|
||||||
export type IntegerInputFieldValue = InputFieldValueBase & {
|
export const zLatentsField = z.object({
|
||||||
type: 'integer';
|
latents_name: z.string().trim().min(1),
|
||||||
value?: number;
|
seed: z.number().int().optional(),
|
||||||
};
|
});
|
||||||
|
export type LatentsField = z.infer<typeof zLatentsField>;
|
||||||
|
|
||||||
export type FloatInputFieldValue = InputFieldValueBase & {
|
export const zConditioningField = z.object({
|
||||||
type: 'float';
|
conditioning_name: z.string().trim().min(1),
|
||||||
value?: number;
|
});
|
||||||
};
|
export type ConditioningField = z.infer<typeof zConditioningField>;
|
||||||
|
|
||||||
export type SeedInputFieldValue = InputFieldValueBase & {
|
export const zIntegerInputFieldValue = zInputFieldValueBase.extend({
|
||||||
type: 'Seed';
|
type: z.literal('integer'),
|
||||||
value?: number;
|
value: z.number().optional(),
|
||||||
};
|
});
|
||||||
|
export type IntegerInputFieldValue = z.infer<typeof zIntegerInputFieldValue>;
|
||||||
|
|
||||||
export type StringInputFieldValue = InputFieldValueBase & {
|
export const zFloatInputFieldValue = zInputFieldValueBase.extend({
|
||||||
type: 'string';
|
type: z.literal('float'),
|
||||||
value?: string;
|
value: z.number().optional(),
|
||||||
};
|
});
|
||||||
|
export type FloatInputFieldValue = z.infer<typeof zFloatInputFieldValue>;
|
||||||
|
|
||||||
export type BooleanInputFieldValue = InputFieldValueBase & {
|
export const zStringInputFieldValue = zInputFieldValueBase.extend({
|
||||||
type: 'boolean';
|
type: z.literal('string'),
|
||||||
value?: boolean;
|
value: z.string().optional(),
|
||||||
};
|
});
|
||||||
|
export type StringInputFieldValue = z.infer<typeof zStringInputFieldValue>;
|
||||||
|
|
||||||
export type EnumInputFieldValue = InputFieldValueBase & {
|
export const zBooleanInputFieldValue = zInputFieldValueBase.extend({
|
||||||
type: 'enum';
|
type: z.literal('boolean'),
|
||||||
value?: number | string;
|
value: z.boolean().optional(),
|
||||||
};
|
});
|
||||||
|
export type BooleanInputFieldValue = z.infer<typeof zBooleanInputFieldValue>;
|
||||||
|
|
||||||
export type LatentsInputFieldValue = InputFieldValueBase & {
|
export const zEnumInputFieldValue = zInputFieldValueBase.extend({
|
||||||
type: 'LatentsField';
|
type: z.literal('enum'),
|
||||||
value?: undefined;
|
value: z.union([z.string(), z.number()]).optional(),
|
||||||
};
|
});
|
||||||
|
export type EnumInputFieldValue = z.infer<typeof zEnumInputFieldValue>;
|
||||||
|
|
||||||
export type ConditioningInputFieldValue = InputFieldValueBase & {
|
export const zLatentsInputFieldValue = zInputFieldValueBase.extend({
|
||||||
type: 'ConditioningField';
|
type: z.literal('LatentsField'),
|
||||||
value?: string;
|
value: zLatentsField.optional(),
|
||||||
};
|
});
|
||||||
|
export type LatentsInputFieldValue = z.infer<typeof zLatentsInputFieldValue>;
|
||||||
|
|
||||||
export type ControlInputFieldValue = InputFieldValueBase & {
|
export const zConditioningInputFieldValue = zInputFieldValueBase.extend({
|
||||||
type: 'ControlField';
|
type: z.literal('ConditioningField'),
|
||||||
value?: undefined;
|
value: zConditioningField.optional(),
|
||||||
};
|
});
|
||||||
|
export type ConditioningInputFieldValue = z.infer<
|
||||||
|
typeof zConditioningInputFieldValue
|
||||||
|
>;
|
||||||
|
|
||||||
export type UNetInputFieldValue = InputFieldValueBase & {
|
export const zControlNetModel = zModelIdentifier;
|
||||||
type: 'UNetField';
|
export type ControlNetModel = z.infer<typeof zControlNetModel>;
|
||||||
value?: undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ClipInputFieldValue = InputFieldValueBase & {
|
export const zControlField = zInputFieldValueBase.extend({
|
||||||
type: 'ClipField';
|
image: zImageField,
|
||||||
value?: undefined;
|
control_model: zControlNetModel,
|
||||||
};
|
control_weight: z.union([z.number(), z.array(z.number())]).optional(),
|
||||||
|
begin_step_percent: z.number().optional(),
|
||||||
|
end_step_percent: z.number().optional(),
|
||||||
|
control_mode: z
|
||||||
|
.enum(['balanced', 'more_prompt', 'more_control', 'unbalanced'])
|
||||||
|
.optional(),
|
||||||
|
resize_mode: z
|
||||||
|
.enum(['just_resize', 'crop_resize', 'fill_resize', 'just_resize_simple'])
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
export type ControlField = z.infer<typeof zControlField>;
|
||||||
|
|
||||||
export type VaeInputFieldValue = InputFieldValueBase & {
|
export const zControlInputFieldTemplate = zInputFieldValueBase.extend({
|
||||||
type: 'VaeField';
|
type: z.literal('ControlField'),
|
||||||
value?: undefined;
|
value: zControlField.optional(),
|
||||||
};
|
});
|
||||||
|
export type ControlInputFieldValue = z.infer<typeof zControlInputFieldTemplate>;
|
||||||
|
|
||||||
export type ImageInputFieldValue = InputFieldValueBase & {
|
export const zModelType = z.enum([
|
||||||
type: 'ImageField';
|
'onnx',
|
||||||
value?: ImageField;
|
'main',
|
||||||
};
|
'vae',
|
||||||
|
'lora',
|
||||||
|
'controlnet',
|
||||||
|
'embedding',
|
||||||
|
]);
|
||||||
|
export type ModelType = z.infer<typeof zModelType>;
|
||||||
|
|
||||||
export type ImageCollectionInputFieldValue = InputFieldValueBase & {
|
export const zSubModelType = z.enum([
|
||||||
type: 'ImageCollection';
|
'unet',
|
||||||
value?: ImageField[];
|
'text_encoder',
|
||||||
};
|
'text_encoder_2',
|
||||||
|
'tokenizer',
|
||||||
|
'tokenizer_2',
|
||||||
|
'vae',
|
||||||
|
'vae_decoder',
|
||||||
|
'vae_encoder',
|
||||||
|
'scheduler',
|
||||||
|
'safety_checker',
|
||||||
|
]);
|
||||||
|
export type SubModelType = z.infer<typeof zSubModelType>;
|
||||||
|
|
||||||
export type MainModelInputFieldValue = InputFieldValueBase & {
|
export const zModelInfo = zModelIdentifier.extend({
|
||||||
type: 'MainModelField';
|
model_type: zModelType,
|
||||||
value?: MainModelParam | OnnxModelParam;
|
submodel: zSubModelType.optional(),
|
||||||
};
|
});
|
||||||
|
export type ModelInfo = z.infer<typeof zModelInfo>;
|
||||||
|
|
||||||
export type SDXLMainModelInputFieldValue = InputFieldValueBase & {
|
export const zLoraInfo = zModelInfo.extend({
|
||||||
type: 'SDXLMainModelField';
|
weight: z.number().optional(),
|
||||||
value?: MainModelParam | OnnxModelParam;
|
});
|
||||||
};
|
export type LoraInfo = z.infer<typeof zLoraInfo>;
|
||||||
|
|
||||||
export type SDXLRefinerModelInputFieldValue = InputFieldValueBase & {
|
export const zUNetField = z.object({
|
||||||
type: 'SDXLRefinerModelField';
|
unet: zModelInfo,
|
||||||
value?: MainModelParam | OnnxModelParam;
|
scheduler: zModelInfo,
|
||||||
};
|
loras: z.array(zLoraInfo),
|
||||||
|
});
|
||||||
|
export type UNetField = z.infer<typeof zUNetField>;
|
||||||
|
|
||||||
export type VaeModelInputFieldValue = InputFieldValueBase & {
|
export const zUNetInputFieldValue = zInputFieldValueBase.extend({
|
||||||
type: 'VaeModelField';
|
type: z.literal('UNetField'),
|
||||||
value?: VaeModelParam;
|
value: zUNetField.optional(),
|
||||||
};
|
});
|
||||||
|
export type UNetInputFieldValue = z.infer<typeof zUNetInputFieldValue>;
|
||||||
|
|
||||||
export type LoRAModelInputFieldValue = InputFieldValueBase & {
|
export const zClipField = z.object({
|
||||||
type: 'LoRAModelField';
|
tokenizer: zModelInfo,
|
||||||
value?: LoRAModelParam;
|
text_encoder: zModelInfo,
|
||||||
};
|
skipped_layers: z.number(),
|
||||||
|
loras: z.array(zLoraInfo),
|
||||||
|
});
|
||||||
|
export type ClipField = z.infer<typeof zClipField>;
|
||||||
|
|
||||||
export type ControlNetModelInputFieldValue = InputFieldValueBase & {
|
export const zClipInputFieldValue = zInputFieldValueBase.extend({
|
||||||
type: 'ControlNetModelField';
|
type: z.literal('ClipField'),
|
||||||
value?: ControlNetModelParam;
|
value: zClipField.optional(),
|
||||||
};
|
});
|
||||||
|
export type ClipInputFieldValue = z.infer<typeof zClipInputFieldValue>;
|
||||||
|
|
||||||
export type CollectionInputFieldValue = InputFieldValueBase & {
|
export const zVaeField = z.object({
|
||||||
type: 'Collection';
|
vae: zModelInfo,
|
||||||
value?: (string | number)[];
|
});
|
||||||
};
|
export type VaeField = z.infer<typeof zVaeField>;
|
||||||
|
|
||||||
export type CollectionItemInputFieldValue = InputFieldValueBase & {
|
export const zVaeInputFieldValue = zInputFieldValueBase.extend({
|
||||||
type: 'CollectionItem';
|
type: z.literal('VaeField'),
|
||||||
value?: undefined;
|
value: zVaeField.optional(),
|
||||||
};
|
});
|
||||||
|
export type VaeInputFieldValue = z.infer<typeof zVaeInputFieldValue>;
|
||||||
|
|
||||||
export type ColorInputFieldValue = InputFieldValueBase & {
|
export const zImageInputFieldValue = zInputFieldValueBase.extend({
|
||||||
type: 'ColorField';
|
type: z.literal('ImageField'),
|
||||||
value?: RgbaColor;
|
value: zImageField.optional(),
|
||||||
};
|
});
|
||||||
|
export type ImageInputFieldValue = z.infer<typeof zImageInputFieldValue>;
|
||||||
|
|
||||||
export type SchedulerInputFieldValue = InputFieldValueBase & {
|
export const zImageCollectionInputFieldValue = zInputFieldValueBase.extend({
|
||||||
type: 'Scheduler';
|
type: z.literal('ImageCollection'),
|
||||||
value?: SchedulerParam;
|
value: z.array(zImageField).optional(),
|
||||||
};
|
});
|
||||||
|
export type ImageCollectionInputFieldValue = z.infer<
|
||||||
|
typeof zImageCollectionInputFieldValue
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const zMainModelInputFieldValue = zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('MainModelField'),
|
||||||
|
value: zMainOrOnnxModel.optional(),
|
||||||
|
});
|
||||||
|
export type MainModelInputFieldValue = z.infer<
|
||||||
|
typeof zMainModelInputFieldValue
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const zSDXLMainModelInputFieldValue = zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('SDXLMainModelField'),
|
||||||
|
value: zMainOrOnnxModel.optional(),
|
||||||
|
});
|
||||||
|
export type SDXLMainModelInputFieldValue = z.infer<
|
||||||
|
typeof zSDXLMainModelInputFieldValue
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const zSDXLRefinerModelInputFieldValue = zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('SDXLRefinerModelField'),
|
||||||
|
value: zMainOrOnnxModel.optional(), // TODO: should narrow this down to a refiner model
|
||||||
|
});
|
||||||
|
export type SDXLRefinerModelInputFieldValue = z.infer<
|
||||||
|
typeof zSDXLRefinerModelInputFieldValue
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const zVaeModelField = zModelIdentifier;
|
||||||
|
|
||||||
|
export const zVaeModelInputFieldValue = zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('VaeModelField'),
|
||||||
|
value: zVaeModelField.optional(),
|
||||||
|
});
|
||||||
|
export type VaeModelInputFieldValue = z.infer<typeof zVaeModelInputFieldValue>;
|
||||||
|
|
||||||
|
export const zLoRAModelField = zModelIdentifier;
|
||||||
|
export type LoRAModelField = z.infer<typeof zLoRAModelField>;
|
||||||
|
|
||||||
|
export const zLoRAModelInputFieldValue = zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('LoRAModelField'),
|
||||||
|
value: zLoRAModelField.optional(),
|
||||||
|
});
|
||||||
|
export type LoRAModelInputFieldValue = z.infer<
|
||||||
|
typeof zLoRAModelInputFieldValue
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const zControlNetModelField = zModelIdentifier;
|
||||||
|
export type ControlNetModelField = z.infer<typeof zControlNetModelField>;
|
||||||
|
|
||||||
|
export const zControlNetModelInputFieldValue = zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('ControlNetModelField'),
|
||||||
|
value: zControlNetModelField.optional(),
|
||||||
|
});
|
||||||
|
export type ControlNetModelInputFieldValue = z.infer<
|
||||||
|
typeof zControlNetModelInputFieldValue
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const zCollectionInputFieldValue = zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('Collection'),
|
||||||
|
value: z.array(z.any()).optional(), // TODO: should this field ever have a value?
|
||||||
|
});
|
||||||
|
export type CollectionInputFieldValue = z.infer<
|
||||||
|
typeof zCollectionInputFieldValue
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const zCollectionItemInputFieldValue = zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('CollectionItem'),
|
||||||
|
value: z.any().optional(), // TODO: should this field ever have a value?
|
||||||
|
});
|
||||||
|
export type CollectionItemInputFieldValue = z.infer<
|
||||||
|
typeof zCollectionItemInputFieldValue
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const zColorField = z.object({
|
||||||
|
r: z.number().int().min(0).max(255),
|
||||||
|
g: z.number().int().min(0).max(255),
|
||||||
|
b: z.number().int().min(0).max(255),
|
||||||
|
a: z.number().int().min(0).max(255),
|
||||||
|
});
|
||||||
|
export type ColorField = z.infer<typeof zColorField>;
|
||||||
|
|
||||||
|
export const zColorInputFieldValue = zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('ColorField'),
|
||||||
|
value: zColorField.optional(),
|
||||||
|
});
|
||||||
|
export type ColorInputFieldValue = z.infer<typeof zColorInputFieldValue>;
|
||||||
|
|
||||||
|
export const zSchedulerInputFieldValue = zInputFieldValueBase.extend({
|
||||||
|
type: z.literal('Scheduler'),
|
||||||
|
value: zScheduler.optional(),
|
||||||
|
});
|
||||||
|
export type SchedulerInputFieldValue = z.infer<
|
||||||
|
typeof zSchedulerInputFieldValue
|
||||||
|
>;
|
||||||
|
|
||||||
|
export const zInputFieldValue = z.discriminatedUnion('type', [
|
||||||
|
zIntegerInputFieldValue,
|
||||||
|
zFloatInputFieldValue,
|
||||||
|
zStringInputFieldValue,
|
||||||
|
zBooleanInputFieldValue,
|
||||||
|
zImageInputFieldValue,
|
||||||
|
zLatentsInputFieldValue,
|
||||||
|
zConditioningInputFieldValue,
|
||||||
|
zUNetInputFieldValue,
|
||||||
|
zClipInputFieldValue,
|
||||||
|
zVaeInputFieldValue,
|
||||||
|
zControlInputFieldTemplate,
|
||||||
|
zEnumInputFieldValue,
|
||||||
|
zMainModelInputFieldValue,
|
||||||
|
zSDXLMainModelInputFieldValue,
|
||||||
|
zSDXLRefinerModelInputFieldValue,
|
||||||
|
zVaeModelInputFieldValue,
|
||||||
|
zLoRAModelInputFieldValue,
|
||||||
|
zControlNetModelInputFieldValue,
|
||||||
|
zCollectionInputFieldValue,
|
||||||
|
zCollectionItemInputFieldValue,
|
||||||
|
zColorInputFieldValue,
|
||||||
|
zImageCollectionInputFieldValue,
|
||||||
|
zSchedulerInputFieldValue,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type InputFieldValue = z.infer<typeof zInputFieldValue>;
|
||||||
|
|
||||||
export type InputFieldTemplateBase = {
|
export type InputFieldTemplateBase = {
|
||||||
name: string;
|
name: string;
|
||||||
@ -576,24 +715,26 @@ export const isInvocationFieldSchema = (
|
|||||||
|
|
||||||
export type InvocationEdgeExtra = { type: 'default' | 'collapsed' };
|
export type InvocationEdgeExtra = { type: 'default' | 'collapsed' };
|
||||||
|
|
||||||
export const zInputFieldValue = z.object({
|
|
||||||
id: z.string().trim().min(1),
|
|
||||||
name: z.string().trim().min(1),
|
|
||||||
type: zFieldType,
|
|
||||||
label: z.string(),
|
|
||||||
isExposed: z.boolean(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const zInvocationNodeData = z.object({
|
export const zInvocationNodeData = z.object({
|
||||||
id: z.string().trim().min(1),
|
id: z.string().trim().min(1),
|
||||||
|
// no easy way to build this dynamically, and we don't want to anyways, because this will be used
|
||||||
|
// to validate incoming workflows, and we want to allow community nodes.
|
||||||
type: z.string().trim().min(1),
|
type: z.string().trim().min(1),
|
||||||
inputs: z.record(z.any()),
|
inputs: z.record(zInputFieldValue),
|
||||||
outputs: z.record(z.any()),
|
outputs: z.record(zOutputFieldValue),
|
||||||
label: z.string(),
|
label: z.string(),
|
||||||
isOpen: z.boolean(),
|
isOpen: z.boolean(),
|
||||||
notes: z.string(),
|
notes: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Massage this to get better type safety while developing
|
||||||
|
export type InvocationNodeData = Omit<
|
||||||
|
z.infer<typeof zInvocationNodeData>,
|
||||||
|
'type'
|
||||||
|
> & {
|
||||||
|
type: AnyInvocationType;
|
||||||
|
};
|
||||||
|
|
||||||
export const zNotesNodeData = z.object({
|
export const zNotesNodeData = z.object({
|
||||||
id: z.string().trim().min(1),
|
id: z.string().trim().min(1),
|
||||||
type: z.literal('notes'),
|
type: z.literal('notes'),
|
||||||
@ -602,6 +743,69 @@ export const zNotesNodeData = z.object({
|
|||||||
notes: z.string(),
|
notes: z.string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export type NotesNodeData = z.infer<typeof zNotesNodeData>;
|
||||||
|
|
||||||
|
export const zWorkflowInvocationNode = z.object({
|
||||||
|
id: z.string().trim().min(1),
|
||||||
|
type: z.literal('invocation'),
|
||||||
|
data: zInvocationNodeData,
|
||||||
|
width: z.number().gt(0),
|
||||||
|
height: z.number().gt(0),
|
||||||
|
position: z.object({
|
||||||
|
x: z.number(),
|
||||||
|
y: z.number(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const zWorkflowNotesNode = z.object({
|
||||||
|
id: z.string().trim().min(1),
|
||||||
|
type: z.literal('notes'),
|
||||||
|
data: zNotesNodeData,
|
||||||
|
width: z.number().gt(0),
|
||||||
|
height: z.number().gt(0),
|
||||||
|
position: z.object({
|
||||||
|
x: z.number(),
|
||||||
|
y: z.number(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const zWorkflowNode = z.discriminatedUnion('type', [
|
||||||
|
zWorkflowInvocationNode,
|
||||||
|
zWorkflowNotesNode,
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type WorkflowNode = z.infer<typeof zWorkflowNode>;
|
||||||
|
|
||||||
|
export const zWorkflowEdge = z.object({
|
||||||
|
source: z.string().trim().min(1),
|
||||||
|
sourceHandle: z.string().trim().min(1),
|
||||||
|
target: z.string().trim().min(1),
|
||||||
|
targetHandle: z.string().trim().min(1),
|
||||||
|
id: z.string().trim().min(1),
|
||||||
|
type: z.enum(['default', 'collapsed']),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const zFieldIdentifier = z.object({
|
||||||
|
nodeId: z.string().trim().min(1),
|
||||||
|
fieldName: z.string().trim().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type FieldIdentifier = z.infer<typeof zFieldIdentifier>;
|
||||||
|
|
||||||
|
export const zSemVer = z.string().refine((val) => {
|
||||||
|
const [major, minor, patch] = val.split('.');
|
||||||
|
return (
|
||||||
|
major !== undefined &&
|
||||||
|
minor !== undefined &&
|
||||||
|
patch !== undefined &&
|
||||||
|
Number.isInteger(Number(major)) &&
|
||||||
|
Number.isInteger(Number(minor)) &&
|
||||||
|
Number.isInteger(Number(patch))
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SemVer = z.infer<typeof zSemVer>;
|
||||||
|
|
||||||
export const zWorkflow = z.object({
|
export const zWorkflow = z.object({
|
||||||
name: z.string().trim().min(1),
|
name: z.string().trim().min(1),
|
||||||
author: z.string(),
|
author: z.string(),
|
||||||
@ -610,67 +814,12 @@ export const zWorkflow = z.object({
|
|||||||
contact: z.string(),
|
contact: z.string(),
|
||||||
tags: z.string(),
|
tags: z.string(),
|
||||||
notes: z.string(),
|
notes: z.string(),
|
||||||
nodes: z.array(
|
nodes: z.array(zWorkflowNode),
|
||||||
z.object({
|
edges: z.array(zWorkflowEdge),
|
||||||
id: z.string().trim().min(1),
|
exposedFields: z.array(zFieldIdentifier),
|
||||||
type: z.string().trim().min(1),
|
|
||||||
data: z.union([zInvocationNodeData, zNotesNodeData]),
|
|
||||||
width: z.number().gt(0),
|
|
||||||
height: z.number().gt(0),
|
|
||||||
position: z.object({
|
|
||||||
x: z.number(),
|
|
||||||
y: z.number(),
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
edges: z.array(
|
|
||||||
z.object({
|
|
||||||
source: z.string().trim().min(1),
|
|
||||||
sourceHandle: z.string().trim().min(1),
|
|
||||||
target: z.string().trim().min(1),
|
|
||||||
targetHandle: z.string().trim().min(1),
|
|
||||||
id: z.string().trim().min(1),
|
|
||||||
type: z.string().trim().min(1),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export type Workflow = {
|
export type Workflow = z.infer<typeof zWorkflow>;
|
||||||
name: string;
|
|
||||||
author: string;
|
|
||||||
description: string;
|
|
||||||
version: string;
|
|
||||||
contact: string;
|
|
||||||
tags: string;
|
|
||||||
notes: string;
|
|
||||||
nodes: Pick<
|
|
||||||
Node<InvocationNodeData | NotesNodeData>,
|
|
||||||
'id' | 'type' | 'data' | 'width' | 'height' | 'position'
|
|
||||||
>[];
|
|
||||||
edges: Pick<
|
|
||||||
Edge<InvocationEdgeExtra>,
|
|
||||||
'source' | 'sourceHandle' | 'target' | 'targetHandle' | 'id' | 'type'
|
|
||||||
>[];
|
|
||||||
exposedFields: FieldIdentifier[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type InvocationNodeData = {
|
|
||||||
id: string;
|
|
||||||
type: AnyInvocationType;
|
|
||||||
inputs: Record<string, InputFieldValue>;
|
|
||||||
outputs: Record<string, OutputFieldValue>;
|
|
||||||
label: string;
|
|
||||||
isOpen: boolean;
|
|
||||||
notes: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type NotesNodeData = {
|
|
||||||
id: string;
|
|
||||||
type: 'notes';
|
|
||||||
label: string;
|
|
||||||
notes: string;
|
|
||||||
isOpen: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CurrentImageNodeData = {
|
export type CurrentImageNodeData = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -710,25 +859,6 @@ export enum NodeStatus {
|
|||||||
FAILED,
|
FAILED,
|
||||||
}
|
}
|
||||||
|
|
||||||
type SavedOutput =
|
|
||||||
| components['schemas']['StringOutput']
|
|
||||||
| components['schemas']['IntegerOutput']
|
|
||||||
| components['schemas']['FloatOutput']
|
|
||||||
| components['schemas']['ImageOutput'];
|
|
||||||
|
|
||||||
export const isSavedOutput = (
|
|
||||||
output: GraphExecutionState['results'][string]
|
|
||||||
): output is SavedOutput =>
|
|
||||||
Boolean(
|
|
||||||
output &&
|
|
||||||
[
|
|
||||||
'string_output',
|
|
||||||
'integer_output',
|
|
||||||
'float_output',
|
|
||||||
'image_output',
|
|
||||||
].includes(output?.type)
|
|
||||||
);
|
|
||||||
|
|
||||||
export type NodeExecutionState = {
|
export type NodeExecutionState = {
|
||||||
nodeId: string;
|
nodeId: string;
|
||||||
status: NodeStatus;
|
status: NodeStatus;
|
||||||
@ -738,11 +868,6 @@ export type NodeExecutionState = {
|
|||||||
outputs: AnyResult[];
|
outputs: AnyResult[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldIdentifier = {
|
|
||||||
nodeId: string;
|
|
||||||
fieldName: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type FieldComponentProps<
|
export type FieldComponentProps<
|
||||||
V extends InputFieldValue,
|
V extends InputFieldValue,
|
||||||
T extends InputFieldTemplate,
|
T extends InputFieldTemplate,
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
import { stateSelector } from 'app/store/store';
|
import { stateSelector } from 'app/store/store';
|
||||||
import { pick } from 'lodash-es';
|
|
||||||
import { NodesState } from '../store/types';
|
import { NodesState } from '../store/types';
|
||||||
import { Workflow, isInvocationNode, isNotesNode } from '../types/types';
|
import { Workflow, zWorkflowEdge, zWorkflowNode } from '../types/types';
|
||||||
|
|
||||||
export const buildWorkflow = (nodesState: NodesState): Workflow => {
|
export const buildWorkflow = (nodesState: NodesState): Workflow => {
|
||||||
const { workflow: workflowMeta, nodes, edges } = nodesState;
|
const { workflow: workflowMeta, nodes, edges } = nodesState;
|
||||||
@ -13,25 +12,19 @@ export const buildWorkflow = (nodesState: NodesState): Workflow => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
nodes.forEach((node) => {
|
nodes.forEach((node) => {
|
||||||
if (!isInvocationNode(node) && !isNotesNode(node)) {
|
const result = zWorkflowNode.safeParse(node);
|
||||||
|
if (!result.success) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
workflow.nodes.push(
|
workflow.nodes.push(result.data);
|
||||||
pick(node, ['id', 'type', 'position', 'width', 'height', 'data'])
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
edges.forEach((edge) => {
|
edges.forEach((edge) => {
|
||||||
workflow.edges.push(
|
const result = zWorkflowEdge.safeParse(edge);
|
||||||
pick(edge, [
|
if (!result.success) {
|
||||||
'source',
|
return;
|
||||||
'sourceHandle',
|
}
|
||||||
'target',
|
workflow.edges.push(result.data);
|
||||||
'targetHandle',
|
|
||||||
'id',
|
|
||||||
'type',
|
|
||||||
])
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return workflow;
|
return workflow;
|
||||||
|
@ -210,7 +210,7 @@ export type HeightParam = z.infer<typeof zHeight>;
|
|||||||
export const isValidHeight = (val: unknown): val is HeightParam =>
|
export const isValidHeight = (val: unknown): val is HeightParam =>
|
||||||
zHeight.safeParse(val).success;
|
zHeight.safeParse(val).success;
|
||||||
|
|
||||||
const zBaseModel = z.enum(['sd-1', 'sd-2', 'sdxl', 'sdxl-refiner']);
|
export const zBaseModel = z.enum(['sd-1', 'sd-2', 'sdxl', 'sdxl-refiner']);
|
||||||
|
|
||||||
export type BaseModelParam = z.infer<typeof zBaseModel>;
|
export type BaseModelParam = z.infer<typeof zBaseModel>;
|
||||||
|
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
|
||||||
import { useMemo } from 'react';
|
|
||||||
import { configSelector } from '../store/configSelectors';
|
|
||||||
import { systemSelector } from '../store/systemSelectors';
|
|
||||||
|
|
||||||
const isApplicationReadySelector = createSelector(
|
|
||||||
[systemSelector, configSelector],
|
|
||||||
(system, config) => {
|
|
||||||
const { wasSchemaParsed } = system;
|
|
||||||
|
|
||||||
const { disabledTabs } = config;
|
|
||||||
|
|
||||||
return {
|
|
||||||
disabledTabs,
|
|
||||||
wasSchemaParsed,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the application is ready to be used, i.e. if the initial startup process is finished.
|
|
||||||
*/
|
|
||||||
export const useIsApplicationReady = () => {
|
|
||||||
const { disabledTabs, wasSchemaParsed } = useAppSelector(
|
|
||||||
isApplicationReadySelector
|
|
||||||
);
|
|
||||||
|
|
||||||
const isApplicationReady = useMemo(() => {
|
|
||||||
if (!disabledTabs.includes('nodes') && !wasSchemaParsed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}, [disabledTabs, wasSchemaParsed]);
|
|
||||||
|
|
||||||
return isApplicationReady;
|
|
||||||
};
|
|
@ -16,7 +16,6 @@ export const systemPersistDenylist: (keyof SystemState)[] = [
|
|||||||
'isCancelScheduled',
|
'isCancelScheduled',
|
||||||
'progressImage',
|
'progressImage',
|
||||||
'wereModelsReceived',
|
'wereModelsReceived',
|
||||||
'wasSchemaParsed',
|
|
||||||
'isPersisted',
|
'isPersisted',
|
||||||
'isUploading',
|
'isUploading',
|
||||||
];
|
];
|
||||||
|
@ -2,7 +2,6 @@ import { UseToastOptions } from '@chakra-ui/react';
|
|||||||
import { PayloadAction, createSlice, isAnyOf } from '@reduxjs/toolkit';
|
import { PayloadAction, createSlice, isAnyOf } from '@reduxjs/toolkit';
|
||||||
import { InvokeLogLevel } from 'app/logging/logger';
|
import { InvokeLogLevel } from 'app/logging/logger';
|
||||||
import { userInvoked } from 'app/store/actions';
|
import { userInvoked } from 'app/store/actions';
|
||||||
import { nodeTemplatesBuilt } from 'features/nodes/store/nodesSlice';
|
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { startCase, upperFirst } from 'lodash-es';
|
import { startCase, upperFirst } from 'lodash-es';
|
||||||
import { LogLevelName } from 'roarr';
|
import { LogLevelName } from 'roarr';
|
||||||
@ -68,10 +67,6 @@ export interface SystemState {
|
|||||||
* Whether or not the available models were received
|
* Whether or not the available models were received
|
||||||
*/
|
*/
|
||||||
wereModelsReceived: boolean;
|
wereModelsReceived: boolean;
|
||||||
/**
|
|
||||||
* Whether or not the OpenAPI schema was received and parsed
|
|
||||||
*/
|
|
||||||
wasSchemaParsed: boolean;
|
|
||||||
/**
|
/**
|
||||||
* The console output logging level
|
* The console output logging level
|
||||||
*/
|
*/
|
||||||
@ -112,7 +107,6 @@ export const initialSystemState: SystemState = {
|
|||||||
isCancelScheduled: false,
|
isCancelScheduled: false,
|
||||||
subscribedNodeIds: [],
|
subscribedNodeIds: [],
|
||||||
wereModelsReceived: false,
|
wereModelsReceived: false,
|
||||||
wasSchemaParsed: false,
|
|
||||||
consoleLogLevel: 'debug',
|
consoleLogLevel: 'debug',
|
||||||
shouldLogToConsole: true,
|
shouldLogToConsole: true,
|
||||||
statusTranslationKey: 'common.statusDisconnected',
|
statusTranslationKey: 'common.statusDisconnected',
|
||||||
@ -339,13 +333,6 @@ export const systemSlice = createSlice({
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
|
||||||
* OpenAPI schema was parsed
|
|
||||||
*/
|
|
||||||
builder.addCase(nodeTemplatesBuilt, (state) => {
|
|
||||||
state.wasSchemaParsed = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
// *** Matchers - must be after all cases ***
|
// *** Matchers - must be after all cases ***
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -6238,12 +6238,6 @@ export type components = {
|
|||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
StableDiffusion1ModelFormat: "checkpoint" | "diffusers";
|
StableDiffusion1ModelFormat: "checkpoint" | "diffusers";
|
||||||
/**
|
|
||||||
* ControlNetModelFormat
|
|
||||||
* @description An enumeration.
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
ControlNetModelFormat: "checkpoint" | "diffusers";
|
|
||||||
/**
|
/**
|
||||||
* StableDiffusionXLModelFormat
|
* StableDiffusionXLModelFormat
|
||||||
* @description An enumeration.
|
* @description An enumeration.
|
||||||
@ -6256,6 +6250,12 @@ export type components = {
|
|||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
StableDiffusion2ModelFormat: "checkpoint" | "diffusers";
|
StableDiffusion2ModelFormat: "checkpoint" | "diffusers";
|
||||||
|
/**
|
||||||
|
* ControlNetModelFormat
|
||||||
|
* @description An enumeration.
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
ControlNetModelFormat: "checkpoint" | "diffusers";
|
||||||
/**
|
/**
|
||||||
* StableDiffusionOnnxModelFormat
|
* StableDiffusionOnnxModelFormat
|
||||||
* @description An enumeration.
|
* @description An enumeration.
|
||||||
|
@ -7063,6 +7063,11 @@ z-schema@~5.0.2:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
commander "^10.0.0"
|
commander "^10.0.0"
|
||||||
|
|
||||||
|
zod-validation-error@^1.5.0:
|
||||||
|
version "1.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-1.5.0.tgz#2b355007a1c3b7fb04fa476bfad4e7b3fd5491e3"
|
||||||
|
integrity sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw==
|
||||||
|
|
||||||
zod@^3.22.2:
|
zod@^3.22.2:
|
||||||
version "3.22.2"
|
version "3.22.2"
|
||||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.2.tgz#3add8c682b7077c05ac6f979fea6998b573e157b"
|
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.2.tgz#3add8c682b7077c05ac6f979fea6998b573e157b"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user