Compare commits

..

8 Commits

Author SHA1 Message Date
be48323a06 add a new FAQ for converting safetensors 2024-08-10 18:03:18 -04:00
8ecf72838d fix(api): image downloads with correct filename
Closes #6730
2024-08-10 09:53:56 -04:00
c3ab8a6aa8 chore(ui): bump rest of deps 2024-08-10 07:45:23 -04:00
1931aa3e70 chore(ui): typegen 2024-08-10 07:45:23 -04:00
d3d8055055 feat(ui): update typegen script 2024-08-10 07:45:23 -04:00
476b0a0403 chore(ui): bump openapi-typescript 2024-08-10 07:45:23 -04:00
f66584713c fix(api): sort OpenAPI schema properties for InvocationOutputMap
This makes the schema output deterministic!
2024-08-10 07:45:23 -04:00
33624fc2fa fix(api): duplicate operation id for get_image_full
There's a FastAPI bug that results in the OpenAPI spec outputting the same operation id for each operation when specifying multiple HTTP methods.

- Discussion: https://github.com/tiangolo/fastapi/discussions/8449
- Pending PR to fix: https://github.com/tiangolo/fastapi/pull/10694

In our case, we have a `get_image_full` endpoint that handles GET and HEAD.

This results in an invalid OpenAPI schema. A workaround is to use two route decorators for the operation handler. This works as expected - HEAD requests get the header, and GET requests get the resource. And the OpenAPI schema is valid.
2024-08-10 07:45:23 -04:00
51 changed files with 20640 additions and 18988 deletions

View File

@ -299,7 +299,7 @@ Migration logic is in [migrations.ts].
[pydantic]: https://github.com/pydantic/pydantic 'pydantic'
[zod]: https://github.com/colinhacks/zod 'zod'
[openapi-types]: https://github.com/kogosoftwarellc/open-api/tree/main/packages/openapi-types 'openapi-types'
[reactflow]: https://github.com/xyflow/xyflow '@xyflow/react'
[reactflow]: https://github.com/xyflow/xyflow 'reactflow'
[reactflow-concepts]: https://reactflow.dev/learn/concepts/terms-and-definitions
[reactflow-events]: https://reactflow.dev/api-reference/react-flow#event-handlers
[buildWorkflow.ts]: https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/src/features/nodes/util/workflow/buildWorkflow.ts

View File

@ -196,6 +196,22 @@ tips to reduce the problem:
=== "12GB VRAM GPU"
This should be sufficient to generate larger images up to about 1280x1280.
## Checkpoint Models Load Slowly or Use Too Much RAM
The difference between diffusers models (a folder containing multiple
subfolders) and checkpoint models (a file ending with .safetensors or
.ckpt) is that InvokeAI is able to load diffusers models into memory
incrementally, while checkpoint models must be loaded all at
once. With very large models, or systems with limited RAM, you may
experience slowdowns and other memory-related issues when loading
checkpoint models.
To solve this, go to the Model Manager tab (the cube), select the
checkpoint model that's giving you trouble, and press the "Convert"
button in the upper right of your browser window. This will conver the
checkpoint into a diffusers model, after which loading should be
faster and less memory-intensive.
## Memory Leak (Linux)

View File

