mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-03-14 17:37:46 +00:00
Update rh-test (as of 2025-10-11) (#6044)
## Summary Tested these changes and confirmed that: 1. Feedback button shows. 2. You can run workflows and switch out models. 3. You can use the mask editor. (thank you @ric-yu for helping me verify). ## Changes A lot, please see commits. Gets us up to date with `main` as of 10-11-2025. --------- Co-authored-by: Simula_r <18093452+simula-r@users.noreply.github.com> Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: snomiao <snomiao@gmail.com> Co-authored-by: Christian Byrne <cbyrne@comfy.org> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: DrJKL <DrJKL@users.noreply.github.com> Co-authored-by: Alexander Brown <drjkl@comfy.org> Co-authored-by: Marwan Ahmed <155799754+marawan206@users.noreply.github.com> Co-authored-by: DrJKL <DrJKL0424@gmail.com> Co-authored-by: Rizumu Ayaka <rizumu@ayaka.moe> Co-authored-by: Comfy Org PR Bot <snomiao+comfy-pr@gmail.com> Co-authored-by: AustinMroz <4284322+AustinMroz@users.noreply.github.com> Co-authored-by: Austin Mroz <austin@comfy.org> Co-authored-by: Johnpaul Chiwetelu <49923152+Myestery@users.noreply.github.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com> Co-authored-by: Benjamin Lu <benceruleanlu@proton.me> Co-authored-by: Jin Yi <jin12cc@gmail.com> Co-authored-by: Robin Huang <robin.j.huang@gmail.com>
This commit is contained in:
@@ -5,15 +5,30 @@
|
||||
import { reactive } from 'vue'
|
||||
|
||||
import { useChainCallback } from '@/composables/functional/useChainCallback'
|
||||
import type { INodeOutputSlot } from '@/lib/litegraph/src/interfaces'
|
||||
import type { INodeInputSlot } from '@/lib/litegraph/src/interfaces'
|
||||
import type {
|
||||
INodeInputSlot,
|
||||
INodeOutputSlot
|
||||
} from '@/lib/litegraph/src/interfaces'
|
||||
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
|
||||
import { LayoutSource } from '@/renderer/core/layout/types'
|
||||
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
import { isDOMWidget } from '@/scripts/domWidget'
|
||||
import { useNodeDefStore } from '@/stores/nodeDefStore'
|
||||
import type { WidgetValue } from '@/types/simplifiedWidget'
|
||||
|
||||
import type { LGraph, LGraphNode } from '../../lib/litegraph/src/litegraph'
|
||||
import type {
|
||||
LGraph,
|
||||
LGraphNode,
|
||||
LGraphTriggerAction,
|
||||
LGraphTriggerEvent,
|
||||
LGraphTriggerParam
|
||||
} from '../../lib/litegraph/src/litegraph'
|
||||
import { NodeSlotType } from '../../lib/litegraph/src/types/globalEnums'
|
||||
|
||||
export interface WidgetSlotMetadata {
|
||||
index: number
|
||||
linked: boolean
|
||||
}
|
||||
|
||||
export interface SafeWidgetData {
|
||||
name: string
|
||||
@@ -23,6 +38,8 @@ export interface SafeWidgetData {
|
||||
options?: Record<string, unknown>
|
||||
callback?: ((value: unknown) => void) | undefined
|
||||
spec?: InputSpec
|
||||
slotMetadata?: WidgetSlotMetadata
|
||||
isDOMWidget?: boolean
|
||||
}
|
||||
|
||||
export interface VueNodeData {
|
||||
@@ -66,6 +83,37 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
// Non-reactive storage for original LiteGraph nodes
|
||||
const nodeRefs = new Map<string, LGraphNode>()
|
||||
|
||||
const refreshNodeSlots = (nodeId: string) => {
|
||||
const nodeRef = nodeRefs.get(nodeId)
|
||||
const currentData = vueNodeData.get(nodeId)
|
||||
|
||||
if (!nodeRef || !currentData) return
|
||||
|
||||
// Only extract slot-related data instead of full node re-extraction
|
||||
const slotMetadata = new Map<string, WidgetSlotMetadata>()
|
||||
|
||||
nodeRef.inputs?.forEach((input, index) => {
|
||||
if (!input?.widget?.name) return
|
||||
slotMetadata.set(input.widget.name, {
|
||||
index,
|
||||
linked: input.link != null
|
||||
})
|
||||
})
|
||||
|
||||
// Update only widgets with new slot metadata, keeping other widget data intact
|
||||
const updatedWidgets = currentData.widgets?.map((widget) => {
|
||||
const slotInfo = slotMetadata.get(widget.name)
|
||||
return slotInfo ? { ...widget, slotMetadata: slotInfo } : widget
|
||||
})
|
||||
|
||||
vueNodeData.set(nodeId, {
|
||||
...currentData,
|
||||
widgets: updatedWidgets,
|
||||
inputs: nodeRef.inputs ? [...nodeRef.inputs] : undefined,
|
||||
outputs: nodeRef.outputs ? [...nodeRef.outputs] : undefined
|
||||
})
|
||||
}
|
||||
|
||||
// Extract safe data from LiteGraph node for Vue consumption
|
||||
const extractVueNodeData = (node: LGraphNode): VueNodeData => {
|
||||
// Determine subgraph ID - null for root graph, string for subgraphs
|
||||
@@ -74,6 +122,16 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
? String(node.graph.id)
|
||||
: null
|
||||
// Extract safe widget data
|
||||
const slotMetadata = new Map<string, WidgetSlotMetadata>()
|
||||
|
||||
node.inputs?.forEach((input, index) => {
|
||||
if (!input?.widget?.name) return
|
||||
slotMetadata.set(input.widget.name, {
|
||||
index,
|
||||
linked: input.link != null
|
||||
})
|
||||
})
|
||||
|
||||
const safeWidgets = node.widgets?.map((widget) => {
|
||||
try {
|
||||
// TODO: Use widget.getReactiveData() once TypeScript types are updated
|
||||
@@ -90,6 +148,7 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
value = widget.options.values[0]
|
||||
}
|
||||
const spec = nodeDefStore.getInputSpecForWidget(node, widget.name)
|
||||
const slotInfo = slotMetadata.get(widget.name)
|
||||
|
||||
return {
|
||||
name: widget.name,
|
||||
@@ -98,7 +157,9 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
label: widget.label,
|
||||
options: widget.options ? { ...widget.options } : undefined,
|
||||
callback: widget.callback,
|
||||
spec
|
||||
spec,
|
||||
slotMetadata: slotInfo,
|
||||
isDOMWidget: isDOMWidget(widget)
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
@@ -373,7 +434,7 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
const createCleanupFunction = (
|
||||
originalOnNodeAdded: ((node: LGraphNode) => void) | undefined,
|
||||
originalOnNodeRemoved: ((node: LGraphNode) => void) | undefined,
|
||||
originalOnTrigger: ((action: string, param: unknown) => void) | undefined
|
||||
originalOnTrigger: ((event: LGraphTriggerEvent) => void) | undefined
|
||||
) => {
|
||||
return () => {
|
||||
// Restore original callbacks
|
||||
@@ -405,29 +466,19 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
handleNodeRemoved(node, originalOnNodeRemoved)
|
||||
}
|
||||
|
||||
// Listen for property change events from instrumented nodes
|
||||
graph.onTrigger = (action: string, param: unknown) => {
|
||||
if (
|
||||
action === 'node:property:changed' &&
|
||||
param &&
|
||||
typeof param === 'object'
|
||||
) {
|
||||
const event = param as {
|
||||
nodeId: string | number
|
||||
property: string
|
||||
oldValue: unknown
|
||||
newValue: unknown
|
||||
}
|
||||
|
||||
const nodeId = String(event.nodeId)
|
||||
const triggerHandlers: {
|
||||
[K in LGraphTriggerAction]: (event: LGraphTriggerParam<K>) => void
|
||||
} = {
|
||||
'node:property:changed': (propertyEvent) => {
|
||||
const nodeId = String(propertyEvent.nodeId)
|
||||
const currentData = vueNodeData.get(nodeId)
|
||||
|
||||
if (currentData) {
|
||||
switch (event.property) {
|
||||
switch (propertyEvent.property) {
|
||||
case 'title':
|
||||
vueNodeData.set(nodeId, {
|
||||
...currentData,
|
||||
title: String(event.newValue)
|
||||
title: String(propertyEvent.newValue)
|
||||
})
|
||||
break
|
||||
case 'flags.collapsed':
|
||||
@@ -435,7 +486,7 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
...currentData,
|
||||
flags: {
|
||||
...currentData.flags,
|
||||
collapsed: Boolean(event.newValue)
|
||||
collapsed: Boolean(propertyEvent.newValue)
|
||||
}
|
||||
})
|
||||
break
|
||||
@@ -444,22 +495,25 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
...currentData,
|
||||
flags: {
|
||||
...currentData.flags,
|
||||
pinned: Boolean(event.newValue)
|
||||
pinned: Boolean(propertyEvent.newValue)
|
||||
}
|
||||
})
|
||||
break
|
||||
case 'mode':
|
||||
vueNodeData.set(nodeId, {
|
||||
...currentData,
|
||||
mode: typeof event.newValue === 'number' ? event.newValue : 0
|
||||
mode:
|
||||
typeof propertyEvent.newValue === 'number'
|
||||
? propertyEvent.newValue
|
||||
: 0
|
||||
})
|
||||
break
|
||||
case 'color':
|
||||
vueNodeData.set(nodeId, {
|
||||
...currentData,
|
||||
color:
|
||||
typeof event.newValue === 'string'
|
||||
? event.newValue
|
||||
typeof propertyEvent.newValue === 'string'
|
||||
? propertyEvent.newValue
|
||||
: undefined
|
||||
})
|
||||
break
|
||||
@@ -467,40 +521,38 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
|
||||
vueNodeData.set(nodeId, {
|
||||
...currentData,
|
||||
bgcolor:
|
||||
typeof event.newValue === 'string'
|
||||
? event.newValue
|
||||
typeof propertyEvent.newValue === 'string'
|
||||
? propertyEvent.newValue
|
||||
: undefined
|
||||
})
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
action === 'node:slot-errors:changed' &&
|
||||
param &&
|
||||
typeof param === 'object'
|
||||
) {
|
||||
const event = param as { nodeId: string | number }
|
||||
const nodeId = String(event.nodeId)
|
||||
const litegraphNode = nodeRefs.get(nodeId)
|
||||
const currentData = vueNodeData.get(nodeId)
|
||||
|
||||
if (litegraphNode && currentData) {
|
||||
// Re-extract slot data with updated hasErrors properties
|
||||
vueNodeData.set(nodeId, {
|
||||
...currentData,
|
||||
inputs: litegraphNode.inputs
|
||||
? [...litegraphNode.inputs]
|
||||
: undefined,
|
||||
outputs: litegraphNode.outputs
|
||||
? [...litegraphNode.outputs]
|
||||
: undefined
|
||||
})
|
||||
},
|
||||
'node:slot-errors:changed': (slotErrorsEvent) => {
|
||||
refreshNodeSlots(String(slotErrorsEvent.nodeId))
|
||||
},
|
||||
'node:slot-links:changed': (slotLinksEvent) => {
|
||||
if (slotLinksEvent.slotType === NodeSlotType.INPUT) {
|
||||
refreshNodeSlots(String(slotLinksEvent.nodeId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Call original trigger handler if it exists
|
||||
if (originalOnTrigger) {
|
||||
originalOnTrigger(action, param)
|
||||
graph.onTrigger = (event: LGraphTriggerEvent) => {
|
||||
switch (event.type) {
|
||||
case 'node:property:changed':
|
||||
triggerHandlers['node:property:changed'](event)
|
||||
break
|
||||
case 'node:slot-errors:changed':
|
||||
triggerHandlers['node:slot-errors:changed'](event)
|
||||
break
|
||||
case 'node:slot-links:changed':
|
||||
triggerHandlers['node:slot-links:changed'](event)
|
||||
break
|
||||
}
|
||||
|
||||
// Chain to original handler
|
||||
originalOnTrigger?.(event)
|
||||
}
|
||||
|
||||
// Initialize state
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import {
|
||||
LGraphEventMode,
|
||||
type LGraphGroup,
|
||||
type LGraphNode
|
||||
} from '@/lib/litegraph/src/litegraph'
|
||||
import { LGraphEventMode } from '@/lib/litegraph/src/litegraph'
|
||||
import type { LGraphGroup, LGraphNode } from '@/lib/litegraph/src/litegraph'
|
||||
import { useSettingStore } from '@/platform/settings/settingStore'
|
||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type Ref, computed, ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import type { LGraphGroup } from '@/lib/litegraph/src/litegraph'
|
||||
import { isLGraphGroup } from '@/utils/litegraphUtil'
|
||||
|
||||
@@ -4,7 +4,8 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useCommandStore } from '@/stores/commandStore'
|
||||
|
||||
import { useFrameNodes } from './useFrameNodes'
|
||||
import { BadgeVariant, type MenuOption } from './useMoreOptionsMenu'
|
||||
import { BadgeVariant } from './useMoreOptionsMenu'
|
||||
import type { MenuOption } from './useMoreOptionsMenu'
|
||||
import { useNodeArrangement } from './useNodeArrangement'
|
||||
import { useSelectionOperations } from './useSelectionOperations'
|
||||
import { useSubgraphOperations } from './useSubgraphOperations'
|
||||
|
||||
@@ -116,7 +116,7 @@ function useVueNodeLifecycleIndividual() {
|
||||
slotSyncManager.attemptStart(canvas as LGraphCanvas)
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
{ immediate: true, flush: 'sync' }
|
||||
)
|
||||
|
||||
// Handle case where Vue nodes are enabled but graph starts empty
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
* Composable for managing widget value synchronization between Vue and LiteGraph
|
||||
* Provides consistent pattern for immediate UI updates and LiteGraph callbacks
|
||||
*/
|
||||
import { type Ref, ref, watch } from 'vue'
|
||||
import { ref, watch } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
|
||||
import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user