Skip to content

Commit b6c0a94

Browse files
Regroup node widgets, fix restored-workflow overflow, drop dead-widget layout debt
Layout was incoherent: image_resize sat between seed and the draft model, two empty textareas broke up the sampling rows, and json_schema showed even in Text mode. Widgets are now grouped most-used first: prompts -> model -> generation -> sampling -> output shaping -> reasoning -> vision -> speculative decoding -> management Reordering is safe precisely now: no v2 workflow exists yet, and the v1 migration writes values onto current widgets by name, so it is order independent. Verified in a browser - a v1 workflow lands correctly on the regrouped widgets, including image_resize and custom_draft_model. Also fixes a real defect this surfaced: a workflow stores the node's size and LiteGraph restores it verbatim without re-checking that the widgets still fit. v2.0.0 has a net two more widgets than v1.5.x, so every upgraded workflow was restored too short and the overflow drew outside the node frame. growToFitWidgets grows (never shrinks) the node on configure and after execution; a v1 node stored at 700px now grows to 820px on load. For the record: a stock ComfyUI Note node's textarea overhangs its own frame by ~13 units at any size. That is upstream behaviour, measured against a freshly created Note, and is not something this pack causes or should chase. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 06c278f commit b6c0a94

7 files changed

Lines changed: 90 additions & 51 deletions

File tree

AGENTS.md

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,21 @@ seed, and no thinking on/off flag.** `seed` exists only in the *load* config.
8080
The `seed` widget is a ComfyUI cache-buster and nothing more.
8181

8282
**Removing or reordering a widget corrupts saved workflows.** ComfyUI serialises
83-
`widgets_values` positionally, so a removal shifts every later value. The
84-
migration in `web/ea_lmstudio.js` keys the old array by the v1.5.x widget-name
85-
order (two variants, with and without the `control_after_generate` widget
86-
ComfyUI inserts after an INT named `seed`). Any future removal needs the same
87-
treatment, and the legacy order table must be kept.
83+
`widgets_values` positionally, so a removal or a regroup shifts every later
84+
value. The migration in `web/ea_lmstudio.js` keys the old array by the v1.5.x
85+
widget-name order (two variants, with and without the `control_after_generate`
86+
widget ComfyUI inserts after an INT named `seed`) and writes values onto current
87+
widgets **by name**, which is why v2.0.0 could both drop two widgets and regroup
88+
the rest. Any future removal or reorder needs the same treatment, and the legacy
89+
order table must be kept.
90+
91+
**A stored node size is restored verbatim and is not re-checked against the
92+
widgets.** Adding a widget therefore leaves every previously saved workflow too
93+
short, and the overflow draws outside the node frame. `growToFitWidgets` in the
94+
frontend extension grows (never shrinks) the node on configure and after
95+
execution. Note that a stock ComfyUI `Note` node's textarea overhangs its own
96+
frame by ~13 units at any size — that is upstream behaviour, not a symptom of
97+
this, so don't chase it.
8898

8999
**A streamed prediction must be drained, not broken out of.** Breaking the `for`
90100
loop closes the generator and `stream.result()` then raises `GeneratorExit`.

