mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 15:15:44 -03:00
Enhance Lora and recipe integration with improved filtering and UI updates
- Added support for filtering LoRAs by hash in both API and UI components. - Implemented session storage management for custom filter states when navigating between recipes and LoRAs. - Introduced a new button in the recipe modal to view associated LoRAs, enhancing user navigation. - Updated CSS styles for new UI elements, including a custom filter indicator and LoRA view button. - Refactored existing JavaScript components to streamline the handling of filter parameters and improve maintainability.
This commit is contained in:
@@ -132,13 +132,9 @@ class ApiRoutes:
|
|||||||
page = int(request.query.get('page', '1'))
|
page = int(request.query.get('page', '1'))
|
||||||
page_size = int(request.query.get('page_size', '20'))
|
page_size = int(request.query.get('page_size', '20'))
|
||||||
sort_by = request.query.get('sort_by', 'name')
|
sort_by = request.query.get('sort_by', 'name')
|
||||||
folder = request.query.get('folder')
|
folder = request.query.get('folder', None)
|
||||||
search = request.query.get('search', '').lower()
|
search = request.query.get('search', None)
|
||||||
fuzzy = request.query.get('fuzzy', 'false').lower() == 'true'
|
fuzzy_search = request.query.get('fuzzy', '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()]
|
|
||||||
|
|
||||||
# Parse search options
|
# Parse search options
|
||||||
search_filename = request.query.get('search_filename', 'true').lower() == 'true'
|
search_filename = request.query.get('search_filename', 'true').lower() == 'true'
|
||||||
@@ -146,62 +142,68 @@ class ApiRoutes:
|
|||||||
search_tags = request.query.get('search_tags', 'false').lower() == 'true'
|
search_tags = request.query.get('search_tags', 'false').lower() == 'true'
|
||||||
recursive = request.query.get('recursive', 'false').lower() == 'true'
|
recursive = request.query.get('recursive', 'false').lower() == 'true'
|
||||||
|
|
||||||
# Validate parameters
|
# Get filter parameters
|
||||||
if page < 1 or page_size < 1 or page_size > 100:
|
base_models = request.query.get('base_models', None)
|
||||||
return web.json_response({
|
tags = request.query.get('tags', None)
|
||||||
'error': 'Invalid pagination parameters'
|
|
||||||
}, status=400)
|
|
||||||
|
|
||||||
if sort_by not in ['date', 'name']:
|
# New parameters for recipe filtering
|
||||||
return web.json_response({
|
lora_hash = request.query.get('lora_hash', None)
|
||||||
'error': 'Invalid sort parameter'
|
lora_hashes = request.query.get('lora_hashes', None)
|
||||||
}, status=400)
|
|
||||||
|
|
||||||
# Parse tags filter parameter
|
# Parse filter parameters
|
||||||
tags = request.query.get('tags', '').split(',')
|
filters = {}
|
||||||
tags = [tag.strip() for tag in tags if tag.strip()]
|
if base_models:
|
||||||
|
filters['base_model'] = base_models.split(',')
|
||||||
|
if tags:
|
||||||
|
filters['tags'] = tags.split(',')
|
||||||
|
|
||||||
# Get paginated data with search and filters
|
# Add search options to filters
|
||||||
result = await self.scanner.get_paginated_data(
|
search_options = {
|
||||||
page=page,
|
'filename': search_filename,
|
||||||
page_size=page_size,
|
'modelname': search_modelname,
|
||||||
sort_by=sort_by,
|
'tags': search_tags,
|
||||||
|
'recursive': recursive
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add lora hash filtering options
|
||||||
|
hash_filters = {}
|
||||||
|
if lora_hash:
|
||||||
|
hash_filters['single_hash'] = lora_hash.lower()
|
||||||
|
elif lora_hashes:
|
||||||
|
hash_filters['multiple_hashes'] = [h.lower() for h in lora_hashes.split(',')]
|
||||||
|
|
||||||
|
# Get file data
|
||||||
|
data = await self.scanner.get_paginated_data(
|
||||||
|
page,
|
||||||
|
page_size,
|
||||||
|
sort_by=sort_by,
|
||||||
folder=folder,
|
folder=folder,
|
||||||
search=search,
|
search=search,
|
||||||
fuzzy=fuzzy,
|
fuzzy_search=fuzzy_search,
|
||||||
base_models=base_models, # Pass base models filter
|
base_models=filters.get('base_model', None),
|
||||||
tags=tags, # Add tags parameter
|
tags=filters.get('tags', None),
|
||||||
search_options={
|
search_options=search_options,
|
||||||
'filename': search_filename,
|
hash_filters=hash_filters
|
||||||
'modelname': search_modelname,
|
|
||||||
'tags': search_tags,
|
|
||||||
'recursive': recursive
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Format the response data
|
|
||||||
formatted_items = [
|
|
||||||
self._format_lora_response(item)
|
|
||||||
for item in result['items']
|
|
||||||
]
|
|
||||||
|
|
||||||
# Get all available folders from cache
|
# Get all available folders from cache
|
||||||
cache = await self.scanner.get_cached_data()
|
cache = await self.scanner.get_cached_data()
|
||||||
|
|
||||||
return web.json_response({
|
# Convert output to match expected format
|
||||||
'items': formatted_items,
|
result = {
|
||||||
'total': result['total'],
|
'items': [self._format_lora_response(lora) for lora in data['items']],
|
||||||
'page': result['page'],
|
'folders': cache.folders,
|
||||||
'page_size': result['page_size'],
|
'total': data['total'],
|
||||||
'total_pages': result['total_pages'],
|
'page': data['page'],
|
||||||
'folders': cache.folders
|
'page_size': data['page_size'],
|
||||||
})
|
'total_pages': data['total_pages']
|
||||||
|
}
|
||||||
|
|
||||||
|
return web.json_response(result)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in get_loras: {str(e)}", exc_info=True)
|
logger.error(f"Error retrieving loras: {e}", exc_info=True)
|
||||||
return web.json_response({
|
return web.json_response({"error": str(e)}, status=500)
|
||||||
'error': 'Internal server error'
|
|
||||||
}, status=500)
|
|
||||||
|
|
||||||
def _format_lora_response(self, lora: Dict) -> Dict:
|
def _format_lora_response(self, lora: Dict) -> Dict:
|
||||||
"""Format LoRA data for API response"""
|
"""Format LoRA data for API response"""
|
||||||
@@ -831,7 +833,10 @@ class ApiRoutes:
|
|||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error getting lora Civitai URL: {e}", exc_info=True)
|
logger.error(f"Error getting lora Civitai URL: {e}", exc_info=True)
|
||||||
return web.Response(text=str(e), status=500)
|
return web.json_response({
|
||||||
|
'success': False,
|
||||||
|
'error': str(e)
|
||||||
|
}, status=500)
|
||||||
|
|
||||||
async def move_models_bulk(self, request: web.Request) -> web.Response:
|
async def move_models_bulk(self, request: web.Request) -> web.Response:
|
||||||
"""Handle bulk model move request"""
|
"""Handle bulk model move request"""
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ class RecipeRoutes:
|
|||||||
|
|
||||||
# New parameter: get LoRA hash filter
|
# New parameter: get LoRA hash filter
|
||||||
lora_hash = request.query.get('lora_hash', None)
|
lora_hash = request.query.get('lora_hash', None)
|
||||||
bypass_filters = request.query.get('bypass_filters', 'false').lower() == 'true'
|
|
||||||
|
|
||||||
# Parse filter parameters
|
# Parse filter parameters
|
||||||
filters = {}
|
filters = {}
|
||||||
@@ -128,8 +127,7 @@ class RecipeRoutes:
|
|||||||
search=search,
|
search=search,
|
||||||
filters=filters,
|
filters=filters,
|
||||||
search_options=search_options,
|
search_options=search_options,
|
||||||
lora_hash=lora_hash,
|
lora_hash=lora_hash
|
||||||
bypass_filters=bypass_filters
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Format the response data with static URLs for file paths
|
# Format the response data with static URLs for file paths
|
||||||
|
|||||||
@@ -136,9 +136,9 @@ class LoraScanner:
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def get_paginated_data(self, page: int, page_size: int, sort_by: str = 'name',
|
async def get_paginated_data(self, page: int, page_size: int, sort_by: str = 'name',
|
||||||
folder: str = None, search: str = None, fuzzy: bool = False,
|
folder: str = None, search: str = None, fuzzy_search: bool = False,
|
||||||
base_models: list = None, tags: list = None,
|
base_models: list = None, tags: list = None,
|
||||||
search_options: dict = None) -> Dict:
|
search_options: dict = None, hash_filters: dict = None) -> Dict:
|
||||||
"""Get paginated and filtered lora data
|
"""Get paginated and filtered lora data
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -147,10 +147,11 @@ class LoraScanner:
|
|||||||
sort_by: Sort method ('name' or 'date')
|
sort_by: Sort method ('name' or 'date')
|
||||||
folder: Filter by folder path
|
folder: Filter by folder path
|
||||||
search: Search term
|
search: Search term
|
||||||
fuzzy: Use fuzzy matching for search
|
fuzzy_search: Use fuzzy matching for search
|
||||||
base_models: List of base models to filter by
|
base_models: List of base models to filter by
|
||||||
tags: List of tags to filter by
|
tags: List of tags to filter by
|
||||||
search_options: Dictionary with search options (filename, modelname, tags, recursive)
|
search_options: Dictionary with search options (filename, modelname, tags, recursive)
|
||||||
|
hash_filters: Dictionary with hash filtering options (single_hash or multiple_hashes)
|
||||||
"""
|
"""
|
||||||
cache = await self.get_cached_data()
|
cache = await self.get_cached_data()
|
||||||
|
|
||||||
@@ -160,90 +161,108 @@ class LoraScanner:
|
|||||||
'filename': True,
|
'filename': True,
|
||||||
'modelname': True,
|
'modelname': True,
|
||||||
'tags': False,
|
'tags': False,
|
||||||
'recursive': False
|
'recursive': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get the base data set
|
# Get the base data set
|
||||||
filtered_data = cache.sorted_by_date if sort_by == 'date' else cache.sorted_by_name
|
filtered_data = cache.sorted_by_date if sort_by == 'date' else cache.sorted_by_name
|
||||||
|
|
||||||
|
# Apply hash filtering if provided (highest priority)
|
||||||
|
if hash_filters:
|
||||||
|
single_hash = hash_filters.get('single_hash')
|
||||||
|
multiple_hashes = hash_filters.get('multiple_hashes')
|
||||||
|
|
||||||
|
if single_hash:
|
||||||
|
# Filter by single hash
|
||||||
|
single_hash = single_hash.lower() # Ensure lowercase for matching
|
||||||
|
filtered_data = [
|
||||||
|
lora for lora in filtered_data
|
||||||
|
if lora.get('sha256', '').lower() == single_hash
|
||||||
|
]
|
||||||
|
elif multiple_hashes:
|
||||||
|
# Filter by multiple hashes
|
||||||
|
hash_set = set(hash.lower() for hash in multiple_hashes) # Convert to set for faster lookup
|
||||||
|
filtered_data = [
|
||||||
|
lora for lora in filtered_data
|
||||||
|
if lora.get('sha256', '').lower() in hash_set
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Jump to pagination
|
||||||
|
total_items = len(filtered_data)
|
||||||
|
start_idx = (page - 1) * page_size
|
||||||
|
end_idx = min(start_idx + page_size, total_items)
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'items': filtered_data[start_idx:end_idx],
|
||||||
|
'total': total_items,
|
||||||
|
'page': page,
|
||||||
|
'page_size': page_size,
|
||||||
|
'total_pages': (total_items + page_size - 1) // page_size
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
# Apply SFW filtering if enabled
|
# Apply SFW filtering if enabled
|
||||||
if settings.get('show_only_sfw', False):
|
if settings.get('show_only_sfw', False):
|
||||||
filtered_data = [
|
filtered_data = [
|
||||||
item for item in filtered_data
|
lora for lora in filtered_data
|
||||||
if not item.get('preview_nsfw_level') or item.get('preview_nsfw_level') < NSFW_LEVELS['R']
|
if not lora.get('preview_nsfw_level') or lora.get('preview_nsfw_level') < NSFW_LEVELS['NSFW']
|
||||||
]
|
]
|
||||||
|
|
||||||
# Apply folder filtering
|
# Apply folder filtering
|
||||||
if folder is not None:
|
if folder is not None:
|
||||||
if search_options.get('recursive', False):
|
if search_options.get('recursive', False):
|
||||||
# Recursive mode: match all paths starting with this folder
|
# Recursive folder filtering - include all subfolders
|
||||||
filtered_data = [
|
filtered_data = [
|
||||||
item for item in filtered_data
|
lora for lora in filtered_data
|
||||||
if item['folder'].startswith(folder + '/') or item['folder'] == folder
|
if lora['folder'].startswith(folder)
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
# Non-recursive mode: match exact folder
|
# Exact folder filtering
|
||||||
filtered_data = [
|
filtered_data = [
|
||||||
item for item in filtered_data
|
lora for lora in filtered_data
|
||||||
if item['folder'] == folder
|
if lora['folder'] == folder
|
||||||
]
|
]
|
||||||
|
|
||||||
# Apply base model filtering
|
# Apply base model filtering
|
||||||
if base_models and len(base_models) > 0:
|
if base_models and len(base_models) > 0:
|
||||||
filtered_data = [
|
filtered_data = [
|
||||||
item for item in filtered_data
|
lora for lora in filtered_data
|
||||||
if item.get('base_model') in base_models
|
if lora.get('base_model') in base_models
|
||||||
]
|
]
|
||||||
|
|
||||||
# Apply tag filtering
|
# Apply tag filtering
|
||||||
if tags and len(tags) > 0:
|
if tags and len(tags) > 0:
|
||||||
filtered_data = [
|
filtered_data = [
|
||||||
item for item in filtered_data
|
lora for lora in filtered_data
|
||||||
if any(tag in item.get('tags', []) for tag in tags)
|
if any(tag in lora.get('tags', []) for tag in tags)
|
||||||
]
|
]
|
||||||
|
|
||||||
# Apply search filtering
|
# Apply search filtering
|
||||||
if search:
|
if search:
|
||||||
search_results = []
|
search_results = []
|
||||||
for item in filtered_data:
|
search_opts = search_options or {}
|
||||||
# Check filename if enabled
|
|
||||||
if search_options.get('filename', True):
|
for lora in filtered_data:
|
||||||
if fuzzy:
|
# Search by file name
|
||||||
if fuzzy_match(item.get('file_name', ''), search):
|
if search_opts.get('filename', True):
|
||||||
search_results.append(item)
|
if fuzzy_match(lora.get('file_name', ''), search):
|
||||||
continue
|
search_results.append(lora)
|
||||||
else:
|
|
||||||
if search.lower() in item.get('file_name', '').lower():
|
|
||||||
search_results.append(item)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Check model name if enabled
|
|
||||||
if search_options.get('modelname', True):
|
|
||||||
if fuzzy:
|
|
||||||
if fuzzy_match(item.get('model_name', ''), search):
|
|
||||||
search_results.append(item)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
if search.lower() in item.get('model_name', '').lower():
|
|
||||||
search_results.append(item)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Check tags if enabled
|
|
||||||
if search_options.get('tags', False) and item.get('tags'):
|
|
||||||
found_tag = False
|
|
||||||
for tag in item['tags']:
|
|
||||||
if fuzzy:
|
|
||||||
if fuzzy_match(tag, search):
|
|
||||||
found_tag = True
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
if search.lower() in tag.lower():
|
|
||||||
found_tag = True
|
|
||||||
break
|
|
||||||
if found_tag:
|
|
||||||
search_results.append(item)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
# Search by model name
|
||||||
|
if search_opts.get('modelname', True):
|
||||||
|
if fuzzy_match(lora.get('model_name', ''), search):
|
||||||
|
search_results.append(lora)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Search by tags
|
||||||
|
if search_opts.get('tags', False) and 'tags' in lora:
|
||||||
|
if any(fuzzy_match(tag, search) for tag in lora['tags']):
|
||||||
|
search_results.append(lora)
|
||||||
|
continue
|
||||||
|
|
||||||
filtered_data = search_results
|
filtered_data = search_results
|
||||||
|
|
||||||
# Calculate pagination
|
# Calculate pagination
|
||||||
|
|||||||
@@ -330,7 +330,7 @@ class RecipeScanner:
|
|||||||
logger.error(f"Error getting base model for lora: {e}")
|
logger.error(f"Error getting base model for lora: {e}")
|
||||||
return None
|
return 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, lora_hash: str = None, bypass_filters: bool = False):
|
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, lora_hash: str = None, bypass_filters: bool = True):
|
||||||
"""Get paginated and filtered recipe data
|
"""Get paginated and filtered recipe data
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -400,6 +400,27 @@
|
|||||||
gap: var(--space-1);
|
gap: var(--space-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* View LoRAs button */
|
||||||
|
.view-loras-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-color);
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: var(--border-radius-xs);
|
||||||
|
transition: all 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.view-loras-btn:hover {
|
||||||
|
opacity: 1;
|
||||||
|
background: var(--lora-surface);
|
||||||
|
color: var(--lora-accent);
|
||||||
|
}
|
||||||
|
|
||||||
#recipeLorasCount {
|
#recipeLorasCount {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
@@ -433,6 +454,14 @@
|
|||||||
will-change: transform;
|
will-change: transform;
|
||||||
/* Create a new containing block for absolutely positioned descendants */
|
/* Create a new containing block for absolutely positioned descendants */
|
||||||
transform: translateZ(0);
|
transform: translateZ(0);
|
||||||
|
cursor: pointer; /* Make it clear the item is clickable */
|
||||||
|
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.recipe-lora-item:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
border-color: var(--lora-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.recipe-lora-item.exists-locally {
|
.recipe-lora-item.exists-locally {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { createLoraCard } from '../components/LoraCard.js';
|
|||||||
import { initializeInfiniteScroll } from '../utils/infiniteScroll.js';
|
import { initializeInfiniteScroll } from '../utils/infiniteScroll.js';
|
||||||
import { showDeleteModal } from '../utils/modalUtils.js';
|
import { showDeleteModal } from '../utils/modalUtils.js';
|
||||||
import { toggleFolder } from '../utils/uiHelpers.js';
|
import { toggleFolder } from '../utils/uiHelpers.js';
|
||||||
|
import { getSessionItem } from '../utils/storageHelpers.js';
|
||||||
|
|
||||||
export async function loadMoreLoras(resetPage = false, updateFolders = false) {
|
export async function loadMoreLoras(resetPage = false, updateFolders = false) {
|
||||||
const pageState = getCurrentPageState();
|
const pageState = getCurrentPageState();
|
||||||
@@ -57,6 +58,28 @@ export async function loadMoreLoras(resetPage = false, updateFolders = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for recipe-based filtering parameters from session storage
|
||||||
|
const filterLoraHash = getSessionItem('recipe_to_lora_filterLoraHash');
|
||||||
|
const filterLoraHashes = getSessionItem('recipe_to_lora_filterLoraHashes');
|
||||||
|
|
||||||
|
console.log('Filter Lora Hash:', filterLoraHash);
|
||||||
|
console.log('Filter Lora Hashes:', filterLoraHashes);
|
||||||
|
|
||||||
|
// Add hash filter parameter if present
|
||||||
|
if (filterLoraHash) {
|
||||||
|
params.append('lora_hash', filterLoraHash);
|
||||||
|
}
|
||||||
|
// Add multiple hashes filter if present
|
||||||
|
else if (filterLoraHashes) {
|
||||||
|
try {
|
||||||
|
if (Array.isArray(filterLoraHashes) && filterLoraHashes.length > 0) {
|
||||||
|
params.append('lora_hashes', filterLoraHashes.join(','));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error parsing lora hashes from session storage:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const response = await fetch(`/api/loras?${params}`);
|
const response = await fetch(`/api/loras?${params}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to fetch loras: ${response.statusText}`);
|
throw new Error(`Failed to fetch loras: ${response.statusText}`);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Recipe Modal Component
|
// Recipe Modal Component
|
||||||
import { showToast } from '../utils/uiHelpers.js';
|
import { showToast } from '../utils/uiHelpers.js';
|
||||||
import { state } from '../state/index.js';
|
import { state } from '../state/index.js';
|
||||||
|
import { setSessionItem, removeSessionItem } from '../utils/storageHelpers.js';
|
||||||
|
|
||||||
class RecipeModal {
|
class RecipeModal {
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -294,7 +295,7 @@ class RecipeModal {
|
|||||||
} else {
|
} else {
|
||||||
// No generation parameters available
|
// No generation parameters available
|
||||||
if (promptElement) promptElement.textContent = 'No prompt information available';
|
if (promptElement) promptElement.textContent = 'No prompt information available';
|
||||||
if (negativePromptElement) negativePromptElement.textContent = 'No negative prompt information available';
|
if (negativePromptElement) promptElement.textContent = 'No negative prompt information available';
|
||||||
if (otherParamsElement) otherParamsElement.innerHTML = '<div class="no-params">No parameters available</div>';
|
if (otherParamsElement) otherParamsElement.innerHTML = '<div class="no-params">No parameters available</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,8 +343,15 @@ class RecipeModal {
|
|||||||
|
|
||||||
lorasCountElement.innerHTML = `<i class="fas fa-layer-group"></i> ${totalCount} LoRAs ${statusHTML}`;
|
lorasCountElement.innerHTML = `<i class="fas fa-layer-group"></i> ${totalCount} LoRAs ${statusHTML}`;
|
||||||
|
|
||||||
// Add click handler for missing LoRAs status
|
// Add event listeners for buttons and status indicators
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
// Set up click handler for View LoRAs button
|
||||||
|
const viewRecipeLorasBtn = document.getElementById('viewRecipeLorasBtn');
|
||||||
|
if (viewRecipeLorasBtn) {
|
||||||
|
viewRecipeLorasBtn.addEventListener('click', () => this.navigateToLorasPage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add click handler for missing LoRAs status
|
||||||
const missingStatus = document.querySelector('.recipe-status.missing');
|
const missingStatus = document.querySelector('.recipe-status.missing');
|
||||||
if (missingStatus && missingLorasCount > 0) {
|
if (missingStatus && missingLorasCount > 0) {
|
||||||
missingStatus.classList.add('clickable');
|
missingStatus.classList.add('clickable');
|
||||||
@@ -433,6 +441,7 @@ class RecipeModal {
|
|||||||
// Add event listeners for reconnect functionality
|
// Add event listeners for reconnect functionality
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.setupReconnectButtons();
|
this.setupReconnectButtons();
|
||||||
|
this.setupLoraItemsClickable();
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
// Generate recipe syntax for copy button (this is now a placeholder, actual syntax will be fetched from the API)
|
// Generate recipe syntax for copy button (this is now a placeholder, actual syntax will be fetched from the API)
|
||||||
@@ -1007,6 +1016,65 @@ class RecipeModal {
|
|||||||
state.loadingManager.hide();
|
state.loadingManager.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New method to navigate to the LoRAs page
|
||||||
|
navigateToLorasPage(specificLoraIndex = null) {
|
||||||
|
// Close the current modal
|
||||||
|
modalManager.closeModal('recipeModal');
|
||||||
|
|
||||||
|
// Clear any previous filters first
|
||||||
|
removeSessionItem('recipe_to_lora_filterLoraHash');
|
||||||
|
removeSessionItem('recipe_to_lora_filterLoraHashes');
|
||||||
|
removeSessionItem('filterRecipeName');
|
||||||
|
removeSessionItem('viewLoraDetail');
|
||||||
|
|
||||||
|
if (specificLoraIndex !== null) {
|
||||||
|
// If a specific LoRA index is provided, navigate to view just that one LoRA
|
||||||
|
const lora = this.currentRecipe.loras[specificLoraIndex];
|
||||||
|
if (lora && lora.hash) {
|
||||||
|
// Set session storage to open the LoRA modal directly
|
||||||
|
setSessionItem('recipe_to_lora_filterLoraHash', lora.hash.toLowerCase());
|
||||||
|
setSessionItem('viewLoraDetail', 'true');
|
||||||
|
setSessionItem('filterRecipeName', this.currentRecipe.title);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If no specific LoRA index is provided, show all LoRAs from this recipe
|
||||||
|
// Collect all hashes from the recipe's LoRAs
|
||||||
|
const loraHashes = this.currentRecipe.loras
|
||||||
|
.filter(lora => lora.hash)
|
||||||
|
.map(lora => lora.hash.toLowerCase());
|
||||||
|
|
||||||
|
if (loraHashes.length > 0) {
|
||||||
|
// Store the LoRA hashes and recipe name in sessionStorage
|
||||||
|
setSessionItem('recipe_to_lora_filterLoraHashes', JSON.stringify(loraHashes));
|
||||||
|
setSessionItem('filterRecipeName', this.currentRecipe.title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate to the LoRAs page
|
||||||
|
window.location.href = '/loras';
|
||||||
|
}
|
||||||
|
|
||||||
|
// New method to make LoRA items clickable
|
||||||
|
setupLoraItemsClickable() {
|
||||||
|
const loraItems = document.querySelectorAll('.recipe-lora-item');
|
||||||
|
loraItems.forEach(item => {
|
||||||
|
// Get the lora index from the data attribute
|
||||||
|
const loraIndex = parseInt(item.dataset.loraIndex);
|
||||||
|
|
||||||
|
item.addEventListener('click', (e) => {
|
||||||
|
// If the click is on the reconnect container or badge, don't navigate
|
||||||
|
if (e.target.closest('.lora-reconnect-container') ||
|
||||||
|
e.target.closest('.deleted-badge') ||
|
||||||
|
e.target.closest('.reconnect-tooltip')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Navigate to the LoRAs page with the specific LoRA index
|
||||||
|
this.navigateToLorasPage(loraIndex);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { RecipeModal };
|
export { RecipeModal };
|
||||||
@@ -192,29 +192,20 @@ function copyRecipeSyntax(recipeId) {
|
|||||||
* @param {string} loraHash - The hash of the Lora to filter by
|
* @param {string} loraHash - The hash of the Lora to filter by
|
||||||
* @param {boolean} createNew - Whether to open the create recipe dialog
|
* @param {boolean} createNew - Whether to open the create recipe dialog
|
||||||
*/
|
*/
|
||||||
function navigateToRecipesPage(loraName, loraHash, createNew = false) {
|
function navigateToRecipesPage(loraName, loraHash) {
|
||||||
// Close the current modal
|
// Close the current modal
|
||||||
if (window.modalManager) {
|
if (window.modalManager) {
|
||||||
modalManager.closeModal('loraModal');
|
modalManager.closeModal('loraModal');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear any previous filters first
|
// Clear any previous filters first
|
||||||
removeSessionItem('filterLoraName');
|
removeSessionItem('lora_to_recipe_filterLoraName');
|
||||||
removeSessionItem('filterLoraHash');
|
removeSessionItem('lora_to_recipe_filterLoraHash');
|
||||||
removeSessionItem('bypassExistingFilters');
|
|
||||||
removeSessionItem('viewRecipeId');
|
removeSessionItem('viewRecipeId');
|
||||||
|
|
||||||
// Store the LoRA name and hash filter in sessionStorage
|
// Store the LoRA name and hash filter in sessionStorage
|
||||||
setSessionItem('filterLoraName', loraName);
|
setSessionItem('lora_to_recipe_filterLoraName', loraName);
|
||||||
setSessionItem('filterLoraHash', loraHash);
|
setSessionItem('lora_to_recipe_filterLoraHash', loraHash);
|
||||||
|
|
||||||
// Set a flag to indicate we're navigating with a specific filter
|
|
||||||
setSessionItem('bypassExistingFilters', 'true');
|
|
||||||
|
|
||||||
// Set flag to open create dialog if requested
|
|
||||||
if (createNew) {
|
|
||||||
setSessionItem('openCreateRecipeDialog', 'true');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Directly navigate to recipes page
|
// Directly navigate to recipes page
|
||||||
window.location.href = '/loras/recipes';
|
window.location.href = '/loras/recipes';
|
||||||
@@ -233,7 +224,6 @@ function navigateToRecipeDetails(recipeId) {
|
|||||||
// Clear any previous filters first
|
// Clear any previous filters first
|
||||||
removeSessionItem('filterLoraName');
|
removeSessionItem('filterLoraName');
|
||||||
removeSessionItem('filterLoraHash');
|
removeSessionItem('filterLoraHash');
|
||||||
removeSessionItem('bypassExistingFilters');
|
|
||||||
removeSessionItem('viewRecipeId');
|
removeSessionItem('viewRecipeId');
|
||||||
|
|
||||||
// Store the recipe ID in sessionStorage to load on recipes page
|
// Store the recipe ID in sessionStorage to load on recipes page
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import { LoraContextMenu } from './components/ContextMenu.js';
|
|||||||
import { moveManager } from './managers/MoveManager.js';
|
import { moveManager } from './managers/MoveManager.js';
|
||||||
import { updateCardsForBulkMode } from './components/LoraCard.js';
|
import { updateCardsForBulkMode } from './components/LoraCard.js';
|
||||||
import { bulkManager } from './managers/BulkManager.js';
|
import { bulkManager } from './managers/BulkManager.js';
|
||||||
import { setStorageItem, getStorageItem } from './utils/storageHelpers.js';
|
import { setStorageItem, getStorageItem, getSessionItem, removeSessionItem } from './utils/storageHelpers.js';
|
||||||
|
|
||||||
// Initialize the LoRA page
|
// Initialize the LoRA page
|
||||||
class LoraPageManager {
|
class LoraPageManager {
|
||||||
@@ -69,6 +69,9 @@ class LoraPageManager {
|
|||||||
initFolderTagsVisibility();
|
initFolderTagsVisibility();
|
||||||
new LoraContextMenu();
|
new LoraContextMenu();
|
||||||
|
|
||||||
|
// Check for custom filters from recipe page navigation
|
||||||
|
this.checkCustomFilters();
|
||||||
|
|
||||||
// Initialize cards for current bulk mode state (should be false initially)
|
// Initialize cards for current bulk mode state (should be false initially)
|
||||||
updateCardsForBulkMode(state.bulkMode);
|
updateCardsForBulkMode(state.bulkMode);
|
||||||
|
|
||||||
@@ -79,6 +82,80 @@ class LoraPageManager {
|
|||||||
appCore.initializePageFeatures();
|
appCore.initializePageFeatures();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for custom filter parameters in session storage
|
||||||
|
checkCustomFilters() {
|
||||||
|
const filterLoraHash = getSessionItem('recipe_to_lora_filterLoraHash');
|
||||||
|
const filterLoraHashes = getSessionItem('recipe_to_lora_filterLoraHashes');
|
||||||
|
const filterRecipeName = getSessionItem('filterRecipeName');
|
||||||
|
const viewLoraDetail = getSessionItem('viewLoraDetail');
|
||||||
|
|
||||||
|
console.log("Checking custom filters...");
|
||||||
|
console.log("filterLoraHash:", filterLoraHash);
|
||||||
|
console.log("filterLoraHashes:", filterLoraHashes);
|
||||||
|
console.log("filterRecipeName:", filterRecipeName);
|
||||||
|
console.log("viewLoraDetail:", viewLoraDetail);
|
||||||
|
|
||||||
|
if ((filterLoraHash || filterLoraHashes) && filterRecipeName) {
|
||||||
|
// Found custom filter parameters, set up the custom filter
|
||||||
|
|
||||||
|
// Show the filter indicator
|
||||||
|
const indicator = document.getElementById('customFilterIndicator');
|
||||||
|
const filterText = indicator.querySelector('.customFilterText');
|
||||||
|
|
||||||
|
if (indicator && filterText) {
|
||||||
|
indicator.classList.remove('hidden');
|
||||||
|
|
||||||
|
// Set text content with recipe name
|
||||||
|
const filterType = filterLoraHash && viewLoraDetail ? "Viewing LoRA from" : "Viewing LoRAs from";
|
||||||
|
const displayText = `${filterType}: ${filterRecipeName}`;
|
||||||
|
|
||||||
|
filterText.textContent = this._truncateText(displayText, 30);
|
||||||
|
filterText.setAttribute('title', displayText);
|
||||||
|
|
||||||
|
// Add click handler for the clear button
|
||||||
|
const clearBtn = indicator.querySelector('.clear-filter');
|
||||||
|
if (clearBtn) {
|
||||||
|
clearBtn.addEventListener('click', this.clearCustomFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're viewing a specific LoRA detail, set up to open the modal
|
||||||
|
if (filterLoraHash && viewLoraDetail) {
|
||||||
|
// Store this to fetch after initial load completes
|
||||||
|
state.pendingLoraHash = filterLoraHash;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to truncate text with ellipsis
|
||||||
|
_truncateText(text, maxLength) {
|
||||||
|
return text.length > maxLength ? text.substring(0, maxLength - 3) + '...' : text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the custom filter and reload the page
|
||||||
|
clearCustomFilter = async () => {
|
||||||
|
console.log("Clearing custom filter...");
|
||||||
|
// Remove filter parameters from session storage
|
||||||
|
removeSessionItem('recipe_to_lora_filterLoraHash');
|
||||||
|
removeSessionItem('recipe_to_lora_filterLoraHashes');
|
||||||
|
removeSessionItem('filterRecipeName');
|
||||||
|
removeSessionItem('viewLoraDetail');
|
||||||
|
|
||||||
|
// Hide the filter indicator
|
||||||
|
const indicator = document.getElementById('customFilterIndicator');
|
||||||
|
if (indicator) {
|
||||||
|
indicator.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset state
|
||||||
|
if (state.pendingLoraHash) {
|
||||||
|
delete state.pendingLoraHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload the loras
|
||||||
|
await resetAndReload();
|
||||||
|
}
|
||||||
|
|
||||||
loadSortPreference() {
|
loadSortPreference() {
|
||||||
const savedSort = getStorageItem('loras_sort');
|
const savedSort = getStorageItem('loras_sort');
|
||||||
if (savedSort) {
|
if (savedSort) {
|
||||||
|
|||||||
@@ -71,18 +71,15 @@ class RecipeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_checkCustomFilter() {
|
_checkCustomFilter() {
|
||||||
// Check for bypass filter flag
|
|
||||||
const bypassExistingFilters = getSessionItem('bypassExistingFilters');
|
|
||||||
|
|
||||||
// Check for Lora filter
|
// Check for Lora filter
|
||||||
const filterLoraName = getSessionItem('filterLoraName');
|
const filterLoraName = getSessionItem('lora_to_recipe_filterLoraName');
|
||||||
const filterLoraHash = getSessionItem('filterLoraHash');
|
const filterLoraHash = getSessionItem('lora_to_recipe_filterLoraHash');
|
||||||
|
|
||||||
// Check for specific recipe ID
|
// Check for specific recipe ID
|
||||||
const viewRecipeId = getSessionItem('viewRecipeId');
|
const viewRecipeId = getSessionItem('viewRecipeId');
|
||||||
|
|
||||||
// Set custom filter if any parameter is present
|
// Set custom filter if any parameter is present
|
||||||
if (bypassExistingFilters || filterLoraName || filterLoraHash || viewRecipeId) {
|
if (filterLoraName || filterLoraHash || viewRecipeId) {
|
||||||
this.customFilter = {
|
this.customFilter = {
|
||||||
active: true,
|
active: true,
|
||||||
loraName: filterLoraName,
|
loraName: filterLoraName,
|
||||||
@@ -90,26 +87,9 @@ class RecipeManager {
|
|||||||
recipeId: viewRecipeId
|
recipeId: viewRecipeId
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clean up session storage after reading
|
|
||||||
removeSessionItem('bypassExistingFilters');
|
|
||||||
|
|
||||||
// Show custom filter indicator
|
// Show custom filter indicator
|
||||||
this._showCustomFilterIndicator();
|
this._showCustomFilterIndicator();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for create recipe dialog flag
|
|
||||||
const openCreateRecipeDialog = getSessionItem('openCreateRecipeDialog');
|
|
||||||
if (openCreateRecipeDialog) {
|
|
||||||
// Clean up session storage
|
|
||||||
removeSessionItem('openCreateRecipeDialog');
|
|
||||||
|
|
||||||
// Schedule showing the create dialog after the page loads
|
|
||||||
setTimeout(() => {
|
|
||||||
if (this.importManager && typeof this.importManager.showImportModal === 'function') {
|
|
||||||
this.importManager.showImportModal();
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_showCustomFilterIndicator() {
|
_showCustomFilterIndicator() {
|
||||||
@@ -176,8 +156,8 @@ class RecipeManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Clear any session storage items
|
// Clear any session storage items
|
||||||
removeSessionItem('filterLoraName');
|
removeSessionItem('lora_to_recipe_filterLoraName');
|
||||||
removeSessionItem('filterLoraHash');
|
removeSessionItem('lora_to_recipe_filterLoraHash');
|
||||||
removeSessionItem('viewRecipeId');
|
removeSessionItem('viewRecipeId');
|
||||||
|
|
||||||
// Reload recipes without custom filter
|
// Reload recipes without custom filter
|
||||||
@@ -366,8 +346,4 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Export for use in other modules
|
// Export for use in other modules
|
||||||
export { RecipeManager };
|
export { RecipeManager };
|
||||||
|
|
||||||
// The RecipesManager class from the original file is preserved below (commented out)
|
|
||||||
// If needed, functionality can be migrated to the new RecipeManager class above
|
|
||||||
// ...rest of the existing code...
|
|
||||||
@@ -31,6 +31,12 @@
|
|||||||
<i class="fas fa-th-large"></i> Bulk
|
<i class="fas fa-th-large"></i> Bulk
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="customFilterIndicator" class="control-group hidden">
|
||||||
|
<div class="filter-active">
|
||||||
|
<i class="fas fa-filter"></i> <span class="customFilterText" title=""></span>
|
||||||
|
<i class="fas fa-times-circle clear-filter"></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="toggle-folders-container">
|
<div class="toggle-folders-container">
|
||||||
<button class="toggle-folders-btn icon-only" onclick="toggleFolderTags()" title="Toggle folder tags">
|
<button class="toggle-folders-btn icon-only" onclick="toggleFolderTags()" title="Toggle folder tags">
|
||||||
|
|||||||
@@ -58,6 +58,9 @@
|
|||||||
<h3>Resources</h3>
|
<h3>Resources</h3>
|
||||||
<div class="recipe-section-actions">
|
<div class="recipe-section-actions">
|
||||||
<span id="recipeLorasCount"><i class="fas fa-layer-group"></i> 0 LoRAs</span>
|
<span id="recipeLorasCount"><i class="fas fa-layer-group"></i> 0 LoRAs</span>
|
||||||
|
<button class="action-btn view-loras-btn" id="viewRecipeLorasBtn" title="View all LoRAs in this recipe">
|
||||||
|
<i class="fas fa-external-link-alt"></i>
|
||||||
|
</button>
|
||||||
<button class="copy-btn" id="copyRecipeSyntaxBtn" title="Copy Recipe Syntax">
|
<button class="copy-btn" id="copyRecipeSyntaxBtn" title="Copy Recipe Syntax">
|
||||||
<i class="fas fa-copy"></i>
|
<i class="fas fa-copy"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user