feat(ui): add Use Cache and Save To Gallery to the form builder, make them connectable - #9456
Conversation
… them connectable The two node footer toggles were the only node controls that could not be added to a workflow's form, because they are node attributes stored on `node.data` rather than input fields in `node.data.inputs`. Add a `node-setting` form element type for them, with an add/remove button and drag handle in the node footer mirroring how node fields are added. The label is editable, since two nodes' "Use Cache" entries would otherwise be indistinguishable in a form. Also expose the underlying fields as connection targets. `BaseInvocation` already declares `is_intermediate` and `use_cache` as pydantic fields, so they only needed `Input.Any` and, for `is_intermediate`, dropping the `_IsIntermediate` ui_type that prevented a BooleanField output from connecting. The frontend now parses them into invocation templates - required for handles and connection validation - but still filters them out of the node's input list and never creates field instances for them. Their value keeps living on the node, so `buildNodesGraph` is unchanged and no workflow migration is needed. Graph execution applies edge values in `GraphExecutionState.next()`, before the cache is consulted and before the output image is saved. Node attribute fields are only reachable on nodes that render a footer, so connections to them are rejected elsewhere - otherwise the edge would have no handle to attach to and, on batch and generator nodes, no effect at all. When an edge drives a setting, the node's checkbox is dropped and the form's toggle is disabled, since the local value is no longer what the node runs with.
The main merge on this branch auto-resolved openapi.json in favor of main, which reverted exactly what the PR changes: use_cache and is_intermediate went back to "input": "direct" / "orig_required": true instead of the connectable "input": "any" / "orig_required": false. That is why openapi-checks was red — the committed schema no longer matched what the code emits. Regenerated with generate_openapi_schema.py + prettier, matching the CI check byte for byte. schema.ts was already correct and is untouched.
JPPhoto
left a comment
There was a problem hiding this comment.
Merge blockers:
invokeai/app/invocations/baseinvocation.py:267: Changes inheritedis_intermediateanduse_cachefrom non-connectable node attributes toInput.Anytargets, changing every invocation schema. No invocation versions change. Effect: version-based workflow migration cannot detect the new input contract. Likelihood: all built-in invocations. Recovery: bump affected invocation versions and add compatibility tests. Test: compare changed OpenAPI input metadata against invocation decorator versions; current diff has no decorator version changes.
Other findings/issues:
-
invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/NodeSettingElementEditMode.tsx:59: Edit mode renders an enableduse_cacheswitch without the admin guard retained inNodeSettingFooterControl. Effect: non-admin multiuser users can change and save the admin-only, process-global cache policy. Likelihood: any shared workflow containing an existinguse_cacheelement. Recovery: admin must reset/remove the setting or clear cache. Test: render as non-admin, click the switch, assert nonodeUseCacheChangeddispatch. -
invokeai/frontend/web/src/features/controlLayers/components/CanvasWorkflowIntegration/WorkflowFormPreview.tsx:245: Everynode-settingis silently returned asnull, while Canvas integration still accepts forms containing these elements. Effect: saved forms show no Use Cache or Save To Gallery control; execution silently uses saved node values. Likelihood: any Canvas-integrated workflow with an ImageField plus node setting. Recovery: edit the workflow node directly or remove the form element. Test: select such a workflow and assert the preview renders the setting control; current branch renders nothing.
Suggestions:
-
Bump every affected invocation version and cover old/new workflow loading.
-
Instead of checking admin status only in the footer, enforce it in shared node-setting edit, view, and add paths.
-
Instead of silently dropping settings in Canvas preview, wire their values into Canvas execution or reject incompatible forms during selection.
Addresses review 4978552086 on invoke-ai#9456. The form builder's edit mode rendered an enabled `use_cache` switch with no admin check, so a shared workflow carrying that element let any multiuser non-admin flip and save the process-global, admin-only cache policy. The check existed twice as an ad-hoc `setting === 'use_cache' && !isAdmin` — in the node footer and in the form's view mode — and was simply missing from edit mode. It now lives in one place, `getIsNodeSettingPermitted` / `useIsNodeSettingPermitted`, which the footer, view mode and edit mode all consult. The add-to-form and drag-into-form paths live inside the footer control, so they are gated by it too. Edit mode keeps the element visible and movable but replaces the switch with a notice, the same treatment a non-applicable setting already gets. The Canvas integration silently returned null for every `node-setting` element while still accepting forms that contain them, so a saved Use Cache or Save To Gallery control was invisible and execution quietly ran with whatever the workflow was saved with. Node settings are node attributes, not inputs, so they cannot ride along in `fieldValues`: the canvas slice now carries a separate `nodeSettingValues` record keyed `"nodeId.setting"`, the preview renders a control for them under the same applicability and permission rules as the editor, and `resolveNodeSettings` applies the result when the executor builds each invocation. `use_cache` overrides are applied for admins only — the slice is persisted per browser, not per user. The canvas output node stays intermediate regardless, since its images go to the staging area, and the preview offers no control that would pretend otherwise. No invocation version bumps. The review asked for one per affected invocation, but a node instance's contract is unchanged: `use_cache` and `is_intermediate` were previously dropped while parsing the schema and are now parsed into the template but explicitly skipped by `buildInvocationNode`, whose keys `updateNode` derives its allowlist from. No field instance existed before and none exists now — the serialized node is byte-identical, and a test on this branch pins that. A bump would mark every node in every existing workflow as needing a no-op update, would not help an older client (which drops an unknown edge either way, and would fail the node update outright on a major bump), and cannot be applied at all to the custom node packs that inherit these fields from `BaseInvocation`. Covered by unit tests for `getIsNodeSettingPermitted` and `resolveNodeSettings`.
Summary
Feature (frontend + backend). Adds the two node footer toggles — Use Cache and Save To Gallery — to the workflow form builder, and makes their underlying fields connectable.
Why
Every other node control can be placed in a workflow's form. These two could not, because they are node attributes: their value lives on
node.data.useCache/node.data.isIntermediate, not innode.data.inputs, so the existingnode-fieldform element cannot address them.How
1. A
node-settingform element (features/nodes/types/workflow.ts)Parallel to
node-field, holding{ nodeId, setting, label }. Added to the form the same way node fields are: a+/−button revealed on hover in the node footer, or by dragging the setting's label into the form. The label is editable on double-click — two nodes' "Use Cache" entries would otherwise be indistinguishable in a form.Rendered as a
Switchto match how BooleanField inputs render. Settings that no longer apply to their node are hidden in view mode and flagged in edit mode;use_cachestays admin-only, mirroring the node footer.2. The fields become connection targets
BaseInvocationalready declares both as ordinary pydantic fields, so the backend change is small: both now declareInput.Any, andis_intermediatedrops itsui_type=_IsIntermediate, which had made it parse as its own field type that no BooleanField output could connect to.The interesting part is that this needs no workflow migration:
node.data.inputsbuildInvocationNodeskips them, andupdateNodederives its allowed keys from therenode.data.useCachebuildNodesGraphstate.edgesand the backend overwrites the literal on resolutionEdge values are applied in
GraphExecutionState.next()before the invocation is handed to the invoker, so a connecteduse_cacheis resolved by the time the cache is consulted, and a connectedis_intermediateby the time the image is saved.3. Guards against edges that would do nothing
The footer hosts the handles, and
useWithFooteronly renders it for executable nodes with a gallery output. Connections to node attribute fields on any other node are rejected — there would be no handle to attach to, andbuildNodesGraphdrops batch and generator nodes entirely.When an edge drives a setting, the node's checkbox is removed (matching how a connected input field renders) and the form's toggle is disabled, since the local value is no longer what the node runs with.
4. Layout
The footer became one row per setting so each connection handle lines up with its own label, following
InputFieldWrapper. This makes affected nodes ~16px taller.UseCacheCheckbox/SaveToGalleryCheckboxare consolidated into oneNodeSettingFooterControl;useIsBatchNodeanduseNodeHasGalleryOutputwere absorbed into shared predicates intypes/invocation.ts.Notes for reviewers
use_cacheis only reachable on nodes with a gallery output. That is pre-existing (useWithFooter = isExecutableNode && hasGalleryOutput) and this PR deliberately does not widen it — the connection guard just matches reality. Happy to change it if that gate is considered a bug.is_intermediateinversion remains. An edge carriesis_intermediate, the UI says "Save To Gallery". Connecting a boolean node means thinking inverted. Fixing that properly means either a dedicated renderer or renaming the backend field tosave_to_gallery; both felt out of scope here.Related Issues / Discussions
QA Instructions
Form builder
+buttons appear next to both settings.+on Use Cache → it appears in the form; the button becomes−.+.Connections
7. Add a Boolean Primitive, drag from its
Valueoutput onto the Use Cache handle on the footer's left edge → the edge connects, and the checkbox disappears (the edge now supplies the value).8. If the setting is also in the form, its toggle is now disabled.
9. Try connecting to a node with no gallery output (e.g. Add Integers) → no handle is offered.
10. Invoke → the connected value is what the node runs with.
Regression
11. Confirm
use_cache/is_intermediatedo not appear in any node's input list.12. Load a workflow saved before this PR → unchanged; no migration warnings.
Merge Plan
Nothing special.
Checklist
What's Newcopy (if doing a release after this PR)