From caf5b1528c14da32265754128c82a7cee02aac8d Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Thu, 20 Mar 2025 08:27:38 +0800 Subject: [PATCH] Enhance recipe search functionality with improved state management and search options - Introduced new search options for recipes, allowing users to filter by title, tags, LoRA filename, and LoRA model name. - Updated the RecipeRoutes and RecipeScanner to accommodate the new search options, enhancing the filtering capabilities. - Refactored RecipeManager and RecipeSearchManager to utilize the hierarchical state structure for managing search parameters and pagination state. - Improved the user interface by dynamically displaying relevant search options based on the current page context. --- py/routes/recipe_routes.py | 17 +++++- py/services/recipe_scanner.py | 46 +++++++++++++-- static/js/managers/RecipeSearchManager.js | 26 ++++---- static/js/managers/SearchManager.js | 10 +++- static/js/recipes.js | 72 +++++++++++++++-------- static/js/state/index.js | 13 ++-- templates/components/header.html | 22 ++++--- 7 files changed, 145 insertions(+), 61 deletions(-) diff --git a/py/routes/recipe_routes.py b/py/routes/recipe_routes.py index e5f7a298..9678105a 100644 --- a/py/routes/recipe_routes.py +++ b/py/routes/recipe_routes.py @@ -76,6 +76,12 @@ class RecipeRoutes: sort_by = request.query.get('sort_by', 'date') search = request.query.get('search', None) + # Get search options (renamed for better clarity) + search_title = request.query.get('search_title', 'true').lower() == 'true' + search_tags = request.query.get('search_tags', 'true').lower() == 'true' + search_lora_name = request.query.get('search_lora_name', 'true').lower() == 'true' + search_lora_model = request.query.get('search_lora_model', 'true').lower() == 'true' + # Get filter parameters base_models = request.query.get('base_models', None) tags = request.query.get('tags', None) @@ -87,13 +93,22 @@ class RecipeRoutes: if tags: filters['tags'] = tags.split(',') + # Add search options to filters + search_options = { + 'title': search_title, + 'tags': search_tags, + 'lora_name': search_lora_name, + 'lora_model': search_lora_model + } + # Get paginated data result = await self.recipe_scanner.get_paginated_data( page=page, page_size=page_size, sort_by=sort_by, search=search, - filters=filters + filters=filters, + search_options=search_options ) # Format the response data with static URLs for file paths diff --git a/py/services/recipe_scanner.py b/py/services/recipe_scanner.py index 196f7189..b99491a1 100644 --- a/py/services/recipe_scanner.py +++ b/py/services/recipe_scanner.py @@ -348,7 +348,7 @@ class RecipeScanner: logger.error(f"Error getting base model for lora: {e}") return None - async def get_paginated_data(self, page: int, page_size: int, sort_by: str = 'date', search: str = None, filters: dict = None): + async def get_paginated_data(self, page: int, page_size: int, sort_by: str = 'date', search: str = None, filters: dict = None, search_options: dict = None): """Get paginated and filtered recipe data Args: @@ -357,6 +357,7 @@ class RecipeScanner: sort_by: Sort method ('name' or 'date') search: Search term filters: Dictionary of filters to apply + search_options: Dictionary of search options to apply """ cache = await self.get_cached_data() @@ -365,11 +366,44 @@ class RecipeScanner: # Apply search filter if search: - filtered_data = [ - item for item in filtered_data - if search.lower() in str(item.get('title', '')).lower() or - search.lower() in str(item.get('prompt', '')).lower() - ] + # Default search options if none provided + if not search_options: + search_options = { + 'title': True, + 'tags': True, + 'lora_name': True, + 'lora_model': True + } + + # Build the search predicate based on search options + def matches_search(item): + # Search in title if enabled + if search_options.get('title', True) and search.lower() in str(item.get('title', '')).lower(): + return True + + # Search in tags if enabled + if search_options.get('tags', True) and 'tags' in item: + for tag in item['tags']: + if search.lower() in tag.lower(): + return True + + # Search in lora file names if enabled + if search_options.get('lora_name', True) and 'loras' in item: + for lora in item['loras']: + if search.lower() in str(lora.get('file_name', '')).lower(): + return True + + # Search in lora model names if enabled + if search_options.get('lora_model', True) and 'loras' in item: + for lora in item['loras']: + if search.lower() in str(lora.get('modelName', '')).lower(): + return True + + # No match found + return False + + # Filter the data using the search predicate + filtered_data = [item for item in filtered_data if matches_search(item)] # Apply additional filters if filters: diff --git a/static/js/managers/RecipeSearchManager.js b/static/js/managers/RecipeSearchManager.js index dac6ee88..13de63d7 100644 --- a/static/js/managers/RecipeSearchManager.js +++ b/static/js/managers/RecipeSearchManager.js @@ -3,7 +3,7 @@ * Extends the base SearchManager with recipe-specific functionality */ import { SearchManager } from './SearchManager.js'; -import { state } from '../state/index.js'; +import { state, getCurrentPageState } from '../state/index.js'; import { showToast } from '../utils/uiHelpers.js'; export class RecipeSearchManager extends SearchManager { @@ -17,7 +17,7 @@ export class RecipeSearchManager extends SearchManager { // Store this instance in the state if (state) { - state.searchManager = this; + state.pages.recipes.searchManager = this; } } @@ -34,7 +34,7 @@ export class RecipeSearchManager extends SearchManager { if (!searchTerm) { if (state) { - state.currentPage = 1; + state.pages.recipes.currentPage = 1; } this.resetAndReloadRecipes(); return; @@ -50,22 +50,24 @@ export class RecipeSearchManager extends SearchManager { const scrollPosition = window.pageYOffset || document.documentElement.scrollTop; if (state) { - state.currentPage = 1; - state.hasMore = true; + state.pages.recipes.currentPage = 1; + state.pages.recipes.hasMore = true; } const url = new URL('/api/recipes', window.location.origin); url.searchParams.set('page', '1'); url.searchParams.set('page_size', '20'); - url.searchParams.set('sort_by', state ? state.sortBy : 'name'); + url.searchParams.set('sort_by', state ? state.pages.recipes.sortBy : 'name'); url.searchParams.set('search', searchTerm); url.searchParams.set('fuzzy', 'true'); // Add search options - const searchOptions = this.getActiveSearchOptions(); - url.searchParams.set('search_name', searchOptions.modelname.toString()); + const recipeState = getCurrentPageState(); + const searchOptions = recipeState.searchOptions; + url.searchParams.set('search_title', searchOptions.title.toString()); url.searchParams.set('search_tags', searchOptions.tags.toString()); - url.searchParams.set('search_loras', searchOptions.loras.toString()); + url.searchParams.set('search_lora_name', searchOptions.loraName.toString()); + url.searchParams.set('search_lora_model', searchOptions.loraModel.toString()); const response = await fetch(url); @@ -81,13 +83,13 @@ export class RecipeSearchManager extends SearchManager { if (data.items.length === 0) { grid.innerHTML = '