diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/Graph.test.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/Graph.test.ts index 60c5ab44b5..c5032271c4 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/Graph.test.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/Graph.test.ts @@ -579,12 +579,20 @@ describe('Graph', () => { const metadata = g.getMetadataNode(); expect(metadata).toHaveProperty('test'); }); - it('should update metadata on the metadata node', () => { + it("should overwrite metadata on the metadata node if the strategy is 'replace'", () => { const g = new Graph(); - g.upsertMetadata({ test: 'test' }); - g.upsertMetadata({ test: 'test2' }); + g.upsertMetadata({ test: { foo: 'test' } }, 'replace'); + g.upsertMetadata({ test: { bar: 'test2' } }, 'replace'); const metadata = g.getMetadataNode(); - expect(metadata.test).toBe('test2'); + expect(metadata.test).toEqual({ bar: 'test2' }); + }); + it("should merge keys if the strategy is 'merge'", () => { + const g = new Graph(); + g.upsertMetadata({ test: { foo: 'test' }, arr: [1] }, 'merge'); + g.upsertMetadata({ test: { bar: 'test2' }, arr: [2] }, 'merge'); + const metadata = g.getMetadataNode(); + expect(metadata.test).toEqual({ foo: 'test', bar: 'test2' }); + expect(metadata.arr).toEqual([1, 2]); }); }); diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/generation/Graph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/generation/Graph.ts index 081e2c4370..7d007c74bd 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/generation/Graph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/generation/Graph.ts @@ -1,4 +1,5 @@ import { objectEquals } from '@observ33r/object-equals'; +import { mergeWith } from 'es-toolkit'; import { forEach, groupBy, unset, values } from 'es-toolkit/compat'; import { getPrefixedId } from 'features/controlLayers/konva/util'; import { type ModelIdentifierField, zModelIdentifierField } from 'features/nodes/types/common'; @@ -387,11 +388,25 @@ export class Graph { * Add metadata to the graph. If the metadata node does not exist, it is created. If the specific metadata key exists, * it is overwritten. * @param metadata The metadata to add. + * @param strategy The strategy to use when adding metadata. If 'replace', any existing key is replaced. If 'add', + * the metadata is deeply merged with the existing metadata. Arrays will be concatenated. * @returns The metadata node. */ - upsertMetadata(metadata: Partial): S['CoreMetadataInvocation'] { + upsertMetadata( + metadata: Partial, + strategy: 'replace' | 'merge' = 'replace' + ): S['CoreMetadataInvocation'] { const node = this.getMetadataNode(); - Object.assign(node, metadata); + if (strategy === 'replace') { + Object.assign(node, metadata); + } else { + // strategy === 'merge' + mergeWith(node, metadata, (objValue, srcValue) => { + if (Array.isArray(objValue)) { + return objValue.concat(srcValue); + } + }); + } return node; }