Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nodes/ui): refactor field types, workflows #5157

Closed

Conversation

psychedelicious
Copy link
Collaborator

@psychedelicious psychedelicious commented Nov 24, 2023

What type of PR is this? (check all applicable)

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update
  • Community Node Submission

Have you discussed this change with the InvokeAI team?

  • Yes
  • No, because:

Have you updated all relevant documentation?

  • Yes - at least, I have added many comments to what was previously unintelligible code
  • No

Description

This PR is a substantial refactor of the frontend workflows logic.

There are some minimal changes to python code to support the frontend changes.

Python Changes

  • Deprecate most UIType enum members, which are no longer needed. If a
    deprecated type is used, a warning will be logged and the type ignored.
  • Deprecate the default_factory arg to InputField() and OutputField(). If
    this arg is used, the callback will be called during invocation registration,
    and set as the default value. A warning will be logged.
    • This doesn't change anything in the core application - in the cases where a
      callback could actually do something (like a random seed), we actually
      always provided a valid value directly, so the callbacks were never actually
      called during graph execution.
    • In the future, we can explore restoring this functionality.
  • Organization/renaming of helper classes (e.g. _InputField ->
    InputJSONSchemaExtra)
  • Handle edge cases for field validation during invocation registration
  • Surface the module name of custom nodes to the frontend as "node_pack"
  • Improved/added comments throughout

Changes outside baseinvocation.py are minor and support these changes.

Frontend Changes

The main frontend change is a refactor of fields, workflows and their types.

This involved renaming and shuffling many files and so there are a ton of small
changes throughout the code with new names or import paths.

Fields

The biggest changes are to fields, particularly field types.

Field Types

Previously, field types were strings. For example, integers would be
"integer", "IntegerCollection" or "IntegerPolymorphic".

Adding a new field type was a major pain, and there was a lot of fiddly logic
related to edge validation.

Field types are now structured. Here's integer:

type T = {
  name: 'IntegerField'; // literal
  isCollection: boolean;
  isPolymorphic: boolean;
};
Stateful and Stateless Field Types

Fields are now conceptually categorized as stateful or stateless.

Stateful fields are things like integers, strings or images. They have state
(value) in the frontend, and UI components allowing users to change their value.

Stateless fields are things like UNetField or ControlField. The user cannot
directly change these fields - they are created and consumed only in python - so
they have no UI component.

The signature of a stateless field type is a wider version of a stateful field:

type T = {
  name: string;
  isCollection: boolean;
  isPolymorphic: boolean;
};

The handling of fields is now generalized such that node authors may freely
create pydantic models for their fields. Many "official" field types are
stateless, and all "custom" field types in community nodes are stateless.

Connection validation still works as it did previously.

zod Schemas and Types

All workflow-related schemas and types have been rewritten, see files in
invokeai/frontend/web/src/features/nodes/types/.

Almost everything is now a zod schema with inferred TS types (previously there
was a lot of pure TS). The OpenAPI schema types are still pure TS.

The naming conventions for these things have been revised and made consistent.

There are many small changes in components and hooks resulting from newly named
schemas, types and type-guards.

Other Changes

  • Migration logic for workflows implemented (see
    invokeai/frontend/web/src/features/nodes/util/workflow/migrations.ts)
    • We are now on workflow schema version 2.0.0. v1.0.0's schemas are retained
      in invokeai/frontend/web/src/features/nodes/types/v1/ and used during
      migration.
  • Community nodes' node packs are displayed in the info icon tooltip for a node
  • Improved workflow loading (includes migration, validation, and improved user
    feedback)
  • Improved node updating logic & UI/UX
  • If a node in loaded workflow is missing from the user's install, the node type
    and node pack name will be displayed
  • Linear UI parameter schemas are cleaned up and deduped (was duplication w/
    some field schemas)
  • OpenAPI schema parsing logic simplified, edge cases handled
  • Added many strings to translation system
  • Updated README.md
  • Added WORKFLOWS_DESIGN_IMPLEMENTATION.md, a high-level review of workflows and
    their implementation on frontend
  • A number of minor bug fixes / edge cases
  • Improved/added comments throughout
  • Fixed polymorphic fields not able to be added to workflow linear view

Related Tickets & Documents

QA Instructions, Screenshots, Recordings

Sorry for the size of this. I don't think I could split up the frontend changes into separate PRs without breaking the app - I rewrote a substantial portion of workflow-related logic across the frontend, and a partial implementation would leave it nonfunctional.

The changes are scoped to the workflow editor. Workflows that work on 3.4.0post2 should work in this PR.

You should be able to add custom fields. I made a small node pack with custom fields to test.

Everything in the workflow editor should work just like it did before.

@psychedelicious psychedelicious changed the title feat(nodes/ui): refactor field types feat(nodes/ui): refactor field types, workflows Nov 24, 2023
psychedelicious added a commit to psychedelicious/XYGrid_nodes that referenced this pull request Nov 25, 2023
These changes are needed for compatibility with invoke-ai/InvokeAI#5157, though leaving them in won't cause any problems - just a warning.
psychedelicious added a commit to psychedelicious/XYGrid_nodes that referenced this pull request Nov 25, 2023
These changes are needed for compatibility with invoke-ai/InvokeAI#5157, though leaving them in won't cause any problems - just a warning.
@psychedelicious psychedelicious force-pushed the feat/refactor-field-types branch 3 times, most recently from 5f77227 to f12f22a Compare November 27, 2023 01:51
Node authors may now create their own arbitrary/custom field types. Any pydantic model is supported.

