mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-05 15:40:10 +00:00
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:
@@ -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;
|
||||
|
||||
@@ -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[]) {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user