From dd5d9cfcb2d8cae0db01e9b66f00747dbbdc0d0b Mon Sep 17 00:00:00 2001 From: Will Miao Date: Tue, 2 Jun 2026 09:50:59 +0800 Subject: [PATCH] fix(recipe): align refresh split button behavior with models page - refreshRecipes() now accepts fullRebuild param and passes it to scan endpoint - Use consistent toast.api.refreshComplete / toast.api.refreshFailed keys - Use loadingManager.show() with progress bar (matching models page style) - Both Refresh and Rebuild Cache now hit the real /api/lm/recipes/scan endpoint - Add sidebarManager.refresh() after recipe scan completes - Backend scan_recipes handler reads full_rebuild query param --- py/routes/handlers/recipe_handlers.py | 6 +++- static/js/api/recipeApi.js | 48 +++++++++++---------------- static/js/recipes.js | 9 ++--- 3 files changed, 27 insertions(+), 36 deletions(-) diff --git a/py/routes/handlers/recipe_handlers.py b/py/routes/handlers/recipe_handlers.py index 7d0574a0..e34b662f 100644 --- a/py/routes/handlers/recipe_handlers.py +++ b/py/routes/handlers/recipe_handlers.py @@ -461,7 +461,11 @@ class RecipeQueryHandler: if recipe_scanner is None: raise RuntimeError("Recipe scanner unavailable") - self._logger.info("Manually triggering recipe cache rebuild") + full_rebuild = request.query.get("full_rebuild", "true").lower() == "true" + self._logger.info( + "Manually triggering recipe cache %s", + "full rebuild" if full_rebuild else "refresh", + ) await recipe_scanner.get_cached_data(force_refresh=True) return web.json_response( {"success": True, "message": "Recipe cache refreshed successfully"} diff --git a/static/js/api/recipeApi.js b/static/js/api/recipeApi.js index 5cbddc7a..5a817a12 100644 --- a/static/js/api/recipeApi.js +++ b/static/js/api/recipeApi.js @@ -294,47 +294,37 @@ export async function resetAndReload(updateFolders = false, options = {}) { } /** - * Sync changes - quick refresh without rebuilding cache (similar to models page) + * Refreshes the recipe list by triggering a backend scan, then reloading. + * @param {boolean} fullRebuild - If true, fully rebuild the cache; if false, incremental scan */ -export async function syncChanges() { +export async function refreshRecipes(fullRebuild = true) { + const actionLabel = fullRebuild ? 'Rebuilding recipe cache' : 'Refreshing recipes'; + const actionToast = fullRebuild ? 'Full rebuild' : 'Refresh'; + try { - state.loadingManager.showSimpleLoading('Syncing changes...'); + state.loadingManager.show(`${actionLabel}...`, 0); - // Simply reload the recipes without rebuilding cache - await resetAndReload(false); + const url = new URL(RECIPE_ENDPOINTS.scan, window.location.origin); + url.searchParams.append('full_rebuild', fullRebuild); - showToast('toast.recipes.syncComplete', {}, 'success'); - } catch (error) { - console.error('Error syncing recipes:', error); - showToast('toast.recipes.syncFailed', { message: error.message }, 'error'); - } finally { - state.loadingManager.hide(); - state.loadingManager.restoreProgressBar(); - } -} - -/** - * Refreshes the recipe list by first rebuilding the cache and then loading recipes - */ -export async function refreshRecipes() { - try { - state.loadingManager.showSimpleLoading('Refreshing recipes...'); - - // Call the API endpoint to rebuild the recipe cache - const response = await fetch(RECIPE_ENDPOINTS.scan); + const response = await fetch(url); if (!response.ok) { - const data = await response.json(); - throw new Error(data.error || 'Failed to refresh recipe cache'); + throw new Error(`Failed to refresh recipe cache: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + if (data.status === 'cancelled') { + showToast('toast.api.operationCancelled', {}, 'info'); + return; } - // After successful cache rebuild, reload the recipes await resetAndReload(false); - showToast('toast.recipes.refreshComplete', {}, 'success'); + showToast('toast.api.refreshComplete', { action: actionToast }, 'success'); } catch (error) { console.error('Error refreshing recipes:', error); - showToast('toast.recipes.refreshFailed', { message: error.message }, 'error'); + showToast('toast.api.refreshFailed', { action: fullRebuild ? 'rebuild' : 'refresh', type: 'recipe' }, 'error'); } finally { state.loadingManager.hide(); state.loadingManager.restoreProgressBar(); diff --git a/static/js/recipes.js b/static/js/recipes.js index 54956167..f3d03728 100644 --- a/static/js/recipes.js +++ b/static/js/recipes.js @@ -8,7 +8,7 @@ import { getSessionItem, removeSessionItem } from './utils/storageHelpers.js'; import { RecipeContextMenu } from './components/ContextMenu/index.js'; import { DuplicatesManager } from './components/DuplicatesManager.js'; import { refreshVirtualScroll } from './utils/infiniteScroll.js'; -import { refreshRecipes, syncChanges, RecipeSidebarApiClient } from './api/recipeApi.js'; +import { refreshRecipes, RecipeSidebarApiClient } from './api/recipeApi.js'; import { sidebarManager } from './components/SidebarManager.js'; class RecipePageControls { @@ -23,12 +23,9 @@ class RecipePageControls { } async refreshModels(fullRebuild = false) { - if (fullRebuild) { - await refreshRecipes(); - return; - } + await refreshRecipes(fullRebuild); - await syncChanges(); + await sidebarManager.refresh(); } getSidebarApiClient() {