diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts index 23edf6ea6..ad030222a 100644 --- a/src/composables/graph/useGraphNodeManager.ts +++ b/src/composables/graph/useGraphNodeManager.ts @@ -10,7 +10,10 @@ import type { INodeInputSlot, INodeOutputSlot } from '@/lib/litegraph/src/interfaces' -import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' +import type { + IBaseWidget, + IWidgetOptions +} from '@/lib/litegraph/src/types/widgets' import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' import { LayoutSource } from '@/renderer/core/layout/types' import type { NodeId } from '@/renderer/core/layout/types' @@ -39,7 +42,7 @@ export interface SafeWidgetData { type: string value: WidgetValue label?: string - options?: Record + options?: IWidgetOptions callback?: ((value: unknown) => void) | undefined spec?: InputSpec slotMetadata?: WidgetSlotMetadata @@ -107,7 +110,7 @@ export function safeWidgetMapper( type: widget.type, value: value, label: widget.label, - options: widget.options ? { ...widget.options } : undefined, + options: widget.options, callback: widget.callback, spec, slotMetadata: slotInfo, diff --git a/src/extensions/core/previewAny.ts b/src/extensions/core/previewAny.ts index d14cfa587..fd8dd96f7 100644 --- a/src/extensions/core/previewAny.ts +++ b/src/extensions/core/previewAny.ts @@ -24,12 +24,43 @@ useExtensionService().registerExtension({ app ).widget as DOMWidget - showValueWidget.options.read_only = true + const showValueWidgetPlain = ComfyWidgets['STRING']( + this, + 'preview', + ['STRING', { multiline: true }], + app + ).widget as DOMWidget + const showAsPlaintextWidget = ComfyWidgets['BOOLEAN']( + this, + 'previewMode', + [ + 'BOOLEAN', + { label_on: 'Markdown', label_off: 'Plaintext', default: false } + ], + app + ) + + showAsPlaintextWidget.widget.callback = (value) => { + showValueWidget.hidden = !value + showValueWidget.options.hidden = !value + showValueWidgetPlain.hidden = value + showValueWidgetPlain.options.hidden = value + } + + showValueWidget.hidden = true + showValueWidget.options.hidden = true + showValueWidget.options.read_only = true showValueWidget.element.readOnly = true showValueWidget.element.disabled = true - showValueWidget.serialize = false + + showValueWidgetPlain.hidden = false + showValueWidgetPlain.options.hidden = false + showValueWidgetPlain.options.read_only = true + showValueWidgetPlain.element.readOnly = true + showValueWidgetPlain.element.disabled = true + showValueWidgetPlain.serialize = false } const onExecuted = nodeType.prototype.onExecuted @@ -39,9 +70,10 @@ useExtensionService().registerExtension({ ? void 0 : onExecuted.apply(this, [message]) - const previewWidget = this.widgets?.find((w) => w.name === 'preview') + const previewWidgets = + this.widgets?.filter((w) => w.name === 'preview') ?? [] - if (previewWidget) { + for (const previewWidget of previewWidgets) { previewWidget.value = message.text[0] } } diff --git a/src/extensions/core/widgetInputs.ts b/src/extensions/core/widgetInputs.ts index 2024a41c7..0a660b454 100644 --- a/src/extensions/core/widgetInputs.ts +++ b/src/extensions/core/widgetInputs.ts @@ -14,7 +14,11 @@ import type { CanvasPointerEvent } from '@/lib/litegraph/src/types/events' import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets' import type { InputSpec } from '@/schemas/nodeDefSchema' import { app } from '@/scripts/app' -import { ComfyWidgets, addValueControlWidgets } from '@/scripts/widgets' +import { + ComfyWidgets, + addValueControlWidgets, + isValidWidgetType +} from '@/scripts/widgets' import { CONFIG, GET_CONFIG } from '@/services/litegraphService' import { mergeInputSpec } from '@/utils/nodeDefUtil' import { applyTextReplacements } from '@/utils/searchAndReplace' @@ -223,8 +227,8 @@ export class PrimitiveNode extends LGraphNode { // Store current size as addWidget resizes the node const [oldWidth, oldHeight] = this.size - let widget: IBaseWidget | undefined - if (type in ComfyWidgets) { + let widget: IBaseWidget + if (isValidWidgetType(type)) { widget = (ComfyWidgets[type](this, 'value', inputData, app) || {}).widget } else { // @ts-expect-error InputSpec is not typed correctly diff --git a/src/lib/litegraph/src/LGraphNode.ts b/src/lib/litegraph/src/LGraphNode.ts index 25ce7770b..6d870e548 100644 --- a/src/lib/litegraph/src/LGraphNode.ts +++ b/src/lib/litegraph/src/LGraphNode.ts @@ -4027,7 +4027,9 @@ export class LGraphNode w: IBaseWidget }[] = [] - for (const w of this.widgets) { + const visibleWidgets = this.widgets.filter((w) => !w.hidden) + + for (const w of visibleWidgets) { if (w.computeSize) { const height = w.computeSize()[1] + 4 w.computedHeight = height @@ -4066,7 +4068,7 @@ export class LGraphNode // Position widgets let y = startY - for (const w of this.widgets) { + for (const w of visibleWidgets) { w.y = y y += w.computedHeight ?? 0 } diff --git a/src/renderer/extensions/vueNodes/components/LGraphNodePreview.vue b/src/renderer/extensions/vueNodes/components/LGraphNodePreview.vue index 1e39d0132..38b0b6884 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNodePreview.vue +++ b/src/renderer/extensions/vueNodes/components/LGraphNodePreview.vue @@ -60,7 +60,6 @@ const nodeData = computed(() => { ? input.options[0] : '', options: { - ...input, hidden: input.hidden, advanced: input.advanced, values: input.type === 'COMBO' ? input.options : undefined // For combo widgets diff --git a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue index 9a0983a4f..2cf54e8e6 100644 --- a/src/renderer/extensions/vueNodes/components/NodeWidgets.vue +++ b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue @@ -16,46 +16,51 @@ @pointermove="handleWidgetPointerEvent" @pointerup="handleWidgetPointerEvent" > -
- -
- + +
+ +
+ +
- - -
+ @@ -132,25 +137,20 @@ const processedWidgets = computed((): ProcessedWidget[] => { const result: ProcessedWidget[] = [] for (const widget of widgets) { - // Skip if widget is in the hidden list for this node type - if (widget.options?.hidden) continue - if (widget.options?.canvasOnly) continue - if (!widget.type) continue if (!shouldRenderAsVue(widget)) continue const vueComponent = getComponent(widget.type, widget.name) || (widget.isDOMWidget ? WidgetDOM : WidgetLegacy) - const slotMetadata = widget.slotMetadata + const { slotMetadata, options } = widget - let widgetOptions = widget.options // Core feature: Disable Vue widgets when their input slots are connected // This prevents conflicting input sources - when a slot is linked to another // node's output, the widget should be read-only to avoid data conflicts - if (slotMetadata?.linked) { - widgetOptions = { ...widget.options, disabled: true } - } + const widgetOptions = slotMetadata?.linked + ? { ...options, disabled: true } + : options const simplified: SimplifiedWidget = { name: widget.name, @@ -162,7 +162,7 @@ const processedWidgets = computed((): ProcessedWidget[] => { spec: widget.spec } - const updateHandler = (value: WidgetValue) => { + function updateHandler(value: WidgetValue) { // Update the widget value directly widget.value = value diff --git a/src/scripts/widgets.ts b/src/scripts/widgets.ts index c07a9c20a..50dabe011 100644 --- a/src/scripts/widgets.ts +++ b/src/scripts/widgets.ts @@ -285,7 +285,7 @@ export function addValueControlWidgets( return widgets } -export const ComfyWidgets: Record = { +export const ComfyWidgets = { INT: transformWidgetConstructorV2ToV1(useIntWidget()), FLOAT: transformWidgetConstructorV2ToV1(useFloatWidget()), BOOLEAN: transformWidgetConstructorV2ToV1(useBooleanWidget()), @@ -299,4 +299,10 @@ export const ComfyWidgets: Record = { GALLERIA: transformWidgetConstructorV2ToV1(useGalleriaWidget()), TEXTAREA: transformWidgetConstructorV2ToV1(useTextareaWidget()), ...dynamicWidgets +} as const + +export function isValidWidgetType( + key: unknown +): key is keyof typeof ComfyWidgets { + return ComfyWidgets[key as keyof typeof ComfyWidgets] !== undefined } diff --git a/tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts b/tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts index 37fc004fe..8158b43da 100644 --- a/tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts +++ b/tests-ui/tests/renderer/extensions/vueNodes/widgets/composables/useWidgetRenderer.test.ts @@ -6,6 +6,7 @@ import { shouldRenderAsVue, FOR_TESTING } from '@/renderer/extensions/vueNodes/widgets/registry/widgetRegistry' +import type { SafeWidgetData } from '@/composables/graph/useGraphNodeManager' const { WidgetAudioUI, @@ -121,7 +122,10 @@ describe('widgetRegistry', () => { }) it('should respect options while checking type', () => { - const widget = { type: 'text', options: { someOption: 'value' } } + const widget: Partial = { + type: 'text', + options: { precision: 5 } + } expect(shouldRenderAsVue(widget)).toBe(true) }) })