handleResizeStart(e, handle.direction)"
- @pointermove="handleResizeMove"
- @pointerup="handleResizeEnd"
- />
+
+ handleResizeStart(e, handle.direction)"
+ @pointermove="handleResizeMove"
+ @pointerup="handleResizeEnd"
+ />
+
-
+
@@ -90,12 +101,16 @@
-
+
diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts
index c40fe95e49..4c87b68355 100644
--- a/src/composables/graph/useGraphNodeManager.ts
+++ b/src/composables/graph/useGraphNodeManager.ts
@@ -41,6 +41,8 @@ import { getExecutionIdByNode } from '@/utils/graphTraversalUtil'
export interface WidgetSlotMetadata {
index: number
linked: boolean
+ originNodeId?: string
+ originOutputName?: string
}
/**
@@ -355,6 +357,36 @@ function safeWidgetMapper(
}
}
+function buildSlotMetadata(
+ inputs: INodeInputSlot[] | undefined,
+ graphRef: LGraph | null | undefined
+): Map
{
+ const metadata = new Map()
+ inputs?.forEach((input, index) => {
+ let originNodeId: string | undefined
+ let originOutputName: string | undefined
+
+ if (input.link != null && graphRef) {
+ const link = graphRef.getLink(input.link)
+ if (link) {
+ originNodeId = String(link.origin_id)
+ const originNode = graphRef.getNodeById(link.origin_id)
+ originOutputName = originNode?.outputs?.[link.origin_slot]?.name
+ }
+ }
+
+ const slotInfo: WidgetSlotMetadata = {
+ index,
+ linked: input.link != null,
+ originNodeId,
+ originOutputName
+ }
+ if (input.name) metadata.set(input.name, slotInfo)
+ if (input.widget?.name) metadata.set(input.widget.name, slotInfo)
+ })
+ return metadata
+}
+
// Extract safe data from LiteGraph node for Vue consumption
export function extractVueNodeData(node: LGraphNode): VueNodeData {
// Determine subgraph ID - null for root graph, string for subgraphs
@@ -427,15 +459,11 @@ export function extractVueNodeData(node: LGraphNode): VueNodeData {
const safeWidgets = reactiveComputed(() => {
const widgetsSnapshot = node.widgets ?? []
+ const freshMetadata = buildSlotMetadata(node.inputs, node.graph)
slotMetadata.clear()
- node.inputs?.forEach((input, index) => {
- const slotInfo = {
- index,
- linked: input.link != null
- }
- if (input.name) slotMetadata.set(input.name, slotInfo)
- if (input.widget?.name) slotMetadata.set(input.widget.name, slotInfo)
- })
+ for (const [key, value] of freshMetadata) {
+ slotMetadata.set(key, value)
+ }
return widgetsSnapshot.map(safeWidgetMapper(node, slotMetadata))
})
@@ -488,17 +516,7 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager {
if (!nodeRef || !currentData) return
- // Only extract slot-related data instead of full node re-extraction
- const slotMetadata = new Map()
-
- nodeRef.inputs?.forEach((input, index) => {
- const slotInfo = {
- index,
- linked: input.link != null
- }
- if (input.name) slotMetadata.set(input.name, slotInfo)
- if (input.widget?.name) slotMetadata.set(input.widget.name, slotInfo)
- })
+ const slotMetadata = buildSlotMetadata(nodeRef.inputs, graph)
// Update only widgets with new slot metadata, keeping other widget data intact
for (const widget of currentData.widgets ?? []) {
diff --git a/src/composables/useUpstreamValue.ts b/src/composables/useUpstreamValue.ts
new file mode 100644
index 0000000000..de578cee4d
--- /dev/null
+++ b/src/composables/useUpstreamValue.ts
@@ -0,0 +1,83 @@
+import { computed } from 'vue'
+
+import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
+import { useWidgetValueStore } from '@/stores/widgetValueStore'
+import type { LinkedUpstreamInfo } from '@/types/simplifiedWidget'
+
+interface UpstreamWidget {
+ name: string
+ type: string
+ value?: unknown
+}
+
+type ValueExtractor = (
+ widgets: UpstreamWidget[],
+ outputName: string | undefined
+) => unknown | undefined
+
+export function useUpstreamValue(
+ getLinkedUpstream: () => LinkedUpstreamInfo | undefined,
+ extractValue: ValueExtractor
+) {
+ const canvasStore = useCanvasStore()
+ const widgetValueStore = useWidgetValueStore()
+
+ return computed(() => {
+ const upstream = getLinkedUpstream()
+ if (!upstream) return undefined
+ const graphId = canvasStore.canvas?.graph?.rootGraph.id
+ if (!graphId) return undefined
+ const widgets = widgetValueStore.getNodeWidgets(graphId, upstream.nodeId)
+ return extractValue(widgets, upstream.outputName)
+ })
+}
+
+export function singleValueExtractor(
+ isValid: (value: unknown) => boolean
+): ValueExtractor {
+ return (widgets, outputName) => {
+ if (outputName) {
+ const matched = widgets.find((w) => w.name === outputName)
+ if (matched && isValid(matched.value)) return matched.value
+ }
+ const valid = widgets.filter((w) => isValid(w.value))
+ return valid.length === 1 ? valid[0].value : undefined
+ }
+}
+
+function isBoundsObject(value: unknown): boolean {
+ if (typeof value !== 'object' || value === null) return false
+ const v = value as Record
+ return (
+ typeof v.x === 'number' &&
+ typeof v.y === 'number' &&
+ typeof v.width === 'number' &&
+ typeof v.height === 'number'
+ )
+}
+
+export function boundsExtractor(): ValueExtractor {
+ const single = singleValueExtractor(isBoundsObject)
+ return (widgets, outputName) => {
+ const singleResult = single(widgets, outputName)
+ if (singleResult) return singleResult
+
+ const getNum = (name: string): number | undefined => {
+ const w = widgets.find((w) => w.name === name)
+ return typeof w?.value === 'number' ? w.value : undefined
+ }
+ const x = getNum('x')
+ const y = getNum('y')
+ const width = getNum('width')
+ const height = getNum('height')
+ if (
+ x !== undefined &&
+ y !== undefined &&
+ width !== undefined &&
+ height !== undefined
+ ) {
+ return { x, y, width, height }
+ }
+ return undefined
+ }
+}
diff --git a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue
index c9e73669ff..d643af4264 100644
--- a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue
+++ b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue
@@ -120,7 +120,11 @@ import {
import { usePromotionStore } from '@/stores/promotionStore'
import { useMissingModelStore } from '@/platform/missingModel/missingModelStore'
import { useExecutionErrorStore } from '@/stores/executionErrorStore'
-import type { SimplifiedWidget, WidgetValue } from '@/types/simplifiedWidget'
+import type {
+ LinkedUpstreamInfo,
+ SimplifiedWidget,
+ WidgetValue
+} from '@/types/simplifiedWidget'
import { cn } from '@/utils/tailwindUtil'
import { getExecutionIdFromNodeData } from '@/utils/graphTraversalUtil'
import { app } from '@/scripts/app'
@@ -297,6 +301,14 @@ const processedWidgets = computed((): ProcessedWidget[] => {
? 'ring ring-component-node-widget-advanced'
: undefined
+ const linkedUpstream: LinkedUpstreamInfo | undefined =
+ slotMetadata?.linked && slotMetadata.originNodeId
+ ? {
+ nodeId: slotMetadata.originNodeId,
+ outputName: slotMetadata.originOutputName
+ }
+ : undefined
+
const simplified: SimplifiedWidget = {
name: widget.name,
type: widget.type,
@@ -305,6 +317,7 @@ const processedWidgets = computed((): ProcessedWidget[] => {
callback: widget.callback,
controlWidget: widget.controlWidget,
label: widgetState?.label,
+ linkedUpstream,
options: widgetOptions,
spec: widget.spec
}
diff --git a/src/types/simplifiedWidget.ts b/src/types/simplifiedWidget.ts
index 10ba258ce9..7703e15c1f 100644
--- a/src/types/simplifiedWidget.ts
+++ b/src/types/simplifiedWidget.ts
@@ -38,6 +38,11 @@ export type SafeControlWidget = {
update: (value: WidgetValue) => void
}
+export interface LinkedUpstreamInfo {
+ nodeId: string
+ outputName?: string
+}
+
export interface SimplifiedWidget<
T extends WidgetValue = WidgetValue,
O extends IWidgetOptions = IWidgetOptions
@@ -77,6 +82,8 @@ export interface SimplifiedWidget<
tooltip?: string
controlWidget?: SafeControlWidget
+
+ linkedUpstream?: LinkedUpstreamInfo
}
export interface SimplifiedControlWidget<