LMStudio.py

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -248,33 +248,12 @@ def INPUT_TYPES(cls):
248248
"tooltip": "Re-roll control only. LM Studio has no inference-time seed, so this does NOT make output reproducible - changing it simply tells ComfyUI the node is dirty so it generates again instead of reusing the cached response. Set control_after_generate to 'randomize' for a fresh answer every queue, or 'fixed' to keep the cached one."
249249
}),
250250
},
251+
# Widget order below is the on-node layout. Grouped most-used first:
252+
# sampling -> output shaping -> reasoning -> vision -> speculative
253+
# decoding -> management. Each group's dependent fields follow the
254+
# control that switches them on, and every dependent field's tooltip
255+
# names that control, so the layout reads top-to-bottom.
251256
"optional": {
252-
# --- Image inputs (for VLMs) ---
253-
"image_resize": (IMAGE_RESIZE_OPTIONS, {
254-
"default": "Medium (768px)",
255-
"tooltip": "Resize images before processing. Smaller = faster inference. 'No Resize' keeps original size. Only applies when images are connected."
256-
}),
257-
"image1": ("IMAGE", {
258-
"tooltip": "First image input for vision models (VLMs). Leave unconnected for text-only inference."
259-
}),
260-
"image2": ("IMAGE", {
261-
"tooltip": "Second image input for multi-image VLMs. Not all VLMs support multiple images."
262-
}),
263-
"image3": ("IMAGE", {
264-
"tooltip": "Third image input for multi-image VLMs. Not all VLMs support multiple images."
265-
}),
266-
"image4": ("IMAGE", {
267-
"tooltip": "Fourth image input for multi-image VLMs. Not all VLMs support multiple images."
268-
}),
269-
# --- Advanced model options ---
270-
"draft_model_selection": (model_choices, {
271-
"default": CUSTOM_MODEL_OPTION,
272-
"tooltip": "Optional draft model for speculative decoding (faster inference). Must share a tokenizer with the main model. Leave on 'Custom' with an empty box to disable. Acceptance stats are reported in troubleshooting."
273-
}),
274-
"custom_draft_model": ("STRING", {
275-
"default": "",
276-
"tooltip": "Manual draft model identifier. Only used when draft 'Custom' is selected. Leave empty to disable."
277-
}),
278257
# --- Sampling parameters ---
279258
"top_p": ("FLOAT", {
280259
"default": 1.0,
@@ -336,6 +315,32 @@ def INPUT_TYPES(cls):
336315
"default": "</think>",
337316
"tooltip": "Custom closing tag for reasoning extraction. Only used when reasoning_mode is 'Custom tags'."
338317
}),
318+
# --- Vision (only relevant when an image input is connected) ---
319+
"image_resize": (IMAGE_RESIZE_OPTIONS, {
320+
"default": "Medium (768px)",
321+
"tooltip": "Resize images before processing. Smaller = faster inference. 'No Resize' keeps original size. Only applies when images are connected."
322+
}),
323+
"image1": ("IMAGE", {
324+
"tooltip": "First image input for vision models (VLMs). Leave unconnected for text-only inference."
325+
}),
326+
"image2": ("IMAGE", {
327+
"tooltip": "Second image input for multi-image VLMs. Not all VLMs support multiple images."
328+
}),
329+
"image3": ("IMAGE", {
330+
"tooltip": "Third image input for multi-image VLMs. Not all VLMs support multiple images."
331+
}),
332+
"image4": ("IMAGE", {
333+
"tooltip": "Fourth image input for multi-image VLMs. Not all VLMs support multiple images."
334+
}),
335+
# --- Speculative decoding ---
336+
"draft_model_selection": (model_choices, {
337+
"default": CUSTOM_MODEL_OPTION,
338+
"tooltip": "Optional draft model for speculative decoding (faster inference). Must share a tokenizer with the main model. Leave on 'Custom' with an empty box to disable. Acceptance stats are reported in troubleshooting."
339+
}),
340+
"custom_draft_model": ("STRING", {
341+
"default": "",
342+
"tooltip": "Manual draft model identifier. Only used when draft 'Custom' is selected. Leave empty to disable."
343+
}),
339344
# --- Management ---
340345
"unload_llm": ("BOOLEAN", {
341346
"default": True,
8.8 KB
Loading
16.8 KB
Loading

example_workflows/01-prompt-enhancer.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"id": 1,
77
"type": "Note",
88
"pos": [40, 40],
9-
"size": [420, 260],
9+
"size": [420, 390],
1010
"flags": {},
1111
"order": 0,
1212
"mode": 0,
@@ -23,7 +23,7 @@
2323
"id": 2,
2424
"type": "EA_LMStudio",
2525
"pos": [500, 40],
26-
"size": [480, 780],
26+
"size": [480, 900],
2727
"flags": {},
2828
"order": 1,
2929
"mode": 0,
@@ -48,9 +48,6 @@
4848
0.8,
4949
0,
5050
"randomize",
51-
"Medium (768px)",
52-
"-- Custom (enter below) --",
53-
"",
5451
0.95,
5552
0,
5653
1.1,
@@ -62,6 +59,9 @@
6259
"Auto-detect (recommended)",
6360
"<think>",
6461
"</think>",
62+
"Medium (768px)",
63+
"-- Custom (enter below) --",
64+
"",
6565
true,
6666
false,
6767
false

example_workflows/02-vision-caption-json.json

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"id": 1,
77
"type": "Note",
88
"pos": [40, 40],
9-
"size": [420, 300],
9+
"size": [460, 460],
1010
"flags": {},
1111
"order": 0,
1212
"mode": 0,
@@ -22,8 +22,8 @@
2222
{
2323
"id": 2,
2424
"type": "LoadImage",
25-
"pos": [40, 380],
26-
"size": [420, 440],
25+
"pos": [40, 560],
26+
"size": [460, 460],
2727
"flags": {},
2828
"order": 1,
2929
"mode": 0,
@@ -38,8 +38,8 @@
3838
{
3939
"id": 3,
4040
"type": "EA_LMStudio",
41-
"pos": [500, 40],
42-
"size": [480, 820],
41+
"pos": [540, 40],
42+
"size": [480, 940],
4343
"flags": {},
4444
"order": 2,
4545
"mode": 0,
@@ -64,9 +64,6 @@
6464
0.3,
6565
0,
6666
"randomize",
67-
"Medium (768px)",
68-
"-- Custom (enter below) --",
69-
"",
7067
1,
7168
0,
7269
1,
@@ -78,6 +75,9 @@
7875
"Auto-detect (recommended)",
7976
"<think>",
8077
"</think>",
78+
"Medium (768px)",
79+
"-- Custom (enter below) --",
80+
"",
8181
true,
8282
false,
8383
false
@@ -86,7 +86,7 @@
8686
{
8787
"id": 4,
8888
"type": "PreviewAny",
89-
"pos": [1020, 40],
89+
"pos": [1060,40],
9090
"size": [400, 320],
9191
"flags": {},
9292
"order": 3,
@@ -100,7 +100,7 @@
100100
{
101101
"id": 5,
102102
"type": "PreviewAny",
103-
"pos": [1020, 400],
103+
"pos": [1060,400],
104104
"size": [400, 420],
105105
"flags": {},
106106
"order": 4,

web/ea_lmstudio.js

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ const LEGACY_ORDER = [
5151
"unload_comfy_models",
5252
"refresh_models",
5353
];
54+
// v2.0.0 also regrouped the widgets, so the migration below cannot assume the
55+
// old and new orders line up - it maps every legacy value onto the current
56+
// widget of the same name, which is order-independent by construction.
5457

5558
const LEGACY_ORDER_WITH_SEED_CONTROL = [
5659
...LEGACY_ORDER.slice(0, 7),
@@ -139,6 +142,30 @@ function migrateLegacyWidgetValues(node, widgetValues, defaults) {
139142
return true;
140143
}
141144

145+
/**
146+
* Grow a node that is stored smaller than its widgets need.
147+
*
148+
* A workflow stores the node's size, and LiteGraph restores it verbatim - it
149+
* does not re-check that the widgets still fit. Any release that adds a widget
150+
* therefore leaves every previously saved workflow a row or two too short, and
151+
* the overflowing widgets draw outside the node's frame. v2.0.0 has a net two
152+
* more widgets than v1.5.x, so this affects every upgraded workflow, not just
153+
* an unlucky few. Only ever grows - a deliberately widened node is preserved.
154+
*/
155+
function growToFitWidgets(node) {
156+
try {
157+
const [minWidth, minHeight] = node.computeSize();
158+
if (node.size[0] < minWidth || node.size[1] < minHeight) {
159+
node.setSize([
160+
Math.max(node.size[0], minWidth),
161+
Math.max(node.size[1], minHeight),
162+
]);
163+
}
164+
} catch (err) {
165+
console.error("[EA_LMStudio] Could not resize node to fit widgets:", err);
166+
}
167+
}
168+
142169
function getPreviewWidget(node) {
143170
let widget = node.widgets?.find((w) => w.name === PREVIEW_WIDGET_NAME);
144171
if (widget) return widget;
@@ -179,6 +206,7 @@ app.registerExtension({
179206
} catch (err) {
180207
console.error("[EA_LMStudio] Legacy workflow migration failed:", err);
181208
}
209+
growToFitWidgets(this);
182210
};
183211

184212
/*
@@ -207,11 +235,7 @@ app.registerExtension({
207235
const widget = getPreviewWidget(this);
208236
widget.value = text;
209237

210-
const [minWidth, minHeight] = this.computeSize();
211-
this.setSize([
212-
Math.max(this.size[0], minWidth),
213-
Math.max(this.size[1], minHeight),
214-
]);
238+
growToFitWidgets(this);
215239
app.graph.setDirtyCanvas(true, false);
216240
};
217241
},

0 commit comments

Comments
 (0)