From 0858356dcf9e59b69b3f30df40a6b25cce9b5dae Mon Sep 17 00:00:00 2001 From: bymyself Date: Mon, 9 Jun 2025 00:24:23 -0700 Subject: [PATCH] alias old float/int widgets --- .claude/commands/create-widget.md | 53 + copy-widget-resources.sh | 89 + .../graph/widgets/BadgedNumberInput.vue | 71 +- .../widgets/useBadgedNumberInput.ts | 108 +- src/composables/widgets/useBooleanWidget.ts | 2 +- .../widgets/useChatHistoryWidget.ts | 2 +- src/composables/widgets/useComboWidget.ts | 22 +- src/composables/widgets/useFloatWidget.ts | 2 +- .../widgets/useImagePreviewWidget.ts | 2 +- src/composables/widgets/useIntWidget.ts | 26 +- src/composables/widgets/useMarkdownWidget.ts | 2 +- .../widgets/useProgressTextWidget.ts | 2 +- src/composables/widgets/useStringWidget.ts | 2 +- src/scripts/widgetTypes.ts | 12 + src/scripts/widgets.ts | 15 +- .../primevue-components.json | 919 +++++++ vue-widget-conversion/primevue-components.md | 2261 +++++++++++++++++ vue-widget-conversion/vue-widget-guide.md | 552 ++++ 18 files changed, 4069 insertions(+), 73 deletions(-) create mode 100644 .claude/commands/create-widget.md create mode 100755 copy-widget-resources.sh create mode 100644 src/scripts/widgetTypes.ts create mode 100644 vue-widget-conversion/primevue-components.json create mode 100644 vue-widget-conversion/primevue-components.md create mode 100644 vue-widget-conversion/vue-widget-guide.md diff --git a/.claude/commands/create-widget.md b/.claude/commands/create-widget.md new file mode 100644 index 000000000..ebe7ec658 --- /dev/null +++ b/.claude/commands/create-widget.md @@ -0,0 +1,53 @@ +# Create a Vue Widget for ComfyUI + +Your task is to create a new Vue widget for ComfyUI based on the widget specification: $ARGUMENTS + +## Instructions + +Follow the comprehensive guide in `vue-widget-conversion/vue-widget-guide.md` to create the widget. This guide contains step-by-step instructions, examples from actual PRs, and best practices. + +### Key Steps to Follow: + +1. **Understand the Widget Type** + - Analyze what type of widget is needed: $ARGUMENTS + - Identify the data type (string, number, array, object, etc.) + - Determine if it needs special behaviors (execution state awareness, dynamic management, etc.) + +2. **Component Creation** + - Create Vue component in `src/components/graph/widgets/` + - REQUIRED: Use PrimeVue components (reference `vue-widget-conversion/primevue-components.md`) + - Use Composition API with ` + + +``` + +## Step 2: Create the Widget Composables (Dual Pattern) + +The Vue widget system uses a **dual composable pattern** for separation of concerns: + +### 2a. Create the Widget Constructor Composable + +Create the core widget constructor in `src/composables/widgets/`: + +```typescript +// src/composables/widgets/useYourWidget.ts +import { ref } from 'vue' +import type { LGraphNode } from '@comfyorg/litegraph' +import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2' +import { ComponentWidgetImpl, addWidget } from '@/scripts/domWidget' +import type { ComfyWidgetConstructorV2 } from '@/scripts/widgets' +import YourWidget from '@/components/graph/widgets/YourWidget.vue' + +const PADDING = 8 + +export const useYourWidget = (options: { defaultValue?: string } = {}) => { + const widgetConstructor: ComfyWidgetConstructorV2 = ( + node: LGraphNode, + inputSpec: InputSpec + ) => { + // Initialize widget value + const widgetValue = ref(options.defaultValue ?? '') + + // Create the widget instance + const widget = new ComponentWidgetImpl({ + node, + name: inputSpec.name, + component: YourWidget, + inputSpec, + options: { + // Required: getter for widget value + getValue: () => widgetValue.value, + + // Required: setter for widget value + setValue: (value: string) => { + widgetValue.value = value + }, + + // Optional: minimum height for the widget + getMinHeight: () => options.minHeight ?? 40 + PADDING, + + // Optional: whether to serialize this widget's value + serialize: true, + + // Optional: custom serialization + serializeValue: (value: string) => { + return { yourWidget: value } + } + } + }) + + // Register the widget with the node + addWidget(node, widget) + + return widget + } + + return widgetConstructor +} +``` + +### 2b. Create the Node-Level Logic Composable (When Needed) + +**Only create this if your widget needs dynamic management** (showing/hiding widgets based on events, execution state, etc.). Most standard widgets only need the widget constructor composable. + +For widgets that need node-level operations (like showing/hiding widgets dynamically), create a separate composable in `src/composables/node/`: + +```typescript +// src/composables/node/useNodeYourWidget.ts +import type { LGraphNode } from '@comfyorg/litegraph' +import { useYourWidget } from '@/composables/widgets/useYourWidget' + +const YOUR_WIDGET_NAME = '$$node-your-widget' + +/** + * Composable for handling node-level operations for YourWidget + */ +export function useNodeYourWidget() { + const yourWidget = useYourWidget() + + const findYourWidget = (node: LGraphNode) => + node.widgets?.find((w) => w.name === YOUR_WIDGET_NAME) + + const addYourWidget = (node: LGraphNode) => + yourWidget(node, { + name: YOUR_WIDGET_NAME, + type: 'yourWidgetType' + }) + + /** + * Shows your widget for a node + * @param node The graph node to show the widget for + * @param value The value to set + */ + function showYourWidget(node: LGraphNode, value: string) { + const widget = findYourWidget(node) ?? addYourWidget(node) + widget.value = value + node.setDirtyCanvas?.(true) + } + + /** + * Removes your widget from a node + * @param node The graph node to remove the widget from + */ + function removeYourWidget(node: LGraphNode) { + if (!node.widgets) return + + const widgetIdx = node.widgets.findIndex( + (w) => w.name === YOUR_WIDGET_NAME + ) + + if (widgetIdx > -1) { + node.widgets[widgetIdx].onRemove?.() + node.widgets.splice(widgetIdx, 1) + } + } + + return { + showYourWidget, + removeYourWidget + } +} +``` + +## Step 3: Register the Widget + +Add your widget to the global widget registry in `src/scripts/widgets.ts`: + +```typescript +// src/scripts/widgets.ts +import { useYourWidget } from '@/composables/widgets/useYourWidget' +import { transformWidgetConstructorV2ToV1 } from '@/scripts/utils' + +export const ComfyWidgets: Record = { + // ... existing widgets ... + YOUR_WIDGET: transformWidgetConstructorV2ToV1(useYourWidget()), +} +``` + +## Step 4: Handle Widget-Specific Logic + +For widgets that need special handling (e.g., listening to execution events): + +```typescript +// In your composable or a separate composable +import { useExecutionStore } from '@/stores/executionStore' +import { watchEffect, onUnmounted } from 'vue' + +export const useYourWidgetLogic = (nodeId: string) => { + const executionStore = useExecutionStore() + + // Watch for execution state changes + const stopWatcher = watchEffect(() => { + if (executionStore.isNodeExecuting(nodeId)) { + // Handle execution start + } + }) + + // Cleanup + onUnmounted(() => { + stopWatcher() + }) +} +``` + +## Step 5: Handle Complex Widget Types + +For widgets with complex data types or special requirements: + +```typescript +// Multi-value widget example +const widget = new ComponentWidgetImpl({ + node, + name: inputSpec.name, + component: MultiSelectWidget, + inputSpec, + options: { + getValue: () => widgetValue.value, + setValue: (value: string[]) => { + widgetValue.value = Array.isArray(value) ? value : [] + }, + getMinHeight: () => 40 + PADDING, + + // Custom validation + isValid: (value: string[]) => { + return Array.isArray(value) && value.length > 0 + } + } +}) +``` + +## Step 6: Add Widget Props (Optional) + +Pass additional props to your Vue component: + +```typescript +const widget = new ComponentWidgetImpl({ + node, + name: inputSpec.name, + component: YourWidget, + inputSpec, + props: { + placeholder: 'Enter value...' + }, + options: { + // ... options + } +}) +``` + +## Step 7: Handle Widget Lifecycle + +For widgets that need cleanup or special lifecycle handling: + +```typescript +// In your widget component + +``` + +## Step 8: Test Your Widget + +1. Create a test node that uses your widget: +```python +class TestYourWidget: + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "value": ("YOUR_WIDGET", {"default": "test"}) + } + } +``` + +2. Write unit tests for your composable: +```typescript +// tests-ui/composables/useYourWidget.test.ts +import { describe, it, expect } from 'vitest' +import { useYourWidget } from '@/composables/widgets/useYourWidget' + +describe('useYourWidget', () => { + it('creates widget with correct default value', () => { + const constructor = useYourWidget({ defaultValue: 'test' }) + // ... test implementation + }) +}) +``` + +## Common Patterns and Best Practices + +### 1. Use PrimeVue Components (REQUIRED) + +**Always use PrimeVue components** for UI elements to maintain consistency across the application. ComfyUI includes PrimeVue 4.2.5 with 147 available components. + +**Reference Documentation**: +- See `primevue-components.md` in the project root directory for a complete list of all available PrimeVue components with descriptions and documentation links +- Alternative location: `vue-widget-conversion/primevue-components.md` (if working in a conversion branch) +- This reference includes all 147 components organized by category (Form, Button, Data, Panel, etc.) with enhanced descriptions + +**Important**: When deciding how to create a widget, always consult the PrimeVue components reference first to find the most appropriate component for your use case. + +Common widget components include: + +```vue +