mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 15:15:44 -03:00
feat: enable move operations for all model types and remove unsupported methods from specific clients
This commit is contained in:
@@ -29,7 +29,7 @@ export const MODEL_CONFIG = {
|
|||||||
defaultPageSize: 100,
|
defaultPageSize: 100,
|
||||||
supportsLetterFilter: false,
|
supportsLetterFilter: false,
|
||||||
supportsBulkOperations: true,
|
supportsBulkOperations: true,
|
||||||
supportsMove: false,
|
supportsMove: true,
|
||||||
templateName: 'checkpoints.html'
|
templateName: 'checkpoints.html'
|
||||||
},
|
},
|
||||||
[MODEL_TYPES.EMBEDDING]: {
|
[MODEL_TYPES.EMBEDDING]: {
|
||||||
@@ -63,6 +63,10 @@ export function getApiEndpoints(modelType) {
|
|||||||
|
|
||||||
// Bulk operations
|
// Bulk operations
|
||||||
bulkDelete: `/api/${modelType}/bulk-delete`,
|
bulkDelete: `/api/${modelType}/bulk-delete`,
|
||||||
|
|
||||||
|
// Move operations (now common for all model types that support move)
|
||||||
|
moveModel: `/api/${modelType}/move_model`,
|
||||||
|
moveBulk: `/api/${modelType}/move_models_bulk`,
|
||||||
|
|
||||||
// CivitAI integration
|
// CivitAI integration
|
||||||
fetchCivitai: `/api/${modelType}/fetch-civitai`,
|
fetchCivitai: `/api/${modelType}/fetch-civitai`,
|
||||||
@@ -99,8 +103,6 @@ export const MODEL_SPECIFIC_ENDPOINTS = {
|
|||||||
previewUrl: `/api/${MODEL_TYPES.LORA}/preview-url`,
|
previewUrl: `/api/${MODEL_TYPES.LORA}/preview-url`,
|
||||||
civitaiUrl: `/api/${MODEL_TYPES.LORA}/civitai-url`,
|
civitaiUrl: `/api/${MODEL_TYPES.LORA}/civitai-url`,
|
||||||
modelDescription: `/api/${MODEL_TYPES.LORA}/model-description`,
|
modelDescription: `/api/${MODEL_TYPES.LORA}/model-description`,
|
||||||
moveModel: `/api/${MODEL_TYPES.LORA}/move_model`,
|
|
||||||
moveBulk: `/api/${MODEL_TYPES.LORA}/move_models_bulk`,
|
|
||||||
getTriggerWordsPost: `/api/${MODEL_TYPES.LORA}/get_trigger_words`,
|
getTriggerWordsPost: `/api/${MODEL_TYPES.LORA}/get_trigger_words`,
|
||||||
civitaiModelByVersion: `/api/${MODEL_TYPES.LORA}/civitai/model/version`,
|
civitaiModelByVersion: `/api/${MODEL_TYPES.LORA}/civitai/model/version`,
|
||||||
civitaiModelByHash: `/api/${MODEL_TYPES.LORA}/civitai/model/hash`,
|
civitaiModelByHash: `/api/${MODEL_TYPES.LORA}/civitai/model/hash`,
|
||||||
|
|||||||
@@ -672,10 +672,105 @@ export class BaseModelApiClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async moveSingleModel(filePath, targetPath) {
|
async moveSingleModel(filePath, targetPath) {
|
||||||
throw new Error("moveSingleModel must be implemented by subclass");
|
// Only allow move if supported
|
||||||
|
if (!this.apiConfig.config.supportsMove) {
|
||||||
|
showToast(`Moving ${this.apiConfig.config.displayName}s is not supported`, 'warning');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (filePath.substring(0, filePath.lastIndexOf('/')) === targetPath) {
|
||||||
|
showToast(`${this.apiConfig.config.displayName} is already in the selected folder`, 'info');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(this.apiConfig.endpoints.moveModel, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
file_path: filePath,
|
||||||
|
target_path: targetPath
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
if (result && result.error) {
|
||||||
|
throw new Error(result.error);
|
||||||
|
}
|
||||||
|
throw new Error(`Failed to move ${this.apiConfig.config.displayName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result && result.message) {
|
||||||
|
showToast(result.message, 'info');
|
||||||
|
} else {
|
||||||
|
showToast(`${this.apiConfig.config.displayName} moved successfully`, 'success');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
return result.new_file_path;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async moveBulkModels(filePaths, targetPath) {
|
async moveBulkModels(filePaths, targetPath) {
|
||||||
throw new Error("moveBulkModels must be implemented by subclass");
|
if (!this.apiConfig.config.supportsMove) {
|
||||||
|
showToast(`Moving ${this.apiConfig.config.displayName}s is not supported`, 'warning');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const movedPaths = filePaths.filter(path => {
|
||||||
|
return path.substring(0, path.lastIndexOf('/')) !== targetPath;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (movedPaths.length === 0) {
|
||||||
|
showToast(`All selected ${this.apiConfig.config.displayName}s are already in the target folder`, 'info');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(this.apiConfig.endpoints.moveBulk, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
file_paths: movedPaths,
|
||||||
|
target_path: targetPath
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to move ${this.apiConfig.config.displayName}s`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let successFilePaths = [];
|
||||||
|
if (result.success) {
|
||||||
|
if (result.failure_count > 0) {
|
||||||
|
showToast(`Moved ${result.success_count} ${this.apiConfig.config.displayName}s, ${result.failure_count} failed`, 'warning');
|
||||||
|
console.log('Move operation results:', result.results);
|
||||||
|
const failedFiles = result.results
|
||||||
|
.filter(r => !r.success)
|
||||||
|
.map(r => {
|
||||||
|
const fileName = r.path.substring(r.path.lastIndexOf('/') + 1);
|
||||||
|
return `${fileName}: ${r.message}`;
|
||||||
|
});
|
||||||
|
if (failedFiles.length > 0) {
|
||||||
|
const failureMessage = failedFiles.length <= 3
|
||||||
|
? failedFiles.join('\n')
|
||||||
|
: failedFiles.slice(0, 3).join('\n') + `\n(and ${failedFiles.length - 3} more)`;
|
||||||
|
showToast(`Failed moves:\n${failureMessage}`, 'warning', 6000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showToast(`Successfully moved ${result.success_count} ${this.apiConfig.config.displayName}s`, 'success');
|
||||||
|
}
|
||||||
|
successFilePaths = result.results
|
||||||
|
.filter(r => r.success)
|
||||||
|
.map(r => r.path);
|
||||||
|
} else {
|
||||||
|
throw new Error(result.message || `Failed to move ${this.apiConfig.config.displayName}s`);
|
||||||
|
}
|
||||||
|
return successFilePaths;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,22 +5,6 @@ import { showToast } from '../utils/uiHelpers.js';
|
|||||||
* Checkpoint-specific API client
|
* Checkpoint-specific API client
|
||||||
*/
|
*/
|
||||||
export class CheckpointApiClient extends BaseModelApiClient {
|
export class CheckpointApiClient extends BaseModelApiClient {
|
||||||
/**
|
|
||||||
* Checkpoints don't support move operations
|
|
||||||
*/
|
|
||||||
async moveSingleModel(filePath, targetPath) {
|
|
||||||
showToast('Moving checkpoints is not supported', 'warning');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checkpoints don't support bulk move operations
|
|
||||||
*/
|
|
||||||
async moveBulkModels(filePaths, targetPath) {
|
|
||||||
showToast('Moving checkpoints is not supported', 'warning');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get checkpoint information
|
* Get checkpoint information
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -5,26 +5,4 @@ import { showToast } from '../utils/uiHelpers.js';
|
|||||||
* Embedding-specific API client
|
* Embedding-specific API client
|
||||||
*/
|
*/
|
||||||
export class EmbeddingApiClient extends BaseModelApiClient {
|
export class EmbeddingApiClient extends BaseModelApiClient {
|
||||||
/**
|
|
||||||
* Move a single embedding to target path
|
|
||||||
*/
|
|
||||||
async moveSingleModel(filePath, targetPath) {
|
|
||||||
if (filePath.substring(0, filePath.lastIndexOf('/')) === targetPath) {
|
|
||||||
showToast('Embedding is already in the selected folder', 'info');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Implement embedding move endpoint when available
|
|
||||||
showToast('Moving embeddings is not yet implemented', 'info');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move multiple embeddings to target path
|
|
||||||
*/
|
|
||||||
async moveBulkModels(filePaths, targetPath) {
|
|
||||||
// TODO: Implement embedding bulk move endpoint when available
|
|
||||||
showToast('Moving embeddings is not yet implemented', 'info');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,106 +26,6 @@ export class LoraApiClient extends BaseModelApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Move a single LoRA to target path
|
|
||||||
*/
|
|
||||||
async moveSingleModel(filePath, targetPath) {
|
|
||||||
if (filePath.substring(0, filePath.lastIndexOf('/')) === targetPath) {
|
|
||||||
showToast('LoRA is already in the selected folder', 'info');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(this.apiConfig.endpoints.specific.moveModel, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
file_path: filePath,
|
|
||||||
target_path: targetPath
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
if (result && result.error) {
|
|
||||||
throw new Error(result.error);
|
|
||||||
}
|
|
||||||
throw new Error('Failed to move LoRA');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result && result.message) {
|
|
||||||
showToast(result.message, 'info');
|
|
||||||
} else {
|
|
||||||
showToast('LoRA moved successfully', 'success');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
return result.new_file_path;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Move multiple LoRAs to target path
|
|
||||||
*/
|
|
||||||
async moveBulkModels(filePaths, targetPath) {
|
|
||||||
const movedPaths = filePaths.filter(path => {
|
|
||||||
return path.substring(0, path.lastIndexOf('/')) !== targetPath;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (movedPaths.length === 0) {
|
|
||||||
showToast('All selected LoRAs are already in the target folder', 'info');
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(this.apiConfig.endpoints.specific.moveBulk, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
file_paths: movedPaths,
|
|
||||||
target_path: targetPath
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to move LoRAs');
|
|
||||||
}
|
|
||||||
|
|
||||||
let successFilePaths = [];
|
|
||||||
if (result.success) {
|
|
||||||
if (result.failure_count > 0) {
|
|
||||||
showToast(`Moved ${result.success_count} LoRAs, ${result.failure_count} failed`, 'warning');
|
|
||||||
console.log('Move operation results:', result.results);
|
|
||||||
const failedFiles = result.results
|
|
||||||
.filter(r => !r.success)
|
|
||||||
.map(r => {
|
|
||||||
const fileName = r.path.substring(r.path.lastIndexOf('/') + 1);
|
|
||||||
return `${fileName}: ${r.message}`;
|
|
||||||
});
|
|
||||||
if (failedFiles.length > 0) {
|
|
||||||
const failureMessage = failedFiles.length <= 3
|
|
||||||
? failedFiles.join('\n')
|
|
||||||
: failedFiles.slice(0, 3).join('\n') + `\n(and ${failedFiles.length - 3} more)`;
|
|
||||||
showToast(`Failed moves:\n${failureMessage}`, 'warning', 6000);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showToast(`Successfully moved ${result.success_count} LoRAs`, 'success');
|
|
||||||
}
|
|
||||||
successFilePaths = result.results
|
|
||||||
.filter(r => r.success)
|
|
||||||
.map(r => r.path);
|
|
||||||
} else {
|
|
||||||
throw new Error(result.message || 'Failed to move LoRAs');
|
|
||||||
}
|
|
||||||
return successFilePaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get LoRA notes
|
* Get LoRA notes
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user