From f614dbf7003296cdbd60f31d2de5203c1292ede5 Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Wed, 5 Mar 2025 19:53:52 +0800 Subject: [PATCH] Add filters - base model only for now --- py/routes/api_routes.py | 9 +++- py/services/lora_scanner.py | 10 +++- py/utils/model_utils.py | 8 +-- static/css/layout.css | 34 +++++-------- static/js/api/loraApi.js | 6 +++ static/js/main.js | 6 +++ static/js/managers/FilterManager.js | 32 ++++++++---- static/js/utils/constants.js | 78 ++++++++++++++++++++++++++--- templates/components/controls.html | 3 -- 9 files changed, 139 insertions(+), 47 deletions(-) diff --git a/py/routes/api_routes.py b/py/routes/api_routes.py index e991bcbb..cd66783d 100644 --- a/py/routes/api_routes.py +++ b/py/routes/api_routes.py @@ -121,6 +121,10 @@ class ApiRoutes: fuzzy = request.query.get('fuzzy', 'false').lower() == 'true' recursive = request.query.get('recursive', 'false').lower() == 'true' + # Parse base models filter parameter + base_models = request.query.get('base_models', '').split(',') + base_models = [model.strip() for model in base_models if model.strip()] + # Validate parameters if page < 1 or page_size < 1 or page_size > 100: return web.json_response({ @@ -132,7 +136,7 @@ class ApiRoutes: 'error': 'Invalid sort parameter' }, status=400) - # Get paginated data with search + # Get paginated data with search and filters result = await self.scanner.get_paginated_data( page=page, page_size=page_size, @@ -140,7 +144,8 @@ class ApiRoutes: folder=folder, search=search, fuzzy=fuzzy, - recursive=recursive # 添加递归参数 + recursive=recursive, + base_models=base_models # Pass base models filter ) # Format the response data diff --git a/py/services/lora_scanner.py b/py/services/lora_scanner.py index 53f6d4b3..5d3fbdf4 100644 --- a/py/services/lora_scanner.py +++ b/py/services/lora_scanner.py @@ -148,7 +148,7 @@ class LoraScanner: async def get_paginated_data(self, page: int, page_size: int, sort_by: str = 'name', folder: str = None, search: str = None, fuzzy: bool = False, - recursive: bool = False): + recursive: bool = False, base_models: list = None): """Get paginated and filtered lora data Args: @@ -159,6 +159,7 @@ class LoraScanner: search: Search term fuzzy: Use fuzzy matching for search recursive: Include subfolders when folder filter is applied + base_models: List of base models to filter by """ cache = await self.get_cached_data() @@ -180,6 +181,13 @@ class LoraScanner: if item['folder'] == folder ] + # Apply base model filtering + if base_models and len(base_models) > 0: + filtered_data = [ + item for item in filtered_data + if item.get('base_model') in base_models + ] + # 应用搜索过滤 if search: if fuzzy: diff --git a/py/utils/model_utils.py b/py/utils/model_utils.py index c1d5581b..9c09e00b 100644 --- a/py/utils/model_utils.py +++ b/py/utils/model_utils.py @@ -2,10 +2,10 @@ from typing import Optional # Base model mapping based on version string BASE_MODEL_MAPPING = { - "sd-v1-5": "SD1.5", - "sd-v2-1": "SD2.1", - "sdxl": "SDXL", - "sd-v2": "SD2.0", + "sd-v1-5": "SD 1.5", + "sd-v2-1": "SD 2.1", + "sdxl": "SDXL 1.0", + "sd-v2": "SD 2.0", "flux1": "Flux.1 D", "flux.1 d": "Flux.1 D", "illustrious": "IL", diff --git a/static/css/layout.css b/static/css/layout.css index 72e364bf..c9214d43 100644 --- a/static/css/layout.css +++ b/static/css/layout.css @@ -438,6 +438,10 @@ font-size: 14px; cursor: pointer; transition: all 0.2s ease; + user-select: none; /* Prevent text selection */ + -webkit-user-select: none; /* For Safari */ + -moz-user-select: none; /* For Firefox */ + -ms-user-select: none; /* For IE/Edge */ } .filter-tag:hover { @@ -467,42 +471,28 @@ /* Filter actions */ .filter-actions { display: flex; - justify-content: space-between; + justify-content: center; margin-top: 16px; gap: 8px; } -.clear-filters-btn, -.apply-filters-btn { - padding: 6px 12px; - border-radius: var(--border-radius-sm); - font-size: 14px; - cursor: pointer; - transition: background-color 0.2s ease; -} - .clear-filters-btn { background-color: transparent; color: var(--text-color); border: 1px solid var(--border-color); - flex: 1; + padding: 6px 12px; + border-radius: var(--border-radius-sm); + font-size: 14px; + cursor: pointer; + transition: background-color 0.2s ease; + width: 100%; } .clear-filters-btn:hover { background-color: var(--lora-surface-hover); } -.apply-filters-btn { - background-color: var(--lora-accent); - color: white; - border: 1px solid var(--lora-accent); - flex: 1; -} - -.apply-filters-btn:hover { - background-color: var(--lora-accent-hover, var(--lora-accent)); - filter: brightness(1.1); -} +/* Remove apply-filters-btn styles since we no longer need it */ @media (max-width: 768px) { .actions { diff --git a/static/js/api/loraApi.js b/static/js/api/loraApi.js index d4357d41..1ac6f012 100644 --- a/static/js/api/loraApi.js +++ b/static/js/api/loraApi.js @@ -30,6 +30,12 @@ export async function loadMoreLoras(boolUpdateFolders = false) { params.append('search', searchInput.value.trim()); params.append('fuzzy', 'true'); } + + // Add filter parameters if active + if (state.filters && state.filters.baseModel && state.filters.baseModel.length > 0) { + // Convert the array of base models to a comma-separated string + params.append('base_models', state.filters.baseModel.join(',')); + } console.log('Loading loras with params:', params.toString()); diff --git a/static/js/main.js b/static/js/main.js index d868e449..7c767553 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -55,6 +55,12 @@ document.addEventListener('DOMContentLoaded', () => { modalManager.initialize(); // Initialize modalManager after DOM is loaded window.downloadManager = new DownloadManager(); // Move this after modalManager initialization window.filterManager = new FilterManager(); // Initialize filter manager + + // Initialize state filters from filterManager if available + if (window.filterManager && window.filterManager.filters) { + state.filters = { ...window.filterManager.filters }; + } + initializeInfiniteScroll(); initializeEventListeners(); lazyLoadImages(); diff --git a/static/js/managers/FilterManager.js b/static/js/managers/FilterManager.js index 30620937..a5d8ae03 100644 --- a/static/js/managers/FilterManager.js +++ b/static/js/managers/FilterManager.js @@ -1,6 +1,7 @@ import { BASE_MODELS, BASE_MODEL_CLASSES } from '../utils/constants.js'; import { state } from '../state/index.js'; import { showToast } from '../utils/uiHelpers.js'; +import { resetAndReload } from '../api/loraApi.js'; export class FilterManager { constructor() { @@ -45,8 +46,8 @@ export class FilterManager { tag.dataset.baseModel = value; tag.innerHTML = value; - // Add click handler to toggle selection - tag.addEventListener('click', () => { + // Add click handler to toggle selection and automatically apply + tag.addEventListener('click', async () => { tag.classList.toggle('active'); if (tag.classList.contains('active')) { @@ -58,6 +59,9 @@ export class FilterManager { } this.updateActiveFiltersCount(); + + // Auto-apply filter when tag is clicked + await this.applyFilters(false); }); baseModelTagsContainer.appendChild(tag); @@ -101,30 +105,39 @@ export class FilterManager { } } - applyFilters() { + async applyFilters(showToastNotification = true) { // Save filters to localStorage localStorage.setItem('loraFilters', JSON.stringify(this.filters)); - // Apply filters to cards (will be implemented later) - showToast('Filters applied', 'success'); + // Update state with current filters + state.filters = { ...this.filters }; - // Close the filter panel - this.closeFilterPanel(); + // Reload loras with filters applied + await resetAndReload(); // Update filter button to show active state if (this.hasActiveFilters()) { this.filterButton.classList.add('active'); + if (showToastNotification) { + showToast(`Filtering by ${this.filters.baseModel.length} base models`, 'success'); + } } else { this.filterButton.classList.remove('active'); + if (showToastNotification) { + showToast('Filters cleared', 'info'); + } } } - clearFilters() { + async clearFilters() { // Clear all filters this.filters = { baseModel: [] }; + // Update state + state.filters = { ...this.filters }; + // Update UI this.updateTagSelections(); this.updateActiveFiltersCount(); @@ -132,8 +145,9 @@ export class FilterManager { // Remove from localStorage localStorage.removeItem('loraFilters'); + // Update UI and reload data this.filterButton.classList.remove('active'); - showToast('All filters cleared', 'info'); + await resetAndReload(); } loadFiltersFromStorage() { diff --git a/static/js/utils/constants.js b/static/js/utils/constants.js index f49bfefe..6be4c2de 100644 --- a/static/js/utils/constants.js +++ b/static/js/utils/constants.js @@ -1,24 +1,90 @@ export const BASE_MODELS = { - SD_1_5: "SD1.5", - SD_2_0: "SD2.0", - SD_2_1: "SD2.1", - SDXL: "SDXL", + // Stable Diffusion 1.x models + SD_1_4: "SD 1.4", + SD_1_5: "SD 1.5", + SD_1_5_LCM: "SD 1.5 LCM", + SD_1_5_HYPER: "SD 1.5 Hyper", + + // Stable Diffusion 2.x models + SD_2_0: "SD 2.0", + SD_2_1: "SD 2.1", + + // Stable Diffusion 3.x models + SD_3: "SD 3", + SD_3_5: "SD 3.5", + SD_3_5_MEDIUM: "SD 3.5 Medium", + SD_3_5_LARGE: "SD 3.5 Large", + SD_3_5_LARGE_TURBO: "SD 3.5 Large Turbo", + + // SDXL models + SDXL: "SDXL 1.0", + SDXL_LIGHTNING: "SDXL Lightning", + SDXL_HYPER: "SDXL Hyper", + + // Other models FLUX_1_D: "Flux.1 D", + FLUX_1_S: "Flux.1 S", + AURAFLOW: "AuraFlow", + PIXART_A: "PixArt a", + PIXART_E: "PixArt E", + HUNYUAN_1: "Hunyuan 1", + LUMINA: "Lumina", + KOLORS: "Kolors", + NOOBAI: "NoobAI", IL: "IL", PONY: "Pony", + + // Video models + SVD: "SVD", + WAN_VIDEO: "Wan Video", HUNYUAN_VIDEO: "Hunyuan Video", + + // Default UNKNOWN: "Unknown" }; // Base model display names and their corresponding class names (for styling) export const BASE_MODEL_CLASSES = { + // Stable Diffusion 1.x models + [BASE_MODELS.SD_1_4]: "sd-1-4", [BASE_MODELS.SD_1_5]: "sd-1-5", + [BASE_MODELS.SD_1_5_LCM]: "sd-1-5-lcm", + [BASE_MODELS.SD_1_5_HYPER]: "sd-1-5-hyper", + + // Stable Diffusion 2.x models [BASE_MODELS.SD_2_0]: "sd-2-0", [BASE_MODELS.SD_2_1]: "sd-2-1", + + // Stable Diffusion 3.x models + [BASE_MODELS.SD_3]: "sd-3", + [BASE_MODELS.SD_3_5]: "sd-3-5", + [BASE_MODELS.SD_3_5_MEDIUM]: "sd-3-5-medium", + [BASE_MODELS.SD_3_5_LARGE]: "sd-3-5-large", + [BASE_MODELS.SD_3_5_LARGE_TURBO]: "sd-3-5-large-turbo", + + // SDXL models [BASE_MODELS.SDXL]: "sdxl", - [BASE_MODELS.FLUX_1_D]: "flux", + [BASE_MODELS.SDXL_LIGHTNING]: "sdxl-lightning", + [BASE_MODELS.SDXL_HYPER]: "sdxl-hyper", + + // Video models + [BASE_MODELS.SVD]: "svd", + [BASE_MODELS.WAN_VIDEO]: "wan-video", + [BASE_MODELS.HUNYUAN_VIDEO]: "hunyuan-video", + + // Other models + [BASE_MODELS.FLUX_1_D]: "flux-d", + [BASE_MODELS.FLUX_1_S]: "flux-s", + [BASE_MODELS.AURAFLOW]: "auraflow", + [BASE_MODELS.PIXART_A]: "pixart-a", + [BASE_MODELS.PIXART_E]: "pixart-e", + [BASE_MODELS.HUNYUAN_1]: "hunyuan-1", + [BASE_MODELS.LUMINA]: "lumina", + [BASE_MODELS.KOLORS]: "kolors", + [BASE_MODELS.NOOBAI]: "noobai", [BASE_MODELS.IL]: "il", [BASE_MODELS.PONY]: "pony", - [BASE_MODELS.HUNYUAN_VIDEO]: "hunyuan", + + // Default [BASE_MODELS.UNKNOWN]: "unknown" }; \ No newline at end of file diff --git a/templates/components/controls.html b/templates/components/controls.html index ef1d0332..bd79d1cd 100644 --- a/templates/components/controls.html +++ b/templates/components/controls.html @@ -61,8 +61,5 @@ - \ No newline at end of file