mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
fix(ui): reset form initial values when workflow is saved
This commit is contained in:
@@ -10,10 +10,11 @@ import {
|
||||
} from 'features/nodes/components/sidePanel/builder/form-manipulation';
|
||||
import { workflowLoaded } from 'features/nodes/store/actions';
|
||||
import { isAnyNodeOrEdgeMutation, nodeEditorReset, nodesChanged } from 'features/nodes/store/nodesSlice';
|
||||
import type { WorkflowMode, WorkflowsState as WorkflowState } from 'features/nodes/store/types';
|
||||
import type { NodesState, WorkflowMode, WorkflowsState as WorkflowState } from 'features/nodes/store/types';
|
||||
import type { FieldIdentifier, StatefulFieldValue } from 'features/nodes/types/field';
|
||||
import { isInvocationNode } from 'features/nodes/types/invocation';
|
||||
import type {
|
||||
BuilderForm,
|
||||
ContainerElement,
|
||||
ElementId,
|
||||
FormElement,
|
||||
@@ -188,35 +189,19 @@ export const workflowSlice = createSlice({
|
||||
formElementContainerDataChanged: (state, action: FormElementDataChangedAction<ContainerElement>) => {
|
||||
formElementDataChangedReducer(state, action, isContainerElement);
|
||||
},
|
||||
formFieldInitialValuesChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ formFieldInitialValues: WorkflowState['formFieldInitialValues'] }>
|
||||
) => {
|
||||
const { formFieldInitialValues } = action.payload;
|
||||
state.formFieldInitialValues = formFieldInitialValues;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(workflowLoaded, (state, action): WorkflowState => {
|
||||
const { nodes, edges: _edges, ...workflowExtra } = action.payload;
|
||||
|
||||
const formFieldInitialValues: Record<string, StatefulFieldValue> = {};
|
||||
|
||||
if (workflowExtra.form) {
|
||||
for (const el of Object.values(workflowExtra.form.elements)) {
|
||||
if (!isNodeFieldElement(el)) {
|
||||
continue;
|
||||
}
|
||||
const { nodeId, fieldName } = el.data.fieldIdentifier;
|
||||
|
||||
const node = nodes.find((n) => n.id === nodeId);
|
||||
|
||||
if (!isInvocationNode(node)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const field = node.data.inputs[fieldName];
|
||||
|
||||
if (!field) {
|
||||
continue;
|
||||
}
|
||||
|
||||
formFieldInitialValues[el.id] = field.value;
|
||||
}
|
||||
}
|
||||
const formFieldInitialValues = getFormFieldInitialValues(workflowExtra.form, nodes);
|
||||
|
||||
return {
|
||||
...deepClone(initialWorkflowState),
|
||||
@@ -322,6 +307,7 @@ export const {
|
||||
formElementTextDataChanged,
|
||||
formElementNodeFieldDataChanged,
|
||||
formElementContainerDataChanged,
|
||||
formFieldInitialValuesChanged,
|
||||
} = workflowSlice.actions;
|
||||
|
||||
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
|
||||
@@ -343,6 +329,34 @@ export const selectWorkflowSlice = (state: RootState) => state.workflow;
|
||||
const createWorkflowSelector = <T>(selector: Selector<WorkflowState, T>) =>
|
||||
createSelector(selectWorkflowSlice, selector);
|
||||
|
||||
// The form builder's initial values are based on the current values of the node fields in the workflow.
|
||||
export const getFormFieldInitialValues = (form: BuilderForm, nodes: NodesState['nodes']) => {
|
||||
const formFieldInitialValues: Record<string, StatefulFieldValue> = {};
|
||||
|
||||
for (const el of Object.values(form.elements)) {
|
||||
if (!isNodeFieldElement(el)) {
|
||||
continue;
|
||||
}
|
||||
const { nodeId, fieldName } = el.data.fieldIdentifier;
|
||||
|
||||
const node = nodes.find((n) => n.id === nodeId);
|
||||
|
||||
if (!isInvocationNode(node)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const field = node.data.inputs[fieldName];
|
||||
|
||||
if (!field) {
|
||||
continue;
|
||||
}
|
||||
|
||||
formFieldInitialValues[el.id] = field.value;
|
||||
}
|
||||
|
||||
return formFieldInitialValues;
|
||||
};
|
||||
|
||||
export const selectWorkflowName = createWorkflowSelector((workflow) => workflow.name);
|
||||
export const selectWorkflowId = createWorkflowSelector((workflow) => workflow.id);
|
||||
export const selectWorkflowMode = createWorkflowSelector((workflow) => workflow.mode);
|
||||
@@ -351,6 +365,7 @@ export const selectWorkflowSearchTerm = createWorkflowSelector((workflow) => wor
|
||||
export const selectWorkflowOrderBy = createWorkflowSelector((workflow) => workflow.orderBy);
|
||||
export const selectWorkflowOrderDirection = createWorkflowSelector((workflow) => workflow.orderDirection);
|
||||
export const selectWorkflowDescription = createWorkflowSelector((workflow) => workflow.description);
|
||||
export const selectWorkflowForm = createWorkflowSelector((workflow) => workflow.form);
|
||||
|
||||
export const selectCleanEditor = createSelector([selectNodesSlice, selectWorkflowSlice], (nodes, workflow) => {
|
||||
const noNodes = !nodes.nodes.length;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
import { useAppStore } from 'app/store/nanostores/store';
|
||||
import { selectNodesSlice } from 'features/nodes/store/selectors';
|
||||
import {
|
||||
getFormFieldInitialValues as _getFormFieldInitialValues,
|
||||
selectWorkflowForm,
|
||||
} from 'features/nodes/store/workflowSlice';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const useGetFormFieldInitialValues = () => {
|
||||
const store = useAppStore();
|
||||
|
||||
const getFormFieldInitialValues = useCallback(() => {
|
||||
const form = selectWorkflowForm(store.getState());
|
||||
const { nodes } = selectNodesSlice(store.getState());
|
||||
return _getFormFieldInitialValues(form, nodes);
|
||||
}, [store]);
|
||||
|
||||
return getFormFieldInitialValues;
|
||||
};
|
||||
@@ -2,8 +2,9 @@ import type { ToastId } from '@invoke-ai/ui-library';
|
||||
import { useToast } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { $builtWorkflow } from 'features/nodes/hooks/useWorkflowWatcher';
|
||||
import { workflowIDChanged, workflowSaved } from 'features/nodes/store/workflowSlice';
|
||||
import { formFieldInitialValuesChanged, workflowIDChanged, workflowSaved } from 'features/nodes/store/workflowSlice';
|
||||
import type { WorkflowV3 } from 'features/nodes/types/workflow';
|
||||
import { useGetFormFieldInitialValues } from 'features/workflowLibrary/hooks/useGetFormInitialValues';
|
||||
import { workflowUpdated } from 'features/workflowLibrary/store/actions';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -24,6 +25,7 @@ export const isWorkflowWithID = (workflow: WorkflowV3): workflow is SetRequired<
|
||||
export const useSaveLibraryWorkflow: UseSaveLibraryWorkflow = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const getFormFieldInitialValues = useGetFormFieldInitialValues();
|
||||
const [updateWorkflow, updateWorkflowResult] = useUpdateWorkflowMutation();
|
||||
const [createWorkflow, createWorkflowResult] = useCreateWorkflowMutation();
|
||||
const toast = useToast();
|
||||
@@ -48,6 +50,8 @@ export const useSaveLibraryWorkflow: UseSaveLibraryWorkflow = () => {
|
||||
dispatch(workflowIDChanged(data.workflow.id));
|
||||
}
|
||||
dispatch(workflowSaved());
|
||||
// When a workflow is saved, the form field initial values are updated to the current form field values
|
||||
dispatch(formFieldInitialValuesChanged({ formFieldInitialValues: getFormFieldInitialValues() }));
|
||||
toast.update(toastRef.current, {
|
||||
title: t('workflows.workflowSaved'),
|
||||
status: 'success',
|
||||
@@ -69,7 +73,7 @@ export const useSaveLibraryWorkflow: UseSaveLibraryWorkflow = () => {
|
||||
toast.close(toastRef.current);
|
||||
}
|
||||
}
|
||||
}, [updateWorkflow, dispatch, toast, t, createWorkflow]);
|
||||
}, [toast, t, dispatch, getFormFieldInitialValues, updateWorkflow, createWorkflow]);
|
||||
return {
|
||||
saveWorkflow,
|
||||
isLoading: updateWorkflowResult.isLoading || createWorkflowResult.isLoading,
|
||||
|
||||
@@ -3,12 +3,14 @@ import { useToast } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { $builtWorkflow } from 'features/nodes/hooks/useWorkflowWatcher';
|
||||
import {
|
||||
formFieldInitialValuesChanged,
|
||||
workflowCategoryChanged,
|
||||
workflowIDChanged,
|
||||
workflowNameChanged,
|
||||
workflowSaved,
|
||||
} from 'features/nodes/store/workflowSlice';
|
||||
import type { WorkflowCategory } from 'features/nodes/types/workflow';
|
||||
import { useGetFormFieldInitialValues } from 'features/workflowLibrary/hooks/useGetFormInitialValues';
|
||||
import { newWorkflowSaved } from 'features/workflowLibrary/store/actions';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@@ -33,6 +35,8 @@ export const useSaveWorkflowAs: UseSaveWorkflowAs = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const [createWorkflow, createWorkflowResult] = useCreateWorkflowMutation();
|
||||
const getFormFieldInitialValues = useGetFormFieldInitialValues();
|
||||
|
||||
const toast = useToast();
|
||||
const toastRef = useRef<ToastId | undefined>();
|
||||
const saveWorkflowAs = useCallback(
|
||||
@@ -57,6 +61,8 @@ export const useSaveWorkflowAs: UseSaveWorkflowAs = () => {
|
||||
dispatch(workflowNameChanged(data.workflow.name));
|
||||
dispatch(workflowCategoryChanged(data.workflow.meta.category));
|
||||
dispatch(workflowSaved());
|
||||
// When a workflow is saved, the form field initial values are updated to the current form field values
|
||||
dispatch(formFieldInitialValuesChanged({ formFieldInitialValues: getFormFieldInitialValues() }));
|
||||
dispatch(newWorkflowSaved({ category }));
|
||||
|
||||
onSuccess && onSuccess();
|
||||
@@ -80,7 +86,7 @@ export const useSaveWorkflowAs: UseSaveWorkflowAs = () => {
|
||||
}
|
||||
}
|
||||
},
|
||||
[toast, createWorkflow, dispatch, t]
|
||||
[toast, t, createWorkflow, dispatch, getFormFieldInitialValues]
|
||||
);
|
||||
return {
|
||||
saveWorkflowAs,
|
||||
|
||||
Reference in New Issue
Block a user