diff --git a/py/services/model_cache.py b/py/services/model_cache.py index 3c552806..8494531e 100644 --- a/py/services/model_cache.py +++ b/py/services/model_cache.py @@ -32,12 +32,13 @@ class ModelCache: all_folders = set(l['folder'] for l in self.raw_data) self.folders = sorted(list(all_folders), key=lambda x: x.lower()) - async def update_preview_url(self, file_path: str, preview_url: str) -> bool: + async def update_preview_url(self, file_path: str, preview_url: str, preview_nsfw_level: int) -> bool: """Update preview_url for a specific model in all cached data Args: file_path: The file path of the model to update preview_url: The new preview URL + preview_nsfw_level: The NSFW level of the preview Returns: bool: True if the update was successful, False if the model wasn't found @@ -47,19 +48,9 @@ class ModelCache: for item in self.raw_data: if item['file_path'] == file_path: item['preview_url'] = preview_url + item['preview_nsfw_level'] = preview_nsfw_level break else: return False # Model not found - - # Update in sorted lists (references to the same dict objects) - for item in self.sorted_by_name: - if item['file_path'] == file_path: - item['preview_url'] = preview_url - break - - for item in self.sorted_by_date: - if item['file_path'] == file_path: - item['preview_url'] = preview_url - break return True \ No newline at end of file diff --git a/py/services/model_scanner.py b/py/services/model_scanner.py index c29cdedc..38096cb6 100644 --- a/py/services/model_scanner.py +++ b/py/services/model_scanner.py @@ -1184,12 +1184,13 @@ class ModelScanner: """Get list of excluded model file paths""" return self._excluded_models.copy() - async def update_preview_in_cache(self, file_path: str, preview_url: str) -> bool: + async def update_preview_in_cache(self, file_path: str, preview_url: str, preview_nsfw_level: int) -> bool: """Update preview URL in cache for a specific lora Args: file_path: The file path of the lora to update preview_url: The new preview URL + preview_nsfw_level: The NSFW level of the preview Returns: bool: True if the update was successful, False if cache doesn't exist or lora wasn't found @@ -1197,7 +1198,7 @@ class ModelScanner: if self._cache is None: return False - updated = await self._cache.update_preview_url(file_path, preview_url) + updated = await self._cache.update_preview_url(file_path, preview_url, preview_nsfw_level) if updated: # Save updated cache to disk await self._save_cache_to_disk() diff --git a/py/utils/routes_common.py b/py/utils/routes_common.py index 4f729204..88068459 100644 --- a/py/utils/routes_common.py +++ b/py/utils/routes_common.py @@ -409,6 +409,15 @@ class ModelRouteUtils: raise ValueError("Expected 'model_path' field") model_path = (await field.read()).decode() + # Read NSFW level (new parameter) + nsfw_level = 0 # Default to 0 (unknown) + field = await reader.next() + if field and field.name == 'nsfw_level': + try: + nsfw_level = int((await field.read()).decode()) + except (ValueError, TypeError): + logger.warning("Invalid NSFW level format, using default 0") + # Save preview file base_name = os.path.splitext(os.path.basename(model_path))[0] folder = os.path.dirname(model_path) @@ -435,7 +444,7 @@ class ModelRouteUtils: if os.path.exists(existing_preview): try: os.remove(existing_preview) - logger.info(f"Deleted existing preview: {existing_preview}") + logger.debug(f"Deleted existing preview: {existing_preview}") except Exception as e: logger.warning(f"Failed to delete existing preview {existing_preview}: {e}") @@ -444,26 +453,28 @@ class ModelRouteUtils: with open(preview_path, 'wb') as f: f.write(optimized_data) - # Update preview path in metadata + # Update preview path and NSFW level in metadata metadata_path = os.path.splitext(model_path)[0] + '.metadata.json' if os.path.exists(metadata_path): try: with open(metadata_path, 'r', encoding='utf-8') as f: metadata = json.load(f) - # Update preview_url directly in the metadata dict + # Update preview_url and preview_nsfw_level in the metadata dict metadata['preview_url'] = preview_path + metadata['preview_nsfw_level'] = nsfw_level await MetadataManager.save_metadata(model_path, metadata) except Exception as e: logger.error(f"Error updating metadata: {e}") # Update preview URL in scanner cache - await scanner.update_preview_in_cache(model_path, preview_path) + await scanner.update_preview_in_cache(model_path, preview_path, nsfw_level) return web.json_response({ "success": True, - "preview_url": config.get_preview_static_url(preview_path) + "preview_url": config.get_preview_static_url(preview_path), + "preview_nsfw_level": nsfw_level }) except Exception as e: diff --git a/static/css/components/lora-modal/showcase.css b/static/css/components/lora-modal/showcase.css index 6efe8b1d..252c927a 100644 --- a/static/css/components/lora-modal/showcase.css +++ b/static/css/components/lora-modal/showcase.css @@ -94,7 +94,6 @@ pointer-events: none; } -.media-wrapper:hover .media-controls, .media-controls.visible { opacity: 1; transform: translateY(0); @@ -115,6 +114,8 @@ transition: all 0.2s ease; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.15); padding: 0; + position: relative; + overflow: hidden; } .media-control-btn:hover { @@ -128,18 +129,47 @@ border-color: var(--lora-accent); } -.media-control-btn.example-delete-btn:hover { +.media-control-btn.example-delete-btn:hover:not(.disabled) { background: var(--lora-error); color: white; border-color: var(--lora-error); } +/* Disabled state for delete button */ +.media-control-btn.example-delete-btn.disabled { + opacity: 0.5; + cursor: not-allowed; +} + /* Two-step confirmation for delete button */ +.media-control-btn.example-delete-btn .confirm-icon { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + background: var(--lora-error); + color: white; + font-size: 1em; + opacity: 0; + transition: opacity 0.2s ease; +} + +.media-control-btn.example-delete-btn.confirm .fa-trash-alt { + opacity: 0; +} + +.media-control-btn.example-delete-btn.confirm .confirm-icon { + opacity: 1; +} + .media-control-btn.example-delete-btn.confirm { background: var(--lora-error); color: white; border-color: var(--lora-error); - animation: pulse 1.5s infinite; } @keyframes pulse { diff --git a/static/js/api/baseModelApi.js b/static/js/api/baseModelApi.js index 2944c3a6..43abbedf 100644 --- a/static/js/api/baseModelApi.js +++ b/static/js/api/baseModelApi.js @@ -542,19 +542,17 @@ export async function excludeModel(filePath, modelType = 'lora') { } } -// Private methods - // Upload a preview image -async function uploadPreview(filePath, file, modelType = 'lora') { +export async function uploadPreview(filePath, file, modelType = 'lora', nsfwLevel = 0) { try { state.loadingManager.showSimpleLoading('Uploading preview...'); const formData = new FormData(); - // Use appropriate parameter names and endpoint based on model type // Prepare common form data formData.append('preview_file', file); formData.append('model_path', filePath); + formData.append('nsfw_level', nsfwLevel.toString()); // Add nsfw_level parameter // Set endpoint based on model type const endpoint = modelType === 'checkpoint' @@ -587,7 +585,8 @@ async function uploadPreview(filePath, file, modelType = 'lora') { } const updateData = { - preview_url: data.preview_url + preview_url: data.preview_url, + preview_nsfw_level: data.preview_nsfw_level // Include nsfw level in update data }; state.virtualScroller.updateSingleItem(filePath, updateData); @@ -601,6 +600,8 @@ async function uploadPreview(filePath, file, modelType = 'lora') { } } +// Private methods + // Private function to perform the delete operation async function performDelete(filePath, modelType = 'lora') { try { diff --git a/static/js/components/checkpointModal/index.js b/static/js/components/checkpointModal/index.js index 3a287f3b..cdd61b91 100644 --- a/static/js/components/checkpointModal/index.js +++ b/static/js/components/checkpointModal/index.js @@ -108,7 +108,7 @@ export function showCheckpointModal(checkpoint) { -
+
diff --git a/static/js/components/loraModal/index.js b/static/js/components/loraModal/index.js index 566899cb..e57c121d 100644 --- a/static/js/components/loraModal/index.js +++ b/static/js/components/loraModal/index.js @@ -134,7 +134,7 @@ export function showLoraModal(lora) {
-
+
diff --git a/static/js/components/shared/showcase/MediaRenderers.js b/static/js/components/shared/showcase/MediaRenderers.js index 1cb371f3..b8ef109f 100644 --- a/static/js/components/shared/showcase/MediaRenderers.js +++ b/static/js/components/shared/showcase/MediaRenderers.js @@ -16,8 +16,10 @@ * @returns {string} HTML content */ export function generateVideoWrapper(media, heightPercent, shouldBlur, nsfwText, metadataPanel, localUrl, remoteUrl, mediaControlsHtml = '') { + const nsfwLevel = media.nsfwLevel !== undefined ? media.nsfwLevel : 0; + return ` -
+
${shouldBlur ? ` + - ${isCustomImage ? ` - - ` : ''}
`;