diff --git a/src/composables/graph/useWidgetRenderer.ts b/src/composables/graph/useWidgetRenderer.ts
index 762d9affa..ad7e185a9 100644
--- a/src/composables/graph/useWidgetRenderer.ts
+++ b/src/composables/graph/useWidgetRenderer.ts
@@ -5,7 +5,7 @@
import {
WidgetType,
widgetTypeToComponent
-} from '@/components/graph/vueWidgets/widgetRegistry'
+} from '@/renderer/extensions/vueNodes/widgets/registry/widgetRegistry'
/**
* Static mapping of LiteGraph widget types to Vue widget component names
diff --git a/src/composables/node/useNodeCanvasImagePreview.ts b/src/composables/node/useNodeCanvasImagePreview.ts
index 98d49b485..008119407 100644
--- a/src/composables/node/useNodeCanvasImagePreview.ts
+++ b/src/composables/node/useNodeCanvasImagePreview.ts
@@ -1,5 +1,5 @@
-import { useImagePreviewWidget } from '@/composables/widgets/useImagePreviewWidget'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
+import { useImagePreviewWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useImagePreviewWidget'
const CANVAS_IMAGE_PREVIEW_WIDGET = '$$canvas-image-preview'
diff --git a/src/composables/node/useNodeChatHistory.ts b/src/composables/node/useNodeChatHistory.ts
index 8fbe78895..a1fa3ad3a 100644
--- a/src/composables/node/useNodeChatHistory.ts
+++ b/src/composables/node/useNodeChatHistory.ts
@@ -1,6 +1,6 @@
import type ChatHistoryWidget from '@/components/graph/widgets/ChatHistoryWidget.vue'
-import { useChatHistoryWidget } from '@/composables/widgets/useChatHistoryWidget'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
+import { useChatHistoryWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useChatHistoryWidget'
const CHAT_HISTORY_WIDGET_NAME = '$$node-chat-history'
diff --git a/src/composables/node/useNodeProgressText.ts b/src/composables/node/useNodeProgressText.ts
index 12e09bd5e..07e7488ea 100644
--- a/src/composables/node/useNodeProgressText.ts
+++ b/src/composables/node/useNodeProgressText.ts
@@ -1,5 +1,5 @@
-import { useTextPreviewWidget } from '@/composables/widgets/useProgressTextWidget'
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
+import { useTextPreviewWidget } from '@/renderer/extensions/vueNodes/widgets/composables/useProgressTextWidget'
const TEXT_PREVIEW_WIDGET_NAME = '$$node-text-preview'
diff --git a/src/components/graph/vueNodes/InputSlot.vue b/src/renderer/extensions/vueNodes/components/InputSlot.vue
similarity index 100%
rename from src/components/graph/vueNodes/InputSlot.vue
rename to src/renderer/extensions/vueNodes/components/InputSlot.vue
diff --git a/src/components/graph/vueNodes/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue
similarity index 97%
rename from src/components/graph/vueNodes/LGraphNode.vue
rename to src/renderer/extensions/vueNodes/components/LGraphNode.vue
index df97ffa7c..369fe047e 100644
--- a/src/components/graph/vueNodes/LGraphNode.vue
+++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue
@@ -91,11 +91,11 @@ import { computed, onErrorCaptured, ref, toRef, watch } from 'vue'
// Import the VueNodeData type
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
-import { LODLevel, useLOD } from '@/composables/graph/useLOD'
import { useErrorHandling } from '@/composables/useErrorHandling'
-import { useNodeLayout } from '@/renderer/extensions/vue-nodes/composables/useNodeLayout'
+import { LiteGraph } from '@/lib/litegraph/src/litegraph'
+import { useNodeLayout } from '@/renderer/extensions/vueNodes/layout/useNodeLayout'
+import { LODLevel, useLOD } from '@/renderer/extensions/vueNodes/lod/useLOD'
-import { LiteGraph } from '../../../lib/litegraph/src/litegraph'
import NodeContent from './NodeContent.vue'
import NodeHeader from './NodeHeader.vue'
import NodeSlots from './NodeSlots.vue'
diff --git a/src/components/graph/vueNodes/NodeContent.vue b/src/renderer/extensions/vueNodes/components/NodeContent.vue
similarity index 89%
rename from src/components/graph/vueNodes/NodeContent.vue
rename to src/renderer/extensions/vueNodes/components/NodeContent.vue
index 41ae8df34..48a03670c 100644
--- a/src/components/graph/vueNodes/NodeContent.vue
+++ b/src/renderer/extensions/vueNodes/components/NodeContent.vue
@@ -16,10 +16,9 @@
import { onErrorCaptured, ref } from 'vue'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
-import type { LODLevel } from '@/composables/graph/useLOD'
import { useErrorHandling } from '@/composables/useErrorHandling'
-
-import type { LGraphNode } from '../../../lib/litegraph/src/litegraph'
+import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
+import type { LODLevel } from '@/renderer/extensions/vueNodes/lod/useLOD'
interface NodeContentProps {
node?: LGraphNode // For backwards compatibility
diff --git a/src/components/graph/vueNodes/NodeHeader.vue b/src/renderer/extensions/vueNodes/components/NodeHeader.vue
similarity index 96%
rename from src/components/graph/vueNodes/NodeHeader.vue
rename to src/renderer/extensions/vueNodes/components/NodeHeader.vue
index 3eaaa774a..0a9e15fb1 100644
--- a/src/components/graph/vueNodes/NodeHeader.vue
+++ b/src/renderer/extensions/vueNodes/components/NodeHeader.vue
@@ -45,10 +45,9 @@ import { computed, onErrorCaptured, ref, watch } from 'vue'
import EditableText from '@/components/common/EditableText.vue'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
-import type { LODLevel } from '@/composables/graph/useLOD'
import { useErrorHandling } from '@/composables/useErrorHandling'
-
-import type { LGraphNode } from '../../../lib/litegraph/src/litegraph'
+import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
+import type { LODLevel } from '@/renderer/extensions/vueNodes/lod/useLOD'
interface NodeHeaderProps {
node?: LGraphNode // For backwards compatibility
diff --git a/src/components/graph/vueNodes/NodeSlots.vue b/src/renderer/extensions/vueNodes/components/NodeSlots.vue
similarity index 96%
rename from src/components/graph/vueNodes/NodeSlots.vue
rename to src/renderer/extensions/vueNodes/components/NodeSlots.vue
index 527c88d8a..8310dcf33 100644
--- a/src/components/graph/vueNodes/NodeSlots.vue
+++ b/src/renderer/extensions/vueNodes/components/NodeSlots.vue
@@ -35,14 +35,11 @@ import { computed, onErrorCaptured, onUnmounted, ref } from 'vue'
import { useEventForwarding } from '@/composables/graph/useEventForwarding'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
-import type { LODLevel } from '@/composables/graph/useLOD'
import { useErrorHandling } from '@/composables/useErrorHandling'
+import type { INodeSlot, LGraphNode } from '@/lib/litegraph/src/litegraph'
+import type { LODLevel } from '@/renderer/extensions/vueNodes/lod/useLOD'
import { isSlotObject } from '@/utils/typeGuardUtil'
-import type {
- INodeSlot,
- LGraphNode
-} from '../../../lib/litegraph/src/litegraph'
import InputSlot from './InputSlot.vue'
import OutputSlot from './OutputSlot.vue'
diff --git a/src/components/graph/vueNodes/NodeWidgets.vue b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue
similarity index 90%
rename from src/components/graph/vueNodes/NodeWidgets.vue
rename to src/renderer/extensions/vueNodes/components/NodeWidgets.vue
index 10bc978c1..8b077fcca 100644
--- a/src/components/graph/vueNodes/NodeWidgets.vue
+++ b/src/renderer/extensions/vueNodes/components/NodeWidgets.vue
@@ -41,23 +41,23 @@
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetChart.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetChart.vue
new file mode 100644
index 000000000..1c40d35a7
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetChart.vue
@@ -0,0 +1,78 @@
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.vue
new file mode 100644
index 000000000..16a3dd374
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetColorPicker.vue
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetFileUpload.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetFileUpload.vue
new file mode 100644
index 000000000..e918a4302
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetFileUpload.vue
@@ -0,0 +1,324 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ selectedFile?.name }}
+
+ {{
+ selectedFile ? (selectedFile.size / 1024).toFixed(1) + ' KB' : ''
+ }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Drop your file or
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.vue
new file mode 100644
index 000000000..3603b7ab6
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetGalleria.vue
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
![]()
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetImage.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetImage.vue
new file mode 100644
index 000000000..d7af95cf0
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetImage.vue
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetImageCompare.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetImageCompare.vue
new file mode 100644
index 000000000..e51413a30
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetImageCompare.vue
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetInputText.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputText.vue
new file mode 100644
index 000000000..8cb41dfa9
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetInputText.vue
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetMarkdown.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetMarkdown.vue
new file mode 100644
index 000000000..4749e561c
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetMarkdown.vue
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetMultiSelect.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetMultiSelect.vue
new file mode 100644
index 000000000..d90bc3ac7
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetMultiSelect.vue
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
new file mode 100644
index 000000000..266282f96
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelect.vue
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectButton.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectButton.vue
new file mode 100644
index 000000000..95ae3b0ef
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSelectButton.vue
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetSlider.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetSlider.vue
new file mode 100644
index 000000000..de645c2b2
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetSlider.vue
@@ -0,0 +1,169 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue
new file mode 100644
index 000000000..2b994432d
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetTextarea.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.vue
new file mode 100644
index 000000000..7fee347ae
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetToggleSwitch.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/widgets/components/WidgetTreeSelect.vue b/src/renderer/extensions/vueNodes/widgets/components/WidgetTreeSelect.vue
new file mode 100644
index 000000000..990af664e
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/components/WidgetTreeSelect.vue
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useBooleanWidget.ts b/src/renderer/extensions/vueNodes/widgets/composables/useBooleanWidget.ts
new file mode 100644
index 000000000..bc307e3b0
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/composables/useBooleanWidget.ts
@@ -0,0 +1,33 @@
+import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
+import {
+ type InputSpec,
+ isBooleanInputSpec
+} from '@/schemas/nodeDef/nodeDefSchemaV2'
+import { type ComfyWidgetConstructorV2 } from '@/scripts/widgets'
+
+export const useBooleanWidget = () => {
+ const widgetConstructor: ComfyWidgetConstructorV2 = (
+ node: LGraphNode,
+ inputSpec: InputSpec
+ ) => {
+ if (!isBooleanInputSpec(inputSpec)) {
+ throw new Error(`Invalid input data: ${inputSpec}`)
+ }
+
+ const defaultVal = inputSpec.default ?? false
+ const options = {
+ on: inputSpec.label_on,
+ off: inputSpec.label_off
+ }
+
+ return node.addWidget(
+ 'toggle',
+ inputSpec.name,
+ defaultVal,
+ () => {},
+ options
+ )
+ }
+
+ return widgetConstructor
+}
diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useChartWidget.ts b/src/renderer/extensions/vueNodes/widgets/composables/useChartWidget.ts
new file mode 100644
index 000000000..4c94155a6
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/composables/useChartWidget.ts
@@ -0,0 +1,28 @@
+import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
+import type { IChartWidget } from '@/lib/litegraph/src/types/widgets'
+import {
+ type ChartInputSpec,
+ type InputSpec as InputSpecV2,
+ isChartInputSpec
+} from '@/schemas/nodeDef/nodeDefSchemaV2'
+import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets'
+
+export const useChartWidget = (): ComfyWidgetConstructorV2 => {
+ return (node: LGraphNode, inputSpec: InputSpecV2): IChartWidget => {
+ if (!isChartInputSpec(inputSpec)) {
+ throw new Error('Invalid input spec for chart widget')
+ }
+
+ const { name, options = {} } = inputSpec as ChartInputSpec
+
+ const chartType = options.type || 'line'
+
+ const widget = node.addWidget('chart', name, options.data || {}, () => {}, {
+ serialize: true,
+ type: chartType,
+ ...options
+ }) as IChartWidget
+
+ return widget
+ }
+}
diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useChatHistoryWidget.ts b/src/renderer/extensions/vueNodes/widgets/composables/useChatHistoryWidget.ts
new file mode 100644
index 000000000..53fe6ce6e
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/composables/useChatHistoryWidget.ts
@@ -0,0 +1,52 @@
+import { ref } from 'vue'
+
+import ChatHistoryWidget from '@/components/graph/widgets/ChatHistoryWidget.vue'
+import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
+import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
+import {
+ ComponentWidgetImpl,
+ type ComponentWidgetStandardProps,
+ addWidget
+} from '@/scripts/domWidget'
+import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets'
+
+type ChatHistoryCustomProps = Omit<
+ InstanceType['$props'],
+ ComponentWidgetStandardProps
+>
+
+const PADDING = 16
+
+export const useChatHistoryWidget = (
+ options: {
+ props?: ChatHistoryCustomProps
+ } = {}
+) => {
+ const widgetConstructor: ComfyWidgetConstructorV2 = (
+ node: LGraphNode,
+ inputSpec: InputSpec
+ ) => {
+ const widgetValue = ref('')
+ const widget = new ComponentWidgetImpl<
+ string | object,
+ ChatHistoryCustomProps
+ >({
+ node,
+ name: inputSpec.name,
+ component: ChatHistoryWidget,
+ props: options.props,
+ inputSpec,
+ options: {
+ getValue: () => widgetValue.value,
+ setValue: (value: string | object) => {
+ widgetValue.value = typeof value === 'string' ? value : String(value)
+ },
+ getMinHeight: () => 400 + PADDING
+ }
+ })
+ addWidget(node, widget)
+ return widget
+ }
+
+ return widgetConstructor
+}
diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useColorWidget.ts b/src/renderer/extensions/vueNodes/widgets/composables/useColorWidget.ts
new file mode 100644
index 000000000..420e31ce0
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/composables/useColorWidget.ts
@@ -0,0 +1,20 @@
+import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
+import type { IColorWidget } from '@/lib/litegraph/src/types/widgets'
+import type {
+ ColorInputSpec,
+ InputSpec as InputSpecV2
+} from '@/schemas/nodeDef/nodeDefSchemaV2'
+import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets'
+
+export const useColorWidget = (): ComfyWidgetConstructorV2 => {
+ return (node: LGraphNode, inputSpec: InputSpecV2): IColorWidget => {
+ const { name, options } = inputSpec as ColorInputSpec
+ const defaultValue = options?.default || '#000000'
+
+ const widget = node.addWidget('color', name, defaultValue, () => {}, {
+ serialize: true
+ }) as IColorWidget
+
+ return widget
+ }
+}
diff --git a/src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts b/src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
new file mode 100644
index 000000000..ad973325d
--- /dev/null
+++ b/src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
@@ -0,0 +1,111 @@
+import { ref } from 'vue'
+
+import MultiSelectWidget from '@/components/graph/widgets/MultiSelectWidget.vue'
+import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
+import type { IComboWidget } from '@/lib/litegraph/src/types/widgets'
+import { transformInputSpecV2ToV1 } from '@/schemas/nodeDef/migration'
+import {
+ ComboInputSpec,
+ type InputSpec,
+ isComboInputSpec
+} from '@/schemas/nodeDef/nodeDefSchemaV2'
+import {
+ type BaseDOMWidget,
+ ComponentWidgetImpl,
+ addWidget
+} from '@/scripts/domWidget'
+import {
+ type ComfyWidgetConstructorV2,
+ addValueControlWidgets
+} from '@/scripts/widgets'
+
+import { useRemoteWidget } from './useRemoteWidget'
+
+const getDefaultValue = (inputSpec: ComboInputSpec) => {
+ if (inputSpec.default) return inputSpec.default
+ if (inputSpec.options?.length) return inputSpec.options[0]
+ if (inputSpec.remote) return 'Loading...'
+ return undefined
+}
+
+const addMultiSelectWidget = (node: LGraphNode, inputSpec: ComboInputSpec) => {
+ const widgetValue = ref([])
+ const widget = new ComponentWidgetImpl({
+ node,
+ name: inputSpec.name,
+ component: MultiSelectWidget,
+ inputSpec,
+ options: {
+ getValue: () => widgetValue.value,
+ setValue: (value: string[]) => {
+ widgetValue.value = value
+ }
+ }
+ })
+ addWidget(node, widget as BaseDOMWidget