Skip to content

Commit 9dcb286

Browse files
Merge pull request #12 from EnragedAntelope/claude/fix-lmstudio-model-refresh-VIoV5
Add instant model refresh with frontend extension
2 parents 1d87619 + b3f9bf4 commit 9dcb286

5 files changed

Lines changed: 120 additions & 11 deletions

File tree

LMStudio.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import logging
66
import re
77
from typing import Optional, Tuple, List
8+
import os
89
from tempfile import NamedTemporaryFile
910
import numpy as np
1011
from PIL import Image
@@ -220,7 +221,7 @@ def INPUT_TYPES(cls):
220221
}),
221222
"refresh_models": ("BOOLEAN", {
222223
"default": False,
223-
"tooltip": "Re-fetch model list from LM Studio server. Enable, queue once, then disable. Updates dropdown on next node load."
224+
"tooltip": "Toggle ON to re-fetch the model list from LM Studio and update the dropdowns instantly. Automatically toggles back off."
224225
}),
225226
}
226227
}
@@ -468,7 +469,6 @@ def generate(
468469
success, message = refresh_model_cache(server_url, timeout)
469470
if success:
470471
troubleshooting_lines.append(f"[INFO] Model refresh: {message}")
471-
troubleshooting_lines.append("[INFO] Dropdown will update on next node load")
472472
else:
473473
troubleshooting_lines.append(f"[WARNING] Model refresh failed: {message}")
474474

@@ -549,12 +549,21 @@ def generate(
549549
if pil_images:
550550
# Prepare all images for the SDK
551551
image_handles = []
552-
for pil_img in pil_images:
553-
with NamedTemporaryFile(suffix=".jpg", delete=False) as temp:
554-
pil_img.save(temp, format="JPEG", quality=95)
555-
temp.flush()
556-
image_handle = client.files.prepare_image(temp.name)
557-
image_handles.append(image_handle)
552+
temp_paths = []
553+
try:
554+
for pil_img in pil_images:
555+
with NamedTemporaryFile(suffix=".jpg", delete=False) as temp:
556+
pil_img.save(temp, format="JPEG", quality=95)
557+
temp.flush()
558+
temp_paths.append(temp.name)
559+
image_handle = client.files.prepare_image(temp.name)
560+
image_handles.append(image_handle)
561+
finally:
562+
for path in temp_paths:
563+
try:
564+
os.unlink(path)
565+
except OSError:
566+
pass
558567

559568
chat.add_user_message(prompt, images=image_handles)
560569
else:

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pip install -r EA_LMStudio/requirements.txt
3838

3939
## Tips
4040

41-
- **Models not showing?** LM Studio must be running before ComfyUI starts. Use `refresh_models` checkbox to re-fetch.
41+
- **Models not showing?** LM Studio must be running before ComfyUI starts. Toggle the `refresh_models` checkbox to instantly re-fetch and update the dropdowns.
4242
- **Context errors?** Increase context length in LM Studio settings (not max_tokens).
4343
- **VLM issues?** Try a smaller image resize option or single image if multi-image fails.
4444
- **Force thinking mode:** Add `/think` to prompts for Qwen3, or "Think step by step" for others.

__init__.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,31 @@
55
"""
66
from .LMStudio import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
77

8-
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS"]
8+
# Serve web extension for frontend model refresh
9+
WEB_DIRECTORY = "./web"
10+
11+
# Register server API route for model refresh
12+
from aiohttp import web
13+
from server import PromptServer
14+
15+
from .model_fetcher import refresh_model_cache, get_model_choices, CUSTOM_MODEL_OPTION
16+
from .lms_config.config_manager import ConfigManager
17+
18+
@PromptServer.instance.routes.post("/ea_lmstudio/refresh_models")
19+
async def _refresh_models_route(request):
20+
"""API endpoint to refresh model list from LM Studio server."""
21+
config_manager = ConfigManager()
22+
server_url = config_manager.get_server_url()
23+
timeout = config_manager.get_timeout()
24+
25+
success, message = refresh_model_cache(server_url, timeout)
26+
choices = get_model_choices()
27+
models = [m for m in choices if m != CUSTOM_MODEL_OPTION]
28+
29+
return web.json_response({
30+
"success": success,
31+
"message": message,
32+
"models": models,
33+
})
34+
35+
__all__ = ["NODE_CLASS_MAPPINGS", "NODE_DISPLAY_NAME_MAPPINGS", "WEB_DIRECTORY"]

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[project]
22
name = "EA_LMStudio"
33
description = "A custom node for LM Studio integration into ComfyUI."
4-
version = "1.2.2"
4+
version = "1.2.3"
55
license = {file = "LICENSE"}
66

77
[project.urls]

web/ea_lmstudio.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { app } from "../../scripts/app.js";
2+
import { api } from "../../scripts/api.js";
3+
4+
/*
5+
* EA_LMStudio Model Refresh Extension
6+
*
7+
* Intercepts the refresh_models toggle to fetch an updated model list from
8+
* the LM Studio server and update the dropdown widgets in-place.
9+
*
10+
* Compatibility:
11+
* - Legacy LiteGraph frontend: full support via widget.options.values
12+
* - Nodes 2.0 / Vue frontend: the server-side cache is always updated,
13+
* so a browser refresh (F5) will pick up new models even if the
14+
* in-place widget update doesn't propagate in a future Vue renderer.
15+
*/
16+
17+
app.registerExtension({
18+
name: "EA_LMStudio.ModelRefresh",
19+
20+
async nodeCreated(node) {
21+
if (node.comfyClass !== "EA_LMStudio") return;
22+
23+
const refreshWidget = node.widgets?.find(w => w.name === "refresh_models");
24+
if (!refreshWidget) return;
25+
26+
const originalCallback = refreshWidget.callback;
27+
28+
refreshWidget.callback = async function (value) {
29+
if (!value) {
30+
if (originalCallback) originalCallback.call(this, value);
31+
return;
32+
}
33+
34+
try {
35+
const resp = await api.fetchApi("/ea_lmstudio/refresh_models", {
36+
method: "POST",
37+
});
38+
const data = await resp.json();
39+
40+
if (data.success && data.models) {
41+
const choices = ["-- Custom (enter below) --", ...data.models];
42+
43+
for (const widgetName of ["model_selection", "draft_model_selection"]) {
44+
const w = node.widgets?.find(ww => ww.name === widgetName);
45+
if (w && w.options) {
46+
// Replace values array (breaks shared reference intentionally
47+
// so other node instances also get the update)
48+
w.options.values = choices;
49+
if (!choices.includes(w.value)) {
50+
w.value = choices[0];
51+
}
52+
}
53+
}
54+
55+
console.log(
56+
`[EA_LMStudio] Refreshed models: ${data.models.length} found`
57+
);
58+
} else {
59+
console.warn(
60+
`[EA_LMStudio] Model refresh failed: ${data.message || "unknown error"}`
61+
);
62+
}
63+
} catch (err) {
64+
console.error("[EA_LMStudio] Failed to refresh models:", err);
65+
}
66+
67+
// Toggle back off so it acts like a one-shot button
68+
refreshWidget.value = false;
69+
70+
if (originalCallback) originalCallback.call(this, false);
71+
};
72+
},
73+
});

0 commit comments

Comments
 (0)