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:
Will Miao
2026-08-24 09:13:53 +08:00
committed by pixelpaws
parent 87e93636dc
commit 06c270a6e1
3 changed files with 57 additions and 4 deletions
+10 -2
View File
@@ -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,
+40
View File
@@ -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
+7 -2
View File
@@ -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) {