From acc625ead3c18f7f40f1e6f35194d682e96e8afd Mon Sep 17 00:00:00 2001 From: Will Miao Date: Wed, 4 Mar 2026 20:31:58 +0800 Subject: [PATCH] feat(recipes): add sync changes dropdown menu for recipe refresh - Add syncChanges() function to recipeApi.js for quick refresh without cache rebuild - Implement dropdown menu UI in recipes page with quick refresh and full rebuild options - Add initDropdowns() method to RecipeManager for dropdown interaction handling - Update AGENTS.md with more precise instruction about running sync_translation_keys.py - Integrate sync changes functionality as default refresh behavior --- AGENTS.md | 2 +- static/js/api/recipeApi.js | 20 +++++++++++ static/js/recipes.js | 68 ++++++++++++++++++++++++++++++++++++-- templates/recipes.html | 18 +++++++--- 4 files changed, 101 insertions(+), 7 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 2609695f..ccb50a22 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -135,7 +135,7 @@ npm run test:coverage # Generate coverage report - ALWAYS use English for comments (per copilot-instructions.md) - Dual mode: ComfyUI plugin (folder_paths) vs standalone (settings.json) - Detection: `os.environ.get("LORA_MANAGER_STANDALONE", "0") == "1"` -- Run `python scripts/sync_translation_keys.py` after UI string updates +- Run `python scripts/sync_translation_keys.py` after adding UI strings to `locales/en.json` - Symlinks require normalized paths ## Frontend UI Architecture diff --git a/static/js/api/recipeApi.js b/static/js/api/recipeApi.js index 62a9e2e1..6718bc17 100644 --- a/static/js/api/recipeApi.js +++ b/static/js/api/recipeApi.js @@ -259,6 +259,26 @@ export async function resetAndReload(updateFolders = false) { }); } +/** + * Sync changes - quick refresh without rebuilding cache (similar to models page) + */ +export async function syncChanges() { + try { + state.loadingManager.showSimpleLoading('Syncing changes...'); + + // Simply reload the recipes without rebuilding cache + await resetAndReload(); + + 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 */ diff --git a/static/js/recipes.js b/static/js/recipes.js index 94f158f7..a834faa7 100644 --- a/static/js/recipes.js +++ b/static/js/recipes.js @@ -7,7 +7,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, RecipeSidebarApiClient } from './api/recipeApi.js'; +import { refreshRecipes, syncChanges, RecipeSidebarApiClient } from './api/recipeApi.js'; import { sidebarManager } from './components/SidebarManager.js'; class RecipePageControls { @@ -27,7 +27,7 @@ class RecipePageControls { return; } - refreshVirtualScroll(); + await syncChanges(); } getSidebarApiClient() { @@ -236,6 +236,70 @@ class RecipeManager { refreshVirtualScroll(); }); } + + // Initialize dropdown functionality for refresh button + this.initDropdowns(); + } + + initDropdowns() { + // Handle dropdown toggles + const dropdownToggles = document.querySelectorAll('.dropdown-toggle'); + dropdownToggles.forEach(toggle => { + toggle.addEventListener('click', (e) => { + e.stopPropagation(); + const dropdownGroup = toggle.closest('.dropdown-group'); + + // Close all other open dropdowns first + document.querySelectorAll('.dropdown-group.active').forEach(group => { + if (group !== dropdownGroup) { + group.classList.remove('active'); + } + }); + + dropdownGroup.classList.toggle('active'); + }); + }); + + // Handle quick refresh option (Sync Changes) + const quickRefreshOption = document.querySelector('[data-action="quick-refresh"]'); + if (quickRefreshOption) { + quickRefreshOption.addEventListener('click', (e) => { + e.stopPropagation(); + this.pageControls.refreshModels(false); + this.closeDropdowns(); + }); + } + + // Handle full rebuild option (Rebuild Cache) + const fullRebuildOption = document.querySelector('[data-action="full-rebuild"]'); + if (fullRebuildOption) { + fullRebuildOption.addEventListener('click', (e) => { + e.stopPropagation(); + this.pageControls.refreshModels(true); + this.closeDropdowns(); + }); + } + + // Handle main refresh button (default: sync changes) + const refreshBtn = document.querySelector('[data-action="refresh"]'); + if (refreshBtn) { + refreshBtn.addEventListener('click', () => { + this.pageControls.refreshModels(false); + }); + } + + // Close dropdowns when clicking outside + document.addEventListener('click', (e) => { + if (!e.target.closest('.dropdown-group')) { + this.closeDropdowns(); + } + }); + } + + closeDropdowns() { + document.querySelectorAll('.dropdown-group.active').forEach(group => { + group.classList.remove('active'); + }); } // This method is kept for compatibility but now uses virtual scrolling diff --git a/templates/recipes.html b/templates/recipes.html index 7f930874..4cdbe1ac 100644 --- a/templates/recipes.html +++ b/templates/recipes.html @@ -66,10 +66,20 @@ -
- +