feat(ui): organize node utils

This commit is contained in:
psychedelicious
2023-11-27 00:00:44 +11:00
parent 4309f3bd58
commit 8d99113bef
60 changed files with 106 additions and 95 deletions

View File

@ -4,7 +4,7 @@ import { cloneDeep, omit, reduce } from 'lodash-es';
import { Graph } from 'services/api/types';
import { AnyInvocation } from 'services/events/types';
import { v4 as uuidv4 } from 'uuid';
import { buildWorkflow } from '../buildWorkflow';
import { buildWorkflow } from '../workflow/buildWorkflow';
import {
FieldInputInstance,
isColorFieldInputInstance,

View File

@ -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;
};

View File

@ -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;
};

View File

@ -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;
};

View File

@ -1,5 +1,5 @@
import { isNil } from 'lodash-es';
import { FieldInputTemplate, FieldOutputTemplate } from '../types/field';
import { FieldInputTemplate, FieldOutputTemplate } from '../../types/field';
export const getSortedFilteredFieldNames = (
fields: FieldInputTemplate[] | FieldOutputTemplate[]

View File

@ -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;
};

View File

@ -1,5 +1,5 @@
import { get } from 'lodash-es';
import { FieldInputInstance, FieldInputTemplate } from '../types/field';
import { FieldInputInstance, FieldInputTemplate } from '../../types/field';
const FIELD_VALUE_FALLBACK_MAP = {
EnumField: '',

View File

@ -22,8 +22,8 @@ import {
T2IAdapterModelFieldInputTemplate,
VAEModelFieldInputTemplate,
isStatefulFieldType,
} from '../types/field';
import { InvocationFieldSchema } from '../types/openapi';
} from '../../types/field';
import { InvocationFieldSchema } from '../../types/openapi';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type FieldInputTemplateBuilder<T extends FieldInputTemplate = any> = // valid `any`!

View File

@ -1,6 +1,6 @@
import { startCase } from 'lodash-es';
import { FieldOutputTemplate, FieldType } from '../types/field';
import { InvocationFieldSchema } from '../types/openapi';
import { FieldOutputTemplate, FieldType } from '../../types/field';
import { InvocationFieldSchema } from '../../types/openapi';
export const buildFieldOutputTemplate = (
fieldSchema: InvocationFieldSchema,

View File

@ -1,8 +1,11 @@
import { t } from 'i18next';
import { isArray } from 'lodash-es';
import { OpenAPIV3_1 } from 'openapi-types';
import { FieldTypeParseError, UnsupportedFieldTypeError } from '../types/error';
import { FieldType } from '../types/field';
import {
FieldTypeParseError,
UnsupportedFieldTypeError,
} from '../../types/error';
import { FieldType } from '../../types/field';
import {
OpenAPIV3_1SchemaOrRef,
isArraySchemaObject,
@ -10,7 +13,7 @@ import {
isNonArraySchemaObject,
isRefObject,
isSchemaObject,
} from '../types/openapi';
} from '../../types/openapi';
/**
* Transforms an invocation output ref object to field type.

View File

@ -3,15 +3,18 @@ import { parseify } from 'common/util/serialize';
import { t } from 'i18next';
import { reduce } from 'lodash-es';
import { OpenAPIV3_1 } from 'openapi-types';
import { FieldTypeParseError, UnsupportedFieldTypeError } from '../types/error';
import { FieldInputTemplate, FieldOutputTemplate } from '../types/field';
import { InvocationTemplate } from '../types/invocation';
import {
FieldTypeParseError,
UnsupportedFieldTypeError,
} from '../../types/error';
import { FieldInputTemplate, FieldOutputTemplate } from '../../types/field';
import { InvocationTemplate } from '../../types/invocation';
import {
InvocationSchemaObject,
isInvocationFieldSchema,
isInvocationOutputSchemaObject,
isInvocationSchemaObject,
} from '../types/openapi';
} from '../../types/openapi';
import { buildFieldInputTemplate } from './buildFieldInputTemplate';
import { buildFieldOutputTemplate } from './buildFieldOutputTemplate';
import { parseFieldType } from './parseFieldType';

View File

@ -1,6 +1,6 @@
import { logger } from 'app/logging/logger';
import { NodesState } from '../store/types';
import { WorkflowV2, zWorkflowEdge, zWorkflowNode } from '../types/workflow';
import { NodesState } from '../../store/types';
import { WorkflowV2, zWorkflowEdge, zWorkflowNode } from '../../types/workflow';
import { fromZodError } from 'zod-validation-error';
import { parseify } from 'common/util/serialize';
import i18n from 'i18next';

View File

@ -2,10 +2,10 @@ import { parseify } from 'common/util/serialize';
import { t } from 'i18next';
import { keyBy } from 'lodash-es';
import { JsonObject } from 'type-fest';
import { getNeedsUpdate } from '../store/util/nodeUpdate';
import { InvocationTemplate } from '../types/invocation';
import { parseAndMigrateWorkflow } from '../types/migration/migrations';
import { WorkflowV2, isWorkflowInvocationNode } from '../types/workflow';
import { getNeedsUpdate } from '../node/nodeUpdate';
import { InvocationTemplate } from '../../types/invocation';
import { parseAndMigrateWorkflow } from '../../types/migration/migrations';
import { WorkflowV2, isWorkflowInvocationNode } from '../../types/workflow';
type WorkflowWarning = {
message: string;