mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-26 07:35:44 -03:00
feat: refactor API routes for renaming models and update related functions
This commit is contained in:
@@ -65,7 +65,7 @@ class ApiRoutes:
|
|||||||
app.router.add_get('/api/loras/top-tags', routes.get_top_tags) # Add new route for top tags
|
app.router.add_get('/api/loras/top-tags', routes.get_top_tags) # Add new route for top tags
|
||||||
app.router.add_get('/api/loras/base-models', routes.get_base_models) # Add new route for base models
|
app.router.add_get('/api/loras/base-models', routes.get_base_models) # Add new route for base models
|
||||||
app.router.add_get('/api/lora-civitai-url', routes.get_lora_civitai_url) # Add new route for Civitai URL
|
app.router.add_get('/api/lora-civitai-url', routes.get_lora_civitai_url) # Add new route for Civitai URL
|
||||||
app.router.add_post('/api/rename_lora', routes.rename_lora) # Add new route for renaming LoRA files
|
app.router.add_post('/api/loras/rename', routes.rename_lora) # Add new route for renaming LoRA files
|
||||||
app.router.add_get('/api/loras/scan', routes.scan_loras) # Add new route for scanning LoRA files
|
app.router.add_get('/api/loras/scan', routes.scan_loras) # Add new route for scanning LoRA files
|
||||||
|
|
||||||
# Add the new trigger words route
|
# Add the new trigger words route
|
||||||
@@ -873,128 +873,10 @@ class ApiRoutes:
|
|||||||
|
|
||||||
async def rename_lora(self, request: web.Request) -> web.Response:
|
async def rename_lora(self, request: web.Request) -> web.Response:
|
||||||
"""Handle renaming a LoRA file and its associated files"""
|
"""Handle renaming a LoRA file and its associated files"""
|
||||||
try:
|
|
||||||
if self.scanner is None:
|
if self.scanner is None:
|
||||||
self.scanner = await ServiceRegistry.get_lora_scanner()
|
self.scanner = await ServiceRegistry.get_lora_scanner()
|
||||||
|
|
||||||
if self.download_manager is None:
|
return await ModelRouteUtils.handle_rename_model(request, self.scanner)
|
||||||
self.download_manager = await ServiceRegistry.get_download_manager()
|
|
||||||
|
|
||||||
data = await request.json()
|
|
||||||
file_path = data.get('file_path')
|
|
||||||
new_file_name = data.get('new_file_name')
|
|
||||||
|
|
||||||
if not file_path or not new_file_name:
|
|
||||||
return web.json_response({
|
|
||||||
'success': False,
|
|
||||||
'error': 'File path and new file name are required'
|
|
||||||
}, status=400)
|
|
||||||
|
|
||||||
# Validate the new file name (no path separators or invalid characters)
|
|
||||||
invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|']
|
|
||||||
if any(char in new_file_name for char in invalid_chars):
|
|
||||||
return web.json_response({
|
|
||||||
'success': False,
|
|
||||||
'error': 'Invalid characters in file name'
|
|
||||||
}, status=400)
|
|
||||||
|
|
||||||
# Get the directory and current file name
|
|
||||||
target_dir = os.path.dirname(file_path)
|
|
||||||
old_file_name = os.path.splitext(os.path.basename(file_path))[0]
|
|
||||||
|
|
||||||
# Check if the target file already exists
|
|
||||||
new_file_path = os.path.join(target_dir, f"{new_file_name}.safetensors").replace(os.sep, '/')
|
|
||||||
if os.path.exists(new_file_path):
|
|
||||||
return web.json_response({
|
|
||||||
'success': False,
|
|
||||||
'error': 'A file with this name already exists'
|
|
||||||
}, status=400)
|
|
||||||
|
|
||||||
# Define the patterns for associated files
|
|
||||||
patterns = [
|
|
||||||
f"{old_file_name}.safetensors", # Required
|
|
||||||
f"{old_file_name}.metadata.json",
|
|
||||||
f"{old_file_name}.metadata.json.bak",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Add all preview file extensions
|
|
||||||
for ext in PREVIEW_EXTENSIONS:
|
|
||||||
patterns.append(f"{old_file_name}{ext}")
|
|
||||||
|
|
||||||
# Find all matching files
|
|
||||||
existing_files = []
|
|
||||||
for pattern in patterns:
|
|
||||||
path = os.path.join(target_dir, pattern)
|
|
||||||
if os.path.exists(path):
|
|
||||||
existing_files.append((path, pattern))
|
|
||||||
|
|
||||||
# Get the hash from the main file to update hash index
|
|
||||||
hash_value = None
|
|
||||||
metadata = None
|
|
||||||
metadata_path = os.path.join(target_dir, f"{old_file_name}.metadata.json")
|
|
||||||
|
|
||||||
if os.path.exists(metadata_path):
|
|
||||||
metadata = await ModelRouteUtils.load_local_metadata(metadata_path)
|
|
||||||
hash_value = metadata.get('sha256')
|
|
||||||
|
|
||||||
# Rename all files
|
|
||||||
renamed_files = []
|
|
||||||
new_metadata_path = None
|
|
||||||
|
|
||||||
for old_path, pattern in existing_files:
|
|
||||||
# Get the file extension like .safetensors or .metadata.json
|
|
||||||
ext = ModelRouteUtils.get_multipart_ext(pattern)
|
|
||||||
|
|
||||||
# Create the new path
|
|
||||||
new_path = os.path.join(target_dir, f"{new_file_name}{ext}").replace(os.sep, '/')
|
|
||||||
|
|
||||||
# Rename the file
|
|
||||||
os.rename(old_path, new_path)
|
|
||||||
renamed_files.append(new_path)
|
|
||||||
|
|
||||||
# Keep track of metadata path for later update
|
|
||||||
if ext == '.metadata.json':
|
|
||||||
new_metadata_path = new_path
|
|
||||||
|
|
||||||
# Update the metadata file with new file name and paths
|
|
||||||
if new_metadata_path and metadata:
|
|
||||||
# Update file_name, file_path and preview_url in metadata
|
|
||||||
metadata['file_name'] = new_file_name
|
|
||||||
metadata['file_path'] = new_file_path
|
|
||||||
|
|
||||||
# Update preview_url if it exists
|
|
||||||
if 'preview_url' in metadata and metadata['preview_url']:
|
|
||||||
old_preview = metadata['preview_url']
|
|
||||||
ext = ModelRouteUtils.get_multipart_ext(old_preview)
|
|
||||||
new_preview = os.path.join(target_dir, f"{new_file_name}{ext}").replace(os.sep, '/')
|
|
||||||
metadata['preview_url'] = new_preview
|
|
||||||
|
|
||||||
# Save updated metadata
|
|
||||||
await MetadataManager.save_metadata(new_file_path, metadata)
|
|
||||||
|
|
||||||
# Update the scanner cache
|
|
||||||
if metadata:
|
|
||||||
await self.scanner.update_single_model_cache(file_path, new_file_path, metadata)
|
|
||||||
|
|
||||||
# Update recipe files and cache if hash is available
|
|
||||||
if hash_value:
|
|
||||||
recipe_scanner = await ServiceRegistry.get_recipe_scanner()
|
|
||||||
recipes_updated, cache_updated = await recipe_scanner.update_lora_filename_by_hash(hash_value, new_file_name)
|
|
||||||
logger.info(f"Updated {recipes_updated} recipe files and {cache_updated} cache entries for renamed LoRA")
|
|
||||||
|
|
||||||
return web.json_response({
|
|
||||||
'success': True,
|
|
||||||
'new_file_path': new_file_path,
|
|
||||||
'renamed_files': renamed_files,
|
|
||||||
'reload_required': False
|
|
||||||
})
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error renaming LoRA: {e}", exc_info=True)
|
|
||||||
return web.json_response({
|
|
||||||
'success': False,
|
|
||||||
'error': str(e)
|
|
||||||
}, status=500)
|
|
||||||
|
|
||||||
async def get_trigger_words(self, request: web.Request) -> web.Response:
|
async def get_trigger_words(self, request: web.Request) -> web.Response:
|
||||||
"""Get trigger words for specified LoRA models"""
|
"""Get trigger words for specified LoRA models"""
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class CheckpointsRoutes:
|
|||||||
app.router.add_post('/api/checkpoints/replace-preview', self.replace_preview)
|
app.router.add_post('/api/checkpoints/replace-preview', self.replace_preview)
|
||||||
app.router.add_post('/api/checkpoints/download', self.download_checkpoint)
|
app.router.add_post('/api/checkpoints/download', self.download_checkpoint)
|
||||||
app.router.add_post('/api/checkpoints/save-metadata', self.save_metadata) # Add new route
|
app.router.add_post('/api/checkpoints/save-metadata', self.save_metadata) # Add new route
|
||||||
|
app.router.add_post('/api/checkpoints/rename', self.rename_checkpoint) # Add new rename endpoint
|
||||||
|
|
||||||
# Add new WebSocket endpoint for checkpoint progress
|
# Add new WebSocket endpoint for checkpoint progress
|
||||||
app.router.add_get('/ws/checkpoint-progress', ws_manager.handle_checkpoint_connection)
|
app.router.add_get('/ws/checkpoint-progress', ws_manager.handle_checkpoint_connection)
|
||||||
@@ -836,3 +837,7 @@ class CheckpointsRoutes:
|
|||||||
async def verify_duplicates(self, request: web.Request) -> web.Response:
|
async def verify_duplicates(self, request: web.Request) -> web.Response:
|
||||||
"""Handle verification of duplicate checkpoint hashes"""
|
"""Handle verification of duplicate checkpoint hashes"""
|
||||||
return await ModelRouteUtils.handle_verify_duplicates(request, self.scanner)
|
return await ModelRouteUtils.handle_verify_duplicates(request, self.scanner)
|
||||||
|
|
||||||
|
async def rename_checkpoint(self, request: web.Request) -> web.Response:
|
||||||
|
"""Handle renaming a checkpoint file and its associated files"""
|
||||||
|
return await ModelRouteUtils.handle_rename_model(request, self.scanner)
|
||||||
|
|||||||
@@ -836,3 +836,132 @@ class ModelRouteUtils:
|
|||||||
'success': False,
|
'success': False,
|
||||||
'error': str(e)
|
'error': str(e)
|
||||||
}, status=500)
|
}, status=500)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def handle_rename_model(request: web.Request, scanner) -> web.Response:
|
||||||
|
"""Handle renaming a model file and its associated files
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request: The aiohttp request
|
||||||
|
scanner: The model scanner instance
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
web.Response: The HTTP response
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
data = await request.json()
|
||||||
|
file_path = data.get('file_path')
|
||||||
|
new_file_name = data.get('new_file_name')
|
||||||
|
|
||||||
|
if not file_path or not new_file_name:
|
||||||
|
return web.json_response({
|
||||||
|
'success': False,
|
||||||
|
'error': 'File path and new file name are required'
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
# Validate the new file name (no path separators or invalid characters)
|
||||||
|
invalid_chars = ['/', '\\', ':', '*', '?', '"', '<', '>', '|']
|
||||||
|
if any(char in new_file_name for char in invalid_chars):
|
||||||
|
return web.json_response({
|
||||||
|
'success': False,
|
||||||
|
'error': 'Invalid characters in file name'
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
# Get the directory and current file name
|
||||||
|
target_dir = os.path.dirname(file_path)
|
||||||
|
old_file_name = os.path.splitext(os.path.basename(file_path))[0]
|
||||||
|
|
||||||
|
# Check if the target file already exists
|
||||||
|
new_file_path = os.path.join(target_dir, f"{new_file_name}.safetensors").replace(os.sep, '/')
|
||||||
|
if os.path.exists(new_file_path):
|
||||||
|
return web.json_response({
|
||||||
|
'success': False,
|
||||||
|
'error': 'A file with this name already exists'
|
||||||
|
}, status=400)
|
||||||
|
|
||||||
|
# Define the patterns for associated files
|
||||||
|
patterns = [
|
||||||
|
f"{old_file_name}.safetensors", # Required
|
||||||
|
f"{old_file_name}.metadata.json",
|
||||||
|
f"{old_file_name}.metadata.json.bak",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add all preview file extensions
|
||||||
|
for ext in PREVIEW_EXTENSIONS:
|
||||||
|
patterns.append(f"{old_file_name}{ext}")
|
||||||
|
|
||||||
|
# Find all matching files
|
||||||
|
existing_files = []
|
||||||
|
for pattern in patterns:
|
||||||
|
path = os.path.join(target_dir, pattern)
|
||||||
|
if os.path.exists(path):
|
||||||
|
existing_files.append((path, pattern))
|
||||||
|
|
||||||
|
# Get the hash from the main file to update hash index
|
||||||
|
hash_value = None
|
||||||
|
metadata = None
|
||||||
|
metadata_path = os.path.join(target_dir, f"{old_file_name}.metadata.json")
|
||||||
|
|
||||||
|
if os.path.exists(metadata_path):
|
||||||
|
metadata = await ModelRouteUtils.load_local_metadata(metadata_path)
|
||||||
|
hash_value = metadata.get('sha256')
|
||||||
|
|
||||||
|
# Rename all files
|
||||||
|
renamed_files = []
|
||||||
|
new_metadata_path = None
|
||||||
|
|
||||||
|
for old_path, pattern in existing_files:
|
||||||
|
# Get the file extension like .safetensors or .metadata.json
|
||||||
|
ext = ModelRouteUtils.get_multipart_ext(pattern)
|
||||||
|
|
||||||
|
# Create the new path
|
||||||
|
new_path = os.path.join(target_dir, f"{new_file_name}{ext}").replace(os.sep, '/')
|
||||||
|
|
||||||
|
# Rename the file
|
||||||
|
os.rename(old_path, new_path)
|
||||||
|
renamed_files.append(new_path)
|
||||||
|
|
||||||
|
# Keep track of metadata path for later update
|
||||||
|
if ext == '.metadata.json':
|
||||||
|
new_metadata_path = new_path
|
||||||
|
|
||||||
|
# Update the metadata file with new file name and paths
|
||||||
|
if new_metadata_path and metadata:
|
||||||
|
# Update file_name, file_path and preview_url in metadata
|
||||||
|
metadata['file_name'] = new_file_name
|
||||||
|
metadata['file_path'] = new_file_path
|
||||||
|
|
||||||
|
# Update preview_url if it exists
|
||||||
|
if 'preview_url' in metadata and metadata['preview_url']:
|
||||||
|
old_preview = metadata['preview_url']
|
||||||
|
ext = ModelRouteUtils.get_multipart_ext(old_preview)
|
||||||
|
new_preview = os.path.join(target_dir, f"{new_file_name}{ext}").replace(os.sep, '/')
|
||||||
|
metadata['preview_url'] = new_preview
|
||||||
|
|
||||||
|
# Save updated metadata
|
||||||
|
await MetadataManager.save_metadata(new_file_path, metadata)
|
||||||
|
|
||||||
|
# Update the scanner cache
|
||||||
|
if metadata:
|
||||||
|
await scanner.update_single_model_cache(file_path, new_file_path, metadata)
|
||||||
|
|
||||||
|
# Update recipe files and cache if hash is available and recipe_scanner exists
|
||||||
|
if hash_value and hasattr(scanner, 'update_lora_filename_by_hash'):
|
||||||
|
recipe_scanner = await ServiceRegistry.get_recipe_scanner()
|
||||||
|
if recipe_scanner:
|
||||||
|
recipes_updated, cache_updated = await recipe_scanner.update_lora_filename_by_hash(hash_value, new_file_name)
|
||||||
|
logger.info(f"Updated {recipes_updated} recipe files and {cache_updated} cache entries for renamed model")
|
||||||
|
|
||||||
|
return web.json_response({
|
||||||
|
'success': True,
|
||||||
|
'new_file_path': new_file_path,
|
||||||
|
'renamed_files': renamed_files,
|
||||||
|
'reload_required': False
|
||||||
|
})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error renaming model: {e}", exc_info=True)
|
||||||
|
return web.json_response({
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}, status=500)
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ export async function renameCheckpointFile(filePath, newFileName) {
|
|||||||
// Show loading indicator
|
// Show loading indicator
|
||||||
state.loadingManager.showSimpleLoading('Renaming checkpoint file...');
|
state.loadingManager.showSimpleLoading('Renaming checkpoint file...');
|
||||||
|
|
||||||
const response = await fetch('/api/rename_checkpoint', {
|
const response = await fetch('/api/checkpoints/rename', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -160,7 +160,6 @@ export async function renameCheckpointFile(filePath, newFileName) {
|
|||||||
console.error('Error renaming checkpoint file:', error);
|
console.error('Error renaming checkpoint file:', error);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
// Hide loading indicator
|
|
||||||
state.loadingManager.hide();
|
state.loadingManager.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +149,7 @@ export async function renameLoraFile(filePath, newFileName) {
|
|||||||
// Show loading indicator
|
// Show loading indicator
|
||||||
state.loadingManager.showSimpleLoading('Renaming LoRA file...');
|
state.loadingManager.showSimpleLoading('Renaming LoRA file...');
|
||||||
|
|
||||||
const response = await fetch('/api/rename_lora', {
|
const response = await fetch('/api/loras/rename', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { showToast, getNSFWLevelName, openExampleImagesFolder } from '../../utils/uiHelpers.js';
|
import { showToast, getNSFWLevelName, openExampleImagesFolder } from '../../utils/uiHelpers.js';
|
||||||
import { NSFW_LEVELS } from '../../utils/constants.js';
|
|
||||||
import { getStorageItem } from '../../utils/storageHelpers.js';
|
|
||||||
import { modalManager } from '../../managers/ModalManager.js';
|
import { modalManager } from '../../managers/ModalManager.js';
|
||||||
import { state } from '../../state/index.js';
|
import { state } from '../../state/index.js';
|
||||||
|
|
||||||
@@ -44,149 +42,6 @@ export const ModelContextMenuMixin = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateCardBlurEffect(card, level) {
|
|
||||||
// Get user settings for blur threshold
|
|
||||||
const blurThreshold = parseInt(getStorageItem('nsfwBlurLevel') || '4');
|
|
||||||
|
|
||||||
// Get card preview container
|
|
||||||
const previewContainer = card.querySelector('.card-preview');
|
|
||||||
if (!previewContainer) return;
|
|
||||||
|
|
||||||
// Get preview media element
|
|
||||||
const previewMedia = previewContainer.querySelector('img') || previewContainer.querySelector('video');
|
|
||||||
if (!previewMedia) return;
|
|
||||||
|
|
||||||
// Check if blur should be applied
|
|
||||||
if (level >= blurThreshold) {
|
|
||||||
// Add blur class to the preview container
|
|
||||||
previewContainer.classList.add('blurred');
|
|
||||||
|
|
||||||
// Get or create the NSFW overlay
|
|
||||||
let nsfwOverlay = previewContainer.querySelector('.nsfw-overlay');
|
|
||||||
if (!nsfwOverlay) {
|
|
||||||
// Create new overlay
|
|
||||||
nsfwOverlay = document.createElement('div');
|
|
||||||
nsfwOverlay.className = 'nsfw-overlay';
|
|
||||||
|
|
||||||
// Create and configure the warning content
|
|
||||||
const warningContent = document.createElement('div');
|
|
||||||
warningContent.className = 'nsfw-warning';
|
|
||||||
|
|
||||||
// Determine NSFW warning text based on level
|
|
||||||
let nsfwText = "Mature Content";
|
|
||||||
if (level >= NSFW_LEVELS.XXX) {
|
|
||||||
nsfwText = "XXX-rated Content";
|
|
||||||
} else if (level >= NSFW_LEVELS.X) {
|
|
||||||
nsfwText = "X-rated Content";
|
|
||||||
} else if (level >= NSFW_LEVELS.R) {
|
|
||||||
nsfwText = "R-rated Content";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add warning text and show button
|
|
||||||
warningContent.innerHTML = `
|
|
||||||
<p>${nsfwText}</p>
|
|
||||||
<button class="show-content-btn">Show</button>
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Add click event to the show button
|
|
||||||
const showBtn = warningContent.querySelector('.show-content-btn');
|
|
||||||
showBtn.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
previewContainer.classList.remove('blurred');
|
|
||||||
nsfwOverlay.style.display = 'none';
|
|
||||||
|
|
||||||
// Update toggle button icon if it exists
|
|
||||||
const toggleBtn = card.querySelector('.toggle-blur-btn');
|
|
||||||
if (toggleBtn) {
|
|
||||||
toggleBtn.querySelector('i').className = 'fas fa-eye-slash';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
nsfwOverlay.appendChild(warningContent);
|
|
||||||
previewContainer.appendChild(nsfwOverlay);
|
|
||||||
} else {
|
|
||||||
// Update existing overlay
|
|
||||||
const warningText = nsfwOverlay.querySelector('p');
|
|
||||||
if (warningText) {
|
|
||||||
let nsfwText = "Mature Content";
|
|
||||||
if (level >= NSFW_LEVELS.XXX) {
|
|
||||||
nsfwText = "XXX-rated Content";
|
|
||||||
} else if (level >= NSFW_LEVELS.X) {
|
|
||||||
nsfwText = "X-rated Content";
|
|
||||||
} else if (level >= NSFW_LEVELS.R) {
|
|
||||||
nsfwText = "R-rated Content";
|
|
||||||
}
|
|
||||||
warningText.textContent = nsfwText;
|
|
||||||
}
|
|
||||||
nsfwOverlay.style.display = 'flex';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get or create the toggle button in the header
|
|
||||||
const cardHeader = previewContainer.querySelector('.card-header');
|
|
||||||
if (cardHeader) {
|
|
||||||
let toggleBtn = cardHeader.querySelector('.toggle-blur-btn');
|
|
||||||
|
|
||||||
if (!toggleBtn) {
|
|
||||||
toggleBtn = document.createElement('button');
|
|
||||||
toggleBtn.className = 'toggle-blur-btn';
|
|
||||||
toggleBtn.title = 'Toggle blur';
|
|
||||||
toggleBtn.innerHTML = '<i class="fas fa-eye"></i>';
|
|
||||||
|
|
||||||
// Add click event to toggle button
|
|
||||||
toggleBtn.addEventListener('click', (e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
const isBlurred = previewContainer.classList.toggle('blurred');
|
|
||||||
const icon = toggleBtn.querySelector('i');
|
|
||||||
|
|
||||||
// Update icon and overlay visibility
|
|
||||||
if (isBlurred) {
|
|
||||||
icon.className = 'fas fa-eye';
|
|
||||||
nsfwOverlay.style.display = 'flex';
|
|
||||||
} else {
|
|
||||||
icon.className = 'fas fa-eye-slash';
|
|
||||||
nsfwOverlay.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add to the beginning of header
|
|
||||||
cardHeader.insertBefore(toggleBtn, cardHeader.firstChild);
|
|
||||||
|
|
||||||
// Update base model label class
|
|
||||||
const baseModelLabel = cardHeader.querySelector('.base-model-label');
|
|
||||||
if (baseModelLabel && !baseModelLabel.classList.contains('with-toggle')) {
|
|
||||||
baseModelLabel.classList.add('with-toggle');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Update existing toggle button
|
|
||||||
toggleBtn.querySelector('i').className = 'fas fa-eye';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Remove blur
|
|
||||||
previewContainer.classList.remove('blurred');
|
|
||||||
|
|
||||||
// Hide overlay if it exists
|
|
||||||
const overlay = previewContainer.querySelector('.nsfw-overlay');
|
|
||||||
if (overlay) overlay.style.display = 'none';
|
|
||||||
|
|
||||||
// Remove toggle button when content is set to PG or PG13
|
|
||||||
const cardHeader = previewContainer.querySelector('.card-header');
|
|
||||||
if (cardHeader) {
|
|
||||||
const toggleBtn = cardHeader.querySelector('.toggle-blur-btn');
|
|
||||||
if (toggleBtn) {
|
|
||||||
// Remove the toggle button completely
|
|
||||||
toggleBtn.remove();
|
|
||||||
|
|
||||||
// Update base model label class if it exists
|
|
||||||
const baseModelLabel = cardHeader.querySelector('.base-model-label');
|
|
||||||
if (baseModelLabel && baseModelLabel.classList.contains('with-toggle')) {
|
|
||||||
baseModelLabel.classList.remove('with-toggle');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
showNSFWLevelSelector(x, y, card) {
|
showNSFWLevelSelector(x, y, card) {
|
||||||
const selector = document.getElementById('nsfwLevelSelector');
|
const selector = document.getElementById('nsfwLevelSelector');
|
||||||
const currentLevelEl = document.getElementById('currentNSFWLevel');
|
const currentLevelEl = document.getElementById('currentNSFWLevel');
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { showToast } from '../../utils/uiHelpers.js';
|
import { showToast } from '../../utils/uiHelpers.js';
|
||||||
import { BASE_MODELS } from '../../utils/constants.js';
|
import { BASE_MODELS } from '../../utils/constants.js';
|
||||||
import { updateModelCard } from '../../utils/cardUpdater.js';
|
import { state } from '../../state/index.js';
|
||||||
import { saveModelMetadata, renameCheckpointFile } from '../../api/checkpointApi.js';
|
import { saveModelMetadata, renameCheckpointFile } from '../../api/checkpointApi.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -412,30 +412,10 @@ export function setupFileNameEditing(filePath) {
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
showToast('File name updated successfully', 'success');
|
showToast('File name updated successfully', 'success');
|
||||||
|
|
||||||
// Get the new file path from the result
|
const newFilePath = filePath.replace(originalValue, newFileName);
|
||||||
const pathParts = filePath.split(/[\\/]/);
|
|
||||||
pathParts.pop(); // Remove old filename
|
|
||||||
const newFilePath = [...pathParts, newFileName].join('/');
|
|
||||||
|
|
||||||
// Update the checkpoint card with new file path
|
state.virtualScroller.updateSingleItem(filePath, { file_name: newFileName, file_path: newFilePath });
|
||||||
updateModelCard(filePath, {
|
this.textContent = newFileName;
|
||||||
filepath: newFilePath,
|
|
||||||
file_name: newFileName
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update the file name display in the modal
|
|
||||||
document.querySelector('#file-name').textContent = newFileName;
|
|
||||||
|
|
||||||
// Update the modal's data-filepath attribute
|
|
||||||
const modalContent = document.querySelector('#checkpointModal .modal-content');
|
|
||||||
if (modalContent) {
|
|
||||||
modalContent.dataset.filepath = newFilePath;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reload the page after a short delay to reflect changes
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload();
|
|
||||||
}, 1500);
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result.error || 'Unknown error');
|
throw new Error(result.error || 'Unknown error');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
import { showToast } from '../../utils/uiHelpers.js';
|
import { showToast } from '../../utils/uiHelpers.js';
|
||||||
import { BASE_MODELS } from '../../utils/constants.js';
|
import { BASE_MODELS } from '../../utils/constants.js';
|
||||||
import { updateModelCard } from '../../utils/cardUpdater.js';
|
import { state } from '../../state/index.js';
|
||||||
import { saveModelMetadata, renameLoraFile } from '../../api/loraApi.js';
|
import { saveModelMetadata, renameLoraFile } from '../../api/loraApi.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -420,8 +420,8 @@ export function setupFileNameEditing(filePath) {
|
|||||||
|
|
||||||
// Get the new file path and update the card
|
// Get the new file path and update the card
|
||||||
const newFilePath = filePath.replace(originalValue, newFileName);
|
const newFilePath = filePath.replace(originalValue, newFileName);
|
||||||
// Pass the new file_name in the updates object for proper card update
|
;
|
||||||
updateModelCard(filePath, { file_name: newFileName, filepath: newFilePath });
|
state.virtualScroller.updateSingleItem(filePath, { file_name: newFileName, file_path: newFilePath });
|
||||||
} else {
|
} else {
|
||||||
throw new Error(result.error || 'Unknown error');
|
throw new Error(result.error || 'Unknown error');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { showToast } from '../utils/uiHelpers.js';
|
|||||||
import { state, getCurrentPageState } from '../state/index.js';
|
import { state, getCurrentPageState } from '../state/index.js';
|
||||||
import { modalManager } from './ModalManager.js';
|
import { modalManager } from './ModalManager.js';
|
||||||
import { getStorageItem } from '../utils/storageHelpers.js';
|
import { getStorageItem } from '../utils/storageHelpers.js';
|
||||||
import { updateModelCard } from '../utils/cardUpdater.js';
|
|
||||||
|
|
||||||
class MoveManager {
|
class MoveManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -151,8 +150,8 @@ class MoveManager {
|
|||||||
const filename = filePath.substring(filePath.lastIndexOf('/') + 1);
|
const filename = filePath.substring(filePath.lastIndexOf('/') + 1);
|
||||||
// Construct new filepath
|
// Construct new filepath
|
||||||
const newFilePath = `${targetPath}/${filename}`;
|
const newFilePath = `${targetPath}/${filename}`;
|
||||||
// Update the card with new filepath
|
|
||||||
updateModelCard(filePath, {filepath: newFilePath});
|
state.virtualScroller.updateSingleItem(filePath, {file_path: newFilePath});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -169,8 +168,8 @@ class MoveManager {
|
|||||||
const filename = this.currentFilePath.substring(this.currentFilePath.lastIndexOf('/') + 1);
|
const filename = this.currentFilePath.substring(this.currentFilePath.lastIndexOf('/') + 1);
|
||||||
// Construct new filepath
|
// Construct new filepath
|
||||||
const newFilePath = `${targetPath}/${filename}`;
|
const newFilePath = `${targetPath}/${filename}`;
|
||||||
// Update the card with new filepath
|
|
||||||
updateModelCard(this.currentFilePath, {filepath: newFilePath});
|
state.virtualScroller.updateSingleItem(this.currentFilePath, {file_path: newFilePath});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,47 +2,6 @@
|
|||||||
* Utility functions to update checkpoint cards after modal edits
|
* Utility functions to update checkpoint cards after modal edits
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the Lora card after metadata edits in the modal
|
|
||||||
* @param {string} filePath - Path to the Lora file
|
|
||||||
* @param {Object} updates - Object containing the updates (model_name, base_model, notes, usage_tips, etc)
|
|
||||||
*/
|
|
||||||
export function updateModelCard(filePath, updates) {
|
|
||||||
// Find the card with matching filepath
|
|
||||||
const modelCard = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
|
|
||||||
if (!modelCard) return;
|
|
||||||
|
|
||||||
// Update card dataset and visual elements based on the updates object
|
|
||||||
Object.entries(updates).forEach(([key, value]) => {
|
|
||||||
// Update dataset
|
|
||||||
modelCard.dataset[key] = value;
|
|
||||||
|
|
||||||
// Update visual elements based on the property
|
|
||||||
switch(key) {
|
|
||||||
case 'model_name':
|
|
||||||
// Update the model name in the card title
|
|
||||||
const titleElement = modelCard.querySelector('.card-title');
|
|
||||||
if (titleElement) titleElement.textContent = value;
|
|
||||||
|
|
||||||
// Also update the model name in the footer if it exists
|
|
||||||
const modelNameElement = modelCard.querySelector('.model-name');
|
|
||||||
if (modelNameElement) modelNameElement.textContent = value;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'base_model':
|
|
||||||
// Update the base model label in the card header if it exists
|
|
||||||
const baseModelLabel = modelCard.querySelector('.base-model-label');
|
|
||||||
if (baseModelLabel) {
|
|
||||||
baseModelLabel.textContent = value;
|
|
||||||
baseModelLabel.title = value;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return modelCard; // Return the updated card element for chaining
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the recipe card after metadata edits in the modal
|
* Update the recipe card after metadata edits in the modal
|
||||||
* @param {string} recipeId - ID of the recipe to update
|
* @param {string} recipeId - ID of the recipe to update
|
||||||
|
|||||||
Reference in New Issue
Block a user