mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat: add recipe root directory and move recipe endpoints
- Add GET /api/lm/recipes/roots endpoint to retrieve recipe root directories - Add POST /api/lm/recipe/move endpoint to move recipes between directories - Register new endpoints in route definitions - Implement error handling for both new endpoints with proper status codes - Enable recipe management operations for better file organization
This commit is contained in:
@@ -7,19 +7,28 @@ const RECIPE_ENDPOINTS = {
|
||||
detail: '/api/lm/recipe',
|
||||
scan: '/api/lm/recipes/scan',
|
||||
update: '/api/lm/recipe',
|
||||
roots: '/api/lm/recipes/roots',
|
||||
folders: '/api/lm/recipes/folders',
|
||||
folderTree: '/api/lm/recipes/folder-tree',
|
||||
unifiedFolderTree: '/api/lm/recipes/unified-folder-tree',
|
||||
move: '/api/lm/recipe/move',
|
||||
};
|
||||
|
||||
const RECIPE_SIDEBAR_CONFIG = {
|
||||
config: {
|
||||
displayName: 'Recipes',
|
||||
supportsMove: false,
|
||||
supportsMove: true,
|
||||
},
|
||||
endpoints: RECIPE_ENDPOINTS,
|
||||
};
|
||||
|
||||
function extractRecipeId(filePath) {
|
||||
if (!filePath) return null;
|
||||
const basename = filePath.split('/').pop().split('\\').pop();
|
||||
const dotIndex = basename.lastIndexOf('.');
|
||||
return dotIndex > 0 ? basename.substring(0, dotIndex) : basename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch recipes with pagination for virtual scrolling
|
||||
* @param {number} page - Page number to fetch
|
||||
@@ -302,8 +311,10 @@ export async function updateRecipeMetadata(filePath, updates) {
|
||||
state.loadingManager.showSimpleLoading('Saving metadata...');
|
||||
|
||||
// Extract recipeId from filePath (basename without extension)
|
||||
const basename = filePath.split('/').pop().split('\\').pop();
|
||||
const recipeId = basename.substring(0, basename.lastIndexOf('.'));
|
||||
const recipeId = extractRecipeId(filePath);
|
||||
if (!recipeId) {
|
||||
throw new Error('Unable to determine recipe ID');
|
||||
}
|
||||
|
||||
const response = await fetch(`${RECIPE_ENDPOINTS.update}/${recipeId}/update`, {
|
||||
method: 'PUT',
|
||||
@@ -345,6 +356,14 @@ export class RecipeSidebarApiClient {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async fetchModelRoots() {
|
||||
const response = await fetch(this.apiConfig.endpoints.roots);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch recipe roots');
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async fetchModelFolders() {
|
||||
const response = await fetch(this.apiConfig.endpoints.folders);
|
||||
if (!response.ok) {
|
||||
@@ -353,11 +372,69 @@ export class RecipeSidebarApiClient {
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async moveBulkModels() {
|
||||
throw new Error('Recipe move operations are not supported.');
|
||||
async moveBulkModels(filePaths, targetPath) {
|
||||
const results = [];
|
||||
for (const path of filePaths) {
|
||||
try {
|
||||
const result = await this.moveSingleModel(path, targetPath);
|
||||
results.push({
|
||||
original_file_path: path,
|
||||
new_file_path: result?.new_file_path,
|
||||
success: !!result,
|
||||
message: result?.message,
|
||||
});
|
||||
} catch (error) {
|
||||
results.push({
|
||||
original_file_path: path,
|
||||
new_file_path: null,
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
async moveSingleModel() {
|
||||
throw new Error('Recipe move operations are not supported.');
|
||||
async moveSingleModel(filePath, targetPath) {
|
||||
if (!this.apiConfig.config.supportsMove) {
|
||||
showToast('toast.api.moveNotSupported', { type: this.apiConfig.config.displayName }, 'warning');
|
||||
return null;
|
||||
}
|
||||
|
||||
const recipeId = extractRecipeId(filePath);
|
||||
if (!recipeId) {
|
||||
showToast('toast.api.moveFailed', { message: 'Recipe ID missing' }, 'error');
|
||||
return null;
|
||||
}
|
||||
|
||||
const response = await fetch(this.apiConfig.endpoints.move, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
recipe_id: recipeId,
|
||||
target_path: targetPath,
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok || !result.success) {
|
||||
throw new Error(result.error || `Failed to move ${this.apiConfig.config.displayName}`);
|
||||
}
|
||||
|
||||
if (result.message) {
|
||||
showToast('toast.api.moveInfo', { message: result.message }, 'info');
|
||||
} else {
|
||||
showToast('toast.api.moveSuccess', { type: this.apiConfig.config.displayName }, 'success');
|
||||
}
|
||||
|
||||
return {
|
||||
original_file_path: result.original_file_path || filePath,
|
||||
new_file_path: result.new_file_path || filePath,
|
||||
folder: result.folder || '',
|
||||
message: result.message,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { showToast, copyToClipboard, sendLoraToWorkflow } from '../../utils/uiHe
|
||||
import { setSessionItem, removeSessionItem } from '../../utils/storageHelpers.js';
|
||||
import { updateRecipeMetadata } from '../../api/recipeApi.js';
|
||||
import { state } from '../../state/index.js';
|
||||
import { moveManager } from '../../managers/MoveManager.js';
|
||||
|
||||
export class RecipeContextMenu extends BaseContextMenu {
|
||||
constructor() {
|
||||
@@ -77,6 +78,9 @@ export class RecipeContextMenu extends BaseContextMenu {
|
||||
// Share recipe
|
||||
this.currentCard.querySelector('.fa-share-alt')?.click();
|
||||
break;
|
||||
case 'move':
|
||||
moveManager.showMoveModal(this.currentCard.dataset.filepath);
|
||||
break;
|
||||
case 'delete':
|
||||
// Delete recipe
|
||||
this.currentCard.querySelector('.fa-trash')?.click();
|
||||
|
||||
@@ -3,6 +3,7 @@ import { state, getCurrentPageState } from '../state/index.js';
|
||||
import { modalManager } from './ModalManager.js';
|
||||
import { bulkManager } from './BulkManager.js';
|
||||
import { getModelApiClient } from '../api/modelApiFactory.js';
|
||||
import { RecipeSidebarApiClient } from '../api/recipeApi.js';
|
||||
import { FolderTreeManager } from '../components/FolderTreeManager.js';
|
||||
import { sidebarManager } from '../components/SidebarManager.js';
|
||||
|
||||
@@ -12,11 +13,22 @@ class MoveManager {
|
||||
this.bulkFilePaths = null;
|
||||
this.folderTreeManager = new FolderTreeManager();
|
||||
this.initialized = false;
|
||||
this.recipeApiClient = null;
|
||||
|
||||
// Bind methods
|
||||
this.updateTargetPath = this.updateTargetPath.bind(this);
|
||||
}
|
||||
|
||||
_getApiClient(modelType = null) {
|
||||
if (state.currentPageType === 'recipes') {
|
||||
if (!this.recipeApiClient) {
|
||||
this.recipeApiClient = new RecipeSidebarApiClient();
|
||||
}
|
||||
return this.recipeApiClient;
|
||||
}
|
||||
return getModelApiClient(modelType);
|
||||
}
|
||||
|
||||
initializeEventListeners() {
|
||||
if (this.initialized) return;
|
||||
|
||||
@@ -36,7 +48,7 @@ class MoveManager {
|
||||
this.currentFilePath = null;
|
||||
this.bulkFilePaths = null;
|
||||
|
||||
const apiClient = getModelApiClient();
|
||||
const apiClient = this._getApiClient(modelType);
|
||||
const currentPageType = state.currentPageType;
|
||||
const modelConfig = apiClient.apiConfig.config;
|
||||
|
||||
@@ -121,7 +133,7 @@ class MoveManager {
|
||||
|
||||
async initializeFolderTree() {
|
||||
try {
|
||||
const apiClient = getModelApiClient();
|
||||
const apiClient = this._getApiClient();
|
||||
// Fetch unified folder tree
|
||||
const treeData = await apiClient.fetchUnifiedFolderTree();
|
||||
|
||||
@@ -141,7 +153,7 @@ class MoveManager {
|
||||
updateTargetPath() {
|
||||
const pathDisplay = document.getElementById('moveTargetPathDisplay');
|
||||
const modelRoot = document.getElementById('moveModelRoot').value;
|
||||
const apiClient = getModelApiClient();
|
||||
const apiClient = this._getApiClient();
|
||||
const config = apiClient.apiConfig.config;
|
||||
|
||||
let fullPath = modelRoot || `Select a ${config.displayName.toLowerCase()} root directory`;
|
||||
@@ -158,7 +170,7 @@ class MoveManager {
|
||||
|
||||
async moveModel() {
|
||||
const selectedRoot = document.getElementById('moveModelRoot').value;
|
||||
const apiClient = getModelApiClient();
|
||||
const apiClient = this._getApiClient();
|
||||
const config = apiClient.apiConfig.config;
|
||||
|
||||
if (!selectedRoot) {
|
||||
|
||||
Reference in New Issue
Block a user