diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json
index ab10276491..404c2013e4 100644
--- a/invokeai/frontend/web/public/locales/en.json
+++ b/invokeai/frontend/web/public/locales/en.json
@@ -702,9 +702,9 @@
},
"nodes": {
"reloadSchema": "Reload Schema",
- "saveNodes": "Save Nodes",
- "loadNodes": "Load Nodes",
- "clearNodes": "Clear Nodes",
+ "saveGraph": "Save Graph",
+ "loadGraph": "Load Graph (saved from Node Editor) (Do not copy-paste metadata)",
+ "clearGraph": "Clear Graph",
"zoomInNodes": "Zoom In",
"zoomOutNodes": "Zoom Out",
"fitViewportNodes": "Fit View",
diff --git a/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx
index 90f8039285..21076e16f5 100644
--- a/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx
@@ -2,11 +2,11 @@ import { HStack } from '@chakra-ui/react';
import CancelButton from 'features/parameters/components/ProcessButtons/CancelButton';
import { memo } from 'react';
import { Panel } from 'reactflow';
-import LoadNodesButton from '../ui/LoadNodesButton';
+import ClearGraphButton from '../ui/ClearGraphButton';
+import LoadGraphButton from '../ui/LoadGraphButton';
import NodeInvokeButton from '../ui/NodeInvokeButton';
import ReloadSchemaButton from '../ui/ReloadSchemaButton';
-import SaveNodesButton from '../ui/SaveNodesButton';
-import ClearNodesButton from '../ui/ClearNodesButton';
+import SaveGraphButton from '../ui/SaveGraphButton';
const TopCenterPanel = () => {
return (
@@ -15,9 +15,9 @@ const TopCenterPanel = () => {
-
-
-
+
+
+
);
diff --git a/invokeai/frontend/web/src/features/nodes/components/ui/ClearNodesButton.tsx b/invokeai/frontend/web/src/features/nodes/components/ui/ClearGraphButton.tsx
similarity index 87%
rename from invokeai/frontend/web/src/features/nodes/components/ui/ClearNodesButton.tsx
rename to invokeai/frontend/web/src/features/nodes/components/ui/ClearGraphButton.tsx
index 86d9d08a84..88fb60ee0f 100644
--- a/invokeai/frontend/web/src/features/nodes/components/ui/ClearNodesButton.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/ui/ClearGraphButton.tsx
@@ -9,17 +9,17 @@ import {
Text,
useDisclosure,
} from '@chakra-ui/react';
-import { makeToast } from 'features/system/util/makeToast';
import { RootState } from 'app/store/store';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton';
import { nodeEditorReset } from 'features/nodes/store/nodesSlice';
import { addToast } from 'features/system/store/systemSlice';
-import { memo, useRef, useCallback } from 'react';
+import { makeToast } from 'features/system/util/makeToast';
+import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { FaTrash } from 'react-icons/fa';
-const ClearNodesButton = () => {
+const ClearGraphButton = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { isOpen, onOpen, onClose } = useDisclosure();
@@ -46,8 +46,8 @@ const ClearNodesButton = () => {
<>
}
- tooltip={t('nodes.clearNodes')}
- aria-label={t('nodes.clearNodes')}
+ tooltip={t('nodes.clearGraph')}
+ aria-label={t('nodes.clearGraph')}
onClick={onOpen}
isDisabled={nodes.length === 0}
/>
@@ -62,11 +62,11 @@ const ClearNodesButton = () => {
- {t('nodes.clearNodes')}
+ {t('nodes.clearGraph')}
- {t('common.clearNodes')}
+ {t('common.clearGraph')}
@@ -83,4 +83,4 @@ const ClearNodesButton = () => {
);
};
-export default memo(ClearNodesButton);
+export default memo(ClearGraphButton);
diff --git a/invokeai/frontend/web/src/features/nodes/components/ui/LoadNodesButton.tsx b/invokeai/frontend/web/src/features/nodes/components/ui/LoadGraphButton.tsx
similarity index 82%
rename from invokeai/frontend/web/src/features/nodes/components/ui/LoadNodesButton.tsx
rename to invokeai/frontend/web/src/features/nodes/components/ui/LoadGraphButton.tsx
index 2aa369bc11..437418e18a 100644
--- a/invokeai/frontend/web/src/features/nodes/components/ui/LoadNodesButton.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/ui/LoadGraphButton.tsx
@@ -13,17 +13,28 @@ interface JsonFile {
[key: string]: unknown;
}
-function validateInvokeAIGraph(jsonFile: JsonFile): boolean {
+function sanityCheckInvokeAIGraph(jsonFile: JsonFile): boolean {
const keys = ['nodes', 'edges', 'viewport'];
for (const key of keys) {
if (!(key in jsonFile)) {
return false;
}
}
+
+ if (!Array.isArray(jsonFile.nodes) || !Array.isArray(jsonFile.edges)) {
+ return false;
+ }
+
+ for (const node of jsonFile.nodes) {
+ if (!('data' in node)) {
+ return false;
+ }
+ }
+
return true;
}
-const LoadNodesButton = () => {
+const LoadGraphButton = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { fitView } = useReactFlow();
@@ -39,9 +50,9 @@ const LoadNodesButton = () => {
try {
const retrievedNodeTree = await JSON.parse(String(json));
- const isValidNodeTree = validateInvokeAIGraph(retrievedNodeTree);
+ const isSaneNodeTree = sanityCheckInvokeAIGraph(retrievedNodeTree);
- if (isValidNodeTree) {
+ if (isSaneNodeTree) {
dispatch(loadFileNodes(retrievedNodeTree.nodes));
dispatch(loadFileEdges(retrievedNodeTree.edges));
fitView();
@@ -93,8 +104,8 @@ const LoadNodesButton = () => {
{(props) => (
}
- tooltip={t('nodes.loadNodes')}
- aria-label={t('nodes.loadNodes')}
+ tooltip={t('nodes.loadGraph')}
+ aria-label={t('nodes.loadGraph')}
{...props}
/>
)}
@@ -102,4 +113,4 @@ const LoadNodesButton = () => {
);
};
-export default memo(LoadNodesButton);
+export default memo(LoadGraphButton);
diff --git a/invokeai/frontend/web/src/features/nodes/components/ui/SaveNodesButton.tsx b/invokeai/frontend/web/src/features/nodes/components/ui/SaveGraphButton.tsx
similarity index 90%
rename from invokeai/frontend/web/src/features/nodes/components/ui/SaveNodesButton.tsx
rename to invokeai/frontend/web/src/features/nodes/components/ui/SaveGraphButton.tsx
index 5833182456..42e545258e 100644
--- a/invokeai/frontend/web/src/features/nodes/components/ui/SaveNodesButton.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/ui/SaveGraphButton.tsx
@@ -6,7 +6,7 @@ import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { FaSave } from 'react-icons/fa';
-const SaveNodesButton = () => {
+const SaveGraphButton = () => {
const { t } = useTranslation();
const editorInstance = useAppSelector(
(state: RootState) => state.nodes.editorInstance
@@ -37,12 +37,12 @@ const SaveNodesButton = () => {
}
fontSize={18}
- tooltip={t('nodes.saveNodes')}
- aria-label={t('nodes.saveNodes')}
+ tooltip={t('nodes.saveGraph')}
+ aria-label={t('nodes.saveGraph')}
onClick={saveEditorToJSON}
isDisabled={nodes.length === 0}
/>
);
};
-export default memo(SaveNodesButton);
+export default memo(SaveGraphButton);