Add show recommended button, preview work

Adds the framework for a system to automate display of a curated list of
recommended widgets to the node.

As part of this, a return to display of "image previews" was made.
This code is causing lots of problems. Much of the logic is dependent
upon the actual node going through the draw loop. As nodes in the
subgraph don't receive redraws, there's lots of issues with managing the
initial display and ensuring that an initial draw occurs.

This commit includes support for updating previews, but is more brittle
than I would like.
This commit is contained in:
Austin Mroz
2025-09-19 09:51:04 -05:00
parent 02bf937741
commit 3e677e3dbe
4 changed files with 76 additions and 20 deletions

View File

@@ -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<WidgetItem[]>(() => {
const query = searchQuery.value.toLowerCase()
@@ -227,6 +249,9 @@ const filteredActive = computed<WidgetItem[]>(() => {
/>
</div>
</div>
<a @click.stop="showRecommended">
{{ t('subgraphStore.showRecommended') }}</a
>
</template>
</SidebarTabTemplate>
</template>
@@ -244,7 +269,7 @@ const filteredActive = computed<WidgetItem[]>(() => {
font-weight: 600;
text-transform: uppercase;
}
.widgets-section-header a {
a {
cursor: pointer;
color: var(--color-blue-100, #0b8ce9);
text-align: right;

View File

@@ -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<IBaseWidget> & {
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[]) {

View File

@@ -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(

View File

@@ -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