From d2c2bfbe6a85d4f1fb13f4c7259c5c060d961d4f Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Thu, 9 Oct 2025 17:07:10 +0800 Subject: [PATCH] feat(sidebar): add recursive search functionality and toggle button --- locales/de.json | 5 +- locales/en.json | 5 +- locales/es.json | 5 +- locales/fr.json | 5 +- locales/he.json | 9 ++- locales/ja.json | 9 ++- locales/ko.json | 7 ++- locales/ru.json | 7 ++- locales/zh-CN.json | 7 ++- locales/zh-TW.json | 9 ++- static/js/components/SidebarManager.js | 71 +++++++++++++++++++++++- static/js/state/index.js | 8 +-- templates/components/folder_sidebar.html | 3 + 13 files changed, 124 insertions(+), 26 deletions(-) diff --git a/locales/de.json b/locales/de.json index fcab555a..b52eeead 100644 --- a/locales/de.json +++ b/locales/de.json @@ -529,12 +529,15 @@ "title": "Embedding-Modelle" }, "sidebar": { - "modelRoot": "Modell-Stammverzeichnis", + "modelRoot": "Stammverzeichnis", "collapseAll": "Alle Ordner einklappen", "pinSidebar": "Sidebar anheften", "unpinSidebar": "Sidebar lösen", "switchToListView": "Zur Listenansicht wechseln", "switchToTreeView": "Zur Baumansicht wechseln", + "recursiveOn": "Unterordner durchsuchen", + "recursiveOff": "Nur aktuellen Ordner durchsuchen", + "recursiveUnavailable": "Rekursive Suche ist nur in der Baumansicht verfügbar", "collapseAllDisabled": "Im Listenmodus nicht verfügbar" }, "statistics": { diff --git a/locales/en.json b/locales/en.json index 9fff5e22..49fd7004 100644 --- a/locales/en.json +++ b/locales/en.json @@ -529,12 +529,15 @@ "title": "Embedding Models" }, "sidebar": { - "modelRoot": "Model Root", + "modelRoot": "Root", "collapseAll": "Collapse All Folders", "pinSidebar": "Pin Sidebar", "unpinSidebar": "Unpin Sidebar", "switchToListView": "Switch to List View", "switchToTreeView": "Switch to Tree View", + "recursiveOn": "Search subfolders", + "recursiveOff": "Search current folder only", + "recursiveUnavailable": "Recursive search is available in tree view only", "collapseAllDisabled": "Not available in list view" }, "statistics": { diff --git a/locales/es.json b/locales/es.json index 3cb739a0..84268244 100644 --- a/locales/es.json +++ b/locales/es.json @@ -529,12 +529,15 @@ "title": "Modelos embedding" }, "sidebar": { - "modelRoot": "Raíz del modelo", + "modelRoot": "Raíz", "collapseAll": "Colapsar todas las carpetas", "pinSidebar": "Fijar barra lateral", "unpinSidebar": "Desfijar barra lateral", "switchToListView": "Cambiar a vista de lista", "switchToTreeView": "Cambiar a vista de árbol", + "recursiveOn": "Buscar en subcarpetas", + "recursiveOff": "Buscar solo en la carpeta actual", + "recursiveUnavailable": "La búsqueda recursiva solo está disponible en la vista en árbol", "collapseAllDisabled": "No disponible en vista de lista" }, "statistics": { diff --git a/locales/fr.json b/locales/fr.json index 21e965c4..69360a8d 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -529,12 +529,15 @@ "title": "Modèles Embedding" }, "sidebar": { - "modelRoot": "Racine du modèle", + "modelRoot": "Racine", "collapseAll": "Réduire tous les dossiers", "pinSidebar": "Épingler la barre latérale", "unpinSidebar": "Désépingler la barre latérale", "switchToListView": "Passer en vue liste", "switchToTreeView": "Passer en vue arborescence", + "recursiveOn": "Rechercher dans les sous-dossiers", + "recursiveOff": "Rechercher uniquement dans le dossier actuel", + "recursiveUnavailable": "La recherche récursive n'est disponible qu'en vue arborescente", "collapseAllDisabled": "Non disponible en vue liste" }, "statistics": { diff --git a/locales/he.json b/locales/he.json index a273c451..2c9a7f66 100644 --- a/locales/he.json +++ b/locales/he.json @@ -529,12 +529,15 @@ "title": "מודלי Embedding" }, "sidebar": { - "modelRoot": "שורש המודלים", + "modelRoot": "שורש", "collapseAll": "כווץ את כל התיקיות", "pinSidebar": "נעל סרגל צד", "unpinSidebar": "שחרר סרגל צד", "switchToListView": "עבור לתצוגת רשימה", - "switchToTreeView": "עבור לתצוגת עץ", + "switchToTreeView": "תצוגת עץ", + "recursiveOn": "חיפוש בתיקיות משנה", + "recursiveOff": "חיפוש רק בתיקייה הנוכחית", + "recursiveUnavailable": "חיפוש רקורסיבי זמין רק בתצוגת עץ", "collapseAllDisabled": "לא זמין בתצוגת רשימה" }, "statistics": { @@ -1264,4 +1267,4 @@ "learnMore": "LM Civitai Extension Tutorial" } } -} +} \ No newline at end of file diff --git a/locales/ja.json b/locales/ja.json index 61bfb9ea..319d46aa 100644 --- a/locales/ja.json +++ b/locales/ja.json @@ -529,12 +529,15 @@ "title": "Embeddingモデル" }, "sidebar": { - "modelRoot": "モデルルート", + "modelRoot": "ルート", "collapseAll": "すべてのフォルダを折りたたむ", "pinSidebar": "サイドバーを固定", "unpinSidebar": "サイドバーの固定を解除", "switchToListView": "リストビューに切り替え", - "switchToTreeView": "ツリービューに切り替え", + "switchToTreeView": "ツリー表示に切り替え", + "recursiveOn": "サブフォルダーを検索", + "recursiveOff": "現在のフォルダーのみを検索", + "recursiveUnavailable": "再帰検索はツリービューでのみ利用できます", "collapseAllDisabled": "リストビューでは利用できません" }, "statistics": { @@ -1264,4 +1267,4 @@ "learnMore": "LM Civitai Extension Tutorial" } } -} +} \ No newline at end of file diff --git a/locales/ko.json b/locales/ko.json index aae9ed40..98813f25 100644 --- a/locales/ko.json +++ b/locales/ko.json @@ -529,12 +529,15 @@ "title": "Embedding 모델" }, "sidebar": { - "modelRoot": "모델 루트", + "modelRoot": "루트", "collapseAll": "모든 폴더 접기", "pinSidebar": "사이드바 고정", "unpinSidebar": "사이드바 고정 해제", "switchToListView": "목록 보기로 전환", "switchToTreeView": "트리 보기로 전환", + "recursiveOn": "하위 폴더 검색", + "recursiveOff": "현재 폴더만 검색", + "recursiveUnavailable": "재귀 검색은 트리 보기에서만 사용할 수 있습니다", "collapseAllDisabled": "목록 보기에서는 사용할 수 없습니다" }, "statistics": { @@ -1264,4 +1267,4 @@ "learnMore": "LM Civitai Extension Tutorial" } } -} +} \ No newline at end of file diff --git a/locales/ru.json b/locales/ru.json index 7ae561ae..ab6a2e5a 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -529,12 +529,15 @@ "title": "Модели Embedding" }, "sidebar": { - "modelRoot": "Корень моделей", + "modelRoot": "Корень", "collapseAll": "Свернуть все папки", "pinSidebar": "Закрепить боковую панель", "unpinSidebar": "Открепить боковую панель", "switchToListView": "Переключить на вид списка", "switchToTreeView": "Переключить на древовидный вид", + "recursiveOn": "Искать во вложенных папках", + "recursiveOff": "Искать только в текущей папке", + "recursiveUnavailable": "Рекурсивный поиск доступен только в режиме дерева", "collapseAllDisabled": "Недоступно в виде списка" }, "statistics": { @@ -1264,4 +1267,4 @@ "learnMore": "LM Civitai Extension Tutorial" } } -} +} \ No newline at end of file diff --git a/locales/zh-CN.json b/locales/zh-CN.json index d8a20d70..ecc4edb8 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -535,12 +535,15 @@ "title": "Embedding 模型" }, "sidebar": { - "modelRoot": "模型根目录", + "modelRoot": "根目录", "collapseAll": "折叠所有文件夹", "pinSidebar": "固定侧边栏", "unpinSidebar": "取消固定侧边栏", "switchToListView": "切换到列表视图", "switchToTreeView": "切换到树状视图", + "recursiveOn": "搜索子文件夹", + "recursiveOff": "仅搜索当前文件夹", + "recursiveUnavailable": "仅在树形视图中可使用递归搜索", "collapseAllDisabled": "列表视图下不可用" }, "statistics": { @@ -1270,4 +1273,4 @@ "learnMore": "LM Civitai Extension Tutorial" } } -} +} \ No newline at end of file diff --git a/locales/zh-TW.json b/locales/zh-TW.json index 09856038..53ed58be 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -529,12 +529,15 @@ "title": "Embedding 模型" }, "sidebar": { - "modelRoot": "模型根目錄", + "modelRoot": "根目錄", "collapseAll": "全部摺疊資料夾", "pinSidebar": "固定側邊欄", "unpinSidebar": "取消固定側邊欄", "switchToListView": "切換至列表檢視", - "switchToTreeView": "切換至樹狀檢視", + "switchToTreeView": "切換到樹狀檢視", + "recursiveOn": "搜尋子資料夾", + "recursiveOff": "僅搜尋目前資料夾", + "recursiveUnavailable": "遞迴搜尋僅能在樹狀檢視中使用", "collapseAllDisabled": "列表檢視下不可用" }, "statistics": { @@ -1264,4 +1267,4 @@ "learnMore": "LM Civitai Extension Tutorial" } } -} +} \ No newline at end of file diff --git a/static/js/components/SidebarManager.js b/static/js/components/SidebarManager.js index 674fe825..dffe8f18 100644 --- a/static/js/components/SidebarManager.js +++ b/static/js/components/SidebarManager.js @@ -21,6 +21,7 @@ export class SidebarManager { this.isInitialized = false; this.displayMode = 'tree'; // 'tree' or 'list' this.foldersList = []; + this.recursiveSearchEnabled = true; // Bind methods this.handleTreeClick = this.handleTreeClick.bind(this); @@ -36,6 +37,7 @@ export class SidebarManager { this.updateContainerMargin = this.updateContainerMargin.bind(this); this.handleDisplayModeToggle = this.handleDisplayModeToggle.bind(this); this.handleFolderListClick = this.handleFolderListClick.bind(this); + this.handleRecursiveToggle = this.handleRecursiveToggle.bind(this); } async initialize(pageControls) { @@ -89,6 +91,7 @@ export class SidebarManager { this.isHovering = false; this.apiClient = null; this.isInitialized = false; + this.recursiveSearchEnabled = true; // Reset container margin const container = document.querySelector('.container'); @@ -111,6 +114,7 @@ export class SidebarManager { const sidebar = document.getElementById('folderSidebar'); const hoverArea = document.getElementById('sidebarHoverArea'); const displayModeToggleBtn = document.getElementById('sidebarDisplayModeToggle'); + const recursiveToggleBtn = document.getElementById('sidebarRecursiveToggle'); if (pinToggleBtn) { pinToggleBtn.removeEventListener('click', this.handlePinToggle); @@ -145,6 +149,9 @@ export class SidebarManager { if (displayModeToggleBtn) { displayModeToggleBtn.removeEventListener('click', this.handleDisplayModeToggle); } + if (recursiveToggleBtn) { + recursiveToggleBtn.removeEventListener('click', this.handleRecursiveToggle); + } } async init() { @@ -197,7 +204,7 @@ export class SidebarManager { updateSidebarTitle() { const sidebarTitle = document.getElementById('sidebarTitle'); if (sidebarTitle) { - sidebarTitle.textContent = `${this.apiClient.apiConfig.config.displayName} Root`; + sidebarTitle.textContent = translate('sidebar.modelRoot'); } } @@ -220,6 +227,12 @@ export class SidebarManager { collapseAllBtn.addEventListener('click', this.handleCollapseAll); } + // Recursive toggle button + const recursiveToggleBtn = document.getElementById('sidebarRecursiveToggle'); + if (recursiveToggleBtn) { + recursiveToggleBtn.addEventListener('click', this.handleRecursiveToggle); + } + // Tree click handler const folderTree = document.getElementById('sidebarFolderTree'); if (folderTree) { @@ -645,11 +658,33 @@ export class SidebarManager { this.displayMode = this.displayMode === 'tree' ? 'list' : 'tree'; this.updateDisplayModeButton(); this.updateCollapseAllButton(); + this.updateRecursiveToggleButton(); this.updateSearchRecursiveOption(); this.saveDisplayMode(); this.loadFolderTree(); // Reload with new display mode } + async handleRecursiveToggle(event) { + event.stopPropagation(); + + if (this.displayMode !== 'tree') { + return; + } + + this.recursiveSearchEnabled = !this.recursiveSearchEnabled; + setStorageItem(`${this.pageType}_recursiveSearch`, this.recursiveSearchEnabled); + this.updateSearchRecursiveOption(); + this.updateRecursiveToggleButton(); + + if (this.pageControls && typeof this.pageControls.resetAndReload === 'function') { + try { + await this.pageControls.resetAndReload(true); + } catch (error) { + console.error('Failed to reload models after toggling recursive search:', error); + } + } + } + updateDisplayModeButton() { const displayModeBtn = document.getElementById('sidebarDisplayModeToggle'); if (displayModeBtn) { @@ -679,8 +714,35 @@ export class SidebarManager { } } + updateRecursiveToggleButton() { + const recursiveToggleBtn = document.getElementById('sidebarRecursiveToggle'); + if (!recursiveToggleBtn) return; + + const icon = recursiveToggleBtn.querySelector('i'); + const isTreeMode = this.displayMode === 'tree'; + const isActive = isTreeMode && this.recursiveSearchEnabled; + + recursiveToggleBtn.classList.toggle('active', isActive); + recursiveToggleBtn.classList.toggle('disabled', !isTreeMode); + recursiveToggleBtn.setAttribute('aria-pressed', isActive ? 'true' : 'false'); + recursiveToggleBtn.setAttribute('aria-disabled', isTreeMode ? 'false' : 'true'); + + if (icon) { + icon.className = 'fas fa-code-branch'; + } + + if (!isTreeMode) { + recursiveToggleBtn.title = translate('sidebar.recursiveUnavailable'); + } else if (this.recursiveSearchEnabled) { + recursiveToggleBtn.title = translate('sidebar.recursiveOn'); + } else { + recursiveToggleBtn.title = translate('sidebar.recursiveOff'); + } + } + updateSearchRecursiveOption() { - this.pageControls.pageState.searchOptions.recursive = this.displayMode === 'tree'; + const isRecursive = this.displayMode === 'tree' && this.recursiveSearchEnabled; + this.pageControls.pageState.searchOptions.recursive = isRecursive; } updateTreeSelection() { @@ -925,15 +987,18 @@ export class SidebarManager { const isPinned = getStorageItem(`${this.pageType}_sidebarPinned`, true); const expandedPaths = getStorageItem(`${this.pageType}_expandedNodes`, []); const displayMode = getStorageItem(`${this.pageType}_displayMode`, 'tree'); // 'tree' or 'list', default to 'tree' + const recursiveSearchEnabled = getStorageItem(`${this.pageType}_recursiveSearch`, true); this.isPinned = isPinned; this.expandedNodes = new Set(expandedPaths); this.displayMode = displayMode; + this.recursiveSearchEnabled = recursiveSearchEnabled; this.updatePinButton(); this.updateDisplayModeButton(); this.updateCollapseAllButton(); this.updateSearchRecursiveOption(); + this.updateRecursiveToggleButton(); } restoreSelectedFolder() { @@ -974,4 +1039,4 @@ export class SidebarManager { } // Create and export global instance -export const sidebarManager = new SidebarManager(); \ No newline at end of file +export const sidebarManager = new SidebarManager(); diff --git a/static/js/state/index.js b/static/js/state/index.js index 6f0cedd1..43f30454 100644 --- a/static/js/state/index.js +++ b/static/js/state/index.js @@ -67,7 +67,7 @@ export const state = { modelname: true, tags: false, creator: false, - recursive: true, + recursive: getStorageItem(`${MODEL_TYPES.LORA}_recursiveSearch`, true), }, filters: { baseModel: [], @@ -116,7 +116,7 @@ export const state = { filename: true, modelname: true, creator: false, - recursive: true, + recursive: getStorageItem(`${MODEL_TYPES.CHECKPOINT}_recursiveSearch`, true), }, filters: { baseModel: [], @@ -144,7 +144,7 @@ export const state = { modelname: true, tags: false, creator: false, - recursive: true, + recursive: getStorageItem(`${MODEL_TYPES.EMBEDDING}_recursiveSearch`, true), }, filters: { baseModel: [], @@ -261,4 +261,4 @@ export function initPageState(pageType) { return getCurrentPageState(); } return null; -} \ No newline at end of file +} diff --git a/templates/components/folder_sidebar.html b/templates/components/folder_sidebar.html index 9fdbc3fe..5f3748c8 100644 --- a/templates/components/folder_sidebar.html +++ b/templates/components/folder_sidebar.html @@ -9,6 +9,9 @@ +