mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-08-24 06:21:26 -03:00
fix(recipes): show initialization screen and auto-reload during first scan
The recipes page always rendered with is_initializing=False, so a cold start displayed an empty grid that never updated until a manual refresh. Mirror the model pages: gate render_page on the scanner state, broadcast init progress from RecipeScanner (including a completion message, and a failure fallback so the page never stalls), and teach initialization.js to detect the /loras/recipes page before the generic /loras match.
This commit is contained in:
@@ -176,11 +176,19 @@ class RecipePageView:
|
|||||||
user_language = self._settings.get("language", "en")
|
user_language = self._settings.get("language", "en")
|
||||||
self._server_i18n.set_locale(user_language)
|
self._server_i18n.set_locale(user_language)
|
||||||
|
|
||||||
|
# While the initial scan is running, show the initialization
|
||||||
|
# screen (same as the model pages) instead of an empty grid; the
|
||||||
|
# page reloads itself when the scanner broadcasts completion.
|
||||||
|
is_initializing = (
|
||||||
|
recipe_scanner._cache is None or recipe_scanner.is_initializing()
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await recipe_scanner.get_cached_data(force_refresh=False)
|
if not is_initializing:
|
||||||
|
await recipe_scanner.get_cached_data(force_refresh=False)
|
||||||
rendered = self._template_env.get_template(self._template_name).render(
|
rendered = self._template_env.get_template(self._template_name).render(
|
||||||
recipes=[],
|
recipes=[],
|
||||||
is_initializing=False,
|
is_initializing=is_initializing,
|
||||||
settings=self._settings,
|
settings=self._settings,
|
||||||
request=request,
|
request=request,
|
||||||
t=self._server_i18n.get_translation,
|
t=self._server_i18n.get_translation,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from ..utils.recipe_open_stats import RecipeOpenStats
|
|||||||
from .model_scanner import WEIGHT_FILE_EXTENSIONS
|
from .model_scanner import WEIGHT_FILE_EXTENSIONS
|
||||||
from .recipe_cache import RecipeCache
|
from .recipe_cache import RecipeCache
|
||||||
from .recipes.errors import RecipeNotFoundError, RecipePersistenceError
|
from .recipes.errors import RecipeNotFoundError, RecipePersistenceError
|
||||||
|
from .websocket_manager import ws_manager
|
||||||
from natsort import natsorted
|
from natsort import natsorted
|
||||||
import sys
|
import sys
|
||||||
import re
|
import re
|
||||||
@@ -481,6 +482,10 @@ class RecipeScanner:
|
|||||||
return str(value)
|
return str(value)
|
||||||
return "unknown"
|
return "unknown"
|
||||||
|
|
||||||
|
def is_initializing(self) -> bool:
|
||||||
|
"""Check if the scanner is currently initializing"""
|
||||||
|
return self._is_initializing
|
||||||
|
|
||||||
def on_library_changed(self) -> None:
|
def on_library_changed(self) -> None:
|
||||||
"""Reset cached state when the active library changes."""
|
"""Reset cached state when the active library changes."""
|
||||||
|
|
||||||
@@ -1410,6 +1415,14 @@ class RecipeScanner:
|
|||||||
self._is_initializing = True
|
self._is_initializing = True
|
||||||
self._initialization_task = asyncio.current_task()
|
self._initialization_task = asyncio.current_task()
|
||||||
try:
|
try:
|
||||||
|
await ws_manager.broadcast_init_progress({
|
||||||
|
'stage': 'loading_cache',
|
||||||
|
'progress': 0,
|
||||||
|
'details': 'Loading recipe cache...',
|
||||||
|
'scanner_type': 'recipe',
|
||||||
|
'pageType': 'recipes',
|
||||||
|
})
|
||||||
|
|
||||||
await self._wait_for_lora_scanner()
|
await self._wait_for_lora_scanner()
|
||||||
|
|
||||||
# Set initial empty cache to avoid None reference errors
|
# Set initial empty cache to avoid None reference errors
|
||||||
@@ -1442,11 +1455,38 @@ class RecipeScanner:
|
|||||||
logger.info(
|
logger.info(
|
||||||
f"Recipe cache initialized in {elapsed_time:.2f} seconds. Found {recipe_count} recipes"
|
f"Recipe cache initialized in {elapsed_time:.2f} seconds. Found {recipe_count} recipes"
|
||||||
)
|
)
|
||||||
|
await ws_manager.broadcast_init_progress({
|
||||||
|
'stage': 'finalizing',
|
||||||
|
'progress': 100,
|
||||||
|
'status': 'complete',
|
||||||
|
'details': f'Found {recipe_count} recipes.',
|
||||||
|
'scanner_type': 'recipe',
|
||||||
|
'pageType': 'recipes',
|
||||||
|
})
|
||||||
self._schedule_post_scan_enrichment()
|
self._schedule_post_scan_enrichment()
|
||||||
# Schedule FTS index build in background (non-blocking)
|
# Schedule FTS index build in background (non-blocking)
|
||||||
self._schedule_fts_index_build()
|
self._schedule_fts_index_build()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Recipe Scanner: Error initializing cache in background: {e}")
|
logger.error(f"Recipe Scanner: Error initializing cache in background: {e}")
|
||||||
|
# Ensure the cache is never None so the page stops showing the
|
||||||
|
# initialization screen, and let waiting clients reload into the
|
||||||
|
# regular (possibly empty) view instead of stalling.
|
||||||
|
if self._cache is None:
|
||||||
|
self._cache = RecipeCache(
|
||||||
|
raw_data=[],
|
||||||
|
sorted_by_name=[],
|
||||||
|
sorted_by_date=[],
|
||||||
|
folders=[],
|
||||||
|
folder_tree={},
|
||||||
|
)
|
||||||
|
await ws_manager.broadcast_init_progress({
|
||||||
|
'stage': 'finalizing',
|
||||||
|
'progress': 100,
|
||||||
|
'status': 'complete',
|
||||||
|
'details': 'Recipe cache initialization failed.',
|
||||||
|
'scanner_type': 'recipe',
|
||||||
|
'pageType': 'recipes',
|
||||||
|
})
|
||||||
finally:
|
finally:
|
||||||
# Mark initialization as complete regardless of outcome
|
# Mark initialization as complete regardless of outcome
|
||||||
self._is_initializing = False
|
self._is_initializing = False
|
||||||
|
|||||||
@@ -52,7 +52,11 @@ class InitializationManager {
|
|||||||
detectPageType() {
|
detectPageType() {
|
||||||
// Get the current page type from URL or data attribute
|
// Get the current page type from URL or data attribute
|
||||||
const path = window.location.pathname;
|
const path = window.location.pathname;
|
||||||
if (path.includes('/checkpoints')) {
|
// The recipes page lives at /loras/recipes, so it must be matched
|
||||||
|
// before the generic '/loras' check.
|
||||||
|
if (path.includes('/recipes')) {
|
||||||
|
this.pageType = 'recipes';
|
||||||
|
} else if (path.includes('/checkpoints')) {
|
||||||
this.pageType = 'checkpoints';
|
this.pageType = 'checkpoints';
|
||||||
} else if (path.includes('/loras')) {
|
} else if (path.includes('/loras')) {
|
||||||
this.pageType = 'loras';
|
this.pageType = 'loras';
|
||||||
@@ -216,7 +220,8 @@ class InitializationManager {
|
|||||||
const scannerTypeToPageType = {
|
const scannerTypeToPageType = {
|
||||||
'lora': 'loras',
|
'lora': 'loras',
|
||||||
'checkpoint': 'checkpoints',
|
'checkpoint': 'checkpoints',
|
||||||
'embedding': 'embeddings'
|
'embedding': 'embeddings',
|
||||||
|
'recipe': 'recipes'
|
||||||
};
|
};
|
||||||
|
|
||||||
if (scannerTypeToPageType[data.scanner_type] !== this.pageType) {
|
if (scannerTypeToPageType[data.scanner_type] !== this.pageType) {
|
||||||
|
|||||||
Reference in New Issue
Block a user