mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
Compare commits
8 Commits
psyche/cho
...
lstein/doc
Author | SHA1 | Date | |
---|---|---|---|
be48323a06 | |||
8ecf72838d | |||
c3ab8a6aa8 | |||
1931aa3e70 | |||
d3d8055055 | |||
476b0a0403 | |||
f66584713c | |||
33624fc2fa |
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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",
|
||||
|
5370
invokeai/frontend/web/pnpm-lock.yaml
generated
5370
invokeai/frontend/web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -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`);
|
||||
}
|
||||
|
||||
|
@ -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',
|
||||
|
@ -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';
|
||||
|
@ -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', {
|
||||
|
@ -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}
|
||||
|
@ -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 };
|
||||
|
||||
|
@ -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),
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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];
|
||||
|
@ -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);
|
@ -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;
|
||||
|
@ -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 (
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 };
|
||||
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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 = () => {
|
||||
|
@ -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 = () => {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
|
@ -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 => {
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { Connection, Edge } from '@xyflow/react';
|
||||
import type { Connection, Edge } from 'reactflow';
|
||||
import { assert } from 'tsafe';
|
||||
|
||||
/**
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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';
|
||||
|
@ -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 => {
|
||||
|
@ -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 => {
|
||||
|
@ -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 => {
|
||||
|
@ -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
Reference in New Issue
Block a user