mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 22:52:12 -03:00
Add recursive search option for folder filtering and enhance search UI
This commit is contained in:
@@ -109,6 +109,7 @@ class ApiRoutes:
|
|||||||
folder = request.query.get('folder')
|
folder = request.query.get('folder')
|
||||||
search = request.query.get('search', '').lower()
|
search = request.query.get('search', '').lower()
|
||||||
fuzzy = request.query.get('fuzzy', 'false').lower() == 'true'
|
fuzzy = request.query.get('fuzzy', 'false').lower() == 'true'
|
||||||
|
recursive = request.query.get('recursive', 'false').lower() == 'true'
|
||||||
|
|
||||||
# Validate parameters
|
# Validate parameters
|
||||||
if page < 1 or page_size < 1 or page_size > 100:
|
if page < 1 or page_size < 1 or page_size > 100:
|
||||||
@@ -128,7 +129,8 @@ class ApiRoutes:
|
|||||||
sort_by=sort_by,
|
sort_by=sort_by,
|
||||||
folder=folder,
|
folder=folder,
|
||||||
search=search,
|
search=search,
|
||||||
fuzzy=fuzzy
|
fuzzy=fuzzy,
|
||||||
|
recursive=recursive # 添加递归参数
|
||||||
)
|
)
|
||||||
|
|
||||||
# Format the response data
|
# Format the response data
|
||||||
|
|||||||
@@ -129,7 +129,19 @@ class LoraScanner:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
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: bool = False,
|
||||||
|
recursive: bool = False):
|
||||||
|
"""Get paginated and filtered lora data
|
||||||
|
|
||||||
|
Args:
|
||||||
|
page: Current page number (1-based)
|
||||||
|
page_size: Number of items per page
|
||||||
|
sort_by: Sort method ('name' or 'date')
|
||||||
|
folder: Filter by folder path
|
||||||
|
search: Search term
|
||||||
|
fuzzy: Use fuzzy matching for search
|
||||||
|
recursive: Include subfolders when folder filter is applied
|
||||||
|
"""
|
||||||
cache = await self.get_cached_data()
|
cache = await self.get_cached_data()
|
||||||
|
|
||||||
# 先获取基础数据集
|
# 先获取基础数据集
|
||||||
@@ -137,9 +149,20 @@ class LoraScanner:
|
|||||||
|
|
||||||
# 应用文件夹过滤
|
# 应用文件夹过滤
|
||||||
if folder is not None:
|
if folder is not None:
|
||||||
filtered_data = [item for item in filtered_data if item['folder'] == folder]
|
if recursive:
|
||||||
|
# 递归模式:匹配所有以该文件夹开头的路径
|
||||||
|
filtered_data = [
|
||||||
|
item for item in filtered_data
|
||||||
|
if item['folder'].startswith(folder + '/') or item['folder'] == folder
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
# 非递归模式:只匹配确切的文件夹
|
||||||
|
filtered_data = [
|
||||||
|
item for item in filtered_data
|
||||||
|
if item['folder'] == folder
|
||||||
|
]
|
||||||
|
|
||||||
# 应用搜索过滤(只匹配model_name)
|
# 应用搜索过滤
|
||||||
if search:
|
if search:
|
||||||
if fuzzy:
|
if fuzzy:
|
||||||
filtered_data = [
|
filtered_data = [
|
||||||
|
|||||||
@@ -847,6 +847,9 @@ body.modal-open {
|
|||||||
width: 250px;
|
width: 250px;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
flex-shrink: 0; /* 防止搜索框被压缩 */
|
flex-shrink: 0; /* 防止搜索框被压缩 */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 调整搜索框样式以匹配其他控件 */
|
/* 调整搜索框样式以匹配其他控件 */
|
||||||
@@ -869,7 +872,7 @@ body.modal-open {
|
|||||||
|
|
||||||
.search-icon {
|
.search-icon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 12px;
|
right: calc(32px + 8px); /* 调整位置,留出toggle按钮的空间 */
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
color: oklch(var(--text-color) / 0.5);
|
color: oklch(var(--text-color) / 0.5);
|
||||||
@@ -877,6 +880,36 @@ body.modal-open {
|
|||||||
line-height: 1; /* 防止图标影响容器高度 */
|
line-height: 1; /* 防止图标影响容器高度 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.search-mode-toggle {
|
||||||
|
background: var(--lora-surface);
|
||||||
|
border: 1px solid oklch(65% 0.02 256);
|
||||||
|
border-radius: var(--border-radius-sm);
|
||||||
|
color: var(--text-color);
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-mode-toggle:hover {
|
||||||
|
background: var(--lora-accent);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-mode-toggle.active {
|
||||||
|
background: var(--lora-accent);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-mode-toggle i {
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
.corner-controls {
|
.corner-controls {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
@@ -1049,7 +1082,7 @@ body.modal-open {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.back-to-top:hover {
|
.back-to-top:hover {
|
||||||
background: var(--lora-accent);
|
background: var (--lora-accent);
|
||||||
color: white;
|
color: white;
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,12 @@ export async function loadMoreLoras() {
|
|||||||
sort_by: state.sortBy
|
sort_by: state.sortBy
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 使用 state 中的 searchManager 获取递归搜索状态
|
||||||
|
const isRecursiveSearch = state.searchManager?.isRecursiveSearch ?? false;
|
||||||
|
|
||||||
if (state.activeFolder !== null) {
|
if (state.activeFolder !== null) {
|
||||||
params.append('folder', state.activeFolder);
|
params.append('folder', state.activeFolder);
|
||||||
|
params.append('recursive', isRecursiveSearch.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add search parameters if there's a search term
|
// Add search parameters if there's a search term
|
||||||
@@ -251,4 +255,4 @@ export async function refreshLoras() {
|
|||||||
state.loadingManager.hide();
|
state.loadingManager.hide();
|
||||||
state.loadingManager.restoreProgressBar();
|
state.loadingManager.restoreProgressBar();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,5 +6,6 @@ export const state = {
|
|||||||
activeFolder: null,
|
activeFolder: null,
|
||||||
loadingManager: null,
|
loadingManager: null,
|
||||||
observer: null,
|
observer: null,
|
||||||
previewVersions: new Map()
|
previewVersions: new Map(),
|
||||||
};
|
searchManager: null // 添加 searchManager
|
||||||
|
};
|
||||||
@@ -5,14 +5,52 @@ import { showToast } from './uiHelpers.js';
|
|||||||
|
|
||||||
export class SearchManager {
|
export class SearchManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
// Initialize search manager
|
||||||
this.searchInput = document.getElementById('searchInput');
|
this.searchInput = document.getElementById('searchInput');
|
||||||
|
this.searchModeToggle = document.getElementById('searchModeToggle');
|
||||||
this.searchDebounceTimeout = null;
|
this.searchDebounceTimeout = null;
|
||||||
this.currentSearchTerm = '';
|
this.currentSearchTerm = '';
|
||||||
this.isSearching = false;
|
this.isSearching = false;
|
||||||
|
this.isRecursiveSearch = false;
|
||||||
|
|
||||||
|
// Add this instance to state
|
||||||
|
state.searchManager = this;
|
||||||
|
|
||||||
if (this.searchInput) {
|
if (this.searchInput) {
|
||||||
this.searchInput.addEventListener('input', this.handleSearch.bind(this));
|
this.searchInput.addEventListener('input', this.handleSearch.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.searchModeToggle) {
|
||||||
|
// Initialize toggle state from localStorage or default to false
|
||||||
|
this.isRecursiveSearch = localStorage.getItem('recursiveSearch') === 'true';
|
||||||
|
this.updateToggleUI();
|
||||||
|
|
||||||
|
this.searchModeToggle.addEventListener('click', () => {
|
||||||
|
this.isRecursiveSearch = !this.isRecursiveSearch;
|
||||||
|
localStorage.setItem('recursiveSearch', this.isRecursiveSearch);
|
||||||
|
this.updateToggleUI();
|
||||||
|
|
||||||
|
// Rerun search if there's an active search term
|
||||||
|
if (this.currentSearchTerm) {
|
||||||
|
this.performSearch(this.currentSearchTerm);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateToggleUI() {
|
||||||
|
if (this.searchModeToggle) {
|
||||||
|
this.searchModeToggle.classList.toggle('active', this.isRecursiveSearch);
|
||||||
|
this.searchModeToggle.title = this.isRecursiveSearch
|
||||||
|
? 'Recursive folder search (including subfolders)'
|
||||||
|
: 'Current folder search only';
|
||||||
|
|
||||||
|
// Update the icon to indicate the mode
|
||||||
|
const icon = this.searchModeToggle.querySelector('i');
|
||||||
|
if (icon) {
|
||||||
|
icon.className = this.isRecursiveSearch ? 'fas fa-folder-tree' : 'fas fa-folder';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSearch(event) {
|
handleSearch(event) {
|
||||||
@@ -53,8 +91,11 @@ export class SearchManager {
|
|||||||
url.searchParams.set('search', searchTerm);
|
url.searchParams.set('search', searchTerm);
|
||||||
url.searchParams.set('fuzzy', 'true');
|
url.searchParams.set('fuzzy', 'true');
|
||||||
|
|
||||||
|
// Always send folder parameter if there is an active folder
|
||||||
if (state.activeFolder) {
|
if (state.activeFolder) {
|
||||||
url.searchParams.set('folder', state.activeFolder);
|
url.searchParams.set('folder', state.activeFolder);
|
||||||
|
// Add recursive parameter when recursive search is enabled
|
||||||
|
url.searchParams.set('recursive', this.isRecursiveSearch.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
@@ -85,4 +126,4 @@ export class SearchManager {
|
|||||||
state.loadingManager.hide();
|
state.loadingManager.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<input type="text" id="searchInput" placeholder="Search models..." />
|
<input type="text" id="searchInput" placeholder="Search models..." />
|
||||||
|
<button class="search-mode-toggle" id="searchModeToggle" title="Toggle recursive search in folders">
|
||||||
|
<i class="fas fa-folder"></i>
|
||||||
|
</button>
|
||||||
<i class="fas fa-search search-icon"></i>
|
<i class="fas fa-search search-icon"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
Reference in New Issue
Block a user