@ -218,9 +218,8 @@ async def get_image_workflow(
raise HTTPException(status_code=404)
@images_router.api_route(
@images_router.get(
"/i/{image_name}/full",
methods=["GET", "HEAD"],
operation_id="get_image_full",
response_class=Response,
responses={
@ -231,6 +230,18 @@ async def get_image_workflow(
404: {"description": "Image not found"},
},
)
@images_router.head(
"/i/{image_name}/full",
operation_id="get_image_full_head",
response_class=Response,
responses={
200: {
"description": "Return the full-resolution image",
"content": {"image/png": {}},
},
404: {"description": "Image not found"},
},
)
async def get_image_full(
image_name: str = Path(description="The name of full-resolution image file to get"),
) -> Response:
@ -242,6 +253,7 @@ async def get_image_full(
content = f.read()
response = Response(content, media_type="image/png")
response.headers["Cache-Control"] = f"max-age={IMAGE_MAX_AGE}"
response.headers["Content-Disposition"] = f'inline; filename="{image_name}"'
return response
except Exception:
raise HTTPException(status_code=404)

View File

@ -81,7 +81,7 @@ def get_openapi_func(
# Add the output map to the schema
openapi_schema["components"]["schemas"]["InvocationOutputMap"] = {
"type": "object",
"properties": invocation_output_map_properties,
"properties": dict(sorted(invocation_output_map_properties.items())),
"required": invocation_output_map_required,
}

View File

@ -53,61 +53,61 @@
},
"dependencies": {
"@chakra-ui/react-use-size": "^2.1.0",
"@dagrejs/dagre": "^1.1.2",
"@dagrejs/graphlib": "^2.2.2",
"@dagrejs/dagre": "^1.1.3",
"@dagrejs/graphlib": "^2.2.3",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/sortable": "^8.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fontsource-variable/inter": "^5.0.18",
"@fontsource-variable/inter": "^5.0.20",
"@invoke-ai/ui-library": "^0.0.25",
"@nanostores/react": "^0.7.2",
"@nanostores/react": "^0.7.3",
"@reduxjs/toolkit": "2.2.3",
"@roarr/browser-log-writer": "^1.3.0",
"@xyflow/react": "^12.0.4",
"chakra-react-select": "^4.7.6",
"compare-versions": "^6.1.0",
"chakra-react-select": "^4.9.1",
"compare-versions": "^6.1.1",
"dateformat": "^5.0.3",
"fracturedjsonjs": "^4.0.1",
"framer-motion": "^11.1.8",
"i18next": "^23.11.3",
"i18next-http-backend": "^2.5.1",
"fracturedjsonjs": "^4.0.2",
"framer-motion": "^11.3.24",
"i18next": "^23.12.2",
"i18next-http-backend": "^2.5.2",
"idb-keyval": "^6.2.1",
"jsondiffpatch": "^0.6.0",
"konva": "^9.3.6",
"konva": "^9.3.14",
"lodash-es": "^4.17.21",
"nanostores": "^0.10.3",
"nanostores": "^0.11.2",
"new-github-issue-url": "^1.0.0",
"overlayscrollbars": "^2.7.3",
"overlayscrollbars": "^2.10.0",
"overlayscrollbars-react": "^0.5.6",
"query-string": "^9.0.0",
"query-string": "^9.1.0",
"react": "^18.3.1",
"react-colorful": "^5.6.1",
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.3",
"react-error-boundary": "^4.0.13",
"react-hook-form": "^7.51.4",
"react-hook-form": "^7.52.2",
"react-hotkeys-hook": "4.5.0",
"react-i18next": "^14.1.1",
"react-icons": "^5.2.0",
"react-i18next": "^14.1.3",
"react-icons": "^5.2.1",
"react-konva": "^18.2.10",
"react-redux": "9.1.2",
"react-resizable-panels": "^2.0.19",
"react-resizable-panels": "^2.0.23",
"react-select": "5.8.0",
"react-use": "^17.5.0",
"react-virtuoso": "^4.7.10",
"react-use": "^17.5.1",
"react-virtuoso": "^4.9.0",
"reactflow": "^11.11.4",
"redux-dynamic-middlewares": "^2.2.0",
"redux-remember": "^5.1.0",
"redux-undo": "^1.1.0",
"rfdc": "^1.3.1",
"rfdc": "^1.4.1",
"roarr": "^7.21.1",
"serialize-error": "^11.0.3",
"socket.io-client": "^4.7.5",
"use-debounce": "^10.0.0",
"use-debounce": "^10.0.2",
"use-device-pixel-ratio": "^1.1.2",
"use-image": "^1.1.1",
"uuid": "^9.0.1",
"zod": "^3.23.6",
"zod-validation-error": "^3.2.0"
"uuid": "^10.0.0",
"zod": "^3.23.8",
"zod-validation-error": "^3.3.1"
},
"peerDependencies": {
"@chakra-ui/react": "^2.8.2",
@ -118,38 +118,38 @@
"devDependencies": {
"@invoke-ai/eslint-config-react": "^0.0.14",
"@invoke-ai/prettier-config-react": "^0.0.7",
"@storybook/addon-essentials": "^8.0.10",
"@storybook/addon-interactions": "^8.0.10",
"@storybook/addon-links": "^8.0.10",
"@storybook/addon-storysource": "^8.0.10",
"@storybook/manager-api": "^8.0.10",
"@storybook/react": "^8.0.10",
"@storybook/react-vite": "^8.0.10",
"@storybook/theming": "^8.0.10",
"@storybook/addon-essentials": "^8.2.8",
"@storybook/addon-interactions": "^8.2.8",
"@storybook/addon-links": "^8.2.8",
"@storybook/addon-storysource": "^8.2.8",
"@storybook/manager-api": "^8.2.8",
"@storybook/react": "^8.2.8",
"@storybook/react-vite": "^8.2.8",
"@storybook/theming": "^8.2.8",
"@types/dateformat": "^5.0.2",
"@types/lodash-es": "^4.17.12",
"@types/node": "^20.12.10",
"@types/react": "^18.3.1",
"@types/node": "^20.14.15",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@types/uuid": "^9.0.8",
"@vitejs/plugin-react-swc": "^3.6.0",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-react-swc": "^3.7.0",
"@vitest/coverage-v8": "^1.5.0",
"@vitest/ui": "^1.5.0",
"concurrently": "^8.2.2",
"dpdm": "^3.14.0",
"eslint": "^8.57.0",
"eslint-plugin-i18next": "^6.0.3",
"eslint-plugin-i18next": "^6.0.9",
"eslint-plugin-path": "^1.3.0",
"knip": "^5.12.3",
"knip": "^5.27.2",
"openapi-types": "^12.1.3",
"openapi-typescript": "^6.7.5",
"prettier": "^3.2.5",
"openapi-typescript": "^7.3.0",
"prettier": "^3.3.3",
"rollup-plugin-visualizer": "^5.12.0",
"storybook": "^8.0.10",
"storybook": "^8.2.8",
"ts-toolbelt": "^9.6.0",
"tsafe": "^1.6.6",
"typescript": "^5.4.5",
"vite": "^5.2.11",
"tsafe": "^1.7.2",
"typescript": "^5.5.4",
"vite": "^5.4.0",
"vite-plugin-css-injected-by-js": "^3.5.1",
"vite-plugin-dts": "^3.9.1",
"vite-plugin-eslint": "^1.8.1",

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,40 @@
/* eslint-disable no-console */
import fs from 'node:fs';
import openapiTS from 'openapi-typescript';
import openapiTS, { astToString } from 'openapi-typescript';
import ts from 'typescript';
const OPENAPI_URL = 'http://127.0.0.1:9090/openapi.json';
const OUTPUT_FILE = 'src/services/api/schema.ts';
async function generateTypes(schema) {
process.stdout.write(`Generating types ${OUTPUT_FILE}...`);
// Use https://ts-ast-viewer.com to figure out how to create these AST nodes - define a type and use the bottom-left pane's output
// `Blob` type
const BLOB = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Blob'));
// `null` type
const NULL = ts.factory.createLiteralTypeNode(ts.factory.createNull());
// `Record<string, unknown>` type
const RECORD_STRING_UNKNOWN = ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Record'), [
ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
]);
const types = await openapiTS(schema, {
exportType: true,
transform: (schemaObject) => {
if ('format' in schemaObject && schemaObject.format === 'binary') {
return schemaObject.nullable ? 'Blob | null' : 'Blob';
return schemaObject.nullable ? ts.factory.createUnionTypeNode([BLOB, NULL]) : BLOB;
}
if (schemaObject.title === 'MetadataField') {
// This is `Record<string, never>` by default, but it actually accepts any a dict of any valid JSON value.
return 'Record<string, unknown>';
return RECORD_STRING_UNKNOWN;
}
},
defaultNonNullable: false,
});
fs.writeFileSync(OUTPUT_FILE, types);
fs.writeFileSync(OUTPUT_FILE, astToString(types));
process.stdout.write(`\nOK!\r\n`);
}

View File

@ -1,5 +1,4 @@
import { useStore } from '@nanostores/react';
import { getConnectedEdges } from '@xyflow/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import {
@ -23,6 +22,7 @@ import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import i18n from 'i18next';
import { forEach, upperFirst } from 'lodash-es';
import { useMemo } from 'react';
import { getConnectedEdges } from 'reactflow';
const LAYER_TYPE_TO_TKEY: Record<Layer['type'], string> = {
initial_image_layer: 'controlLayers.globalInitialImage',

View File

@ -1,4 +1,4 @@
import '@xyflow/react/dist/style.css';
import 'reactflow/dist/style.css';
import { Flex } from '@invoke-ai/ui-library';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';

View File

@ -1,7 +1,8 @@
import 'reactflow/dist/style.css';
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
import { Combobox, Flex, Popover, PopoverAnchor, PopoverBody, PopoverContent } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import type { EdgeChange, NodeChange } from '@xyflow/react';
import { useAppDispatch, useAppStore } from 'app/store/storeHooks';
import type { SelectInstance } from 'chakra-react-select';
import { useBuildNode } from 'features/nodes/hooks/useBuildNode';
@ -20,7 +21,7 @@ import { findUnoccupiedPosition } from 'features/nodes/store/util/findUnoccupied
import { getFirstValidConnection } from 'features/nodes/store/util/getFirstValidConnection';
import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
import { validateConnectionTypes } from 'features/nodes/store/util/validateConnectionTypes';
import type { AppNode } from 'features/nodes/types/invocation';
import type { AnyNode } from 'features/nodes/types/invocation';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { toast } from 'features/toast/toast';
import { filter, map, memoize, some } from 'lodash-es';
@ -30,6 +31,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import type { HotkeyCallback } from 'react-hotkeys-hook/dist/types';
import { useTranslation } from 'react-i18next';
import type { FilterOptionOption } from 'react-select/dist/declarations/src/filters';
import type { EdgeChange, NodeChange } from 'reactflow';
const createRegex = memoize(
(inputValue: string) =>
@ -118,7 +120,7 @@ const AddNodePopover = () => {
}, [filteredTemplates, pendingConnection, t]);
const addNode = useCallback(
(nodeType: string): AppNode | null => {
(nodeType: string): AnyNode | null => {
const node = buildInvocation(nodeType);
if (!node) {
const errorMessage = t('nodes.unknownNode', {

View File

@ -1,20 +1,5 @@
import { useGlobalMenuClose, useToken } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import type {
EdgeChange,
EdgeTypes,
NodeChange,
NodeTypes,
OnEdgesChange,
OnInit,
OnMoveEnd,
OnNodesChange,
OnReconnect,
ProOptions,
ReactFlowProps,
ReactFlowState,
} from '@xyflow/react';
import { Background, ReactFlow, useStore as useReactFlowStore, useUpdateNodeInternals } from '@xyflow/react';
import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHooks';
import { useConnection } from 'features/nodes/hooks/useConnection';
import { useCopyPaste } from 'features/nodes/hooks/useCopyPaste';
@ -39,23 +24,36 @@ import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
import type { CSSProperties, MouseEvent } from 'react';
import { memo, useCallback, useMemo, useRef } from 'react';
import { useHotkeys } from 'react-hotkeys-hook';
import type {
EdgeChange,
NodeChange,
OnEdgesChange,
OnEdgeUpdateFunc,
OnInit,
OnMoveEnd,
OnNodesChange,
ProOptions,
ReactFlowProps,
ReactFlowState,
} from 'reactflow';
import { Background, ReactFlow, useStore as useReactFlowStore, useUpdateNodeInternals } from 'reactflow';
import CustomConnectionLine from './connectionLines/CustomConnectionLine';
import InvocationCollapsedEdge from './edges/InvocationCollapsedEdge';
import InvocationDefaultEdge from './edges/InvocationDefaultEdge';
import CurrentImageNode from './nodes/CurrentImage/CurrentImageNode';
import InvocationNodeWrapper from './nodes/Invocation/InvocationNodeWrapper';
import { NotesNodeComponent } from './nodes/Notes/NotesNode';
import NotesNode from './nodes/Notes/NotesNode';
const edgeTypes: EdgeTypes = {
const edgeTypes = {
collapsed: InvocationCollapsedEdge,
default: InvocationDefaultEdge,
};
const nodeTypes: NodeTypes = {
const nodeTypes = {
invocation: InvocationNodeWrapper,
current_image: CurrentImageNode,
notes: NotesNodeComponent,
notes: NotesNode,
};
// TODO: can we support reactflow? if not, we could style the attribution so it matches the app
@ -153,13 +151,13 @@ export const Flow = memo(() => {
* where the edge is deleted if you click it accidentally).
*/
const onReconnectStart: NonNullable<ReactFlowProps['onReconnectStart']> = useCallback((e, edge, _handleType) => {
const onEdgeUpdateStart: NonNullable<ReactFlowProps['onEdgeUpdateStart']> = useCallback((e, edge, _handleType) => {
$edgePendingUpdate.set(edge);
$didUpdateEdge.set(false);
$lastEdgeUpdateMouseEvent.set(e);
}, []);
const onReconnect: OnReconnect = useCallback(
const onEdgeUpdate: OnEdgeUpdateFunc = useCallback(
(oldEdge, newConnection) => {
// This event is fired when an edge update is successful
$didUpdateEdge.set(true);
@ -178,7 +176,7 @@ export const Flow = memo(() => {
[dispatch, updateNodeInternals]
);
const onReconnectEnd: NonNullable<ReactFlowProps['onReconnectEnd']> = useCallback(
const onEdgeUpdateEnd: NonNullable<ReactFlowProps['onEdgeUpdateEnd']> = useCallback(
(e, edge, _handleType) => {
const didUpdateEdge = $didUpdateEdge.get();
// Fall back to a reasonable default event
@ -318,9 +316,9 @@ export const Flow = memo(() => {
onMouseMove={onMouseMove}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onReconnect={onReconnect}
onReconnectStart={onReconnectStart}
onReconnectEnd={onReconnectEnd}
onEdgeUpdate={onEdgeUpdate}
onEdgeUpdateStart={onEdgeUpdateStart}
onEdgeUpdateEnd={onEdgeUpdateEnd}
onConnectStart={onConnectStart}
onConnect={onConnect}
onConnectEnd={onConnectEnd}

View File

@ -1,12 +1,12 @@
import { useStore } from '@nanostores/react';
import type { ConnectionLineComponentProps } from '@xyflow/react';
import { getBezierPath } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { $pendingConnection } from 'features/nodes/store/nodesSlice';
import type { CSSProperties } from 'react';
import { memo, useMemo } from 'react';
import type { ConnectionLineComponentProps } from 'reactflow';
import { getBezierPath } from 'reactflow';
const pathStyles: CSSProperties = { opacity: 0.8 };

View File

@ -1,15 +1,13 @@
import { Badge, Flex } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import type { Edge, EdgeProps } from '@xyflow/react';
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
import { getEdgeStyles } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { makeEdgeSelector } from 'features/nodes/components/flow/edges/util/makeEdgeSelector';
import { $templates } from 'features/nodes/store/nodesSlice';
import { memo, useMemo } from 'react';
export type CollapsedEdge = Edge<{ count: number }>;
import type { EdgeProps } from 'reactflow';
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow';
const InvocationCollapsedEdge = ({
sourceX,
@ -25,7 +23,7 @@ const InvocationCollapsedEdge = ({
sourceHandleId,
target,
targetHandleId,
}: EdgeProps<CollapsedEdge>) => {
}: EdgeProps<{ count: number }>) => {
const templates = useStore($templates);
const selector = useMemo(
() => makeEdgeSelector(templates, source, sourceHandleId, target, targetHandleId),

View File

@ -1,11 +1,11 @@
import { Flex, Text } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import type { EdgeProps } from '@xyflow/react';
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { getEdgeStyles } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { $templates } from 'features/nodes/store/nodesSlice';
import { memo, useMemo } from 'react';
import type { EdgeProps } from 'reactflow';
import { BaseEdge, EdgeLabelRenderer, getBezierPath } from 'reactflow';
import { makeEdgeSelector } from './util/makeEdgeSelector';

View File

@ -1,5 +1,4 @@
import { Flex, Image, Text } from '@invoke-ai/ui-library';
import type { NodeProps } from '@xyflow/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import IAIDndImage from 'common/components/IAIDndImage';
@ -14,6 +13,7 @@ import { motion } from 'framer-motion';
import type { CSSProperties, PropsWithChildren } from 'react';
import { memo, useCallback, useState } from 'react';
import { useTranslation } from 'react-i18next';
import type { NodeProps } from 'reactflow';
const selector = createMemoizedSelector(selectSystemSlice, selectGallerySlice, (system, gallery) => {
const imageDTO = gallery.selection[gallery.selection.length - 1];

View File

@ -16,10 +16,10 @@ type Props = {
isOpen: boolean;
label: string;
type: string;
selected?: boolean;
selected: boolean;
};
export const InvocationNodeComponent = memo(({ nodeId, isOpen, label, type, selected = false }: Props) => {
const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
const fieldNames = useFieldNames(nodeId);
const withFooter = useWithFooter(nodeId);
const outputFieldNames = useOutputFieldNames(nodeId);
@ -78,6 +78,6 @@ export const InvocationNodeComponent = memo(({ nodeId, isOpen, label, type, sele
)}
</NodeWrapper>
);
});
};
InvocationNodeComponent.displayName = 'InvocationNodeComponent';
export default memo(InvocationNode);

View File

@ -1,9 +1,9 @@
import { Handle, Position } from '@xyflow/react';
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { map } from 'lodash-es';
import type { CSSProperties } from 'react';
import { memo, useMemo } from 'react';
import { Handle, Position } from 'reactflow';
interface Props {
nodeId: string;

View File

@ -11,10 +11,10 @@ type Props = {
isOpen: boolean;
label: string;
type: string;
selected?: boolean;
selected: boolean;
};
const InvocationNodeUnknownFallback = ({ nodeId, isOpen, label, type, selected = false }: Props) => {
const InvocationNodeUnknownFallback = ({ nodeId, isOpen, label, type, selected }: Props) => {
const { t } = useTranslation();
const nodePack = useNodePack(nodeId);
return (

View File

@ -1,14 +1,14 @@
import { useStore } from '@nanostores/react';
import type { NodeProps } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { InvocationNodeComponent } from 'features/nodes/components/flow/nodes/Invocation/InvocationNodeComponent';
import InvocationNode from 'features/nodes/components/flow/nodes/Invocation/InvocationNode';
import { $templates } from 'features/nodes/store/nodesSlice';
import type { InvocationNode } from 'features/nodes/types/invocation';
import type { InvocationNodeData } from 'features/nodes/types/invocation';
import { memo, useMemo } from 'react';
import type { NodeProps } from 'reactflow';
import InvocationNodeUnknownFallback from './InvocationNodeUnknownFallback';
const InvocationNodeWrapper = (props: NodeProps<InvocationNode>) => {
const InvocationNodeWrapper = (props: NodeProps<InvocationNodeData>) => {
const { data, selected } = props;
const { id: nodeId, type, isOpen, label } = data;
const templates = useStore($templates);
@ -25,7 +25,7 @@ const InvocationNodeWrapper = (props: NodeProps<InvocationNode>) => {
);
}
return <InvocationNodeComponent nodeId={nodeId} isOpen={isOpen} label={label} type={type} selected={selected} />;
return <InvocationNode nodeId={nodeId} isOpen={isOpen} label={label} type={type} selected={selected} />;
};
export default memo(InvocationNodeWrapper);

View File

@ -1,6 +1,4 @@
import { Tooltip } from '@invoke-ai/ui-library';
import type { HandleType } from '@xyflow/react';
import { Handle, Position } from '@xyflow/react';
import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { getFieldColor } from 'features/nodes/components/flow/edges/util/getEdgeColor';
import { useFieldTypeName } from 'features/nodes/hooks/usePrettyFieldType';
@ -10,6 +8,8 @@ import { type FieldInputTemplate, type FieldOutputTemplate, isSingle } from 'fea
import type { CSSProperties } from 'react';
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import type { HandleType } from 'reactflow';
import { Handle, Position } from 'reactflow';
type FieldHandleProps = {
fieldTemplate: FieldInputTemplate | FieldOutputTemplate;

View File

@ -1,15 +1,15 @@
import { Box, Flex, Textarea } from '@invoke-ai/ui-library';
import type { NodeProps } from '@xyflow/react';
import { useAppDispatch } from 'app/store/storeHooks';
import NodeCollapseButton from 'features/nodes/components/flow/nodes/common/NodeCollapseButton';
import NodeTitle from 'features/nodes/components/flow/nodes/common/NodeTitle';
import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper';
import { notesNodeValueChanged } from 'features/nodes/store/nodesSlice';
import type { NotesNode } from 'features/nodes/types/invocation';
import type { NotesNodeData } from 'features/nodes/types/invocation';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
import type { NodeProps } from 'reactflow';
export const NotesNodeComponent = memo((props: NodeProps<NotesNode>) => {
const NotesNode = (props: NodeProps<NotesNodeData>) => {
const { id: nodeId, data, selected } = props;
const { notes, isOpen } = data;
const dispatch = useAppDispatch();
@ -55,6 +55,6 @@ export const NotesNodeComponent = memo((props: NodeProps<NotesNode>) => {
)}
</NodeWrapper>
);
});
};
NotesNodeComponent.displayName = 'NotesNodeComponent';
export default memo(NotesNode);

View File

@ -1,9 +1,9 @@
import { Icon, IconButton } from '@invoke-ai/ui-library';
import { useUpdateNodeInternals } from '@xyflow/react';
import { useAppDispatch } from 'app/store/storeHooks';
import { nodeIsOpenChanged } from 'features/nodes/store/nodesSlice';
import { memo, useCallback } from 'react';
import { PiCaretUpBold } from 'react-icons/pi';
import { useUpdateNodeInternals } from 'reactflow';
interface Props {
nodeId: string;

View File

@ -1,6 +1,5 @@
import type { ChakraProps } from '@invoke-ai/ui-library';
import { Box, useGlobalMenuClose, useToken } from '@invoke-ai/ui-library';
import type { NodeChange } from '@xyflow/react';
import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHooks';
import NodeSelectionOverlay from 'common/components/NodeSelectionOverlay';
import { useExecutionState } from 'features/nodes/hooks/useExecutionState';
@ -10,15 +9,16 @@ import { DRAG_HANDLE_CLASSNAME, NODE_WIDTH } from 'features/nodes/types/constant
import { zNodeStatus } from 'features/nodes/types/invocation';
import type { MouseEvent, PropsWithChildren } from 'react';
import { memo, useCallback } from 'react';
import type { NodeChange } from 'reactflow';
type NodeWrapperProps = PropsWithChildren & {
nodeId: string;
selected?: boolean;
selected: boolean;
width?: ChakraProps['w'];
};
const NodeWrapper = (props: NodeWrapperProps) => {
const { nodeId, width, children, selected = false } = props;
const { nodeId, width, children, selected } = props;
const store = useAppStore();
const { isMouseOverNode, handleMouseOut, handleMouseOver } = useMouseOverNode(nodeId);

View File

@ -1,5 +1,4 @@
import { ButtonGroup, IconButton } from '@invoke-ai/ui-library';
import { useReactFlow } from '@xyflow/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { shouldShowMinimapPanelChanged } from 'features/nodes/store/workflowSettingsSlice';
import { memo, useCallback } from 'react';
@ -10,6 +9,7 @@ import {
PiMagnifyingGlassPlusBold,
PiMapPinBold,
} from 'react-icons/pi';
import { useReactFlow } from 'reactflow';
const ViewportControls = () => {
const { t } = useTranslation();

View File

@ -1,8 +1,8 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { chakra, Flex } from '@invoke-ai/ui-library';
import { MiniMap } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { memo } from 'react';
import { MiniMap } from 'reactflow';
const ChakraMiniMap = chakra(MiniMap);

View File

@ -16,7 +16,6 @@ import {
Switch,
useDisclosure,
} from '@invoke-ai/ui-library';
import { SelectionMode } from '@xyflow/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import ReloadNodeTemplatesButton from 'features/nodes/components/flow/panels/TopRightPanel/ReloadSchemaButton';
@ -32,6 +31,7 @@ import {
import type { ChangeEvent, ReactNode } from 'react';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { SelectionMode } from 'reactflow';
const formLabelProps: FormLabelProps = { flexGrow: 1 };

View File

@ -1,3 +1,5 @@
import 'reactflow/dist/style.css';
import { Flex } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import QueueControls from 'features/queue/components/QueueControls';

View File

@ -1,12 +1,12 @@
import { useStore } from '@nanostores/react';
import { useReactFlow } from '@xyflow/react';
import { $templates } from 'features/nodes/store/nodesSlice';
import { NODE_WIDTH } from 'features/nodes/types/constants';
import type { AppNode, InvocationTemplate } from 'features/nodes/types/invocation';
import type { AnyNode, InvocationTemplate } from 'features/nodes/types/invocation';
import { buildCurrentImageNode } from 'features/nodes/util/node/buildCurrentImageNode';
import { buildInvocationNode } from 'features/nodes/util/node/buildInvocationNode';
import { buildNotesNode } from 'features/nodes/util/node/buildNotesNode';
import { useCallback } from 'react';
import { useReactFlow } from 'reactflow';
export const useBuildNode = () => {
const templates = useStore($templates);
@ -14,7 +14,7 @@ export const useBuildNode = () => {
return useCallback(
// string here is "any invocation type"
(type: string | 'current_image' | 'notes'): AppNode => {
(type: string | 'current_image' | 'notes'): AnyNode => {
let _x = window.innerWidth / 2;
let _y = window.innerHeight / 2;

View File

@ -1,6 +1,4 @@
import { useStore } from '@nanostores/react';
import type { EdgeChange, OnConnect, OnConnectEnd, OnConnectStart } from '@xyflow/react';
import { useUpdateNodeInternals } from '@xyflow/react';
import { useAppStore } from 'app/store/storeHooks';
import { $mouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
import {
@ -14,6 +12,8 @@ import {
import { getFirstValidConnection } from 'features/nodes/store/util/getFirstValidConnection';
import { connectionToEdge } from 'features/nodes/store/util/reactFlowUtil';
import { useCallback, useMemo } from 'react';
import type { EdgeChange, OnConnect, OnConnectEnd, OnConnectStart } from 'reactflow';
import { useUpdateNodeInternals } from 'reactflow';
import { assert } from 'tsafe';
export const useConnection = () => {

View File

@ -1,4 +1,3 @@
import type { EdgeChange, NodeChange } from '@xyflow/react';
import { getStore } from 'app/store/nanostores/store';
import { deepClone } from 'common/util/deepClone';
import {
@ -12,6 +11,7 @@ import {
} from 'features/nodes/store/nodesSlice';
import { findUnoccupiedPosition } from 'features/nodes/store/util/findUnoccupiedPosition';
import { isEqual, uniqWith } from 'lodash-es';
import type { EdgeChange, NodeChange } from 'reactflow';
import { v4 as uuidv4 } from 'uuid';
const copySelection = () => {

View File

@ -1,10 +1,10 @@
// TODO: enable this at some point
import { useStore } from '@nanostores/react';
import type { IsValidConnection } from '@xyflow/react';
import { useAppSelector, useAppStore } from 'app/store/storeHooks';
import { $edgePendingUpdate, $templates } from 'features/nodes/store/nodesSlice';
import { validateConnection } from 'features/nodes/store/util/validateConnection';
import { useCallback } from 'react';
import type { Connection } from 'reactflow';
/**
* NOTE: The logic here must be duplicated in `invokeai/frontend/web/src/features/nodes/store/util/makeIsConnectionValidSelector.ts`
@ -15,8 +15,8 @@ export const useIsValidConnection = () => {
const store = useAppStore();
const templates = useStore($templates);
const shouldValidateGraph = useAppSelector((s) => s.workflowSettings.shouldValidateGraph);
const isValidConnection = useCallback<IsValidConnection>(
({ source, sourceHandle, target, targetHandle }) => {
const isValidConnection = useCallback(
({ source, sourceHandle, target, targetHandle }: Connection): boolean => {
// Connection must have valid targets
if (!(source && sourceHandle && target && targetHandle)) {
return false;

View File

@ -1,9 +1,6 @@
import type { PayloadAction, UnknownAction } from '@reduxjs/toolkit';
import { createSlice, isAnyOf } from '@reduxjs/toolkit';
import type { Edge, EdgeChange, NodeChange, Viewport, XYPosition } from '@xyflow/react';
import { applyEdgeChanges, applyNodeChanges, getConnectedEdges, getIncomers, getOutgoers } from '@xyflow/react';
import type { PersistConfig, RootState } from 'app/store/store';
import type { CollapsedEdge } from 'features/nodes/components/flow/edges/InvocationCollapsedEdge';
import { workflowLoaded } from 'features/nodes/store/actions';
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import type {
@ -49,10 +46,12 @@ import {
zT2IAdapterModelFieldValue,
zVAEModelFieldValue,
} from 'features/nodes/types/field';
import type { AppNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import type { AnyNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import { isInvocationNode, isNotesNode } from 'features/nodes/types/invocation';
import { atom } from 'nanostores';
import type { MouseEvent } from 'react';
import type { Edge, EdgeChange, NodeChange, Viewport, XYPosition } from 'reactflow';
import { applyEdgeChanges, applyNodeChanges, getConnectedEdges, getIncomers, getOutgoers } from 'reactflow';
import type { UndoableOptions } from 'redux-undo';
import type { z } from 'zod';
@ -93,10 +92,10 @@ export const nodesSlice = createSlice({
name: 'nodes',
initialState: initialNodesState,
reducers: {
nodesChanged: (state, action: PayloadAction<NodeChange<AppNode>[]>) => {
state.nodes = applyNodeChanges<AppNode>(action.payload, state.nodes);
nodesChanged: (state, action: PayloadAction<NodeChange[]>) => {
state.nodes = applyNodeChanges(action.payload, state.nodes);
// Remove edges that are no longer valid, due to a removed or otherwise changed node
const edgeChanges: EdgeChange<InvocationNodeEdge | CollapsedEdge>[] = [];
const edgeChanges: EdgeChange[] = [];
state.edges.forEach((e) => {
const sourceExists = state.nodes.some((n) => n.id === e.source);
const targetExists = state.nodes.some((n) => n.id === e.target);
@ -104,10 +103,10 @@ export const nodesSlice = createSlice({
edgeChanges.push({ type: 'remove', id: e.id });
}
});
state.edges = applyEdgeChanges<InvocationNodeEdge | CollapsedEdge>(edgeChanges, state.edges);
state.edges = applyEdgeChanges(edgeChanges, state.edges);
},
edgesChanged: (state, action: PayloadAction<EdgeChange<InvocationNodeEdge | CollapsedEdge>[]>) => {
const changes: EdgeChange<InvocationNodeEdge | CollapsedEdge>[] = [];
edgesChanged: (state, action: PayloadAction<EdgeChange[]>) => {
const changes: EdgeChange[] = [];
// We may need to massage the edge changes or otherwise handle them
action.payload.forEach((change) => {
if (change.type === 'remove' || change.type === 'select') {
@ -135,7 +134,7 @@ export const nodesSlice = createSlice({
}
changes.push(change);
});
state.edges = applyEdgeChanges<InvocationNodeEdge | CollapsedEdge>(changes, state.edges);
state.edges = applyEdgeChanges(changes, state.edges);
},
fieldLabelChanged: (
state,
@ -219,7 +218,7 @@ export const nodesSlice = createSlice({
(node) => isInvocationNode(node) && node.data.isOpen === false
);
const collapsedEdgesToCreate: CollapsedEdge[] = [];
const collapsedEdgesToCreate: Edge<{ count: number }>[] = [];
// hide all edges
connectedEdges.forEach((edge) => {
@ -239,7 +238,7 @@ export const nodesSlice = createSlice({
target: edge.target,
type: 'collapsed',
data: { count: 1 },
reconnectable: false,
updatable: false,
selected: edge.selected,
});
}
@ -260,14 +259,14 @@ export const nodesSlice = createSlice({
target: edge.target,
type: 'collapsed',
data: { count: 1 },
reconnectable: false,
updatable: false,
selected: edge.selected,
});
}
}
});
if (collapsedEdgesToCreate.length) {
state.edges = applyEdgeChanges<CollapsedEdge | InvocationNodeEdge>(
state.edges = applyEdgeChanges(
collapsedEdgesToCreate.map((edge) => ({ type: 'add', item: edge })),
state.edges
);
@ -367,7 +366,7 @@ export const nodesSlice = createSlice({
extraReducers: (builder) => {
builder.addCase(workflowLoaded, (state, action) => {
const { nodes, edges } = action.payload;
state.nodes = applyNodeChanges<AppNode>(
state.nodes = applyNodeChanges(
nodes.map((node) => ({
type: 'add',
item: { ...node, ...SHARED_NODE_PROPERTIES },
@ -417,7 +416,7 @@ export const {
export const $cursorPos = atom<XYPosition | null>(null);
export const $templates = atom<Templates>({});
export const $copiedNodes = atom<AppNode[]>([]);
export const $copiedNodes = atom<AnyNode[]>([]);
export const $copiedEdges = atom<InvocationNodeEdge[]>([]);
export const $edgesToCopiedNodes = atom<InvocationNodeEdge[]>([]);
export const $pendingConnection = atom<PendingConnection | null>(null);

View File

@ -1,5 +1,5 @@
import type { ReactFlowInstance } from '@xyflow/react';
import { atom } from 'nanostores';
import type { ReactFlowInstance } from 'reactflow';
export const $flow = atom<ReactFlowInstance | null>(null);
export const $needsFit = atom<boolean>(true);

View File

@ -1,5 +1,3 @@
import type { HandleType } from '@xyflow/react';
import type { CollapsedEdge } from 'features/nodes/components/flow/edges/InvocationCollapsedEdge';
import type {
FieldIdentifier,
FieldInputTemplate,
@ -7,12 +5,13 @@ import type {
StatefulFieldValue,
} from 'features/nodes/types/field';
import type {
AppNode,
AnyNode,
InvocationNodeEdge,
InvocationTemplate,
NodeExecutionState,
} from 'features/nodes/types/invocation';
import type { WorkflowV3 } from 'features/nodes/types/workflow';
import type { HandleType } from 'reactflow';
export type Templates = Record<string, InvocationTemplate>;
export type NodeExecutionStates = Record<string, NodeExecutionState | undefined>;
@ -26,8 +25,8 @@ export type PendingConnection = {
export type NodesState = {
_version: 1;
nodes: AppNode[];
edges: (InvocationNodeEdge | CollapsedEdge)[];
nodes: AnyNode[];
edges: InvocationNodeEdge[];
};
export type WorkflowMode = 'edit' | 'view';

View File

@ -1,4 +1,4 @@
import type { Node } from '@xyflow/react';
import type { Node } from 'reactflow';
export const findUnoccupiedPosition = (nodes: Node[], x: number, y: number) => {
let newX = x;

View File

@ -1,6 +1,6 @@
import type { Templates } from 'features/nodes/store/types';
import type { FieldType } from 'features/nodes/types/field';
import type { AppNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import type { AnyNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
/**
* Given a collect node, return the type of the items it collects. The graph is traversed to find the first node and
@ -14,7 +14,7 @@ import type { AppNode, InvocationNodeEdge } from 'features/nodes/types/invocatio
*/
export const getCollectItemType = (
templates: Templates,
nodes: AppNode[],
nodes: AnyNode[],
edges: InvocationNodeEdge[],
nodeId: string
): FieldType | null => {

View File

@ -1,9 +1,9 @@
import type { Connection, Edge } from '@xyflow/react';
import type { Templates } from 'features/nodes/store/types';
import { validateConnection } from 'features/nodes/store/util/validateConnection';
import type { FieldInputTemplate, FieldOutputTemplate } from 'features/nodes/types/field';
import type { AppNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import type { AnyNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import { map } from 'lodash-es';
import type { Connection, Edge } from 'reactflow';
/**
*
@ -22,7 +22,7 @@ export const getFirstValidConnection = (
sourceHandle: string | null,
target: string,
targetHandle: string | null,
nodes: AppNode[],
nodes: AnyNode[],
edges: InvocationNodeEdge[],
templates: Templates,
edgePendingUpdate: Edge | null
@ -80,7 +80,7 @@ export const getTargetCandidateFields = (
source: string,
sourceHandle: string,
target: string,
nodes: AppNode[],
nodes: AnyNode[],
edges: Edge[],
templates: Templates,
edgePendingUpdate: Edge | null
@ -116,7 +116,7 @@ export const getSourceCandidateFields = (
target: string,
targetHandle: string,
source: string,
nodes: AppNode[],
nodes: AnyNode[],
edges: Edge[],
templates: Templates,
edgePendingUpdate: Edge | null

View File

@ -1,5 +1,5 @@
import graphlib from '@dagrejs/graphlib';
import type { Edge, Node } from '@xyflow/react';
import type { Edge, Node } from 'reactflow';
/**
* Check if adding an edge between the source and target nodes would create a cycle in the graph.

View File

@ -1,9 +1,9 @@
import type { Edge, HandleType } from '@xyflow/react';
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import type { RootState } from 'app/store/store';
import { selectNodesSlice } from 'features/nodes/store/nodesSlice';
import type { NodesState, PendingConnection, Templates } from 'features/nodes/store/types';
import { buildRejectResult, validateConnection } from 'features/nodes/store/util/validateConnection';
import type { Edge, HandleType } from 'reactflow';
/**
* Creates a selector that validates a pending connection.

View File

@ -1,4 +1,4 @@
import type { Connection, Edge } from '@xyflow/react';
import type { Connection, Edge } from 'reactflow';
import { assert } from 'tsafe';
/**

View File

@ -1,9 +1,10 @@
import type { Templates } from 'features/nodes/store/types';
import type { InvocationNodeEdge, InvocationTemplate } from 'features/nodes/types/invocation';
import type { InvocationTemplate } from 'features/nodes/types/invocation';
import { buildInvocationNode } from 'features/nodes/util/node/buildInvocationNode';
import type { OpenAPIV3_1 } from 'openapi-types';
import type { Edge } from 'reactflow';
export const buildEdge = (source: string, sourceHandle: string, target: string, targetHandle: string): InvocationNodeEdge => ({
export const buildEdge = (source: string, sourceHandle: string, target: string, targetHandle: string): Edge => ({
source,
sourceHandle,
target,

View File

@ -1,10 +1,10 @@
import type { Connection as NullableConnection, Edge } from '@xyflow/react';
import type { Templates } from 'features/nodes/store/types';
import { areTypesEqual } from 'features/nodes/store/util/areTypesEqual';
import { getCollectItemType } from 'features/nodes/store/util/getCollectItemType';
import { getHasCycles } from 'features/nodes/store/util/getHasCycles';
import { validateConnectionTypes } from 'features/nodes/store/util/validateConnectionTypes';
import type { AppNode, InvocationNodeEdge } from 'features/nodes/types/invocation';
import type { AnyNode } from 'features/nodes/types/invocation';
import type { Connection as NullableConnection, Edge } from 'reactflow';
import type { O } from 'ts-toolbelt';
type Connection = O.NonNullable<NullableConnection>;
@ -21,8 +21,8 @@ export type ValidationResult =
type ValidateConnectionFunc = (
connection: Connection,
nodes: AppNode[],
edges: InvocationNodeEdge[],
nodes: AnyNode[],
edges: Edge[],
templates: Templates,
ignoreEdge: Edge | null,
strict?: boolean

View File

@ -1,7 +1,7 @@
import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice } from '@reduxjs/toolkit';
import { SelectionMode } from '@xyflow/react';
import type { PersistConfig, RootState } from 'app/store/store';
import { SelectionMode } from 'reactflow';
type WorkflowSettingsState = {
_version: 1;

View File

@ -1,4 +1,4 @@
import type { Node } from '@xyflow/react';
import type { Node } from 'reactflow';
/**
* How long to wait before showing a tooltip when hovering a field handle.

View File

@ -1,4 +1,4 @@
import type { Edge, Node } from '@xyflow/react';
import type { Edge, Node } from 'reactflow';
import { z } from 'zod';
import { zClassification, zProgressImage } from './common';
@ -59,11 +59,11 @@ type AnyNodeData = z.infer<typeof zAnyNodeData>;
export type InvocationNode = Node<InvocationNodeData, 'invocation'>;
export type NotesNode = Node<NotesNodeData, 'notes'>;
export type CurrentImageNode = Node<CurrentImageNodeData, 'current_image'>;
export type AppNode = Node<AnyNodeData>;
export type AnyNode = Node<AnyNodeData>;
export const isInvocationNode = (node?: AppNode | null): node is InvocationNode =>
export const isInvocationNode = (node?: AnyNode | null): node is InvocationNode =>
Boolean(node && node.type === 'invocation');
export const isNotesNode = (node?: AppNode | null): node is NotesNode => Boolean(node && node.type === 'notes');
export const isNotesNode = (node?: AnyNode | null): node is NotesNode => Boolean(node && node.type === 'notes');
export const isInvocationNodeData = (node?: AnyNodeData | null): node is InvocationNodeData =>
Boolean(node && !['notes', 'current_image'].includes(node.type)); // node.type may be 'notes', 'current_image', or any invocation type
// #endregion

View File

@ -1,5 +1,5 @@
import type * as ReactFlow from '@xyflow/react';
import type { WorkflowCategory, WorkflowV3, XYPosition } from 'features/nodes/types/workflow';
import type * as ReactFlow from 'reactflow';
import type { S } from 'services/api/types';
import type { Equals, Extends } from 'tsafe';
import { assert } from 'tsafe';

View File

@ -1,6 +1,6 @@
import type { XYPosition } from '@xyflow/react';
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import type { CurrentImageNode } from 'features/nodes/types/invocation';
import type { XYPosition } from 'reactflow';
import { v4 as uuidv4 } from 'uuid';
export const buildCurrentImageNode = (position: XYPosition): CurrentImageNode => {

View File

@ -1,9 +1,9 @@
import type { XYPosition } from '@xyflow/react';
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import type { FieldInputInstance } from 'features/nodes/types/field';
import type { InvocationNode, InvocationTemplate } from 'features/nodes/types/invocation';
import { buildFieldInputInstance } from 'features/nodes/util/schema/buildFieldInputInstance';
import { reduce } from 'lodash-es';
import type { XYPosition } from 'reactflow';
import { v4 as uuidv4 } from 'uuid';
export const buildInvocationNode = (position: XYPosition, template: InvocationTemplate): InvocationNode => {

View File

@ -1,6 +1,6 @@
import type { XYPosition } from '@xyflow/react';
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
import type { NotesNode } from 'features/nodes/types/invocation';
import type { XYPosition } from 'reactflow';
import { v4 as uuidv4 } from 'uuid';
export const buildNotesNode = (position: XYPosition): NotesNode => {

View File

@ -1,10 +1,10 @@
import { Box } from '@invoke-ai/ui-library';
import { ReactFlowProvider } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { ImageComparisonDroppable } from 'features/gallery/components/ImageViewer/ImageComparisonDroppable';
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
import NodeEditor from 'features/nodes/components/NodeEditor';
import { memo } from 'react';
import { ReactFlowProvider } from 'reactflow';
const NodesTab = () => {
const mode = useAppSelector((s) => s.workflow.mode);

File diff suppressed because one or more lines are too long