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 = '
No matching recipes found
';
if (state) {
- state.hasMore = false;
+ state.pages.recipes.hasMore = false;
}
} else {
this.appendRecipeCards(data.items);
if (state) {
- state.hasMore = state.currentPage < data.total_pages;
- state.currentPage++;
+ state.pages.recipes.hasMore = state.pages.recipes.currentPage < data.total_pages;
+ state.pages.recipes.currentPage++;
}
}
diff --git a/static/js/managers/SearchManager.js b/static/js/managers/SearchManager.js
index 17259883..54628a43 100644
--- a/static/js/managers/SearchManager.js
+++ b/static/js/managers/SearchManager.js
@@ -194,7 +194,7 @@ export class SearchManager {
});
}
- // Apply recursive search
+ // Apply recursive search - only if the toggle exists
if (this.recursiveSearchToggle && preferences.recursive !== undefined) {
this.recursiveSearchToggle.checked = preferences.recursive;
}
@@ -245,10 +245,14 @@ export class SearchManager {
});
const preferences = {
- options,
- recursive: this.recursiveSearchToggle ? this.recursiveSearchToggle.checked : false
+ options
};
+ // Only add recursive option if the toggle exists
+ if (this.recursiveSearchToggle) {
+ preferences.recursive = this.recursiveSearchToggle.checked;
+ }
+
localStorage.setItem(`${this.currentPage}_search_prefs`, JSON.stringify(preferences));
} catch (error) {
console.error('Error saving search preferences:', error);
diff --git a/static/js/recipes.js b/static/js/recipes.js
index 5d13421a..db474bb0 100644
--- a/static/js/recipes.js
+++ b/static/js/recipes.js
@@ -3,13 +3,15 @@ import { appCore } from './core.js';
import { ImportManager } from './managers/ImportManager.js';
import { RecipeCard } from './components/RecipeCard.js';
import { RecipeModal } from './components/RecipeModal.js';
+import { state, getCurrentPageState, setCurrentPageType, initPageState } from './state/index.js';
class RecipeManager {
constructor() {
- this.currentPage = 1;
- this.pageSize = 20;
- this.sortBy = 'date';
- this.filterParams = {};
+ // Initialize recipe page state
+ initPageState('recipes');
+
+ // Get page state
+ this.pageState = getCurrentPageState();
// Initialize ImportManager
this.importManager = new ImportManager();
@@ -18,14 +20,17 @@ class RecipeManager {
this.recipeModal = new RecipeModal();
// Add state tracking for infinite scroll
- this.isLoading = false;
- this.hasMore = true;
+ this.pageState.isLoading = false;
+ this.pageState.hasMore = true;
}
async initialize() {
// Initialize event listeners
this.initEventListeners();
+ // Set default search options if not already defined
+ this._initSearchOptions();
+
// Load initial set of recipes
await this.loadRecipes();
@@ -36,6 +41,18 @@ class RecipeManager {
appCore.initializePageFeatures();
}
+ _initSearchOptions() {
+ // Ensure recipes search options are properly initialized
+ if (!this.pageState.searchOptions) {
+ this.pageState.searchOptions = {
+ title: true, // Recipe title
+ tags: true, // Recipe tags
+ loraName: true, // LoRA file name
+ loraModel: true // LoRA model name
+ };
+ }
+ }
+
_exposeGlobalFunctions() {
// Only expose what's needed for the page
window.recipeManager = this;
@@ -49,7 +66,7 @@ class RecipeManager {
const sortSelect = document.getElementById('sortSelect');
if (sortSelect) {
sortSelect.addEventListener('change', () => {
- this.sortBy = sortSelect.value;
+ this.pageState.sortBy = sortSelect.value;
this.loadRecipes();
});
}
@@ -61,7 +78,7 @@ class RecipeManager {
searchInput.addEventListener('input', () => {
clearTimeout(debounceTimeout);
debounceTimeout = setTimeout(() => {
- this.filterParams.search = searchInput.value;
+ this.pageState.filters.search = searchInput.value;
this.loadRecipes();
}, 300);
});
@@ -81,11 +98,11 @@ class RecipeManager {
try {
// Show loading indicator
document.body.classList.add('loading');
- this.isLoading = true;
+ this.pageState.isLoading = true;
// Reset to first page if requested
if (resetPage) {
- this.currentPage = 1;
+ this.pageState.currentPage = 1;
// Clear grid if resetting
const grid = document.getElementById('recipeGrid');
if (grid) grid.innerHTML = '';
@@ -93,19 +110,24 @@ class RecipeManager {
// Build query parameters
const params = new URLSearchParams({
- page: this.currentPage,
- page_size: this.pageSize,
- sort_by: this.sortBy
+ page: this.pageState.currentPage,
+ page_size: this.pageState.pageSize || 20,
+ sort_by: this.pageState.sortBy
});
// Add search filter if present
- if (this.filterParams.search) {
- params.append('search', this.filterParams.search);
+ if (this.pageState.filters.search) {
+ params.append('search', this.pageState.filters.search);
}
- // Add other filters
- if (this.filterParams.baseModels && this.filterParams.baseModels.length) {
- params.append('base_models', this.filterParams.baseModels.join(','));
+ // Add base model filters
+ if (this.pageState.filters.baseModel && this.pageState.filters.baseModel.length) {
+ params.append('base_models', this.pageState.filters.baseModel.join(','));
+ }
+
+ // Add tag filters
+ if (this.pageState.filters.tags && this.pageState.filters.tags.length) {
+ params.append('tags', this.pageState.filters.tags.join(','));
}
// Fetch recipes
@@ -121,7 +143,7 @@ class RecipeManager {
this.updateRecipesGrid(data, resetPage);
// Update pagination state
- this.hasMore = data.has_more || false;
+ this.pageState.hasMore = data.has_more || false;
} catch (error) {
console.error('Error loading recipes:', error);
@@ -129,15 +151,15 @@ class RecipeManager {
} finally {
// Hide loading indicator
document.body.classList.remove('loading');
- this.isLoading = false;
+ this.pageState.isLoading = false;
}
}
// Load more recipes for infinite scroll
async loadMoreRecipes() {
- if (this.isLoading || !this.hasMore) return;
+ if (this.pageState.isLoading || !this.pageState.hasMore) return;
- this.currentPage++;
+ this.pageState.currentPage++;
await this.loadRecipes(false);
}
@@ -170,7 +192,7 @@ class RecipeManager {
});
// Add sentinel for infinite scroll if needed
- if (this.hasMore) {
+ if (this.pageState.hasMore) {
let sentinel = document.getElementById('scroll-sentinel');
if (!sentinel) {
sentinel = document.createElement('div');
@@ -179,8 +201,8 @@ class RecipeManager {
grid.appendChild(sentinel);
// Re-observe the sentinel if we have an observer
- if (window.state && window.state.observer) {
- window.state.observer.observe(sentinel);
+ if (state && state.observer) {
+ state.observer.observe(sentinel);
}
}
}
diff --git a/static/js/state/index.js b/static/js/state/index.js
index 1a54a7d0..eeeb699b 100644
--- a/static/js/state/index.js
+++ b/static/js/state/index.js
@@ -42,16 +42,17 @@ export const state = {
sortBy: 'date',
searchManager: null,
searchOptions: {
- filename: true,
- modelname: true,
+ title: true,
tags: true,
- loras: true,
- recursive: false
+ loraName: true,
+ loraModel: true
},
filters: {
baseModel: [],
- tags: []
- }
+ tags: [],
+ search: ''
+ },
+ pageSize: 20
},
checkpoints: {
diff --git a/templates/components/header.html b/templates/components/header.html
index 7a07dd49..f9ddc624 100644
--- a/templates/components/header.html
+++ b/templates/components/header.html
@@ -66,18 +66,23 @@
+ {% if request.path != '/loras/recipes' %}
Include Subfolders
@@ -87,6 +92,7 @@
+ {% endif %}