diff --git a/src/components/selectionbar/SubgraphNode.vue b/src/components/selectionbar/SubgraphNode.vue index 89762d6fc..c23b0488f 100644 --- a/src/components/selectionbar/SubgraphNode.vue +++ b/src/components/selectionbar/SubgraphNode.vue @@ -143,6 +143,28 @@ function hideAll() { node.properties.proxyWidgets = JSON.stringify(toKeep) triggerUpdate.value++ } +const recommendedNodes = [ + 'CLIPTextEncode', + 'LoadImage', + 'SaveImage', + 'PreviewImage' +] +const recommendedWidgetNames = ['seed'] +function showRecommended() { + const node = activeNode.value + if (!node) return //Not reachable + const recommendedWidgets = filteredCandidates.value.filter( + ([node, widget]: WidgetItem) => + recommendedNodes.includes(node.type) || + recommendedWidgetNames.includes(widget.name) + ) + node.properties.proxyWidgets = JSON.stringify( + recommendedWidgets.map(([node, widget]) => [`${node.id}`, widget.name]) + ) + triggerUpdate.value++ + //TODO: Add sort step here + //Input should always be before output by default +} const filteredActive = computed(() => { const query = searchQuery.value.toLowerCase() @@ -227,6 +249,9 @@ const filteredActive = computed(() => { /> + + {{ t('subgraphStore.showRecommended') }} @@ -244,7 +269,7 @@ const filteredActive = computed(() => { font-weight: 600; text-transform: uppercase; } -.widgets-section-header a { +a { cursor: pointer; color: var(--color-blue-100, #0b8ce9); text-align: right; diff --git a/src/scripts/proxyWidget.ts b/src/scripts/proxyWidget.ts index dc7292d54..181a45076 100644 --- a/src/scripts/proxyWidget.ts +++ b/src/scripts/proxyWidget.ts @@ -1,3 +1,4 @@ +import { useNodeImage } from '@/composables/node/useNodeImage' import type { LGraph, LGraphNode } from '@/lib/litegraph/src/litegraph' import { SubgraphNode } from '@/lib/litegraph/src/subgraph/SubgraphNode' import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets.ts' @@ -6,6 +7,7 @@ import { parseProxyWidgets } from '@/schemas/proxyWidget' import { DOMWidgetImpl } from '@/scripts/domWidget' import { useDomWidgetStore } from '@/stores/domWidgetStore' import { useCanvasStore } from '@/stores/graphStore' +import { useNodeOutputStore } from '@/stores/imagePreviewStore' const originalConfigureAfterSlots = SubgraphNode.prototype._internalConfigureAfterSlots @@ -63,6 +65,7 @@ type Overlay = Partial & { nodeId: string widgetName: string isProxyWidget: boolean + node?: LGraphNode } type ProxyWidget = IBaseWidget & { _overlay: Overlay } function isProxyWidget(w: IBaseWidget): w is ProxyWidget { @@ -87,12 +90,13 @@ function addProxyWidget( width: undefined, computedHeight: undefined, afterQueued: undefined, - onRemove: undefined, - node: subgraphNode + onRemove: undefined } return addProxyFromOverlay(subgraphNode, overlay) } -function resolveLinkedWidget(overlay: Overlay): IBaseWidget | undefined { +function resolveLinkedWidget( + overlay: Overlay +): [LGraphNode | undefined, IBaseWidget | undefined] { const { graph, nodeId, widgetName } = overlay let g: LGraph | undefined = graph let n: LGraphNode | SubgraphNode | undefined = undefined @@ -100,12 +104,29 @@ function resolveLinkedWidget(overlay: Overlay): IBaseWidget | undefined { n = g?._nodes_by_id?.[id] g = n?.isSubgraphNode?.() ? n.subgraph : undefined } - if (!n) return undefined - return n.widgets?.find((w: IBaseWidget) => w.name === widgetName) + if (!n) return [undefined, undefined] + return [n, n.widgets?.find((w: IBaseWidget) => w.name === widgetName)] } function addProxyFromOverlay(subgraphNode: SubgraphNode, overlay: Overlay) { - let linkedWidget = resolveLinkedWidget(overlay) + let [linkedNode, linkedWidget] = resolveLinkedWidget(overlay) const bw = linkedWidget ?? disconnectedWidget + overlay.node = new Proxy(subgraphNode, { + get(_t, p) { + if (p == 'imgs') { + if (linkedNode) { + const images = + useNodeOutputStore().getNodeOutputs(linkedNode)?.images ?? [] + if (images !== linkedNode.images) { + linkedNode.images = images + useNodeImage(linkedNode).showPreview() + } + return linkedNode.imgs + } + return [] + } + return Reflect.get(subgraphNode, p) + } + }) const handler = Object.fromEntries( ['get', 'set', 'getPrototypeOf', 'ownKeys', 'has'].map((s) => { const func = function (t: object, p: string, ...rest: object[]) { diff --git a/src/stores/imagePreviewStore.ts b/src/stores/imagePreviewStore.ts index 6fc8f3076..6747f08f5 100644 --- a/src/stores/imagePreviewStore.ts +++ b/src/stores/imagePreviewStore.ts @@ -1,10 +1,6 @@ import { defineStore } from 'pinia' -import { - LGraphNode, - Subgraph, - SubgraphNode -} from '@/lib/litegraph/src/litegraph' +import { LGraphNode, SubgraphNode } from '@/lib/litegraph/src/litegraph' import { ExecutedWsMessage, ResultItem, @@ -37,17 +33,17 @@ interface SetOutputOptions { } export const useNodeOutputStore = defineStore('nodeOutput', () => { - const { nodeIdToNodeLocatorId } = useWorkflowStore() + const { nodeIdToNodeLocatorId, nodeToNodeLocatorId } = useWorkflowStore() const { executionIdToNodeLocatorId } = useExecutionStore() function getNodeOutputs( node: LGraphNode ): ExecutedWsMessage['output'] | undefined { - return app.nodeOutputs[nodeIdToNodeLocatorId(node.id)] + return app.nodeOutputs[nodeToNodeLocatorId(node)] } function getNodePreviews(node: LGraphNode): string[] | undefined { - return app.nodePreviewImages[nodeIdToNodeLocatorId(node.id)] + return app.nodePreviewImages[nodeToNodeLocatorId(node)] } /** @@ -140,10 +136,7 @@ export const useNodeOutputStore = defineStore('nodeOutput', () => { ) { if (!filenames || !node) return - const locatorId = - node.graph instanceof Subgraph - ? nodeIdToNodeLocatorId(node.id, node.graph ?? undefined) - : `${node.id}` + const locatorId = nodeToNodeLocatorId(node) if (!locatorId) return if (typeof filenames === 'string') { setOutputsByLocatorId( diff --git a/src/stores/workflowStore.ts b/src/stores/workflowStore.ts index b0c8e8b37..943787c40 100644 --- a/src/stores/workflowStore.ts +++ b/src/stores/workflowStore.ts @@ -3,7 +3,11 @@ import { defineStore } from 'pinia' import { type Raw, computed, markRaw, ref, shallowRef, watch } from 'vue' import { t } from '@/i18n' -import type { LGraph, Subgraph } from '@/lib/litegraph/src/litegraph' +import type { + LGraph, + LGraphNode, + Subgraph +} from '@/lib/litegraph/src/litegraph' import { useWorkflowThumbnail } from '@/renderer/thumbnail/composables/useWorkflowThumbnail' import { ComfyWorkflowJSON } from '@/schemas/comfyWorkflowSchema' import type { NodeId } from '@/schemas/comfyWorkflowSchema' @@ -185,6 +189,7 @@ interface WorkflowStore { updateActiveGraph: () => void executionIdToCurrentId: (id: string) => any nodeIdToNodeLocatorId: (nodeId: NodeId, subgraph?: Subgraph) => NodeLocatorId + nodeToNodeLocatorId: (node: LGraphNode) => NodeLocatorId nodeExecutionIdToNodeLocatorId: ( nodeExecutionId: NodeExecutionId | string ) => NodeLocatorId | null @@ -575,6 +580,17 @@ export const useWorkflowStore = defineStore('workflow', () => { return createNodeLocatorId(targetSubgraph.id, nodeId) } + /** + * Convert a node to a NodeLocatorId + * Does not assume the node resides in the active graph + * @param The actual node instance + * @returns The NodeLocatorId (for root graph nodes, returns the node ID as-is) + */ + const nodeToNodeLocatorId = (node: LGraphNode): NodeLocatorId => { + if (isSubgraph(node.graph)) + return createNodeLocatorId(node.graph.id, node.id) + return String(node.id) + } /** * Convert an execution ID to a NodeLocatorId @@ -717,6 +733,7 @@ export const useWorkflowStore = defineStore('workflow', () => { updateActiveGraph, executionIdToCurrentId, nodeIdToNodeLocatorId, + nodeToNodeLocatorId, nodeExecutionIdToNodeLocatorId, nodeLocatorIdToNodeId, nodeLocatorIdToNodeExecutionId