Skip to content

Commit ed2ea11

Browse files
[RFC] Vue component rendering system for custom nodes
1 parent 58c4411 commit ed2ea11

File tree

1 file changed

+291
-0
lines changed

1 file changed

+291
-0
lines changed
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
# RFC: Vue Component Rendering System for Custom Nodes
2+
3+
- Start Date: 2025-05-19
4+
- Target Major Version: 1.x
5+
- Reference Issues: N/A
6+
- Implementation PR: (leave this empty)
7+
8+
## Summary
9+
10+
This RFC proposes a standardized public API for custom nodes to render Vue components in the ComfyUI frontend. The system will enable consistent, extensible UI rendering through well-defined interfaces that work seamlessly across distributed environments.
11+
12+
## Basic example
13+
14+
### Backend (Node Implementation)
15+
16+
```python
17+
from comfy.ui_components import render_component
18+
19+
class CustomViewNode(ComfyNodeABC):
20+
"""Example node that renders a custom Vue component in the UI."""
21+
22+
def __init__(self):
23+
self.session_id = str(uuid.uuid4())
24+
self.history = []
25+
26+
@classmethod
27+
def INPUT_TYPES(cls) -> InputTypeDict:
28+
return {
29+
"required": {
30+
"prompt": (IO.STRING, {"multiline": True}),
31+
},
32+
"hidden": {
33+
"unique_id": "UNIQUE_ID" # Node ID is passed via execution
34+
}
35+
}
36+
37+
RETURN_TYPES = (IO.STRING,)
38+
FUNCTION = "process"
39+
CATEGORY = "example"
40+
41+
def process(self, prompt, unique_id=None):
42+
# Process the prompt
43+
result = f"Processed: {prompt}"
44+
45+
# Add to history
46+
self.history.append({
47+
"prompt": prompt,
48+
"response": result,
49+
"timestamp": time.time()
50+
})
51+
52+
# Render the component with history data
53+
render_component(
54+
node_id=unique_id,
55+
component="ChatHistoryWidget",
56+
props={
57+
"history": json.dumps(self.history),
58+
"key": "chat_history" # Use Vue's built-in key property for component identification
59+
}
60+
)
61+
62+
return (result,)
63+
```
64+
65+
### Python Client API
66+
67+
```python
68+
# comfy/ui_components.py
69+
70+
def render_component(node_id, component, props=None):
71+
"""Render a Vue component on a specific node.
72+
73+
Args:
74+
node_id: The ID of the node to render the component on
75+
component: The name of the component to render
76+
props: Optional properties to pass to the component
77+
"""
78+
message = {
79+
"node_id": node_id,
80+
"component": component,
81+
"props": props or {}
82+
}
83+
from server import PromptServer
84+
PromptServer.instance.send_sync("render_component", message)
85+
86+
def update_component_props(node_id, component, props):
87+
"""Update the props of an existing component.
88+
89+
Args:
90+
node_id: The ID of the node with the component
91+
component: The name of the component to update
92+
props: The new props to set
93+
"""
94+
message = {
95+
"node_id": node_id,
96+
"component": component,
97+
"props": props
98+
}
99+
from server import PromptServer
100+
PromptServer.instance.send_sync("update_component_props", message)
101+
```
102+
103+
## Motivation
104+
105+
The current approaches to custom UI elements in ComfyUI nodes have several limitations:
106+
107+
1. **Ad-hoc Implementations**: Most developers either use direct DOM manipulation or rely on injecting raw JavaScript, making it difficult to maintain consistency and functionality across the ecosystem.
108+
109+
2. **Distributed Computing Constraints**: As ComfyUI evolves toward cloud-distributed architecture where nodes may run in separate processes or containers, a stable public API is needed for UI interactions.
110+
111+
3. **Technical Barrier**: Most custom node developers don't know JavaScript well enough to create complex UI elements, leading to poor user interfaces or avoidance of interactive features altogether.
112+
113+
4. **Maintenance Challenges**: Without a standardized API, the frontend team must support various approaches to UI rendering, increasing technical debt and making it harder to evolve the platform.
114+
115+
A standardized Vue component rendering system would provide:
116+
117+
1. **Clean Public API**: A well-defined interface that can have its implementation changed to work with future process-per-node and cloud systems, without breaking custom nodes.
118+
119+
2. **Simplified Development**: Node developers can focus on Python functionality rather than requiring JavaScript expertise.
120+
121+
3. **Consistent User Experience**: A standard component library ensures custom nodes can be distinctive while maintaining a consistent look and feel within the overall application.
122+
123+
4. **Better Extensibility**: UI capabilities can be expanded centrally without requiring changes to individual nodes.
124+
125+
5. **Future-Proof Architecture**: The design accommodates both local and distributed environments through a stable public interface.
126+
127+
## Detailed design
128+
129+
The Vue component rendering system includes several key components:
130+
131+
### 1. Backend Public API
132+
133+
The core of the system is a clean, stable public API for Python nodes:
134+
135+
```python
136+
# comfy/ui_components.py
137+
138+
from server import PromptServer
139+
140+
def render_component(node_id, component, props=None):
141+
"""Render a Vue component on a specific node."""
142+
message = {
143+
"node_id": node_id,
144+
"component": component,
145+
"props": props or {}
146+
}
147+
PromptServer.instance.send_sync("render_component", message)
148+
149+
def update_component_props(node_id, component, props):
150+
"""Update props of a rendered component."""
151+
message = {
152+
"node_id": node_id,
153+
"component": component,
154+
"props": props
155+
}
156+
PromptServer.instance.send_sync("update_component_props", message)
157+
```
158+
159+
The public API will remain stable across versions, while the internal implementation can change to support different deployment models (e.g., local vs. cloud, single-process vs. multi-process).
160+
161+
### 2. Component Library
162+
163+
ComfyUI will provide a standard component library that node developers can use:
164+
165+
1. **Text Components**:
166+
- `MarkdownDisplay` - Renders markdown content
167+
- `CodeDisplay` - Displays code with syntax highlighting
168+
- `TextPreview` - Shows text with formatting options
169+
170+
2. **Chat Components**:
171+
- `ChatHistory` - Displays a conversation history
172+
- `MessageBubble` - Individual chat message rendering
173+
174+
3. **Media Components**:
175+
- `ImageSelector` - Displays multiple images with a selector
176+
- `AudioPlayer` - Audio playback with controls
177+
- `VideoPlayer` - Video playback with controls
178+
- `ImageComparisonSlider` - Compare before/after images
179+
- `AudioWaveformEditor` - Edit audio waveforms
180+
181+
4. **Form Components**:
182+
- Standard form controls following PrimeVue component patterns
183+
- (See https://primevue.org/ for complete list of available components)
184+
185+
The component library will be maintained centrally, ensuring consistency across the application.
186+
187+
### 3. Component State Management
188+
189+
There are two primary ways to manage component state:
190+
191+
1. **Return Dict Method**: Nodes can include UI specifications in their return value:
192+
193+
```python
194+
def process(self, input_data, unique_id=None):
195+
result = process_data(input_data)
196+
197+
# Include UI specification in return dict
198+
return {
199+
"ui": {
200+
"component": "ChatHistoryWidget",
201+
"props": {
202+
"history": self.get_history_text(),
203+
"key": "chat_history"
204+
}
205+
},
206+
"results": result
207+
}
208+
```
209+
210+
2. **Direct Message Method**: Nodes can send UI update messages at any time:
211+
212+
```python
213+
def on_event(self, event_data, unique_id=None):
214+
# Update UI in response to an event
215+
render_component(
216+
node_id=unique_id,
217+
component="StatusDisplay",
218+
props={
219+
"status": "Processing...",
220+
"key": "status_display"
221+
}
222+
)
223+
224+
# Process the event
225+
result = process_event(event_data)
226+
227+
# Update UI again
228+
render_component(
229+
node_id=unique_id,
230+
component="StatusDisplay",
231+
props={
232+
"status": "Complete",
233+
"key": "status_display"
234+
}
235+
)
236+
237+
return result
238+
```
239+
240+
## Drawbacks
241+
242+
243+
1. **Performance**: Additional component rendering may impact performance on complex workflows. Using Vue is much less performant than using canvas. Adding support for dynamic component rendering over WebSocket adds overhead compared with defined lifecycle methods and singular events in which components are updated.
244+
245+
2. **Maintenance Burden**: Supporting component rendering requires ongoing maintenance of both backend APIs and frontend components.
246+
247+
## Alternatives
248+
249+
1. **Jinja/Template-Based Approach**: Use a server-side templating system like Jinja to generate HTML for custom UIs. While simpler, this would be less interactive.
250+
251+
2. **Gradio-Style Components**: Adopt an approach similar to Gradio, with a high-level component API. This might be easier for beginners but less flexible for advanced UI needs.
252+
253+
## Adoption strategy
254+
255+
The adoption will be implemented in parallel tracks:
256+
257+
### Track 1: Core API and Documentation
258+
259+
1. Implement the `comfy.ui_components` module with the public API
260+
2. Create documentation for the new API with clear examples
261+
3. Add support in the node execution engine to handle UI specifications in return values
262+
263+
### Track 2: Component Development
264+
265+
1. Develop the standard component library
266+
2. Implement the frontend message handlers
267+
3. Create example nodes that demonstrate various component usage patterns
268+
269+
## Unresolved questions
270+
271+
1. **Real-time Updates**: How should components handle real-time updates from backend processes?
272+
273+
2. **Error Handling**: What should happen when a component fails to render or a node tries to render an unregistered component?
274+
275+
3. **Component State**: How should stateful components communicate state changes back to the backend when user interaction occurs?
276+
277+
## Future Considerations
278+
279+
1. **Component Registry and Manifest System**: In the future, we could explore a system that allows custom nodes to register their own Vue components:
280+
281+
```json
282+
{
283+
"components": [
284+
{
285+
"name": "MyCustomWidget",
286+
"path": "./components/MyCustomWidget.vue",
287+
"description": "A custom widget for displaying data"
288+
}
289+
]
290+
}
291+
```

0 commit comments

Comments
 (0)