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
This commit is contained in:
Will Miao
2026-05-20 23:14:38 +08:00
parent 818fa34a48
commit 0d0f4defca

View File

@@ -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');