From 641fa8a3d93f212eebc4162c20155ff6a23e4bc0 Mon Sep 17 00:00:00 2001
From: Will Miao <13051207myq@gmail.com>
Date: Wed, 4 Jun 2025 16:46:57 +0800
Subject: [PATCH] Enhance duplicates mode functionality: add toggle for
entering/exiting mode, improve exit button styling, and manage control button
states during duplicates mode.
---
static/css/components/duplicates.css | 48 ++++++++++++-
.../js/components/ModelDuplicatesManager.js | 71 +++++++++++++++++++
static/js/components/controls/PageControls.js | 3 +-
static/js/components/controls/index.js | 14 ----
templates/checkpoints.html | 4 +-
templates/loras.html | 4 +-
6 files changed, 124 insertions(+), 20 deletions(-)
diff --git a/static/css/components/duplicates.css b/static/css/components/duplicates.css
index 54fd6588..2372ed64 100644
--- a/static/css/components/duplicates.css
+++ b/static/css/components/duplicates.css
@@ -50,6 +50,29 @@
align-items: center;
}
+/* Improved exit button in banner */
+.duplicates-banner button.btn-exit-mode {
+ min-width: 120px;
+ background-color: var(--card-bg);
+ color: var(--text-color);
+ border: 1px solid var(--border-color);
+ padding: 6px 12px;
+ border-radius: var(--border-radius-xs);
+ font-size: 0.85em;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 6px;
+ transition: all 0.2s ease;
+}
+
+.duplicates-banner button.btn-exit-mode:hover {
+ background-color: var(--bg-color);
+ border-color: var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h);
+ transform: translateY(-1px);
+}
+
.duplicates-banner button {
min-width: 100px;
display: flex;
@@ -194,7 +217,7 @@
}
.group-toggle-btn:hover {
- border-color: var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h);
+ border-color: var(--lora-accent-l) var(--lora-accent-c) var (--lora-accent-h);
transform: translateY(-1px);
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.08);
}
@@ -363,3 +386,26 @@ html[data-theme="dark"] .duplicates-banner {
html[data-theme="dark"] .duplicate-group {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25); /* Stronger shadow in dark mode */
}
+
+/* Styles for disabled controls during duplicates mode */
+.disabled-during-duplicates {
+ opacity: 0.5 !important;
+ pointer-events: none !important;
+ cursor: not-allowed !important;
+ user-select: none !important;
+ filter: grayscale(50%) !important;
+}
+
+/* Make the active duplicates button more prominent */
+#findDuplicatesBtn.active {
+ background: var(--lora-accent);
+ color: white;
+ border-color: var(--lora-accent);
+ box-shadow: 0 0 0 2px oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.25);
+ position: relative;
+ z-index: 5;
+}
+
+#findDuplicatesBtn.active:hover {
+ background: oklch(calc(var(--lora-accent-l) - 5%) var(--lora-accent-c) var(--lora-accent-h));
+}
diff --git a/static/js/components/ModelDuplicatesManager.js b/static/js/components/ModelDuplicatesManager.js
index c003f691..f0a9b2e1 100644
--- a/static/js/components/ModelDuplicatesManager.js
+++ b/static/js/components/ModelDuplicatesManager.js
@@ -16,6 +16,9 @@ export class ModelDuplicatesManager {
this.renderTooltip = this.renderTooltip.bind(this);
this.checkDuplicatesCount = this.checkDuplicatesCount.bind(this);
+ // Keep track of which controls need to be re-enabled
+ this.disabledControls = [];
+
// Check for duplicates on load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', this.checkDuplicatesCount);
@@ -62,6 +65,15 @@ export class ModelDuplicatesManager {
}
}
+ // Toggle method to enter/exit duplicates mode
+ toggleDuplicateMode() {
+ if (this.inDuplicateMode) {
+ this.exitDuplicateMode();
+ } else {
+ this.findDuplicates();
+ }
+ }
+
async findDuplicates() {
try {
// Determine API endpoint based on model type
@@ -126,6 +138,18 @@ export class ModelDuplicatesManager {
// Update selected count
this.updateSelectedCount();
+
+ // Update Duplicates button to show active state
+ const duplicatesBtn = document.getElementById('findDuplicatesBtn');
+ if (duplicatesBtn) {
+ duplicatesBtn.classList.add('active');
+ duplicatesBtn.title = 'Exit Duplicates Mode';
+ // Change icon and text to indicate it's now an exit button
+ duplicatesBtn.innerHTML = ' Exit Duplicates';
+ }
+
+ // Disable all control buttons except the duplicates button
+ this.disableControlButtons();
}
exitDuplicateMode() {
@@ -153,6 +177,53 @@ export class ModelDuplicatesManager {
// Re-enable virtual scrolling
state.virtualScroller.enable();
+
+ // Restore Duplicates button to its original state
+ const duplicatesBtn = document.getElementById('findDuplicatesBtn');
+ if (duplicatesBtn) {
+ duplicatesBtn.classList.remove('active');
+ duplicatesBtn.title = 'Find duplicate models';
+ duplicatesBtn.innerHTML = ' Duplicates ';
+
+ // Restore badge
+ const newBadge = duplicatesBtn.querySelector('#duplicatesBadge');
+ const oldBadge = document.getElementById('duplicatesBadge');
+ if (oldBadge && oldBadge.textContent) {
+ newBadge.textContent = oldBadge.textContent;
+ newBadge.classList.add('pulse');
+ }
+ }
+
+ // Re-enable all control buttons
+ this.enableControlButtons();
+
+ this.checkDuplicatesCount();
+ }
+
+ // Disable all control buttons except the duplicates button
+ disableControlButtons() {
+ this.disabledControls = [];
+
+ // Select all control buttons except the duplicates button
+ const controlButtons = document.querySelectorAll('.control-group button:not(#findDuplicatesBtn), .dropdown-group, .toggle-folders-btn, #favoriteFilterBtn');
+
+ controlButtons.forEach(button => {
+ // Only disable enabled buttons (don't disable already disabled buttons)
+ if (!button.disabled && !button.classList.contains('disabled')) {
+ this.disabledControls.push(button);
+ button.disabled = true;
+ button.classList.add('disabled-during-duplicates');
+ }
+ });
+ }
+
+ // Re-enable all previously disabled control buttons
+ enableControlButtons() {
+ this.disabledControls.forEach(button => {
+ button.disabled = false;
+ button.classList.remove('disabled-during-duplicates');
+ });
+ this.disabledControls = [];
}
renderDuplicateGroups() {
diff --git a/static/js/components/controls/PageControls.js b/static/js/components/controls/PageControls.js
index 3aab5a4c..42ad0062 100644
--- a/static/js/components/controls/PageControls.js
+++ b/static/js/components/controls/PageControls.js
@@ -516,7 +516,8 @@ export class PageControls {
*/
findDuplicates() {
if (window.modelDuplicatesManager) {
- window.modelDuplicatesManager.findDuplicates();
+ // Change to toggle functionality
+ window.modelDuplicatesManager.toggleDuplicateMode();
} else {
console.error('Model duplicates manager not available');
}
diff --git a/static/js/components/controls/index.js b/static/js/components/controls/index.js
index e139fd5d..c767c62f 100644
--- a/static/js/components/controls/index.js
+++ b/static/js/components/controls/index.js
@@ -2,7 +2,6 @@
import { PageControls } from './PageControls.js';
import { LorasControls } from './LorasControls.js';
import { CheckpointsControls } from './CheckpointsControls.js';
-import { refreshVirtualScroll } from '../../utils/infiniteScroll.js';
// Export the classes
export { PageControls, LorasControls, CheckpointsControls };
@@ -21,17 +20,4 @@ export function createPageControls(pageType) {
console.error(`Unknown page type: ${pageType}`);
return null;
}
-}
-
-// Example for a filter method:
-function applyFilter(filterType, value) {
- // ...existing filter logic...
-
- // After filters are applied, refresh the virtual scroll if it exists
- if (state.virtualScroller) {
- refreshVirtualScroll();
- } else {
- // Fall back to existing reset and reload logic
- resetAndReload(true);
- }
}
\ No newline at end of file
diff --git a/templates/checkpoints.html b/templates/checkpoints.html
index 024e4f9f..702b6b95 100644
--- a/templates/checkpoints.html
+++ b/templates/checkpoints.html
@@ -41,8 +41,8 @@
-