Skip to content

Commit 3822146

Browse files
Chenlei Hurobinjhuang
andauthored
RFC: Workflow JSON Widget Values Format (#2)
* rfc: Workflow JSON Widget Values Format * Add summary section * nit * Add widget to input convertion to example * Add state examples * 1.1 => 2.0 as it is a breaking change * Change version of rfc --------- Co-authored-by: Robin Huang <robin.j.huang@gmail.com>
1 parent 9c4ef7f commit 3822146

File tree

1 file changed

+316
-0
lines changed

1 file changed

+316
-0
lines changed

rfcs/0004-widget-values-format.md

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,316 @@
1+
# RFC: Workflow JSON Widget Values Format
2+
3+
- Start Date: 2025-01-08
4+
- Target Major Version: TBD
5+
6+
## Summary
7+
8+
This RFC proposes a new format for handling widget values in ComfyUI workflows by integrating them directly into the node inputs array instead of storing them in a separate `widgets_values` array. The new format improves type safety, maintainability, and self-documentation of workflows by making each widget value a named, typed input with explicit metadata. This change will require a version bump in the workflow schema from 1.0 to 2.0, but includes backward compatibility measures to ensure a smooth transition for existing workflows and custom nodes.
9+
10+
## Basic example
11+
12+
![image](https://github.com/user-attachments/assets/e36113a9-20d6-406a-9a83-209c86b91107)
13+
14+
Current format node serialization format:
15+
16+
```json
17+
{
18+
"id": 3,
19+
"type": "KSampler",
20+
"pos": [863, 186],
21+
"size": [315, 262],
22+
"flags": {},
23+
"order": 4,
24+
"mode": 0,
25+
"inputs": [
26+
{ "name": "model", "type": "MODEL", "link": 1 },
27+
{ "name": "positive", "type": "CONDITIONING", "link": 4 },
28+
{ "name": "negative", "type": "CONDITIONING", "link": 6 },
29+
{ "name": "latent_image", "type": "LATENT", "link": 2 },
30+
{
31+
"name": "seed",
32+
"type": "INT",
33+
"link": null,
34+
"widget": {
35+
"name": "seed"
36+
}
37+
}
38+
],
39+
"outputs": [{ "name": "LATENT", "type": "LATENT", "links": [7], "slot_index": 0 }],
40+
"properties": {},
41+
"widgets_values": [156680208700286, true, 20, 8, "euler", "normal", 1]
42+
}
43+
```
44+
45+
Proposed format:
46+
47+
```json
48+
{
49+
"id": 3,
50+
"type": "KSampler",
51+
"pos": [863, 186],
52+
"size": [315, 262],
53+
"flags": {},
54+
"order": 4,
55+
"mode": 0,
56+
"inputs": [
57+
{ "name": "model", "type": "MODEL", "link": 1 },
58+
{ "name": "positive", "type": "CONDITIONING", "link": 4 },
59+
{ "name": "negative", "type": "CONDITIONING", "link": 6 },
60+
{ "name": "latent_image", "type": "LATENT", "link": 2 },
61+
{ "name": "seed", "type": "INT", "value": 156680208700286, "link": null },
62+
{ "name": "denoise", "type": "FLOAT", "value": 1.0 },
63+
{ "name": "steps", "type": "INT", "value": 20 },
64+
{ "name": "cfg", "type": "FLOAT", "value": 8 },
65+
{ "name": "sampler_name", "type": "COMBO", "value": "euler" },
66+
{ "name": "scheduler", "type": "COMBO", "value": "normal" },
67+
{ "name": "denoise", "type": "FLOAT", "value": 1.0 },
68+
],
69+
"outputs": [{ "name": "LATENT", "type": "LATENT", "links": [7], "slot_index": 0 }],
70+
"properties": {},
71+
}
72+
```
73+
74+
In the new format, the `link` field determines the input's behavior and UI representation:
75+
76+
- `link: undefined` - Rendered as a widget (e.g., text input, slider, dropdown)
77+
78+
```json
79+
{ "name": "steps", "type": "INT", "value": 20 }
80+
```
81+
82+
- `link: null` - Rendered as an unconnected input socket
83+
84+
```json
85+
{ "name": "seed", "type": "INT", "value": 156680208700286, "link": null }
86+
```
87+
88+
- `link: number` - Rendered as a connected input socket with the specified link ID
89+
90+
```json
91+
{ "name": "model", "type": "MODEL", "link": 1 }
92+
```
93+
94+
This distinction allows nodes to clearly indicate whether an input should be displayed as an interactive widget or as a connection point, while maintaining the ability to store default values for unconnected inputs.
95+
96+
## Motivation
97+
98+
The proposed format change addresses several key limitations in the current widget values implementation:
99+
100+
1. **Unified Input Handling**: By moving widget values into the inputs array, we create a single, consistent way to handle all node inputs. This simplifies the node processing logic and reduces the need for special-case handling of widget values versus connected inputs.
101+
102+
2. **Self-Describing Nodes**: The new format makes nodes more self-documenting by including input names and types directly in the node definition. This allows:
103+
104+
- Reconstruction of node displays without requiring access to the full node definition
105+
- Better error checking and validation of values
106+
- Improved debugging capabilities
107+
- Easier serialization/deserialization of workflows
108+
109+
3. **Flexible Parameter Management**: The array-based structure of the current format makes it difficult to:
110+
111+
- Insert new parameters in the middle of the list
112+
- Remove deprecated parameters
113+
- Maintain backward compatibility The named input approach solves these issues by making parameter order irrelevant.
114+
115+
4. **Type Safety**: Explicit type definitions for each input value helps prevent type-related errors and makes it easier to implement proper validation at both runtime and development time.
116+
117+
## Detailed design
118+
119+
### Schema Changes
120+
121+
The workflow schema will be updated from version 1.0 to 2.0 to accommodate the new widget values format. Here's the proposed Zod schema changes:
122+
123+
```typescript
124+
// Version 2.0 Node Input Schema
125+
const NodeInputV2 = z.object({
126+
name: z.string(),
127+
type: z.string(),
128+
link: z.number().optional(),
129+
value: z.any().optional(), // For widget values
130+
});
131+
132+
// Version 2.0 Node Schema
133+
const NodeV2 = z.object({
134+
id: z.number(),
135+
type: z.string(),
136+
pos: z.tuple([z.number(), z.number()]),
137+
size: z.tuple([z.number(), z.number()]),
138+
flags: z.record(z.any()),
139+
order: z.number(),
140+
mode: z.number(),
141+
inputs: z.array(NodeInputV2),
142+
outputs: z.array(NodeOutput),
143+
properties: z.record(z.any()),
144+
// widgets_values field removed
145+
});
146+
```
147+
148+
### Version Conversion
149+
150+
To maintain backward compatibility, the system will include bidirectional converters between versions:
151+
152+
```typescript
153+
function convertTo2(nodeV1: NodeV1): NodeV2 {
154+
const widgetDefinitions = getNodeWidgetDefinitions(nodeV1.type);
155+
156+
// Convert widget values to input format
157+
const widgetInputs = widgetDefinitions.map((def, index) => ({
158+
name: def.name,
159+
type: def.type,
160+
value: nodeV1.widgets_values[index]
161+
}));
162+
163+
return {
164+
...nodeV1,
165+
inputs: [...nodeV1.inputs, ...widgetInputs],
166+
widgets_values: undefined // Remove widget_values field
167+
};
168+
}
169+
170+
function convertTo1(nodeV2: NodeV2): NodeV1 {
171+
const widgetDefinitions = getNodeWidgetDefinitions(nodeV2.type);
172+
const regularInputs = nodeV2.inputs.filter(input => !widgetDefinitions.find(def => def.name === input.name));
173+
const widgetValues = widgetDefinitions.map(def => {
174+
const input = nodeV2.inputs.find(i => i.name === def.name);
175+
return input?.value;
176+
});
177+
178+
return {
179+
...nodeV2,
180+
inputs: regularInputs,
181+
widgets_values: widgetValues
182+
};
183+
}
184+
```
185+
186+
### Workflow Export Options
187+
188+
When exporting workflows, users will be presented with schema version choices:
189+
190+
1. Latest Version (2.0) - Default
191+
2. Legacy Version (1.0) - For compatibility with older systems (Beta Reroute)
192+
3. Legacy Version (0.4) - For compatibility with older systems
193+
194+
The export dialog will include version selection and automatically convert the workflow to the selected format.
195+
196+
### Unknown Node Handling
197+
198+
For nodes without available definitions, LiteGraph will use the input metadata to render a basic representation:
199+
200+
1. Regular inputs will be rendered as connection points
201+
2. Widget inputs will be rendered as appropriate UI controls based on their type:
202+
203+
- INT/FLOAT -> Number input
204+
- STRING -> Text input
205+
- COMBO -> Dropdown (with available values if provided)
206+
- BOOLEAN -> Checkbox
207+
208+
```typescript
209+
class LiteGraphNode {
210+
// ... existing code ...
211+
212+
renderUnknownNode() {
213+
this.inputs.forEach(input => {
214+
if (input.link !== undefined) {
215+
// Render connection point
216+
this.addInput(input.name, input.type);
217+
} else if (input.value !== undefined) {
218+
// Render widget based on type
219+
this.addWidget(
220+
this.getWidgetTypeFromInputType(input.type),
221+
input.name,
222+
input.value,
223+
(v) => { input.value = v; }
224+
);
225+
}
226+
});
227+
}
228+
}
229+
```
230+
231+
This approach ensures that even unknown nodes remain editable and maintain their configuration, even if specialized behavior isn't available.
232+
233+
## Drawbacks
234+
235+
1. **Increased Storage Size**: The new format is more verbose due to including field names and types for each widget value, which will increase the size of saved workflow files. While modern storage and network capabilities make this a minor concern, it's worth noting for systems handling large numbers of workflows.
236+
237+
2. **Migration Complexity**: Existing workflows will need to be migrated to the new format, requiring:
238+
239+
- Development of reliable conversion utilities
240+
- Testing of conversion edge cases
241+
- Potential maintenance of multiple format versions during transition
242+
- Additional documentation for handling legacy formats
243+
244+
3. **Performance Considerations**: The new format requires:
245+
246+
- More complex parsing logic compared to simple array access
247+
- Additional memory usage due to storing metadata with each value
248+
- Potentially slower lookup times when accessing widget values (object property access vs array index)
249+
250+
4. **Backward Compatibility Challenges**: While the proposal includes conversion utilities, there may be:
251+
252+
- Third-party tools that need updating
253+
- Custom node implementations that assume the array-based format
254+
- Existing scripts or automation that parse workflow files directly
255+
256+
5. **Learning Curve**: Users and developers familiar with the current array-based format will need to adapt to the new structure, potentially leading to initial confusion and requiring updates to documentation and tutorials.
257+
258+
Despite these drawbacks, the benefits of improved maintainability, type safety, and self-documentation likely outweigh these concerns in the long term.
259+
260+
## Adoption strategy
261+
262+
The transition to the new widget values format will be implemented through a phased approach to ensure smooth adoption:
263+
264+
1. **Version Support**
265+
266+
- ComfyUI will support both 1.0 and 2.0 formats simultaneously during the transition period
267+
- The internal format will be 2.0, with automatic conversion happening at workflow load/save
268+
- All new features will target the 2.0 format
269+
270+
2. **Breaking Changes**
271+
272+
- This is technically a breaking change, but will be implemented with backward compatibility
273+
- Existing workflows using the 1.0 format will continue to work through automatic conversion
274+
- Node developers will need to update their implementations to support the new format
275+
276+
3. **Migration Path**
277+
278+
- For ComfyUI Users:
279+
280+
- Existing workflows will be automatically converted when loaded
281+
- No manual intervention required
282+
- Option to export in legacy format for compatibility with older systems
283+
284+
- For Node Developers:
285+
286+
- Deprecation notices for direct `widgets_values` access
287+
- New helper functions for accessing widget values through the inputs array
288+
- Migration guide and examples provided in documentation
289+
- Grace period of 2-3 releases before removing `widgets_values` support
290+
291+
4. **Ecosystem Impact**
292+
293+
- Code search shows only ~10 custom node repositories directly accessing `widget_values`
294+
- ComfyUI team can directly contribute fixes to these repositories
295+
- API clients and workflow manipulation tools will need modification
296+
- Web UI extensions may require updates for the new format
297+
- Compatibility layer will be provided during transition:
298+
299+
```javascript
300+
get widgets_values() {
301+
console.warn("Deprecated: accessing widgets_values directly. Please migrate to input values.");
302+
return this.inputs
303+
.filter(input => input.value !== undefined)
304+
.map(input => input.value);
305+
}
306+
```
307+
308+
5. **Timeline**
309+
310+
- Beta release with dual format support
311+
- 3-month transition period with both formats supported
312+
- Full migration to 2.0 format in next major version
313+
314+
## Unresolved questions
315+
316+
TBD

0 commit comments

Comments
 (0)