From 0d0f4defca0f8346ecb11ceeba3bd11efbfcc4b8 Mon Sep 17 00:00:00 2001 From: Will Miao Date: Wed, 20 May 2026 23:14:38 +0800 Subject: [PATCH] feat(recipes): enable bulk Add Tags to Selected for recipes (#934) - Set addTags: true in recipes bulk action config - Add _saveRecipeTags() helper using recipe API endpoint - Replace mode: saves tags array directly via PUT recipe/update - Append mode: merges with existing tags from virtual scroller - Shows bulk Add Tags modal & target menu item on recipes page --- static/js/managers/BulkManager.js | 39 ++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/static/js/managers/BulkManager.js b/static/js/managers/BulkManager.js index 5b63d58e..3366dda0 100644 --- a/static/js/managers/BulkManager.js +++ b/static/js/managers/BulkManager.js @@ -3,7 +3,7 @@ import { showToast, copyToClipboard, sendLoraToWorkflow, buildLoraSyntax, getNSF import { updateCardsForBulkMode } from '../components/shared/ModelCard.js'; import { modalManager } from './ModalManager.js'; import { getModelApiClient, resetAndReload } from '../api/modelApiFactory.js'; -import { RecipeSidebarApiClient, updateRecipeMetadata } from '../api/recipeApi.js'; +import { RecipeSidebarApiClient, updateRecipeMetadata, extractRecipeId } from '../api/recipeApi.js'; import { MODEL_TYPES, MODEL_CONFIG } from '../api/apiConfig.js'; import { BASE_MODEL_CATEGORIES } from '../utils/constants.js'; import { getPriorityTagSuggestions } from '../utils/priorityTagHelpers.js'; @@ -74,7 +74,7 @@ export class BulkManager { unfavorite: true }, recipes: { - addTags: false, + addTags: true, sendToWorkflow: false, copyAll: false, refreshAll: false, @@ -1043,6 +1043,8 @@ export class BulkManager { cancelled = true; }); + const isRecipes = state.currentPageType === 'recipes'; + // Add or replace tags for each selected model based on mode for (const filePath of filePaths) { if (cancelled) { @@ -1050,7 +1052,9 @@ export class BulkManager { break; } try { - if (mode === 'replace') { + if (isRecipes) { + await this._saveRecipeTags(filePath, tags, mode); + } else if (mode === 'replace') { await apiClient.saveModelMetadata(filePath, { tags: tags }); } else { await apiClient.addTags(filePath, { tags: tags }); @@ -1089,6 +1093,35 @@ export class BulkManager { } } + async _saveRecipeTags(filePath, newTags, mode) { + const recipeId = extractRecipeId(filePath); + if (!recipeId) throw new Error('Unable to determine recipe ID'); + + let finalTags = newTags; + if (mode === 'append') { + const recipeItem = state.virtualScroller?.items?.find( + item => item.file_path === filePath + ); + const existingTags = recipeItem?.tags || []; + finalTags = [...new Set([...existingTags, ...newTags])]; + } + + const response = await fetch( + `/api/lm/recipe/${encodeURIComponent(recipeId)}/update`, + { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ tags: finalTags }), + } + ); + const data = await response.json(); + if (!data.success) { + throw new Error(data.error || 'Failed to update recipe tags'); + } + + state.virtualScroller.updateSingleItem(filePath, { tags: finalTags }); + } + cleanupBulkAddTagsModal() { // Clear tags container const tagsContainer = document.getElementById('bulkTagsItems');