Two notes:
1. Your field type's class name must be unique.

Suggest prefixing fields with something related to the node pack as a kind of namespace.

2. Custom field types function as connection-only fields.

For example, if your custom field has string attributes, you will not get a text input for that attribute when you give a node a field with your custom type.

This is the same behaviour as other complex fields that don't have custom UIs in the workflow editor - like, say, a string collection.

feat(ui): fix tooltips for custom types

We need to hold onto the original type of the field so they don't all just show up as "Unknown".

fix(ui): fix ts error with custom fields

feat(ui): custom field types connection validation

In the initial commit, a custom field's original type was added to the *field templates* only as `originalType`. Custom fields' `type` property was `"Custom"`*. This allowed for type safety throughout the UI logic.

*Actually, it was `"Unknown"`, but I changed it to custom for clarity.

Connection validation logic, however, uses the *field instance* of the node/field. Like the templates, *field instances* with custom types have their `type` set to `"Custom"`, but they didn't have an `originalType` property. As a result, all custom fields could be connected to all other custom fields.

To resolve this, we need to add `originalType` to the *field instances*, then switch the validation logic to use this instead of `type`.

This ended up needing a bit of fanagling:

- If we make `originalType` a required property on field instances, existing workflows will break during connection validation, because they won't have this property. We'd need a new layer of logic to migrate the workflows, adding the new `originalType` property.

While this layer is probably needed anyways, typing `originalType` as optional is much simpler. Workflow migration logic can come layer.

(Technically, we could remove all references to field types from the workflow files, and let the templates hold all this information. This feels like a significant change and I'm reluctant to do it now.)

- Because `originalType` is optional, anywhere we care about the type of a field, we need to use it over `type`. So there are a number of `field.originalType ?? field.type` expressions. This is a bit of a gotcha, we'll need to remember this in the future.

- We use `Array.prototype.includes()` often in the workflow editor, e.g. `COLLECTION_TYPES.includes(type)`. In these cases, the const array is of type `FieldType[]`, and `type` is is `FieldType`.

Because we now support custom types, the arg `type` is now widened from `FieldType` to `string`.

This causes a TS error. This behaviour is somewhat controversial (see microsoft/TypeScript#14520). These expressions are now rewritten as `COLLECTION_TYPES.some((t) => t === type)` to satisfy TS. It's logically equivalent.

fix(ui): typo

feat(ui): add CustomCollection and CustomPolymorphic field types

feat(ui): add validation for CustomCollection & CustomPolymorphic types

- Update connection validation for custom types
- Use simple string parsing to determine if a field is a collection or polymorphic type.
- No longer need to keep a list of collection and polymorphic types.
- Added runtime checks in `baseinvocation.py` to ensure no fields are named in such a way that it could mess up the new parsing

chore(ui): remove errant console.log

fix(ui): rename 'nodes.currentConnectionFieldType' -> 'nodes.connectionStartFieldType'

This was confusingly named and kept tripping me up. Renamed to be consistent with the `reactflow` `ConnectionStartParams` type.

fix(ui): fix ts error

feat(nodes): add runtime check for custom field names

"Custom", "CustomCollection" and "CustomPolymorphic" are reserved field names.

chore(ui): add TODO for revising field type names

wip refactor fieldtype structured

wip refactor field types

wip refactor types

wip refactor types

fix node layout

refactor field types

chore: mypy

organisation

organisation

organisation

fix(nodes): fix field orig_required, field_kind and input statuses

feat(nodes): remove broken implementation of default_factory on InputField

Use of this could break connection validation due to the difference in node schemas required fields and invoke() required args.

Removed entirely for now. It wasn't ever actually used by the system, because all graphs always had values provided for fields where default_factory was used.

Also, pydantic is smart enough to not reuse the same object when specifying a default value - it clones the object first. So, the common pattern of `default_factory=list` is extraneous. It can just be `default=[]`.

fix(nodes): fix InputField name validation

workflow validation

validation

chore: ruff

feat(nodes): fix up baseinvocation comments

fix(ui): improve typing & logic of buildFieldInputTemplate

improved error handling in parseFieldType

fix: back compat for deprecated default_factory and UIType

feat(nodes): do not show node packs loaded log if none loaded

chore(ui): typegen
When a node is updated with new fields and workflow needs to be updated, the fields now display "Unknown input/output: FieldName".
We can use the autogenerated types to avoid types
Custom nodes have a new attribute `node_pack` indicating the node pack they came from.

- This is displayed in the UI in the icon icon tooltip.
- If a workflow is loaded and a node is unavailable, its node pack will be displayed (if it is known).
- If a workflow is migrated from v1 to v2, and the node is unknown, it falls back to "Unknown". If the missing node pack is installed and the node is updated, the node pack will be updated as expected.
@psychedelicious psychedelicious marked this pull request as draft November 27, 2023 02:52
@psychedelicious psychedelicious marked this pull request as ready for review November 27, 2023 04:53
@psychedelicious
Copy link
Collaborator Author

I have been working from my fork, which prevents me from stacking another PR on this one.

I've remade this PR in #5175, which merges from origin instead of my fork. Closing in favor of #5175.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[bug]: unable to add a cfg field to the linear ui from the workflow editor
1 participant