mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): organize node utils
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
|
||||
import { CurrentImageNode } from 'features/nodes/types/invocation';
|
||||
import { XYPosition } from 'reactflow';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export const buildCurrentImageNode = (
|
||||
position: XYPosition
|
||||
): CurrentImageNode => {
|
||||
const nodeId = uuidv4();
|
||||
const node: CurrentImageNode = {
|
||||
...SHARED_NODE_PROPERTIES,
|
||||
id: nodeId,
|
||||
type: 'current_image',
|
||||
position,
|
||||
data: {
|
||||
id: nodeId,
|
||||
type: 'current_image',
|
||||
isOpen: true,
|
||||
label: 'Current Image',
|
||||
},
|
||||
};
|
||||
return node;
|
||||
};
|
||||
@@ -0,0 +1,79 @@
|
||||
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
|
||||
import {
|
||||
FieldInputInstance,
|
||||
FieldOutputInstance,
|
||||
} from 'features/nodes/types/field';
|
||||
import {
|
||||
InvocationNode,
|
||||
InvocationTemplate,
|
||||
} from 'features/nodes/types/invocation';
|
||||
import { buildFieldInputInstance } from 'features/nodes/util/schema/buildFieldInputInstance';
|
||||
import { reduce } from 'lodash-es';
|
||||
import { XYPosition } from 'reactflow';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export const buildInvocationNode = (
|
||||
position: XYPosition,
|
||||
template: InvocationTemplate
|
||||
): InvocationNode => {
|
||||
const nodeId = uuidv4();
|
||||
const { type } = template;
|
||||
|
||||
const inputs = reduce(
|
||||
template.inputs,
|
||||
(inputsAccumulator, inputTemplate, inputName) => {
|
||||
const fieldId = uuidv4();
|
||||
|
||||
const inputFieldValue: FieldInputInstance = buildFieldInputInstance(
|
||||
fieldId,
|
||||
inputTemplate
|
||||
);
|
||||
|
||||
inputsAccumulator[inputName] = inputFieldValue;
|
||||
|
||||
return inputsAccumulator;
|
||||
},
|
||||
{} as Record<string, FieldInputInstance>
|
||||
);
|
||||
|
||||
const outputs = reduce(
|
||||
template.outputs,
|
||||
(outputsAccumulator, outputTemplate, outputName) => {
|
||||
const fieldId = uuidv4();
|
||||
|
||||
const outputFieldValue: FieldOutputInstance = {
|
||||
id: fieldId,
|
||||
name: outputName,
|
||||
type: outputTemplate.type,
|
||||
fieldKind: 'output',
|
||||
};
|
||||
|
||||
outputsAccumulator[outputName] = outputFieldValue;
|
||||
|
||||
return outputsAccumulator;
|
||||
},
|
||||
{} as Record<string, FieldOutputInstance>
|
||||
);
|
||||
|
||||
const node: InvocationNode = {
|
||||
...SHARED_NODE_PROPERTIES,
|
||||
id: nodeId,
|
||||
type: 'invocation',
|
||||
position,
|
||||
data: {
|
||||
id: nodeId,
|
||||
type,
|
||||
version: template.version,
|
||||
label: '',
|
||||
notes: '',
|
||||
isOpen: true,
|
||||
embedWorkflow: false,
|
||||
isIntermediate: type === 'save_image' ? false : true,
|
||||
useCache: template.useCache,
|
||||
inputs,
|
||||
outputs,
|
||||
},
|
||||
};
|
||||
|
||||
return node;
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import { SHARED_NODE_PROPERTIES } from 'features/nodes/types/constants';
|
||||
import { NotesNode } from 'features/nodes/types/invocation';
|
||||
import { XYPosition } from 'reactflow';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
export const buildNotesNode = (position: XYPosition): NotesNode => {
|
||||
const nodeId = uuidv4();
|
||||
const node: NotesNode = {
|
||||
...SHARED_NODE_PROPERTIES,
|
||||
id: nodeId,
|
||||
type: 'notes',
|
||||
position,
|
||||
data: {
|
||||
id: nodeId,
|
||||
isOpen: true,
|
||||
label: 'Notes',
|
||||
notes: '',
|
||||
type: 'notes',
|
||||
},
|
||||
};
|
||||
return node;
|
||||
};
|
||||
@@ -0,0 +1,20 @@
|
||||
import { isNil } from 'lodash-es';
|
||||
import { FieldInputTemplate, FieldOutputTemplate } from '../../types/field';
|
||||
|
||||
export const getSortedFilteredFieldNames = (
|
||||
fields: FieldInputTemplate[] | FieldOutputTemplate[]
|
||||
): string[] => {
|
||||
const visibleFields = fields.filter((field) => !field.ui_hidden);
|
||||
|
||||
// we want explicitly ordered fields to be before unordered fields; split the list
|
||||
const orderedFields = visibleFields
|
||||
.filter((f) => !isNil(f.ui_order))
|
||||
.sort((a, b) => (a.ui_order ?? 0) - (b.ui_order ?? 0));
|
||||
const unorderedFields = visibleFields.filter((f) => isNil(f.ui_order));
|
||||
|
||||
// concat the lists, and return the field names, skipping `is_intermediate`
|
||||
return orderedFields
|
||||
.concat(unorderedFields)
|
||||
.map((f) => f.name)
|
||||
.filter((fieldName) => fieldName !== 'is_intermediate');
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
import { satisfies } from 'compare-versions';
|
||||
import { NodeUpdateError } from 'features/nodes/types/error';
|
||||
import {
|
||||
InvocationNodeData,
|
||||
InvocationTemplate,
|
||||
} from 'features/nodes/types/invocation';
|
||||
import { zParsedSemver } from 'features/nodes/types/semver';
|
||||
import { cloneDeep, defaultsDeep } from 'lodash-es';
|
||||
import { Node } from 'reactflow';
|
||||
import { buildInvocationNode } from './buildInvocationNode';
|
||||
|
||||
export const getNeedsUpdate = (
|
||||
node: Node<InvocationNodeData>,
|
||||
template: InvocationTemplate
|
||||
): boolean => {
|
||||
if (node.data.type !== template.type) {
|
||||
return true;
|
||||
}
|
||||
return node.data.version !== template.version;
|
||||
}; /**
|
||||
* Checks if a node may be updated by comparing its major version with the template's major version.
|
||||
* @param node The node to check.
|
||||
* @param template The invocation template to check against.
|
||||
*/
|
||||
|
||||
export const getMayUpdateNode = (
|
||||
node: Node<InvocationNodeData>,
|
||||
template: InvocationTemplate
|
||||
): boolean => {
|
||||
const needsUpdate = getNeedsUpdate(node, template);
|
||||
if (!needsUpdate || node.data.type !== template.type) {
|
||||
return false;
|
||||
}
|
||||
const templateMajor = zParsedSemver.parse(template.version).major;
|
||||
|
||||
return satisfies(node.data.version, `^${templateMajor}`);
|
||||
}; /**
|
||||
* Updates a node to the latest version of its template:
|
||||
* - Create a new node data object with the latest version of the template.
|
||||
* - Recursively merge new node data object into the node to be updated.
|
||||
*
|
||||
* @param node The node to updated.
|
||||
* @param template The invocation template to update to.
|
||||
* @throws {NodeUpdateError} If the node is not an invocation node.
|
||||
*/
|
||||
|
||||
export const updateNode = (
|
||||
node: Node<InvocationNodeData>,
|
||||
template: InvocationTemplate
|
||||
): Node<InvocationNodeData> => {
|
||||
const mayUpdate = getMayUpdateNode(node, template);
|
||||
|
||||
if (!mayUpdate || node.data.type !== template.type) {
|
||||
throw new NodeUpdateError(`Unable to update node ${node.id}`);
|
||||
}
|
||||
|
||||
// Start with a "fresh" node - just as if the user created a new node of this type
|
||||
const defaults = buildInvocationNode(node.position, template);
|
||||
|
||||
// The updateability of a node, via semver comparison, relies on the this kind of recursive merge
|
||||
// being valid. We rely on the template's major version to be majorly incremented if this kind of
|
||||
// merge would result in an invalid node.
|
||||
const clone = cloneDeep(node);
|
||||
clone.data.version = template.version;
|
||||
defaultsDeep(clone, defaults); // mutates!
|
||||
|
||||
return clone;
|
||||
};
|
||||
Reference in New Issue
Block a user