fix(init): add missing /api/lm/init-status endpoint used by polling fallback

initialization.js falls back to polling /api/lm/init-status when the
/ws/init-progress WebSocket cannot be established, but no route ever
registered that path — each poll 404'd and the page never reloaded after
the scan completed. Report the aggregate status of all four scanners and
omit pageType so every initialization page accepts the update.
This commit is contained in:
Will Miao
2026-08-24 09:38:56 +08:00
committed by pixelpaws
parent 3afec0a0be
commit 879745da53
3 changed files with 105 additions and 0 deletions
+52
View File
@@ -649,9 +649,60 @@ class NodeRegistry:
class HealthCheckHandler:
def __init__(
self,
scanner_getters: Mapping[str, Callable[[], Awaitable[Any]]] | None = None,
) -> None:
self._scanner_getters = scanner_getters or {
"lora": ServiceRegistry.get_lora_scanner,
"checkpoint": ServiceRegistry.get_checkpoint_scanner,
"embedding": ServiceRegistry.get_embedding_scanner,
"recipe": ServiceRegistry.get_recipe_scanner,
}
async def health_check(self, request: web.Request) -> web.Response:
return web.json_response({"status": "ok"})
async def get_init_status(self, request: web.Request) -> web.Response:
"""Report aggregate scanner initialization status.
Used by the initialization page's polling fallback when the
/ws/init-progress WebSocket is unavailable. Omits pageType so every
page accepts the update and only reloads once all scanners are done.
"""
pending: list[str] = []
for name, getter in self._scanner_getters.items():
try:
scanner = await getter()
except Exception:
pending.append(name)
continue
cache_ready = getattr(scanner, "_cache", None) is not None
is_initializing = getattr(scanner, "is_initializing", None)
busy = (
is_initializing()
if callable(is_initializing)
else bool(getattr(scanner, "_is_initializing", False))
)
if busy or not cache_ready:
pending.append(name)
if pending:
return web.json_response(
{
"status": "initializing",
"stage": "processing",
"details": "Initializing: " + ", ".join(pending),
}
)
return web.json_response(
{
"status": "complete",
"progress": 100,
"details": "Initialization complete",
}
)
class SupportersHandler:
"""Handler for supporters data."""
@@ -3859,6 +3910,7 @@ class MiscHandlerSet:
) -> Mapping[str, Callable[[web.Request], Awaitable[web.StreamResponse]]]:
return {
"health_check": self.health.health_check,
"get_init_status": self.health.get_init_status,
"get_settings": self.settings.get_settings,
"update_settings": self.settings.update_settings,
"get_doctor_diagnostics": self.doctor.get_doctor_diagnostics,
+1
View File
@@ -32,6 +32,7 @@ MISC_ROUTE_DEFINITIONS: tuple[RouteDefinition, ...] = (
RouteDefinition("GET", "/api/lm/settings/libraries", "get_settings_libraries"),
RouteDefinition("POST", "/api/lm/settings/libraries/activate", "activate_library"),
RouteDefinition("GET", "/api/lm/health-check", "health_check"),
RouteDefinition("GET", "/api/lm/init-status", "get_init_status"),
RouteDefinition("GET", "/api/lm/supporters", "get_supporters"),
RouteDefinition("GET", "/api/lm/wildcards/search", "search_wildcards"),
RouteDefinition("POST", "/api/lm/wildcards/open-location", "open_wildcards_location"),
+52
View File
@@ -16,6 +16,7 @@ from py.routes.handlers.misc_handlers import (
BackupHandler,
DoctorHandler,
FileSystemHandler,
HealthCheckHandler,
LoraCodeHandler,
ModelLibraryHandler,
NodeRegistry,
@@ -2010,3 +2011,54 @@ async def test_resolve_filename_conflicts_handles_scanner_error_gracefully():
assert payload["success"] is True
assert payload["count"] == 0
async def test_get_init_status_reports_complete_when_all_scanners_ready():
async def ready_scanner():
return SimpleNamespace(_cache=object(), is_initializing=lambda: False)
handler = HealthCheckHandler(
scanner_getters={
"lora": ready_scanner,
"recipe": ready_scanner,
}
)
response = await handler.get_init_status(FakeRequest(method="GET")) # pyright: ignore[reportArgumentType]
payload = _json_payload(response)
assert payload["status"] == "complete"
assert payload["progress"] == 100
assert "pageType" not in payload
async def test_get_init_status_reports_pending_scanners():
async def ready_scanner():
return SimpleNamespace(_cache=object(), is_initializing=lambda: False)
async def initializing_scanner():
return SimpleNamespace(_cache=object(), is_initializing=lambda: True)
async def no_cache_scanner():
return SimpleNamespace(_cache=None, is_initializing=lambda: False)
async def failing_scanner():
raise RuntimeError("scanner unavailable")
handler = HealthCheckHandler(
scanner_getters={
"lora": ready_scanner,
"checkpoint": initializing_scanner,
"embedding": no_cache_scanner,
"recipe": failing_scanner,
}
)
response = await handler.get_init_status(FakeRequest(method="GET")) # pyright: ignore[reportArgumentType]
payload = _json_payload(response)
assert payload["status"] == "initializing"
assert "checkpoint" in payload["details"]
assert "embedding" in payload["details"]
assert "recipe" in payload["details"]
assert "lora" not in payload["details"]