diff --git a/src/app/models/flow.ts b/src/app/models/flow.ts index 01f7aad..f63ef23 100644 --- a/src/app/models/flow.ts +++ b/src/app/models/flow.ts @@ -278,6 +278,15 @@ export type GroupedFlowValidation = { export const FLOW_DEPENDANT_PORT_KEY = '__dependant'; export const FLOW_DEPENDENCY_PORT_KEY = '__dependency'; export const FLOW_DEPENDENCY_SOCKET_TYPE = '__FLOW_DEPENDENCY__'; +/** A Storage node's handle, and a StorageOperation's: the link between them is not a connection. */ +export const FLOW_STORAGE_PORT_KEY = '__storage'; +export const FLOW_STORAGE_LINK_PORT_KEY = '__storageNode'; +export const FLOW_STORAGE_SOCKET_TYPE = '__FLOW_STORAGE__'; + +/** A block that works on a Storage node, linked to it rather than connected. */ +export function isStorageOperationNode(node: { typeName?: string; specificConfiguration?: Record | null }): boolean { + return node.typeName === 'StorageOperation' || node.specificConfiguration?.['type'] === 'StorageOperationBlockConfiguration'; +} export function normalizeFlowValidationErrors(raw: unknown): FlowValidationError[] { if (!Array.isArray(raw)) return []; diff --git a/src/app/shared/custom-connection/custom-connection.ts b/src/app/shared/custom-connection/custom-connection.ts index dc406b7..3543e81 100644 --- a/src/app/shared/custom-connection/custom-connection.ts +++ b/src/app/shared/custom-connection/custom-connection.ts @@ -8,6 +8,8 @@ import { DEFAULT_LOOP_MAX_ITERATIONS, FLOW_DEPENDANT_PORT_KEY, FLOW_DEPENDENCY_PORT_KEY, + FLOW_STORAGE_LINK_PORT_KEY, + FLOW_STORAGE_PORT_KEY, MAX_LOOP_MAX_ITERATIONS } from '@models/flow'; import type { LoopAwareConnection } from '@utilities/rete-editor'; @@ -199,6 +201,11 @@ export class CustomConnectionComponent { @Input() end!: { x: number; y: number }; @Input() path = ''; + /** A storage operation's link to the Storage node it works on: not a connection, orders nothing. */ + get isStorageLink(): boolean { + return this.data?.sourceOutput === FLOW_STORAGE_PORT_KEY && this.data?.targetInput === FLOW_STORAGE_LINK_PORT_KEY; + } + get isDependencyConnection(): boolean { return this.data?.sourceOutput === FLOW_DEPENDANT_PORT_KEY && this.data?.targetInput === FLOW_DEPENDENCY_PORT_KEY; } @@ -278,17 +285,19 @@ export class CustomConnectionComponent { if (this.isSelected) return '#f97316'; if (this.isBiasRoutingChange) return '#b45309'; if (this.isLoopBack) return '#7c3aed'; + if (this.isStorageLink) return '#0f766e'; return this.isDependencyConnection ? '#7c8ba1' : '#4682b4'; } get strokeWidth(): number { - if (this.isSelected) return this.isDependencyConnection ? 3.25 : 6; + if (this.isSelected) return this.isDependencyConnection || this.isStorageLink ? 3.25 : 6; if (this.isBiasRoutingChange) return 6; - return this.isDependencyConnection ? 2.25 : 5; + return this.isDependencyConnection || this.isStorageLink ? 2.25 : 5; } get strokeDasharray(): string | null { if (this.isLoopBack) return '12 7'; + if (this.isStorageLink) return '2 6'; return this.isDependencyConnection ? '6 6' : null; } diff --git a/src/app/shared/custom-socket/custom-socket.ts b/src/app/shared/custom-socket/custom-socket.ts index b93c010..96a58c4 100644 --- a/src/app/shared/custom-socket/custom-socket.ts +++ b/src/app/shared/custom-socket/custom-socket.ts @@ -13,7 +13,7 @@ export class CustomSocket { @Input() emit!: any; @Input() rendered!: any; private side: "input" | "output" = "input"; - private kind: "data" | "dependency" = "data"; + private kind: "data" | "dependency" | "storage" = "data"; @HostBinding("title") get title() { return this.data.name; @@ -34,6 +34,9 @@ export class CustomSocket { @HostBinding("style.background") get bg() { + if (this.kind === "storage") { + return "linear-gradient(145deg, #2dd4bf 0%, #0f766e 100%)"; + } if (this.kind === "dependency") { return this.side === "input" ? "linear-gradient(145deg, #fbbf24 0%, #d97706 100%)" @@ -46,6 +49,9 @@ export class CustomSocket { @HostBinding("style.boxShadow") get sh() { + if (this.kind === "storage") { + return "0 2px 6px rgba(15,118,110,0.45), 0 0 0 1px rgba(15,118,110,0.45)"; + } if (this.kind === "dependency") { const c = this.side === "input" ? "rgba(217,119,6,0.45)" : "rgba(37,99,235,0.45)"; return `0 2px 6px ${c}, 0 0 0 1px ${c}`; @@ -59,8 +65,9 @@ export class CustomSocket { return side === "output" ? "output" : "input"; } - private resolveSocketKind(): "data" | "dependency" { - return this.data?.__hfKind === "dependency" ? "dependency" : "data"; + private resolveSocketKind(): "data" | "dependency" | "storage" { + const kind = this.data?.__hfKind; + return kind === "dependency" || kind === "storage" ? kind : "data"; } ngOnChanges(): void { diff --git a/src/app/shared/nodes/generic-node/generic-node.css b/src/app/shared/nodes/generic-node/generic-node.css index 39e5bc4..b4b1bd3 100644 --- a/src/app/shared/nodes/generic-node/generic-node.css +++ b/src/app/shared/nodes/generic-node/generic-node.css @@ -999,3 +999,9 @@ background: linear-gradient(135deg, #14b8a6 0%, #0f766e 100%); border-bottom-color: #99f6e4; } + +.llm-pill-storage { + border-color: #5eead4; + background: #f0fdfa; + color: #0f766e; +} diff --git a/src/app/shared/nodes/generic-node/generic-node.html b/src/app/shared/nodes/generic-node/generic-node.html index 696b9a7..bb10ea3 100644 --- a/src/app/shared/nodes/generic-node/generic-node.html +++ b/src/app/shared/nodes/generic-node/generic-node.html @@ -217,6 +217,54 @@ + @if (storageLinkOutput || storageLinkInput) { +
+
+
Storage
+ @if (storageLinkInput; as storageLinkInput) { +
+
+
+ Works on +
+ } +
+ +
+
 
+ @if (storageLinkOutput; as storageLinkOutput) { +
+ Used by +
+
+
+ } +
+
+ } + @if (hasExecutionDependencyPorts()) {
diff --git a/src/app/shared/nodes/generic-node/generic-node.ts b/src/app/shared/nodes/generic-node/generic-node.ts index 7e507e3..9d91882 100644 --- a/src/app/shared/nodes/generic-node/generic-node.ts +++ b/src/app/shared/nodes/generic-node/generic-node.ts @@ -8,7 +8,7 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, effect, ElementR import { FormsModule } from '@angular/forms'; import { MatTooltipModule } from '@angular/material/tooltip'; import { environment } from '@environment'; -import { BiasAnnotation, BiasAnnotationsDescriptor, BlockType, currentFlowPortValueKind, flowValueKindLabel, FlowBlock, FlowData, FlowGlobalInput, FlowPort, FlowValueKind, FLOW_DEPENDANT_PORT_KEY, FLOW_DEPENDENCY_PORT_KEY, isProbeExecutable, normalizeFlowPortValueKinds } from '@models/flow'; +import { BiasAnnotation, BiasAnnotationsDescriptor, BlockType, currentFlowPortValueKind, flowValueKindLabel, FlowBlock, FlowData, FlowGlobalInput, FlowPort, FlowValueKind, FLOW_DEPENDANT_PORT_KEY, FLOW_DEPENDENCY_PORT_KEY, FLOW_STORAGE_LINK_PORT_KEY, FLOW_STORAGE_PORT_KEY, isProbeExecutable, normalizeFlowPortValueKinds } from '@models/flow'; import { BiasAnnotationsComponent } from '../../bias-annotations/bias-annotations'; import { NodeFocusModalController } from '../node-focus-modal-controller'; import { ClassicPreset } from 'rete'; @@ -245,7 +245,7 @@ export class GenericNodeComponent implements OnDestroy { get outputs(): RenderedSocketPort[] { return Object.entries(this.data?.outputs ?? {}) - .filter(([key]) => key !== FLOW_DEPENDANT_PORT_KEY) + .filter(([key]) => key !== FLOW_DEPENDANT_PORT_KEY && key !== FLOW_STORAGE_PORT_KEY) .map(([key, output]) => ({ key, socket: (output as any).socket as ClassicPreset.Socket @@ -254,13 +254,25 @@ export class GenericNodeComponent implements OnDestroy { get inputs(): RenderedSocketPort[] { return Object.entries(this.data?.inputs ?? {}) - .filter(([key]) => key !== FLOW_DEPENDENCY_PORT_KEY) + .filter(([key]) => key !== FLOW_DEPENDENCY_PORT_KEY && key !== FLOW_STORAGE_LINK_PORT_KEY) .map(([key, input]) => ({ key, socket: (input as any).socket as ClassicPreset.Socket })); } + /** A Storage node's handle, which storage operations are linked to. */ + get storageLinkOutput(): { key: string; socket: ClassicPreset.Socket } | null { + const output = this.data?.outputs?.[FLOW_STORAGE_PORT_KEY]; + return output ? { key: FLOW_STORAGE_PORT_KEY, socket: (output as any).socket as ClassicPreset.Socket } : null; + } + + /** A storage operation's handle, linked to the Storage node it works on. */ + get storageLinkInput(): { key: string; socket: ClassicPreset.Socket } | null { + const input = this.data?.inputs?.[FLOW_STORAGE_LINK_PORT_KEY]; + return input ? { key: FLOW_STORAGE_LINK_PORT_KEY, socket: (input as any).socket as ClassicPreset.Socket } : null; + } + get dependantOutput(): RenderedSocketPort | null { const output = this.data?.outputs?.[FLOW_DEPENDANT_PORT_KEY]; return output diff --git a/src/app/shared/nodes/node-utility.ts b/src/app/shared/nodes/node-utility.ts index a11c74d..ef837f5 100644 --- a/src/app/shared/nodes/node-utility.ts +++ b/src/app/shared/nodes/node-utility.ts @@ -177,6 +177,9 @@ export function orderedSchemaPropertyEntries( isTechnical }; }) + // Said by something else on the canvas - a link drawn between two nodes - not a field. Left + // out of every form and chip, and kept in the configuration, which this does not touch. + .filter((entry) => entry.schema?.['x-ui-hidden'] !== true) .sort((left, right) => { const leftBucket = left.priorityIndex != null ? 0 : left.isTechnical ? 2 : 1; const rightBucket = right.priorityIndex != null ? 0 : right.isTechnical ? 2 : 1; diff --git a/src/app/shared/nodes/task-step-node/task-step-node.css b/src/app/shared/nodes/task-step-node/task-step-node.css index 75cafbb..157bd86 100644 --- a/src/app/shared/nodes/task-step-node/task-step-node.css +++ b/src/app/shared/nodes/task-step-node/task-step-node.css @@ -841,3 +841,9 @@ button.llm-node-bias-summary:focus-visible { background: linear-gradient(135deg, #14b8a6 0%, #0f766e 100%); border-bottom-color: #99f6e4; } + +.llm-pill-storage { + border-color: #5eead4; + background: #f0fdfa; + color: #0f766e; +} diff --git a/src/app/shared/nodes/task-step-node/task-step-node.html b/src/app/shared/nodes/task-step-node/task-step-node.html index fca5882..0b4b259 100644 --- a/src/app/shared/nodes/task-step-node/task-step-node.html +++ b/src/app/shared/nodes/task-step-node/task-step-node.html @@ -249,6 +249,54 @@
+ @if (storageLinkOutput || storageLinkInput) { +
+
+
Storage
+ @if (storageLinkInput; as storageLinkInput) { +
+
+
+ Works on +
+ } +
+ +
+
 
+ @if (storageLinkOutput; as storageLinkOutput) { +
+ Used by +
+
+
+ } +
+
+ } + @if (hasExecutionDependencyPorts()) {
diff --git a/src/app/shared/nodes/task-step-node/task-step-node.ts b/src/app/shared/nodes/task-step-node/task-step-node.ts index 57d69c9..a21b95a 100644 --- a/src/app/shared/nodes/task-step-node/task-step-node.ts +++ b/src/app/shared/nodes/task-step-node/task-step-node.ts @@ -7,7 +7,7 @@ import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, HostBinding, Input, inject, viewChild } from '@angular/core'; import { ClassicPreset } from 'rete'; import { ReteModule } from 'rete-angular-plugin/21'; -import { BiasAnnotation, BlockInteractionContract, BlockType, DEFAULT_NODE_CAPABILITIES, FlowData, FlowNode, FlowPort, isProbeExecutable, FLOW_DEPENDANT_PORT_KEY, FLOW_DEPENDENCY_PORT_KEY, NodeTypeCapabilities } from '@models/flow'; +import { BiasAnnotation, BlockInteractionContract, BlockType, DEFAULT_NODE_CAPABILITIES, FlowData, FlowNode, FlowPort, isProbeExecutable, FLOW_DEPENDANT_PORT_KEY, FLOW_DEPENDENCY_PORT_KEY, FLOW_STORAGE_LINK_PORT_KEY, FLOW_STORAGE_PORT_KEY, NodeTypeCapabilities } from '@models/flow'; import { BiasCapabilities } from '@models/bias-impact'; import { BlocksService } from '@services/blocks/blocks'; import { ContainersService } from '@services/containers/containers'; @@ -152,16 +152,28 @@ export class TaskStepNodeComponent { get outputs(): { key: string; socket: ClassicPreset.Socket }[] { return Object.entries(this.data?.outputs ?? {}) - .filter(([key]) => key !== FLOW_DEPENDANT_PORT_KEY) + .filter(([key]) => key !== FLOW_DEPENDANT_PORT_KEY && key !== FLOW_STORAGE_PORT_KEY) .map(([key, output]) => ({ key, socket: (output as any).socket as ClassicPreset.Socket })); } get inputs(): { key: string; socket: ClassicPreset.Socket }[] { return Object.entries(this.data?.inputs ?? {}) - .filter(([key]) => key !== FLOW_DEPENDENCY_PORT_KEY) + .filter(([key]) => key !== FLOW_DEPENDENCY_PORT_KEY && key !== FLOW_STORAGE_LINK_PORT_KEY) .map(([key, input]) => ({ key, socket: (input as any).socket as ClassicPreset.Socket })); } + /** A Storage node's handle, which storage operations are linked to. */ + get storageLinkOutput(): { key: string; socket: ClassicPreset.Socket } | null { + const output = this.data?.outputs?.[FLOW_STORAGE_PORT_KEY]; + return output ? { key: FLOW_STORAGE_PORT_KEY, socket: (output as any).socket as ClassicPreset.Socket } : null; + } + + /** A storage operation's handle, linked to the Storage node it works on. */ + get storageLinkInput(): { key: string; socket: ClassicPreset.Socket } | null { + const input = this.data?.inputs?.[FLOW_STORAGE_LINK_PORT_KEY]; + return input ? { key: FLOW_STORAGE_LINK_PORT_KEY, socket: (input as any).socket as ClassicPreset.Socket } : null; + } + get dependantOutput(): { key: string; socket: ClassicPreset.Socket } | null { const output = this.data?.outputs?.[FLOW_DEPENDANT_PORT_KEY]; return output diff --git a/src/app/utilities/rete-editor.spec.ts b/src/app/utilities/rete-editor.spec.ts index 7176be8..8ffb86f 100644 --- a/src/app/utilities/rete-editor.spec.ts +++ b/src/app/utilities/rete-editor.spec.ts @@ -83,3 +83,34 @@ describe('isResourceNode', () => { expect(isResourceNode(undefined, makeNode('Storage', 'block'))).toBe(false); }); }); + +describe('the link between a Storage node and a storage operation', () => { + it('is what says which node the operation works on, and nothing when it is gone', async () => { + const { NodeEditor, ClassicPreset } = await import('rete'); + const { exportGraph } = await import('./rete-editor'); + const { FLOW_STORAGE_LINK_PORT_KEY, FLOW_STORAGE_PORT_KEY } = await import('@models/flow'); + const editor = new NodeEditor(); + const socket = new ClassicPreset.Socket('__FLOW_STORAGE__'); + + const storage = new ClassicPreset.Node('Storage') as any; + storage.data = { id: 'store-1', typeName: 'Storage', nodeFamily: 'block', specificConfiguration: { type: 'StorageNodeConfiguration' } }; + storage.addOutput(FLOW_STORAGE_PORT_KEY, new ClassicPreset.Output(socket)); + const operation = new ClassicPreset.Node('StorageOperation') as any; + operation.data = { id: 'op-1', typeName: 'StorageOperation', nodeFamily: 'block', + specificConfiguration: { type: 'StorageOperationBlockConfiguration', storageNode: 'stale' } }; + operation.addInput(FLOW_STORAGE_LINK_PORT_KEY, new ClassicPreset.Input(socket, undefined, true)); + await editor.addNode(storage); + await editor.addNode(operation); + + const link = new ClassicPreset.Connection(storage, FLOW_STORAGE_PORT_KEY, operation, FLOW_STORAGE_LINK_PORT_KEY); + await editor.addConnection(link); + let graph = exportGraph(editor); + expect((graph.blocks.find((block) => block.id === 'op-1')?.specificConfiguration as Record | undefined)?.['storageNode']).toBe('store-1'); + expect(graph.connections).toEqual([]); + expect(graph.dependencies).toEqual([]); + + await editor.removeConnection(link.id); + graph = exportGraph(editor); + expect((graph.blocks.find((block) => block.id === 'op-1')?.specificConfiguration as Record | undefined)?.['storageNode']).toBeNull(); + }); +}); diff --git a/src/app/utilities/rete-editor.ts b/src/app/utilities/rete-editor.ts index 7bc71be..5f71d2f 100644 --- a/src/app/utilities/rete-editor.ts +++ b/src/app/utilities/rete-editor.ts @@ -22,6 +22,10 @@ import { FLOW_DEPENDANT_PORT_KEY, FLOW_DEPENDENCY_PORT_KEY, FLOW_DEPENDENCY_SOCKET_TYPE, + FLOW_STORAGE_LINK_PORT_KEY, + FLOW_STORAGE_PORT_KEY, + FLOW_STORAGE_SOCKET_TYPE, + isStorageOperationNode, FlowNode, NodeTypeCapabilities, normalizeFlowPortValueKinds @@ -53,7 +57,7 @@ export type ReteEditorInstance = { area: AreaPlugin; }; -type GraphConnectionKind = "data" | "dependency"; +type GraphConnectionKind = "data" | "dependency" | "storage"; export type ReteRuntimeContext = { blocksService: BlocksService; @@ -145,6 +149,13 @@ export async function createEditor( const targetCapabilities = resolveNodeCapabilities(runtime, targetNode); const connectionKind = getGraphConnectionKind(context.data.sourceOutput, context.data.targetInput); + if (connectionKind === "storage") { + // From a Storage node to a storage operation, and one node per operation: a new link + // replaces the operation's old one, the way a data input keeps to one connection. + if (!isResourceNode(runtime, sourceNode)) return; + if (!runtime.restoringConnections) await replaceStorageLink(editor, context.data.target); + return context; + } if (connectionKind === "dependency") { if (context.data.source === context.data.target) return; if (!sourceCapabilities.canHaveDependentNodes || !targetCapabilities.canDependOnOtherNodes) return; @@ -281,6 +292,19 @@ export function exportGraph(editor: NodeEditor) { }; }); + // A storage operation says which Storage node it works on in its configuration; the link on the + // canvas is how that is set, so the configuration follows the links as they now stand. + const storageLinks = new Map(allConnections + .filter((connection) => connection.kind === 'storage') + .map((connection) => [connection.targetId, connection.sourceId] as const)); + for (const node of nodes) { + if (!isStorageOperationNode(node as any)) continue; + node.specificConfiguration = { + ...(node.specificConfiguration ?? {}), + storageNode: storageLinks.get(node.id) ?? null + }; + } + return { blocks: nodes.filter((node): node is FlowBlock => node.nodeFamily === 'block'), containers: nodes.filter((node) => node.nodeFamily === 'container'), @@ -638,6 +662,12 @@ export async function addBlockToEditor( }; const capabilities = resolveNodeCapabilities(resolvedRuntime, node); + if (capabilities.visualRole === 'RESOURCE') { + node.addOutput(FLOW_STORAGE_PORT_KEY, new ClassicPreset.Output(getSocket(editor, FLOW_STORAGE_SOCKET_TYPE))); + } + if (isStorageOperationNode(block as any)) { + node.addInput(FLOW_STORAGE_LINK_PORT_KEY, new ClassicPreset.Input(getSocket(editor, FLOW_STORAGE_SOCKET_TYPE), undefined, true)); + } if (capabilities.canHaveDependentNodes) { node.addOutput(FLOW_DEPENDANT_PORT_KEY, new ClassicPreset.Output(getSocket(editor, FLOW_DEPENDENCY_SOCKET_TYPE))); } @@ -718,9 +748,31 @@ async function loadFlowData( new ClassicPreset.Connection(sourceNode, FLOW_DEPENDANT_PORT_KEY, targetNode, FLOW_DEPENDENCY_PORT_KEY) ); } + + // Drawn back from what each storage operation says it works on. + for (const block of flowData.blocks ?? []) { + if (isStale()) return; + const storageNodeId = (block.specificConfiguration as Record | undefined)?.['storageNode']; + if (!isStorageOperationNode(block as any) || typeof storageNodeId !== 'string') continue; + if (!nodeMapping.has(storageNodeId) || !nodeMapping.has(block.id)) continue; + const sourceNode = editor.getNode(nodeMapping.get(storageNodeId)) as any; + const targetNode = editor.getNode(nodeMapping.get(block.id)) as any; + await editor.addConnection( + new ClassicPreset.Connection(sourceNode, FLOW_STORAGE_PORT_KEY, targetNode, FLOW_STORAGE_LINK_PORT_KEY) + ); + } }); } +/** An operation works on one Storage node: linking it to another drops the link it had. */ +async function replaceStorageLink(editor: NodeEditor, operationNodeId: string) { + const existing = editor.getConnections().filter((connection) => + connection.target === operationNodeId && connection.targetInput === FLOW_STORAGE_LINK_PORT_KEY); + for (const connection of existing) { + await editor.removeConnection(connection.id); + } +} + async function withRestoredConnections(runtime: ReteRuntimeContext | undefined, restore: () => Promise): Promise { if (!runtime) return restore(); runtime.restoringConnections = (runtime.restoringConnections ?? 0) + 1; @@ -743,11 +795,6 @@ type NewConnection = { source: string; sourceOutput: string; target: string; tar * new one replaces it, as dropping onto a taken input always did. */ async function makeRoomOnInput(editor: NodeEditor, runtime: ReteRuntimeContext, created: NewConnection) { - // A storage node's target takes every step that writes there. Its view parameters, named - // ., take one value like any input. - if (isResourceNode(runtime, editor.getNode(created.target) as HFNode | undefined) && !created.targetInput.includes('.')) { - return false; - } const newLeadsBack = leadsBack(editor, runtime, created, null); const existing = editor.getConnections().filter((connection) => connection.target === created.target && connection.targetInput === created.targetInput); @@ -766,8 +813,8 @@ function leadsBack(editor: NodeEditor, runtime: ReteRuntimeContext, c const outgoing = new Map(); for (const candidate of editor.getConnections()) { if (candidate.id === ignoredConnectionId) continue; - // A storage node is not a step: writing to it and reading from it is not a path round. - if (touchesResource(editor, runtime, candidate)) continue; + // A link to a Storage node orders nothing: it is not a path round. + if (getGraphConnectionKind(candidate.sourceOutput, candidate.targetInput) === 'storage') continue; outgoing.set(candidate.source, [...(outgoing.get(candidate.source) ?? []), candidate.target]); } const seen = new Set(); @@ -862,7 +909,7 @@ function getSocket(editor: NodeEditor, type: string) { const map = editorSockets.get(editor)!; if (!map.has(type)) { const socket = new ClassicPreset.Socket(type) as ClassicPreset.Socket & { __hfKind?: GraphConnectionKind }; - socket.__hfKind = type === FLOW_DEPENDENCY_SOCKET_TYPE ? 'dependency' : 'data'; + socket.__hfKind = type === FLOW_DEPENDENCY_SOCKET_TYPE ? 'dependency' : type === FLOW_STORAGE_SOCKET_TYPE ? 'storage' : 'data'; map.set(type, socket); } return map.get(type)!; @@ -872,11 +919,6 @@ export function isResourceNode(runtime: ReteRuntimeContext | undefined, node: HF return resolveNodeCapabilities(runtime, node).visualRole === 'RESOURCE'; } -function touchesResource(editor: NodeEditor, runtime: ReteRuntimeContext, connection: { source: string; target: string }) { - return isResourceNode(runtime, editor.getNode(connection.source) as HFNode | undefined) - || isResourceNode(runtime, editor.getNode(connection.target) as HFNode | undefined); -} - export function resolveNodeCapabilities(runtime: ReteRuntimeContext | undefined, node: HFNode | undefined): NodeTypeCapabilities { if (node?.data?.capabilities) return node.data.capabilities; @@ -922,7 +964,7 @@ function cloneValue(value: T): T { } function getGraphConnectionKind(sourceOutput: string, targetInput: string): GraphConnectionKind { - return sourceOutput === FLOW_DEPENDANT_PORT_KEY && targetInput === FLOW_DEPENDENCY_PORT_KEY - ? 'dependency' - : 'data'; + if (sourceOutput === FLOW_DEPENDANT_PORT_KEY && targetInput === FLOW_DEPENDENCY_PORT_KEY) return 'dependency'; + if (sourceOutput === FLOW_STORAGE_PORT_KEY && targetInput === FLOW_STORAGE_LINK_PORT_KEY) return 'storage'; + return 'data'; }