mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): split out workflow redux state
The `nodes` slice is a rather complicated slice. Removing `workflow` makes it a bit more reasonable. Also helps to flatten state out a bit.
This commit is contained in:
parent
92bc04dc87
commit
0f32d260b7
@ -1646,5 +1646,8 @@
|
||||
"clearWorkflowSearchFilter": "Clear Workflow Search Filter",
|
||||
"workflowName": "Workflow Name",
|
||||
"workflowEditorReset": "Workflow Editor Reset"
|
||||
},
|
||||
"app": {
|
||||
"storeNotInitialized": "Store is not initialized"
|
||||
}
|
||||
}
|
||||
|
@ -11,12 +11,18 @@ export const addEnqueueRequestedNodes = () => {
|
||||
enqueueRequested.match(action) && action.payload.tabName === 'nodes',
|
||||
effect: async (action, { getState, dispatch }) => {
|
||||
const state = getState();
|
||||
const { nodes, edges } = state.nodes;
|
||||
const workflow = state.workflow;
|
||||
const graph = buildNodesGraph(state.nodes);
|
||||
const workflow = buildWorkflow(state.nodes);
|
||||
const builtWorkflow = buildWorkflow({
|
||||
nodes,
|
||||
edges,
|
||||
workflow,
|
||||
});
|
||||
const batchConfig: BatchConfig = {
|
||||
batch: {
|
||||
graph,
|
||||
workflow,
|
||||
workflow: builtWorkflow,
|
||||
runs: state.generation.iterations,
|
||||
},
|
||||
prepend: action.payload.prepend,
|
||||
|
@ -11,13 +11,11 @@ import {
|
||||
TypesafeDroppableData,
|
||||
} from 'features/dnd/types';
|
||||
import { imageSelected } from 'features/gallery/store/gallerySlice';
|
||||
import {
|
||||
fieldImageValueChanged,
|
||||
workflowExposedFieldAdded,
|
||||
} from 'features/nodes/store/nodesSlice';
|
||||
import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
||||
import { imagesApi } from 'services/api/endpoints/images';
|
||||
import { startAppListening } from '../';
|
||||
import { workflowExposedFieldAdded } from 'features/nodes/store/workflowSlice';
|
||||
|
||||
export const dndDropped = createAction<{
|
||||
overData: TypesafeDroppableData;
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Store } from '@reduxjs/toolkit';
|
||||
import { createStore } from 'app/store/store';
|
||||
import { atom } from 'nanostores';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export const $store = atom<Store<any> | undefined>();
|
||||
export const $store = atom<
|
||||
Readonly<ReturnType<typeof createStore>> | undefined
|
||||
>();
|
||||
|
@ -14,6 +14,7 @@ import galleryReducer from 'features/gallery/store/gallerySlice';
|
||||
import loraReducer from 'features/lora/store/loraSlice';
|
||||
import modelmanagerReducer from 'features/modelManager/store/modelManagerSlice';
|
||||
import nodesReducer from 'features/nodes/store/nodesSlice';
|
||||
import workflowReducer from 'features/nodes/store/workflowSlice';
|
||||
import generationReducer from 'features/parameters/store/generationSlice';
|
||||
import postprocessingReducer from 'features/parameters/store/postprocessingSlice';
|
||||
import queueReducer from 'features/queue/store/queueSlice';
|
||||
@ -22,6 +23,7 @@ import configReducer from 'features/system/store/configSlice';
|
||||
import systemReducer from 'features/system/store/systemSlice';
|
||||
import hotkeysReducer from 'features/ui/store/hotkeysSlice';
|
||||
import uiReducer from 'features/ui/store/uiSlice';
|
||||
import { createStore as createIDBKeyValStore, get, set } from 'idb-keyval';
|
||||
import dynamicMiddlewares from 'redux-dynamic-middlewares';
|
||||
import { Driver, rememberEnhancer, rememberReducer } from 'redux-remember';
|
||||
import { api } from 'services/api';
|
||||
@ -32,7 +34,6 @@ import { actionSanitizer } from './middleware/devtools/actionSanitizer';
|
||||
import { actionsDenylist } from './middleware/devtools/actionsDenylist';
|
||||
import { stateSanitizer } from './middleware/devtools/stateSanitizer';
|
||||
import { listenerMiddleware } from './middleware/listenerMiddleware';
|
||||
import { createStore as createIDBKeyValStore, get, set } from 'idb-keyval';
|
||||
|
||||
const allReducers = {
|
||||
canvas: canvasReducer,
|
||||
@ -52,6 +53,7 @@ const allReducers = {
|
||||
modelmanager: modelmanagerReducer,
|
||||
sdxl: sdxlReducer,
|
||||
queue: queueReducer,
|
||||
workflow: workflowReducer,
|
||||
[api.reducerPath]: api.reducer,
|
||||
};
|
||||
|
||||
@ -65,6 +67,7 @@ const rememberedKeys: (keyof typeof allReducers)[] = [
|
||||
'generation',
|
||||
'sdxl',
|
||||
'nodes',
|
||||
'workflow',
|
||||
'postprocessing',
|
||||
'system',
|
||||
'ui',
|
||||
|
@ -13,7 +13,7 @@ import { useFieldTemplateTitle } from 'features/nodes/hooks/useFieldTemplateTitl
|
||||
import {
|
||||
workflowExposedFieldAdded,
|
||||
workflowExposedFieldRemoved,
|
||||
} from 'features/nodes/store/nodesSlice';
|
||||
} from 'features/nodes/store/workflowSlice';
|
||||
import { MouseEvent, ReactNode, memo, useCallback, useMemo } from 'react';
|
||||
import { FaMinus, FaPlus } from 'react-icons/fa';
|
||||
import { MENU_LIST_MOTION_PROPS } from 'theme/components/menu';
|
||||
@ -41,9 +41,9 @@ const FieldContextMenu = ({ nodeId, fieldName, kind, children }: Props) => {
|
||||
() =>
|
||||
createSelector(
|
||||
stateSelector,
|
||||
({ nodes }) => {
|
||||
({ workflow }) => {
|
||||
const isExposed = Boolean(
|
||||
nodes.workflow.exposedFields.find(
|
||||
workflow.exposedFields.find(
|
||||
(f) => f.nodeId === nodeId && f.fieldName === fieldName
|
||||
)
|
||||
);
|
||||
|
@ -10,7 +10,7 @@ import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import NodeSelectionOverlay from 'common/components/NodeSelectionOverlay';
|
||||
import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
|
||||
import { workflowExposedFieldRemoved } from 'features/nodes/store/nodesSlice';
|
||||
import { workflowExposedFieldRemoved } from 'features/nodes/store/workflowSlice';
|
||||
import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { FaInfoCircle, FaTrash } from 'react-icons/fa';
|
||||
|
@ -5,6 +5,7 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import IAIInput from 'common/components/IAIInput';
|
||||
import IAITextarea from 'common/components/IAITextarea';
|
||||
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
|
||||
import {
|
||||
workflowAuthorChanged,
|
||||
workflowContactChanged,
|
||||
@ -13,16 +14,15 @@ import {
|
||||
workflowNotesChanged,
|
||||
workflowTagsChanged,
|
||||
workflowVersionChanged,
|
||||
} from 'features/nodes/store/nodesSlice';
|
||||
} from 'features/nodes/store/workflowSlice';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import ScrollableContent from 'features/nodes/components/sidePanel/ScrollableContent';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
({ nodes }) => {
|
||||
({ workflow }) => {
|
||||
const { author, name, description, tags, version, contact, notes } =
|
||||
nodes.workflow;
|
||||
workflow;
|
||||
|
||||
return {
|
||||
name,
|
||||
|
@ -11,9 +11,9 @@ import { useTranslation } from 'react-i18next';
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
({ nodes }) => {
|
||||
({ workflow: workflows }) => {
|
||||
return {
|
||||
fields: nodes.workflow.exposedFields,
|
||||
fields: workflows.exposedFields,
|
||||
};
|
||||
},
|
||||
defaultSelectorOptions
|
||||
|
@ -5,12 +5,16 @@ import { useMemo } from 'react';
|
||||
import { useDebounce } from 'use-debounce';
|
||||
|
||||
export const useWorkflow = () => {
|
||||
const nodes = useAppSelector((state: RootState) => state.nodes);
|
||||
const [debouncedNodes] = useDebounce(nodes, 300);
|
||||
const workflow = useMemo(
|
||||
() => buildWorkflow(debouncedNodes),
|
||||
[debouncedNodes]
|
||||
const nodes_ = useAppSelector((state: RootState) => state.nodes.nodes);
|
||||
const edges_ = useAppSelector((state: RootState) => state.nodes.edges);
|
||||
const workflow_ = useAppSelector((state: RootState) => state.workflow);
|
||||
const [nodes] = useDebounce(nodes_, 300);
|
||||
const [edges] = useDebounce(edges_, 300);
|
||||
const [workflow] = useDebounce(workflow_, 300);
|
||||
const builtWorkflow = useMemo(
|
||||
() => buildWorkflow({ nodes, edges, workflow }),
|
||||
[nodes, edges, workflow]
|
||||
);
|
||||
|
||||
return workflow;
|
||||
return builtWorkflow;
|
||||
};
|
||||
|
@ -29,7 +29,7 @@ import {
|
||||
zNodeStatus,
|
||||
} from 'features/nodes/types/invocation';
|
||||
import { WorkflowV2 } from 'features/nodes/types/workflow';
|
||||
import { cloneDeep, forEach, isEqual, uniqBy } from 'lodash-es';
|
||||
import { cloneDeep, forEach } from 'lodash-es';
|
||||
import {
|
||||
addEdge,
|
||||
applyEdgeChanges,
|
||||
@ -70,20 +70,6 @@ const initialNodeExecutionState: Omit<NodeExecutionState, 'nodeId'> = {
|
||||
outputs: [],
|
||||
};
|
||||
|
||||
const INITIAL_WORKFLOW: WorkflowV2 = {
|
||||
name: '',
|
||||
author: '',
|
||||
description: '',
|
||||
version: '',
|
||||
contact: '',
|
||||
tags: '',
|
||||
notes: '',
|
||||
nodes: [],
|
||||
edges: [],
|
||||
exposedFields: [],
|
||||
meta: { version: '2.0.0', category: 'user' },
|
||||
};
|
||||
|
||||
export const initialNodesState: NodesState = {
|
||||
nodes: [],
|
||||
edges: [],
|
||||
@ -103,7 +89,7 @@ export const initialNodesState: NodesState = {
|
||||
nodeOpacity: 1,
|
||||
selectedNodes: [],
|
||||
selectedEdges: [],
|
||||
workflow: INITIAL_WORKFLOW,
|
||||
// workflow: INITIAL_WORKFLOW,
|
||||
nodeExecutionStates: {},
|
||||
viewport: { x: 0, y: 0, zoom: 1 },
|
||||
mouseOverField: null,
|
||||
@ -308,23 +294,6 @@ const nodesSlice = createSlice({
|
||||
}
|
||||
state.modifyingEdge = false;
|
||||
},
|
||||
workflowExposedFieldAdded: (
|
||||
state,
|
||||
action: PayloadAction<FieldIdentifier>
|
||||
) => {
|
||||
state.workflow.exposedFields = uniqBy(
|
||||
state.workflow.exposedFields.concat(action.payload),
|
||||
(field) => `${field.nodeId}-${field.fieldName}`
|
||||
);
|
||||
},
|
||||
workflowExposedFieldRemoved: (
|
||||
state,
|
||||
action: PayloadAction<FieldIdentifier>
|
||||
) => {
|
||||
state.workflow.exposedFields = state.workflow.exposedFields.filter(
|
||||
(field) => !isEqual(field, action.payload)
|
||||
);
|
||||
},
|
||||
fieldLabelChanged: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
@ -508,9 +477,6 @@ const nodesSlice = createSlice({
|
||||
},
|
||||
nodesDeleted: (state, action: PayloadAction<AnyNode[]>) => {
|
||||
action.payload.forEach((node) => {
|
||||
state.workflow.exposedFields = state.workflow.exposedFields.filter(
|
||||
(f) => f.nodeId !== node.id
|
||||
);
|
||||
if (!isInvocationNode(node)) {
|
||||
return;
|
||||
}
|
||||
@ -673,7 +639,6 @@ const nodesSlice = createSlice({
|
||||
nodeEditorReset: (state) => {
|
||||
state.nodes = [];
|
||||
state.edges = [];
|
||||
state.workflow = cloneDeep(INITIAL_WORKFLOW);
|
||||
},
|
||||
shouldValidateGraphChanged: (state, action: PayloadAction<boolean>) => {
|
||||
state.shouldValidateGraph = action.payload;
|
||||
@ -690,34 +655,8 @@ const nodesSlice = createSlice({
|
||||
nodeOpacityChanged: (state, action: PayloadAction<number>) => {
|
||||
state.nodeOpacity = action.payload;
|
||||
},
|
||||
workflowNameChanged: (state, action: PayloadAction<string>) => {
|
||||
state.workflow.name = action.payload;
|
||||
},
|
||||
workflowDescriptionChanged: (state, action: PayloadAction<string>) => {
|
||||
state.workflow.description = action.payload;
|
||||
},
|
||||
workflowTagsChanged: (state, action: PayloadAction<string>) => {
|
||||
state.workflow.tags = action.payload;
|
||||
},
|
||||
workflowAuthorChanged: (state, action: PayloadAction<string>) => {
|
||||
state.workflow.author = action.payload;
|
||||
},
|
||||
workflowNotesChanged: (state, action: PayloadAction<string>) => {
|
||||
state.workflow.notes = action.payload;
|
||||
},
|
||||
workflowVersionChanged: (state, action: PayloadAction<string>) => {
|
||||
state.workflow.version = action.payload;
|
||||
},
|
||||
workflowContactChanged: (state, action: PayloadAction<string>) => {
|
||||
state.workflow.contact = action.payload;
|
||||
},
|
||||
workflowIDChanged: (state, action: PayloadAction<string>) => {
|
||||
state.workflow.id = action.payload;
|
||||
},
|
||||
workflowLoaded: (state, action: PayloadAction<WorkflowV2>) => {
|
||||
const { nodes, edges, ...workflow } = action.payload;
|
||||
state.workflow = workflow;
|
||||
|
||||
const { nodes, edges } = action.payload;
|
||||
state.nodes = applyNodeChanges(
|
||||
nodes.map((node) => ({
|
||||
item: { ...node, ...SHARED_NODE_PROPERTIES },
|
||||
@ -740,9 +679,6 @@ const nodesSlice = createSlice({
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
workflowReset: (state) => {
|
||||
state.workflow = cloneDeep(INITIAL_WORKFLOW);
|
||||
},
|
||||
viewportChanged: (state, action: PayloadAction<Viewport>) => {
|
||||
state.viewport = action.payload;
|
||||
},
|
||||
@ -996,17 +932,7 @@ export const {
|
||||
shouldSnapToGridChanged,
|
||||
shouldValidateGraphChanged,
|
||||
viewportChanged,
|
||||
workflowAuthorChanged,
|
||||
workflowContactChanged,
|
||||
workflowDescriptionChanged,
|
||||
workflowExposedFieldAdded,
|
||||
workflowExposedFieldRemoved,
|
||||
workflowLoaded,
|
||||
workflowNameChanged,
|
||||
workflowNotesChanged,
|
||||
workflowTagsChanged,
|
||||
workflowVersionChanged,
|
||||
workflowIDChanged,
|
||||
edgeAdded,
|
||||
} = nodesSlice.actions;
|
||||
|
||||
|
@ -29,7 +29,6 @@ export type NodesState = {
|
||||
shouldColorEdges: boolean;
|
||||
selectedNodes: string[];
|
||||
selectedEdges: string[];
|
||||
workflow: Omit<WorkflowV2, 'nodes' | 'edges'>;
|
||||
nodeExecutionStates: Record<string, NodeExecutionState>;
|
||||
viewport: Viewport;
|
||||
isReady: boolean;
|
||||
@ -41,3 +40,5 @@ export type NodesState = {
|
||||
addNewNodePosition: XYPosition | null;
|
||||
selectionMode: SelectionMode;
|
||||
};
|
||||
|
||||
export type WorkflowsState = Omit<WorkflowV2, 'nodes' | 'edges'>;
|
||||
|
@ -0,0 +1,99 @@
|
||||
import { PayloadAction, createSlice } from '@reduxjs/toolkit';
|
||||
import { nodeEditorReset, nodesDeleted } from 'features/nodes/store/nodesSlice';
|
||||
import { WorkflowsState as WorkflowState } from 'features/nodes/store/types';
|
||||
import { FieldIdentifier } from 'features/nodes/types/field';
|
||||
import { WorkflowV2 } from 'features/nodes/types/workflow';
|
||||
import { cloneDeep, isEqual, uniqBy } from 'lodash-es';
|
||||
|
||||
export const initialWorkflowState: WorkflowState = {
|
||||
name: '',
|
||||
author: '',
|
||||
description: '',
|
||||
version: '',
|
||||
contact: '',
|
||||
tags: '',
|
||||
notes: '',
|
||||
exposedFields: [],
|
||||
meta: { version: '2.0.0', category: 'user' },
|
||||
};
|
||||
|
||||
const workflowSlice = createSlice({
|
||||
name: 'workflow',
|
||||
initialState: initialWorkflowState,
|
||||
reducers: {
|
||||
workflowExposedFieldAdded: (
|
||||
state,
|
||||
action: PayloadAction<FieldIdentifier>
|
||||
) => {
|
||||
state.exposedFields = uniqBy(
|
||||
state.exposedFields.concat(action.payload),
|
||||
(field) => `${field.nodeId}-${field.fieldName}`
|
||||
);
|
||||
},
|
||||
workflowExposedFieldRemoved: (
|
||||
state,
|
||||
action: PayloadAction<FieldIdentifier>
|
||||
) => {
|
||||
state.exposedFields = state.exposedFields.filter(
|
||||
(field) => !isEqual(field, action.payload)
|
||||
);
|
||||
},
|
||||
workflowNameChanged: (state, action: PayloadAction<string>) => {
|
||||
state.name = action.payload;
|
||||
},
|
||||
workflowDescriptionChanged: (state, action: PayloadAction<string>) => {
|
||||
state.description = action.payload;
|
||||
},
|
||||
workflowTagsChanged: (state, action: PayloadAction<string>) => {
|
||||
state.tags = action.payload;
|
||||
},
|
||||
workflowAuthorChanged: (state, action: PayloadAction<string>) => {
|
||||
state.author = action.payload;
|
||||
},
|
||||
workflowNotesChanged: (state, action: PayloadAction<string>) => {
|
||||
state.notes = action.payload;
|
||||
},
|
||||
workflowVersionChanged: (state, action: PayloadAction<string>) => {
|
||||
state.version = action.payload;
|
||||
},
|
||||
workflowContactChanged: (state, action: PayloadAction<string>) => {
|
||||
state.contact = action.payload;
|
||||
},
|
||||
workflowIDChanged: (state, action: PayloadAction<string>) => {
|
||||
state.id = action.payload;
|
||||
},
|
||||
workflowLoaded: (state, action: PayloadAction<WorkflowV2>) => {
|
||||
const { nodes: _nodes, edges: _edges, ...workflow } = action.payload;
|
||||
return cloneDeep(workflow);
|
||||
},
|
||||
workflowReset: () => cloneDeep(initialWorkflowState),
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(nodesDeleted, (state, action) => {
|
||||
action.payload.forEach((node) => {
|
||||
state.exposedFields = state.exposedFields.filter(
|
||||
(f) => f.nodeId !== node.id
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
builder.addCase(nodeEditorReset, () => cloneDeep(initialWorkflowState));
|
||||
},
|
||||
});
|
||||
|
||||
export const {
|
||||
workflowExposedFieldAdded,
|
||||
workflowExposedFieldRemoved,
|
||||
workflowNameChanged,
|
||||
workflowDescriptionChanged,
|
||||
workflowTagsChanged,
|
||||
workflowAuthorChanged,
|
||||
workflowNotesChanged,
|
||||
workflowVersionChanged,
|
||||
workflowContactChanged,
|
||||
workflowIDChanged,
|
||||
workflowLoaded,
|
||||
workflowReset,
|
||||
} = workflowSlice.actions;
|
||||
|
||||
export default workflowSlice.reducer;
|
@ -1,27 +1,39 @@
|
||||
import { logger } from 'app/logging/logger';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import { NodesState } from 'features/nodes/store/types';
|
||||
import {
|
||||
WorkflowV2,
|
||||
zWorkflowEdge,
|
||||
zWorkflowNode,
|
||||
} from 'features/nodes/types/workflow';
|
||||
import { fromZodError } from 'zod-validation-error';
|
||||
import { parseify } from 'common/util/serialize';
|
||||
import i18n from 'i18next';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { fromZodError } from 'zod-validation-error';
|
||||
|
||||
export const buildWorkflow = (nodesState: NodesState): WorkflowV2 => {
|
||||
const workflow = cloneDeep(nodesState.workflow);
|
||||
const nodes = cloneDeep(nodesState.nodes);
|
||||
const edges = cloneDeep(nodesState.edges);
|
||||
type BuildWorkflowArg = {
|
||||
nodes: NodesState['nodes'];
|
||||
edges: NodesState['edges'];
|
||||
workflow: Omit<WorkflowV2, 'nodes' | 'edges'>;
|
||||
};
|
||||
|
||||
type BuildWorkflowFunction = (arg: BuildWorkflowArg) => WorkflowV2;
|
||||
|
||||
export const buildWorkflow: BuildWorkflowFunction = ({
|
||||
nodes,
|
||||
edges,
|
||||
workflow,
|
||||
}) => {
|
||||
const clonedWorkflow = cloneDeep(workflow);
|
||||
const clonedNodes = cloneDeep(nodes);
|
||||
const clonedEdges = cloneDeep(edges);
|
||||
|
||||
const newWorkflow: WorkflowV2 = {
|
||||
...workflow,
|
||||
...clonedWorkflow,
|
||||
nodes: [],
|
||||
edges: [],
|
||||
};
|
||||
|
||||
nodes
|
||||
clonedNodes
|
||||
.filter((n) =>
|
||||
['invocation', 'notes'].includes(n.type ?? '__UNKNOWN_NODE_TYPE__')
|
||||
)
|
||||
@ -37,7 +49,7 @@ export const buildWorkflow = (nodesState: NodesState): WorkflowV2 => {
|
||||
newWorkflow.nodes.push(result.data);
|
||||
});
|
||||
|
||||
edges.forEach((edge) => {
|
||||
clonedEdges.forEach((edge) => {
|
||||
const result = zWorkflowEdge.safeParse(edge);
|
||||
if (!result.success) {
|
||||
const { message } = fromZodError(result.error, {
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { $store } from 'app/store/nanostores/store';
|
||||
import { RootState } from 'app/store/store';
|
||||
import {
|
||||
WorkflowMigrationError,
|
||||
WorkflowVersionError,
|
||||
@ -32,8 +31,12 @@ const zWorkflowMetaVersion = z.object({
|
||||
* - Workflow schema version bumped to 2.0.0
|
||||
*/
|
||||
const migrateV1toV2 = (workflowToMigrate: WorkflowV1): WorkflowV2 => {
|
||||
const invocationTemplates = ($store.get()?.getState() as RootState).nodes
|
||||
.nodeTemplates;
|
||||
const invocationTemplates = $store.get()?.getState().nodes.nodeTemplates;
|
||||
|
||||
if (!invocationTemplates) {
|
||||
throw new Error(t('app.storeNotInitialized'));
|
||||
}
|
||||
|
||||
workflowToMigrate.nodes.forEach((node) => {
|
||||
if (node.type === 'invocation') {
|
||||
// Migrate field types
|
||||
@ -66,6 +69,7 @@ const migrateV1toV2 = (workflowToMigrate: WorkflowV1): WorkflowV2 => {
|
||||
});
|
||||
// Bump version
|
||||
(workflowToMigrate as unknown as WorkflowV2).meta.version = '2.0.0';
|
||||
// Add category - should always be 'user', 'default' workflows are only created by the backend
|
||||
(workflowToMigrate as unknown as WorkflowV2).meta.category = 'user';
|
||||
// Parsing strips out any extra properties not in the latest version
|
||||
return zWorkflowV2.parse(workflowToMigrate);
|
||||
|
@ -19,7 +19,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { FaClone } from 'react-icons/fa';
|
||||
|
||||
const SaveWorkflowAsButton = () => {
|
||||
const currentName = useAppSelector((state) => state.nodes.workflow.name);
|
||||
const currentName = useAppSelector((state) => state.workflow.name);
|
||||
const { t } = useTranslation();
|
||||
const { saveWorkflowAs, isLoading } = useSaveWorkflowAs();
|
||||
const [name, setName] = useState(currentName.trim());
|
||||
|
Loading…
Reference in New Issue
Block a user