diff --git a/py/routes/base_model_routes.py b/py/routes/base_model_routes.py index affed6fb..86f3ad43 100644 --- a/py/routes/base_model_routes.py +++ b/py/routes/base_model_routes.py @@ -191,7 +191,7 @@ class BaseModelRoutes(ABC): 'modelname': request.query.get('search_modelname', 'true').lower() == 'true', 'tags': request.query.get('search_tags', 'false').lower() == 'true', 'creator': request.query.get('search_creator', 'false').lower() == 'true', - 'recursive': request.query.get('recursive', 'false').lower() == 'true', + 'recursive': request.query.get('recursive', 'true').lower() == 'true', } # Parse hash filters if provided diff --git a/py/services/base_model_service.py b/py/services/base_model_service.py index cc7d10a9..93a475b1 100644 --- a/py/services/base_model_service.py +++ b/py/services/base_model_service.py @@ -68,7 +68,7 @@ class BaseModelService(ABC): 'filename': True, 'modelname': True, 'tags': False, - 'recursive': False, + 'recursive': True, } # Get the base data set using new sort logic @@ -139,7 +139,7 @@ class BaseModelService(ABC): # Apply folder filtering if folder is not None: - if search_options and search_options.get('recursive', False): + if search_options and search_options.get('recursive', True): # Recursive folder filtering - include all subfolders data = [ item for item in data diff --git a/static/css/base.css b/static/css/base.css index 99aedbb7..cbf89c9b 100644 --- a/static/css/base.css +++ b/static/css/base.css @@ -46,7 +46,7 @@ html, body { /* Composed Colors */ --lora-accent: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h)); - --lora-surface: oklch(100% 0 0 / 0.98); + --lora-surface: oklch(97% 0 0 / 0.95); --lora-border: oklch(90% 0.02 256 / 0.15); --lora-text: oklch(95% 0.02 256); --lora-error: oklch(75% 0.32 29); diff --git a/static/css/components/search-filter.css b/static/css/components/search-filter.css index 7734b85c..f5e91cf4 100644 --- a/static/css/components/search-filter.css +++ b/static/css/components/search-filter.css @@ -445,69 +445,6 @@ border-color: var(--lora-accent); } -/* Switch styles */ -.search-option-switch { - display: flex; - justify-content: space-between; - align-items: center; - padding: 4px 0; -} - -.switch { - position: relative; - display: inline-block; - width: 46px; - height: 24px; -} - -.switch input { - opacity: 0; - width: 0; - height: 0; -} - -.slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: #ccc; - transition: .4s; -} - -.slider:before { - position: absolute; - content: ""; - height: 18px; - width: 18px; - left: 3px; - bottom: 3px; - background-color: white; - transition: .4s; -} - -input:checked + .slider { - background-color: var(--lora-accent); -} - -input:focus + .slider { - box-shadow: 0 0 1px var(--lora-accent); -} - -input:checked + .slider:before { - transform: translateX(22px); -} - -.slider.round { - border-radius: 34px; -} - -.slider.round:before { - border-radius: 50%; -} - /* Mobile adjustments */ @media (max-width: 768px) { .search-options-panel, diff --git a/static/css/components/sidebar.css b/static/css/components/sidebar.css new file mode 100644 index 00000000..28d9a960 --- /dev/null +++ b/static/css/components/sidebar.css @@ -0,0 +1,495 @@ +.folder-sidebar { + position: fixed; + top: 68px; /* Below header */ + left: 10px; + width: 230px; + height: calc(100vh - 88px); + background: var(--bg-color); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-xs); + overflow: hidden; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + flex-shrink: 0; + z-index: var(--z-overlay); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + display: flex; + flex-direction: column; + backdrop-filter: blur(8px); + /* Default state: hidden off-screen */ + transform: translateX(-100%); + opacity: 0; + pointer-events: none; +} + +/* Visible state */ +.folder-sidebar.visible { + transform: translateX(0); + opacity: 1; + pointer-events: all; +} + +/* Auto-hide states */ +.folder-sidebar.auto-hide { + transform: translateX(-100%); + opacity: 0; + pointer-events: none; +} + +.folder-sidebar.auto-hide.hover-active { + transform: translateX(0); + opacity: 1; + pointer-events: all; +} + +.folder-sidebar.collapsed { + transform: translateX(-100%); + opacity: 0; + pointer-events: none; +} + +/* Hover detection area for auto-hide */ +.sidebar-hover-area { + position: fixed; + top: 68px; + left: 0; + width: 20px; + height: calc(100vh - 88px); + z-index: calc(var(--z-overlay) - 1); + background: transparent; + pointer-events: all; +} + +.sidebar-hover-area.disabled { + pointer-events: none; +} + +.sidebar-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 16px; + background: var(--bg-color); + color: var(--text-color); + font-weight: 500; + font-size: 0.9em; + flex-shrink: 0; + border-bottom: 1px solid var(--border-color); + cursor: pointer; + transition: all 0.2s ease; +} + +.sidebar-header:hover { + background: var(--lora-surface); +} + +.sidebar-header.root-selected { + background: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.1); + color: var(--lora-accent); +} + +.sidebar-header h3 { + margin: 0; + font-size: 0.9em; + display: flex; + align-items: center; + gap: 8px; + font-weight: 500; + flex: 1; + pointer-events: none; +} + +.sidebar-header-actions { + display: flex; + align-items: center; + gap: 4px; +} + +.sidebar-action-btn { + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + padding: 4px; + border-radius: 4px; + opacity: 0.6; + transition: all 0.2s ease; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; +} + +.sidebar-action-btn:hover { + opacity: 1; + background: var(--lora-surface); + color: var(--text-color); +} + +.sidebar-action-btn.active { + opacity: 1; + background: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.15); + color: var(--lora-accent); +} + +/* Remove old close button styles */ +.sidebar-toggle-close { + display: none; +} + +.sidebar-content { + flex: 1; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.sidebar-tree-container { + flex: 1; + overflow-y: auto; + padding: 8px 0; +} + +/* Sidebar Tree Node Styles */ +.sidebar-tree-node { + position: relative; + user-select: none; +} + +.sidebar-tree-node-content { + display: flex; + align-items: center; + padding: 8px 16px; + cursor: pointer; + transition: all 0.2s ease; + font-size: 0.85em; + border-left: 3px solid transparent; + color: var(--text-color); +} + +.sidebar-tree-node-content:hover { + background: var(--lora-surface); + color: var(--text-color); +} + +.sidebar-tree-node-content.selected { + background: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.1); + color: var(--lora-accent); + border-left-color: var(--lora-accent); + font-weight: 500; +} + +.sidebar-tree-expand-icon { + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 4px; + transition: transform 0.2s ease; + opacity: 0.6; +} + +.sidebar-tree-expand-icon.expanded { + transform: rotate(90deg); +} + +.sidebar-tree-expand-icon i { + font-size: 10px; +} + +.sidebar-tree-folder-icon { + margin-right: 8px; + color: var(--text-muted); + opacity: 0.7; +} + +.sidebar-tree-node-content.selected .sidebar-tree-folder-icon { + color: var(--lora-accent); + opacity: 1; +} + +.sidebar-tree-node-content:hover .sidebar-tree-folder-icon { + color: var(--text-color); + opacity: 0.9; +} + +.sidebar-tree-folder-name { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.sidebar-tree-children { + overflow: hidden; + max-height: 0; + transition: max-height 0.3s ease; +} + +.sidebar-tree-children.expanded { + max-height: 1000px; +} + +.sidebar-tree-children .sidebar-tree-node-content { + padding-left: 32px; +} + +.sidebar-tree-children .sidebar-tree-children .sidebar-tree-node-content { + padding-left: 48px; +} + +.sidebar-tree-children .sidebar-tree-children .sidebar-tree-children .sidebar-tree-node-content { + padding-left: 64px; +} + +/* Enhanced Sidebar Breadcrumb Styles */ +.sidebar-breadcrumb-container { + margin-top: 8px; + padding: 8px 0; + border-bottom: 1px solid var(--border-color); + background: var(--bg-color); + border-radius: var(--border-radius-xs); +} + +.sidebar-breadcrumb-nav { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 4px; + font-size: 0.85em; + padding: 0 8px; +} + +.sidebar-breadcrumb-item { + display: flex; + align-items: center; + gap: 4px; + padding: 4px 8px; + border-radius: var(--border-radius-xs); + cursor: pointer; + transition: all 0.2s ease; + color: var(--text-muted); + position: relative; +} + +.sidebar-breadcrumb-item:hover { + background: var(--lora-surface); + color: var(--text-color); +} + +.sidebar-breadcrumb-item.active { + background: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.1); + color: var(--lora-accent); + font-weight: 500; +} + +.sidebar-breadcrumb-separator { + color: var(--text-muted); + opacity: 0.6; + margin: 0 2px; +} + +/* New Breadcrumb Dropdown Styles */ +.breadcrumb-dropdown { + position: relative; + display: inline-flex; + align-items: center; +} + +.breadcrumb-dropdown-indicator { + margin-left: 6px; + color: inherit; + opacity: 0.6; + transition: all 0.2s ease; + pointer-events: none; + font-size: 0.9em; +} + +.sidebar-breadcrumb-item:hover .breadcrumb-dropdown-indicator { + opacity: 0.9; +} + +.sidebar-breadcrumb-item.placeholder { + color: var(--text-muted); + font-style: italic; +} + +.sidebar-breadcrumb-item.placeholder:hover { + background: var(--lora-surface); + color: var(--text-color); +} + +.breadcrumb-dropdown.open .breadcrumb-dropdown-indicator { + transform: rotate(180deg); + opacity: 1; +} + +.breadcrumb-dropdown-menu { + position: absolute; + top: 100%; + left: 0; + min-width: 160px; + max-width: 240px; + background: var(--bg-color); + border: 1px solid var(--border-color); + border-radius: var(--border-radius-xs); + box-shadow: 0 3px 8px rgba(0,0,0,0.15); + z-index: calc(var(--z-overlay) + 20); + overflow-y: auto; + max-height: 450px; + display: none; + margin-top: 4px; +} + +.breadcrumb-dropdown.open .breadcrumb-dropdown-menu { + display: block; +} + +.breadcrumb-dropdown-item { + padding: 6px 12px; + cursor: pointer; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + transition: all 0.2s ease; +} + +.breadcrumb-dropdown-item:hover { + background: var(--lora-surface); +} + +.breadcrumb-dropdown-item.active { + background: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.1); + color: var(--lora-accent); + font-weight: 500; +} + +/* Responsive Design */ +@media (min-width: 2000px) { + .folder-sidebar { + width: 280px; + left: 24px; + } +} + +@media (min-width: 3000px) { + .folder-sidebar { + width: 320px; + left: 32px; + } +} + +@media (max-width: 1400px) { + .folder-sidebar { + width: 260px; + left: 16px; + } +} + +/* Empty State */ +.sidebar-tree-placeholder { + padding: 24px 16px; + text-align: center; + color: var(--text-muted); + opacity: 0.7; +} + +.sidebar-tree-placeholder i { + font-size: 2em; + opacity: 0.5; + margin-bottom: 8px; + display: block; +} + +/* Smooth transitions for tree nodes */ +.sidebar-tree-node { + overflow: hidden; +} + +.sidebar-tree-children { + transition: max-height 0.25s cubic-bezier(0.4, 0, 0.2, 1); +} + +.sidebar-tree-expand-icon { + transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1); +} + +/* Visual separator for nested levels */ +.sidebar-tree-children .sidebar-tree-node-content { + position: relative; +} + +.sidebar-tree-children .sidebar-tree-node-content::before { + content: ''; + position: absolute; + left: 8px; + top: 0; + bottom: 0; + width: 1px; + background: var(--border-color); + opacity: 0.3; +} + +/* Responsive Design */ +@media (max-width: 1024px) { + .folder-sidebar { + top: 68px; + left: 16px; + width: calc(100vw - 32px); + max-width: 320px; + height: calc(100vh - 88px); + z-index: calc(var(--z-overlay) + 10); + } + + .folder-sidebar.collapsed { + transform: translateX(-100%); + } + + /* Mobile overlay */ + .folder-sidebar:not(.collapsed)::before { + content: ''; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.3); + z-index: -1; + backdrop-filter: blur(2px); + } +} + +@media (max-width: 768px) { + .folder-sidebar { + width: calc(100vw - 32px); + max-width: 280px; + left: 16px; + } + + .sidebar-breadcrumb-nav { + font-size: 0.8em; + } + + .sidebar-breadcrumb-item { + padding: 3px 6px; + } +} + +/* Hide scrollbar but keep functionality */ +.sidebar-tree-container::-webkit-scrollbar { + width: 6px; +} + +.sidebar-tree-container::-webkit-scrollbar-track { + background: transparent; +} + +.sidebar-tree-container::-webkit-scrollbar-thumb { + background: var(--border-color); + border-radius: 3px; +} + +.sidebar-tree-container::-webkit-scrollbar-thumb:hover { + background: var(--text-muted); +} diff --git a/static/css/layout.css b/static/css/layout.css index 182e50b5..7ee105a7 100644 --- a/static/css/layout.css +++ b/static/css/layout.css @@ -31,7 +31,6 @@ .controls { display: flex; flex-direction: column; - gap: 8px; margin-bottom: var(--space-2); } @@ -225,63 +224,6 @@ display: none !important; } -.folder-tags-container { - position: relative; - width: 100%; - margin-bottom: 8px; /* Add margin to ensure space for the button */ -} - -.folder-tags { - display: flex; - gap: 4px; - padding: 2px 0; - flex-wrap: wrap; - transition: max-height 0.3s ease, opacity 0.2s ease; - max-height: 150px; /* Limit height to prevent overflow */ - opacity: 1; - overflow-y: auto; /* Enable vertical scrolling */ - margin-bottom: 5px; /* Add margin below the tags */ -} - -.folder-tags.collapsed { - max-height: 0; - opacity: 0; - margin: 0; - padding-bottom: 0; - overflow: hidden; -} - -.toggle-folders-container { - margin-left: auto; -} - -/* Toggle Folders Button */ -.toggle-folders-btn { - width: 36px; - height: 36px; - border-radius: 50%; - background: var(--card-bg); - border: 1px solid var(--border-color); - color: var(--text-color); - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: all 0.3s ease; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.toggle-folders-btn:hover { - background: var(--lora-accent); - color: white; - transform: translateY(-2px); - box-shadow: 0 3px 6px rgba(0, 0, 0, 0.1); -} - -.toggle-folders-btn i { - transition: transform 0.3s ease; -} - /* Icon-only button style */ .icon-only { min-width: unset !important; @@ -290,55 +232,6 @@ height: 32px !important; } -/* Rotate icon when folders are collapsed */ -.folder-tags.collapsed ~ .actions .toggle-folders-btn i { - transform: rotate(180deg); -} - -/* Add custom scrollbar for better visibility */ -.folder-tags::-webkit-scrollbar { - width: 6px; -} - -.folder-tags::-webkit-scrollbar-track { - background: var(--card-bg); - border-radius: 3px; -} - -.folder-tags::-webkit-scrollbar-thumb { - background: var(--border-color); - border-radius: 3px; -} - -.folder-tags::-webkit-scrollbar-thumb:hover { - background: var(--lora-accent); -} - -.tag { - cursor: pointer; - padding: 2px 8px; - margin: 2px; - border: 1px solid var(--border-color); - border-radius: var(--border-radius-xs); - display: inline-block; - line-height: 1.2; - font-size: 14px; - background-color: var(--card-bg); - transition: all 0.2s ease; -} - -.tag:hover { - border-color: var(--lora-accent); - background-color: oklch(var(--lora-accent) / 0.1); - transform: translateY(-1px); -} - -.tag.active { - background-color: var(--lora-accent); - color: white; - border-color: var(--lora-accent); -} - /* Back to Top Button */ .back-to-top { position: fixed; @@ -376,10 +269,8 @@ } /* Prevent text selection in control and header areas */ -.tag, .control-group button, .control-group select, -.toggle-folders-btn, .bulk-operations-panel, .app-header, .header-branding, @@ -387,8 +278,7 @@ .main-nav, .nav-item, .header-actions button, -.header-controls, -.toggle-folders-container button { +.header-controls { -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; @@ -472,18 +362,6 @@ justify-content: flex-end; margin-top: 8px; } - - .toggle-folders-container { - margin-left: 0; - } - - .folder-tags-container { - order: -1; - } - - .toggle-folders-btn:hover { - transform: none; /* Disable hover effects on mobile */ - } .control-group button:hover { transform: none; /* Disable hover effects on mobile */ @@ -493,10 +371,6 @@ transform: none; /* Disable hover effects on mobile */ } - .tag:hover { - transform: none; /* Disable hover effects on mobile */ - } - .back-to-top { bottom: 60px; /* Give some extra space from bottom on mobile */ } diff --git a/static/css/style.css b/static/css/style.css index 0c581b3d..2e91f7c1 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -34,10 +34,10 @@ @import 'components/filter-indicator.css'; @import 'components/initialization.css'; @import 'components/progress-panel.css'; -@import 'components/alphabet-bar.css'; /* Add alphabet bar component */ @import 'components/duplicates.css'; /* Add duplicates component */ @import 'components/keyboard-nav.css'; /* Add keyboard navigation component */ @import 'components/statistics.css'; /* Add statistics component */ +@import 'components/sidebar.css'; /* Add sidebar component */ .initialization-notice { display: flex; diff --git a/static/js/api/baseModelApi.js b/static/js/api/baseModelApi.js index 169ca902..4259adf2 100644 --- a/static/js/api/baseModelApi.js +++ b/static/js/api/baseModelApi.js @@ -671,7 +671,6 @@ export class BaseModelApiClient { if (pageState.searchOptions.creator !== undefined) { params.append('search_creator', pageState.searchOptions.creator.toString()); } - params.append('recursive', (pageState.searchOptions?.recursive ?? false).toString()); } } diff --git a/static/js/checkpoints.js b/static/js/checkpoints.js index 89fab5b3..907e92a6 100644 --- a/static/js/checkpoints.js +++ b/static/js/checkpoints.js @@ -30,10 +30,6 @@ class CheckpointsPageManager { } async initialize() { - // Initialize page-specific components - this.pageControls.restoreFolderFilter(); - this.pageControls.initFolderTagsVisibility(); - // Initialize context menu new CheckpointContextMenu(); diff --git a/static/js/components/SidebarManager.js b/static/js/components/SidebarManager.js new file mode 100644 index 00000000..69d97bba --- /dev/null +++ b/static/js/components/SidebarManager.js @@ -0,0 +1,729 @@ +/** + * SidebarManager - Manages hierarchical folder navigation sidebar + */ +import { getStorageItem, setStorageItem } from '../utils/storageHelpers.js'; +import { getModelApiClient } from '../api/modelApiFactory.js'; + +export class SidebarManager { + constructor(pageControls) { + this.pageControls = pageControls; + this.pageType = pageControls.pageType; + this.treeData = {}; + this.selectedPath = ''; + this.expandedNodes = new Set(); + this.isVisible = true; + this.isPinned = false; + this.apiClient = null; + this.openDropdown = null; + this.hoverTimeout = null; + this.isHovering = false; + + // Bind methods + this.handleTreeClick = this.handleTreeClick.bind(this); + this.handleBreadcrumbClick = this.handleBreadcrumbClick.bind(this); + this.handleDocumentClick = this.handleDocumentClick.bind(this); + this.handleSidebarHeaderClick = this.handleSidebarHeaderClick.bind(this); + this.handlePinToggle = this.handlePinToggle.bind(this); + this.handleCollapseAll = this.handleCollapseAll.bind(this); + this.handleMouseEnter = this.handleMouseEnter.bind(this); + this.handleMouseLeave = this.handleMouseLeave.bind(this); + this.handleHoverAreaEnter = this.handleHoverAreaEnter.bind(this); + this.handleHoverAreaLeave = this.handleHoverAreaLeave.bind(this); + + this.init(); + } + + async init() { + this.apiClient = getModelApiClient(); + + // Set initial sidebar state immediately (hidden by default) + this.setInitialSidebarState(); + + this.setupEventHandlers(); + this.updateSidebarTitle(); + this.restoreSidebarState(); + await this.loadFolderTree(); + this.restoreSelectedFolder(); + + // Apply final state with animation after everything is loaded + this.applyFinalSidebarState(); + } + + setInitialSidebarState() { + const sidebar = document.getElementById('folderSidebar'); + const hoverArea = document.getElementById('sidebarHoverArea'); + + if (!sidebar || !hoverArea) return; + + // Get stored pin state + const isPinned = getStorageItem(`${this.pageType}_sidebarPinned`, false); + this.isPinned = isPinned; + + // Sidebar starts hidden by default (CSS handles this) + // Just set up the hover area state + if (window.innerWidth <= 1024) { + hoverArea.classList.add('disabled'); + } else if (this.isPinned) { + hoverArea.classList.add('disabled'); + } else { + hoverArea.classList.remove('disabled'); + } + } + + applyFinalSidebarState() { + // Use requestAnimationFrame to ensure DOM is ready + requestAnimationFrame(() => { + this.updateAutoHideState(); + }); + } + + updateSidebarTitle() { + const sidebarTitle = document.getElementById('sidebarTitle'); + if (sidebarTitle) { + sidebarTitle.textContent = `${this.apiClient.apiConfig.config.displayName} Root`; + } + } + + setupEventHandlers() { + // Sidebar header (root selection) - only trigger on title area + const sidebarHeader = document.getElementById('sidebarHeader'); + if (sidebarHeader) { + sidebarHeader.addEventListener('click', this.handleSidebarHeaderClick); + } + + // Pin toggle button + const pinToggleBtn = document.getElementById('sidebarPinToggle'); + if (pinToggleBtn) { + pinToggleBtn.addEventListener('click', this.handlePinToggle); + } + + // Collapse all button + const collapseAllBtn = document.getElementById('sidebarCollapseAll'); + if (collapseAllBtn) { + collapseAllBtn.addEventListener('click', this.handleCollapseAll); + } + + // Tree click handler + const folderTree = document.getElementById('sidebarFolderTree'); + if (folderTree) { + folderTree.addEventListener('click', this.handleTreeClick); + } + + // Breadcrumb click handler + const sidebarBreadcrumbNav = document.getElementById('sidebarBreadcrumbNav'); + if (sidebarBreadcrumbNav) { + sidebarBreadcrumbNav.addEventListener('click', this.handleBreadcrumbClick); + } + + // Hover detection for auto-hide + const sidebar = document.getElementById('folderSidebar'); + const hoverArea = document.getElementById('sidebarHoverArea'); + + if (sidebar) { + sidebar.addEventListener('mouseenter', this.handleMouseEnter); + sidebar.addEventListener('mouseleave', this.handleMouseLeave); + } + + if (hoverArea) { + hoverArea.addEventListener('mouseenter', this.handleHoverAreaEnter); + hoverArea.addEventListener('mouseleave', this.handleHoverAreaLeave); + } + + // Close sidebar when clicking outside on mobile + document.addEventListener('click', (e) => { + if (window.innerWidth <= 1024 && this.isVisible) { + const sidebar = document.getElementById('folderSidebar'); + + if (sidebar && !sidebar.contains(e.target)) { + this.hideSidebar(); + } + } + }); + + // Handle window resize + window.addEventListener('resize', () => { + this.updateAutoHideState(); + }); + + // Add document click handler for closing dropdowns + document.addEventListener('click', this.handleDocumentClick); + } + + handleDocumentClick(event) { + // Close open dropdown when clicking outside + if (this.openDropdown && !event.target.closest('.breadcrumb-dropdown')) { + this.closeDropdown(); + } + } + + handleSidebarHeaderClick(event) { + // Only trigger root selection if clicking on the title area, not the buttons + if (!event.target.closest('.sidebar-header-actions')) { + this.selectFolder(''); + } + } + + handlePinToggle(event) { + event.stopPropagation(); + this.isPinned = !this.isPinned; + this.updateAutoHideState(); + this.updatePinButton(); + this.saveSidebarState(); + } + + handleCollapseAll(event) { + event.stopPropagation(); + this.expandedNodes.clear(); + this.renderTree(); + this.saveExpandedState(); + } + + handleMouseEnter() { + this.isHovering = true; + if (this.hoverTimeout) { + clearTimeout(this.hoverTimeout); + this.hoverTimeout = null; + } + + if (!this.isPinned) { + this.showSidebar(); + } + } + + handleMouseLeave() { + this.isHovering = false; + if (!this.isPinned) { + this.hoverTimeout = setTimeout(() => { + if (!this.isHovering) { + this.hideSidebar(); + } + }, 300); + } + } + + handleHoverAreaEnter() { + if (!this.isPinned) { + this.showSidebar(); + } + } + + handleHoverAreaLeave() { + // Let the sidebar's mouse leave handler deal with hiding + } + + showSidebar() { + const sidebar = document.getElementById('folderSidebar'); + if (sidebar && !this.isPinned) { + sidebar.classList.add('hover-active'); + this.isVisible = true; + } + } + + hideSidebar() { + const sidebar = document.getElementById('folderSidebar'); + if (sidebar && !this.isPinned) { + sidebar.classList.remove('hover-active'); + this.isVisible = false; + } + } + + updateAutoHideState() { + const sidebar = document.getElementById('folderSidebar'); + const hoverArea = document.getElementById('sidebarHoverArea'); + + if (!sidebar || !hoverArea) return; + + if (window.innerWidth <= 1024) { + // Mobile: always use collapsed state + sidebar.classList.remove('auto-hide', 'hover-active', 'visible'); + sidebar.classList.add('collapsed'); + hoverArea.classList.add('disabled'); + this.isVisible = false; + } else if (this.isPinned) { + // Desktop pinned: always visible + sidebar.classList.remove('auto-hide', 'collapsed', 'hover-active'); + sidebar.classList.add('visible'); + hoverArea.classList.add('disabled'); + this.isVisible = true; + } else { + // Desktop auto-hide: use hover detection + sidebar.classList.remove('collapsed', 'visible'); + sidebar.classList.add('auto-hide'); + hoverArea.classList.remove('disabled'); + + if (this.isHovering) { + sidebar.classList.add('hover-active'); + this.isVisible = true; + } else { + sidebar.classList.remove('hover-active'); + this.isVisible = false; + } + } + } + + updatePinButton() { + const pinBtn = document.getElementById('sidebarPinToggle'); + if (pinBtn) { + pinBtn.classList.toggle('active', this.isPinned); + pinBtn.title = this.isPinned ? 'Unpin Sidebar' : 'Pin Sidebar'; + } + } + + async loadFolderTree() { + try { + const response = await this.apiClient.fetchUnifiedFolderTree(); + this.treeData = response.tree || {}; + this.renderTree(); + } catch (error) { + console.error('Failed to load folder tree:', error); + this.renderEmptyState(); + } + } + + renderTree() { + const folderTree = document.getElementById('sidebarFolderTree'); + if (!folderTree) return; + + if (!this.treeData || Object.keys(this.treeData).length === 0) { + this.renderEmptyState(); + return; + } + + folderTree.innerHTML = this.renderTreeNode(this.treeData, ''); + } + + renderTreeNode(nodeData, basePath) { + const entries = Object.entries(nodeData); + if (entries.length === 0) return ''; + + return entries.map(([folderName, children]) => { + const currentPath = basePath ? `${basePath}/${folderName}` : folderName; + const hasChildren = Object.keys(children).length > 0; + const isExpanded = this.expandedNodes.has(currentPath); + const isSelected = this.selectedPath === currentPath; + + return ` + + `; + }).join(''); + } + + renderEmptyState() { + const folderTree = document.getElementById('sidebarFolderTree'); + if (!folderTree) return; + + folderTree.innerHTML = ` + + `; + } + + handleTreeClick(event) { + const expandIcon = event.target.closest('.sidebar-tree-expand-icon'); + const nodeContent = event.target.closest('.sidebar-tree-node-content'); + + if (expandIcon) { + // Toggle expand/collapse + const treeNode = expandIcon.closest('.sidebar-tree-node'); + const path = treeNode.dataset.path; + const children = treeNode.querySelector('.sidebar-tree-children'); + + if (this.expandedNodes.has(path)) { + this.expandedNodes.delete(path); + expandIcon.classList.remove('expanded'); + if (children) children.classList.remove('expanded'); + } else { + this.expandedNodes.add(path); + expandIcon.classList.add('expanded'); + if (children) children.classList.add('expanded'); + } + + this.saveExpandedState(); + } else if (nodeContent) { + // Select folder + const treeNode = nodeContent.closest('.sidebar-tree-node'); + const path = treeNode.dataset.path; + this.selectFolder(path); + } + } + + handleBreadcrumbClick(event) { + const breadcrumbItem = event.target.closest('.sidebar-breadcrumb-item'); + const dropdownItem = event.target.closest('.breadcrumb-dropdown-item'); + + if (dropdownItem) { + // Handle dropdown item selection + const path = dropdownItem.dataset.path || ''; + this.selectFolder(path); + this.closeDropdown(); + } else if (breadcrumbItem) { + // Handle breadcrumb item click + const path = breadcrumbItem.dataset.path || ''; + const isPlaceholder = breadcrumbItem.classList.contains('placeholder'); + const isActive = breadcrumbItem.classList.contains('active'); + const dropdown = breadcrumbItem.closest('.breadcrumb-dropdown'); + + if (isPlaceholder || (isActive && path === this.selectedPath)) { + // Open dropdown for placeholders or active items + // Close any open dropdown first + if (this.openDropdown && this.openDropdown !== dropdown) { + this.openDropdown.classList.remove('open'); + } + + // Toggle current dropdown + dropdown.classList.toggle('open'); + + // Update open dropdown reference + this.openDropdown = dropdown.classList.contains('open') ? dropdown : null; + } else { + // Navigate to the selected path + this.selectFolder(path); + } + } + } + + async selectFolder(path) { + // Update selected path + this.selectedPath = path; + + // Update UI + this.updateTreeSelection(); + this.updateBreadcrumbs(); + this.updateSidebarHeader(); + + // Update page state + this.pageControls.pageState.activeFolder = path || null; + setStorageItem(`${this.pageType}_activeFolder`, path || null); + + // Reload models with new filter + await this.pageControls.resetAndReload(); + + // Auto-hide sidebar on mobile after selection + if (window.innerWidth <= 1024) { + this.hideSidebar(); + } + } + + updateTreeSelection() { + const folderTree = document.getElementById('sidebarFolderTree'); + if (!folderTree) return; + + // Remove all selections + folderTree.querySelectorAll('.sidebar-tree-node-content').forEach(node => { + node.classList.remove('selected'); + }); + + // Add selection to current path + if (this.selectedPath) { + const selectedNode = folderTree.querySelector(`[data-path="${this.selectedPath}"] .sidebar-tree-node-content`); + if (selectedNode) { + selectedNode.classList.add('selected'); + + // Expand parents to show selection + this.expandPathParents(this.selectedPath); + } + } + } + + expandPathParents(path) { + if (!path) return; + + const parts = path.split('/'); + let currentPath = ''; + + for (let i = 0; i < parts.length - 1; i++) { + currentPath = currentPath ? `${currentPath}/${parts[i]}` : parts[i]; + this.expandedNodes.add(currentPath); + } + + this.renderTree(); + } + + // Get sibling folders for a given path level + getSiblingFolders(pathParts, level) { + if (level === 0) { + // Root level siblings are top-level folders + return Object.keys(this.treeData); + } + + // Navigate to the parent folder to get siblings + let currentNode = this.treeData; + for (let i = 0; i < level; i++) { + if (!currentNode[pathParts[i]]) { + return []; + } + currentNode = currentNode[pathParts[i]]; + } + + return Object.keys(currentNode); + } + + // Get child folders for a given path + getChildFolders(path) { + if (!path) { + return Object.keys(this.treeData); + } + + const parts = path.split('/'); + let currentNode = this.treeData; + + for (const part of parts) { + if (!currentNode[part]) { + return []; + } + currentNode = currentNode[part]; + } + + return Object.keys(currentNode); + } + + updateBreadcrumbs() { + const sidebarBreadcrumbNav = document.getElementById('sidebarBreadcrumbNav'); + if (!sidebarBreadcrumbNav) return; + + const parts = this.selectedPath ? this.selectedPath.split('/') : []; + let currentPath = ''; + + // Start with root breadcrumb + const rootSiblings = Object.keys(this.treeData); + const breadcrumbs = [` + + `]; + + // Add separator and placeholder for next level if we're at root + if (!this.selectedPath) { + const nextLevelFolders = rootSiblings; + if (nextLevelFolders.length > 0) { + breadcrumbs.push(`/`); + breadcrumbs.push(` + + `); + } + } + + // Add breadcrumb items for each path segment + parts.forEach((part, index) => { + currentPath = currentPath ? `${currentPath}/${part}` : part; + const isLast = index === parts.length - 1; + + // Get siblings for this level + const siblings = this.getSiblingFolders(parts, index); + + breadcrumbs.push(`/`); + breadcrumbs.push(` + + `); + + // Add separator and placeholder for next level if not the last item + if (isLast) { + const childFolders = this.getChildFolders(currentPath); + if (childFolders.length > 0) { + breadcrumbs.push(`/`); + breadcrumbs.push(` + + `); + } + } + }); + + sidebarBreadcrumbNav.innerHTML = breadcrumbs.join(''); + } + + updateSidebarHeader() { + const sidebarHeader = document.getElementById('sidebarHeader'); + if (!sidebarHeader) return; + + if (!this.selectedPath) { + sidebarHeader.classList.add('root-selected'); + } else { + sidebarHeader.classList.remove('root-selected'); + } + } + + toggleSidebar() { + const sidebar = document.getElementById('folderSidebar'); + const toggleBtn = document.querySelector('.sidebar-toggle-btn'); + + if (!sidebar) return; + + this.isVisible = !this.isVisible; + + if (this.isVisible) { + sidebar.classList.remove('collapsed'); + sidebar.classList.add('visible'); + } else { + sidebar.classList.remove('visible'); + sidebar.classList.add('collapsed'); + } + + if (toggleBtn) { + toggleBtn.classList.toggle('active', this.isVisible); + } + + this.saveSidebarState(); + } + + closeSidebar() { + const sidebar = document.getElementById('folderSidebar'); + const toggleBtn = document.querySelector('.sidebar-toggle-btn'); + + if (!sidebar) return; + + this.isVisible = false; + sidebar.classList.remove('visible'); + sidebar.classList.add('collapsed'); + + if (toggleBtn) { + toggleBtn.classList.remove('active'); + } + + this.saveSidebarState(); + } + + restoreSidebarState() { + const isPinned = getStorageItem(`${this.pageType}_sidebarPinned`, false); + const expandedPaths = getStorageItem(`${this.pageType}_expandedNodes`, []); + + this.isPinned = isPinned; + this.expandedNodes = new Set(expandedPaths); + + this.updatePinButton(); + } + + restoreSelectedFolder() { + const activeFolder = getStorageItem(`${this.pageType}_activeFolder`); + if (activeFolder) { + this.selectedPath = activeFolder; + this.updateTreeSelection(); + this.updateBreadcrumbs(); + this.updateSidebarHeader(); + } else { + this.updateSidebarHeader(); + this.updateBreadcrumbs(); // Always update breadcrumbs + } + // Removed hidden class toggle since breadcrumbs are always visible now + } + + saveSidebarState() { + setStorageItem(`${this.pageType}_sidebarPinned`, this.isPinned); + } + + saveExpandedState() { + setStorageItem(`${this.pageType}_expandedNodes`, Array.from(this.expandedNodes)); + } + + async refresh() { + await this.loadFolderTree(); + this.restoreSelectedFolder(); + } + + destroy() { + // Clear any pending timeouts + if (this.hoverTimeout) { + clearTimeout(this.hoverTimeout); + } + + // Clean up event handlers + const pinToggleBtn = document.getElementById('sidebarPinToggle'); + const collapseAllBtn = document.getElementById('sidebarCollapseAll'); + const folderTree = document.getElementById('sidebarFolderTree'); + const sidebarBreadcrumbNav = document.getElementById('sidebarBreadcrumbNav'); + const sidebarHeader = document.getElementById('sidebarHeader'); + const sidebar = document.getElementById('folderSidebar'); + const hoverArea = document.getElementById('sidebarHoverArea'); + + if (pinToggleBtn) { + pinToggleBtn.removeEventListener('click', this.handlePinToggle); + } + if (collapseAllBtn) { + collapseAllBtn.removeEventListener('click', this.handleCollapseAll); + } + if (folderTree) { + folderTree.removeEventListener('click', this.handleTreeClick); + } + if (sidebarBreadcrumbNav) { + sidebarBreadcrumbNav.removeEventListener('click', this.handleBreadcrumbClick); + } + if (sidebarHeader) { + sidebarHeader.removeEventListener('click', this.handleSidebarHeaderClick); + } + if (sidebar) { + sidebar.removeEventListener('mouseenter', this.handleMouseEnter); + sidebar.removeEventListener('mouseleave', this.handleMouseLeave); + } + if (hoverArea) { + hoverArea.removeEventListener('mouseenter', this.handleHoverAreaEnter); + hoverArea.removeEventListener('mouseleave', this.handleHoverAreaLeave); + } + + // Remove document click handler + document.removeEventListener('click', this.handleDocumentClick); + } +} diff --git a/static/js/components/controls/PageControls.js b/static/js/components/controls/PageControls.js index b39a8f57..60340231 100644 --- a/static/js/components/controls/PageControls.js +++ b/static/js/components/controls/PageControls.js @@ -2,6 +2,7 @@ import { getCurrentPageState, setCurrentPageType } from '../../state/index.js'; import { getStorageItem, setStorageItem, getSessionItem, setSessionItem } from '../../utils/storageHelpers.js'; import { showToast } from '../../utils/uiHelpers.js'; +import { SidebarManager } from '../SidebarManager.js'; /** * PageControls class - Unified control management for model pages @@ -23,6 +24,9 @@ export class PageControls { // Store API methods this.api = null; + // Initialize sidebar manager + this.sidebarManager = null; + // Initialize event listeners this.initEventListeners(); @@ -55,6 +59,21 @@ export class PageControls { registerAPI(api) { this.api = api; console.log(`API methods registered for ${this.pageType} page`); + + // Initialize sidebar manager after API is registered + this.initSidebarManager(); + } + + /** + * Initialize sidebar manager + */ + async initSidebarManager() { + try { + this.sidebarManager = new SidebarManager(this); + console.log('SidebarManager initialized'); + } catch (error) { + console.error('Failed to initialize SidebarManager:', error); + } } /** @@ -72,17 +91,6 @@ export class PageControls { }); } - // Use event delegation for folder tags - this is the key fix - const folderTagsContainer = document.querySelector('.folder-tags-container'); - if (folderTagsContainer) { - folderTagsContainer.addEventListener('click', (e) => { - const tag = e.target.closest('.tag'); - if (tag) { - this.handleFolderClick(tag); - } - }); - } - // Refresh button handler const refreshBtn = document.querySelector('[data-action="refresh"]'); if (refreshBtn) { @@ -92,12 +100,6 @@ export class PageControls { // Initialize dropdown functionality this.initDropdowns(); - // Toggle folders button - const toggleFoldersBtn = document.querySelector('.toggle-folders-btn'); - if (toggleFoldersBtn) { - toggleFoldersBtn.addEventListener('click', () => this.toggleFolderTags()); - } - // Clear custom filter handler const clearFilterBtn = document.querySelector('.clear-filter'); if (clearFilterBtn) { @@ -199,130 +201,6 @@ export class PageControls { } } - /** - * Toggle folder selection - * @param {HTMLElement} tagElement - The folder tag element that was clicked - */ - handleFolderClick(tagElement) { - const folder = tagElement.dataset.folder; - const wasActive = tagElement.classList.contains('active'); - - document.querySelectorAll('.folder-tags .tag').forEach(t => { - t.classList.remove('active'); - }); - - if (!wasActive) { - tagElement.classList.add('active'); - this.pageState.activeFolder = folder; - setStorageItem(`${this.pageType}_activeFolder`, folder); - } else { - this.pageState.activeFolder = null; - setStorageItem(`${this.pageType}_activeFolder`, null); - } - - this.resetAndReload(); - } - - /** - * Restore folder filter from storage - */ - restoreFolderFilter() { - const activeFolder = getStorageItem(`${this.pageType}_activeFolder`); - const folderTag = activeFolder && document.querySelector(`.tag[data-folder="${activeFolder}"]`); - - if (folderTag) { - folderTag.classList.add('active'); - this.pageState.activeFolder = activeFolder; - this.filterByFolder(activeFolder); - } - } - - /** - * Filter displayed cards by folder - * @param {string} folderPath - Folder path to filter by - */ - filterByFolder(folderPath) { - const cardSelector = this.pageType === 'loras' ? '.model-card' : '.checkpoint-card'; - document.querySelectorAll(cardSelector).forEach(card => { - card.style.display = card.dataset.folder === folderPath ? '' : 'none'; - }); - } - - /** - * Update the folder tags display with new folder list - * @param {Array} folders - List of folder names - */ - updateFolderTags(folders) { - const folderTagsContainer = document.querySelector('.folder-tags'); - if (!folderTagsContainer) return; - - // Keep track of currently selected folder - const currentFolder = this.pageState.activeFolder; - - // Create HTML for folder tags - const tagsHTML = folders.map(folder => { - const isActive = folder === currentFolder; - return `
${folder}
`; - }).join(''); - - // Update the container - folderTagsContainer.innerHTML = tagsHTML; - - // Scroll active folder into view (no need to reattach click handlers) - const activeTag = folderTagsContainer.querySelector(`.tag[data-folder="${currentFolder}"]`); - if (activeTag) { - activeTag.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); - } - } - - /** - * Toggle visibility of folder tags - */ - toggleFolderTags() { - const folderTags = document.querySelector('.folder-tags'); - const toggleBtn = document.querySelector('.toggle-folders-btn i'); - - if (folderTags) { - folderTags.classList.toggle('collapsed'); - - if (folderTags.classList.contains('collapsed')) { - // Change icon to indicate folders are hidden - toggleBtn.className = 'fas fa-folder-plus'; - toggleBtn.parentElement.title = 'Show folder tags'; - setStorageItem('folderTagsCollapsed', 'true'); - } else { - // Change icon to indicate folders are visible - toggleBtn.className = 'fas fa-folder-minus'; - toggleBtn.parentElement.title = 'Hide folder tags'; - setStorageItem('folderTagsCollapsed', 'false'); - } - } - } - - /** - * Initialize folder tags visibility based on stored preference - */ - initFolderTagsVisibility() { - const isCollapsed = getStorageItem('folderTagsCollapsed'); - if (isCollapsed) { - const folderTags = document.querySelector('.folder-tags'); - const toggleBtn = document.querySelector('.toggle-folders-btn i'); - if (folderTags) { - folderTags.classList.add('collapsed'); - } - if (toggleBtn) { - toggleBtn.className = 'fas fa-folder-plus'; - toggleBtn.parentElement.title = 'Show folder tags'; - } - } else { - const toggleBtn = document.querySelector('.toggle-folders-btn i'); - if (toggleBtn) { - toggleBtn.className = 'fas fa-folder-minus'; - toggleBtn.parentElement.title = 'Hide folder tags'; - } - } - } - /** * Load sort preference from storage */ @@ -408,6 +286,11 @@ export class PageControls { try { await this.api.resetAndReload(updateFolders); + + // Refresh sidebar after reload if folders were updated + if (updateFolders && this.sidebarManager) { + await this.sidebarManager.refresh(); + } } catch (error) { console.error(`Error reloading ${this.pageType}:`, error); showToast(`Failed to reload ${this.pageType}: ${error.message}`, 'error'); @@ -426,6 +309,11 @@ export class PageControls { try { await this.api.refreshModels(fullRebuild); + + // Refresh sidebar after rebuild + if (this.sidebarManager) { + await this.sidebarManager.refresh(); + } } catch (error) { console.error(`Error ${fullRebuild ? 'rebuilding' : 'refreshing'} ${this.pageType}:`, error); showToast(`Failed to ${fullRebuild ? 'rebuild' : 'refresh'} ${this.pageType}: ${error.message}`, 'error'); @@ -547,4 +435,13 @@ export class PageControls { console.error('Model duplicates manager not available'); } } + + /** + * Clean up resources + */ + destroy() { + if (this.sidebarManager) { + this.sidebarManager.destroy(); + } + } } \ No newline at end of file diff --git a/static/js/embeddings.js b/static/js/embeddings.js index f32fd670..c2276ce8 100644 --- a/static/js/embeddings.js +++ b/static/js/embeddings.js @@ -30,10 +30,6 @@ class EmbeddingsPageManager { } async initialize() { - // Initialize page-specific components - this.pageControls.restoreFolderFilter(); - this.pageControls.initFolderTagsVisibility(); - // Initialize context menu new EmbeddingContextMenu(); diff --git a/static/js/loras.js b/static/js/loras.js index e1177b93..d8820cac 100644 --- a/static/js/loras.js +++ b/static/js/loras.js @@ -38,8 +38,6 @@ class LoraPageManager { async initialize() { // Initialize page-specific components - this.pageControls.restoreFolderFilter(); - this.pageControls.initFolderTagsVisibility(); new LoraContextMenu(); // Initialize cards for current bulk mode state (should be false initially) diff --git a/static/js/managers/SearchManager.js b/static/js/managers/SearchManager.js index d6c43b05..5ff1e81c 100644 --- a/static/js/managers/SearchManager.js +++ b/static/js/managers/SearchManager.js @@ -19,7 +19,6 @@ export class SearchManager { this.searchOptionsPanel = document.getElementById('searchOptionsPanel'); this.closeSearchOptions = document.getElementById('closeSearchOptions'); this.searchOptionTags = document.querySelectorAll('.search-option-tag'); - this.recursiveSearchToggle = document.getElementById('recursiveSearchToggle'); this.searchTimeout = null; this.currentPage = options.page || document.body.dataset.page || 'loras'; @@ -112,14 +111,6 @@ export class SearchManager { }); } - // Recursive search toggle - if (this.recursiveSearchToggle) { - this.recursiveSearchToggle.addEventListener('change', () => { - this.saveSearchPreferences(); - this.performSearch(); - }); - } - // Add global click handler to close panels when clicking outside document.addEventListener('click', (e) => { // Close search options panel when clicking outside @@ -218,11 +209,6 @@ export class SearchManager { }); } - // Apply recursive search - only if the toggle exists - if (this.recursiveSearchToggle && preferences.recursive !== undefined) { - this.recursiveSearchToggle.checked = preferences.recursive; - } - // Ensure at least one search option is selected this.validateSearchOptions(); } catch (error) { @@ -272,11 +258,6 @@ export class SearchManager { options }; - // Only add recursive option if the toggle exists - if (this.recursiveSearchToggle) { - preferences.recursive = this.recursiveSearchToggle.checked; - } - setStorageItem(`${this.currentPage}_search_prefs`, preferences); } catch (error) { console.error('Error saving search preferences:', error); @@ -294,7 +275,6 @@ export class SearchManager { performSearch() { const query = this.searchInput.value.trim(); const options = this.getActiveSearchOptions(); - const recursive = this.recursiveSearchToggle ? this.recursiveSearchToggle.checked : false; // Update the state with search parameters const pageState = getCurrentPageState(); @@ -318,16 +298,14 @@ export class SearchManager { filename: options.filename || false, modelname: options.modelname || false, tags: options.tags || false, - creator: options.creator || false, - recursive: recursive + creator: options.creator || false }; } else if (this.currentPage === 'checkpoints') { pageState.searchOptions = { filename: options.filename || false, modelname: options.modelname || false, tags: options.tags || false, - creator: options.creator || false, - recursive: recursive + creator: options.creator || false }; } } diff --git a/static/js/state/index.js b/static/js/state/index.js index 26427c2b..f905eb09 100644 --- a/static/js/state/index.js +++ b/static/js/state/index.js @@ -29,7 +29,7 @@ export const state = { isLoading: false, hasMore: true, sortBy: 'name', - activeFolder: null, + activeFolder: getStorageItem(`${MODEL_TYPES.LORA}_activeFolder`), activeLetterFilter: null, previewVersions: loraPreviewVersions, searchManager: null, @@ -38,7 +38,7 @@ export const state = { modelname: true, tags: false, creator: false, - recursive: false + recursive: true, }, filters: { baseModel: [], @@ -78,14 +78,14 @@ export const state = { isLoading: false, hasMore: true, sortBy: 'name', - activeFolder: null, + activeFolder: getStorageItem(`${MODEL_TYPES.CHECKPOINT}_activeFolder`), previewVersions: checkpointPreviewVersions, searchManager: null, searchOptions: { filename: true, modelname: true, creator: false, - recursive: false + recursive: true, }, filters: { baseModel: [], @@ -104,7 +104,7 @@ export const state = { isLoading: false, hasMore: true, sortBy: 'name', - activeFolder: null, + activeFolder: getStorageItem(`${MODEL_TYPES.EMBEDDING}_activeFolder`), activeLetterFilter: null, previewVersions: embeddingPreviewVersions, searchManager: null, @@ -113,7 +113,7 @@ export const state = { modelname: true, tags: false, creator: false, - recursive: false + recursive: true, }, filters: { baseModel: [], diff --git a/templates/checkpoints.html b/templates/checkpoints.html index 8a205e23..64a16269 100644 --- a/templates/checkpoints.html +++ b/templates/checkpoints.html @@ -31,6 +31,7 @@ {% block content %} {% include 'components/controls.html' %} {% include 'components/duplicates_banner.html' %} + {% include 'components/folder_sidebar.html' %}
@@ -38,6 +39,10 @@
{% endblock %} +{% block overlay %} +
+{% endblock %} + {% block main_script %} {% endblock %} diff --git a/templates/components/controls.html b/templates/components/controls.html index c9c25a3a..7ead740c 100644 --- a/templates/components/controls.html +++ b/templates/components/controls.html @@ -1,14 +1,4 @@
- {% if folders|length > 1 %} -
-
- {% for folder in folders %} -
{{ folder }}
- {% endfor %} -
-
- {% endif %} -
@@ -75,12 +65,6 @@
-
- -
-
@@ -107,6 +91,13 @@
+ + +
diff --git a/templates/components/folder_sidebar.html b/templates/components/folder_sidebar.html new file mode 100644 index 00000000..19981e47 --- /dev/null +++ b/templates/components/folder_sidebar.html @@ -0,0 +1,24 @@ + + + + +
+ + +
diff --git a/templates/components/header.html b/templates/components/header.html index d2358f5d..6b30e2c5 100644 --- a/templates/components/header.html +++ b/templates/components/header.html @@ -101,17 +101,6 @@ {% endif %}
- {% if request.path != '/loras/recipes' %} -
-
- Include Subfolders - -
-
- {% endif %} diff --git a/templates/embeddings.html b/templates/embeddings.html index e5e9cbb3..4819c3a6 100644 --- a/templates/embeddings.html +++ b/templates/embeddings.html @@ -31,6 +31,7 @@ {% block content %} {% include 'components/controls.html' %} {% include 'components/duplicates_banner.html' %} + {% include 'components/folder_sidebar.html' %}
@@ -38,6 +39,10 @@
{% endblock %} +{% block overlay %} +
+{% endblock %} + {% block main_script %} {% endblock %} diff --git a/templates/loras.html b/templates/loras.html index 4b1d494f..03060f1e 100644 --- a/templates/loras.html +++ b/templates/loras.html @@ -15,13 +15,14 @@ {% block content %} {% include 'components/controls.html' %} - {% include 'components/alphabet_bar.html' %} {% include 'components/duplicates_banner.html' %} + {% include 'components/folder_sidebar.html' %}
+ {% endblock %}