Add full rebuild option to model refresh functionality and enhance dropdown controls

This commit is contained in:
Will Miao
2025-05-29 15:51:45 +08:00
parent ddf132bd78
commit bae66f94e8
10 changed files with 171 additions and 23 deletions

View File

@@ -106,8 +106,11 @@ class ApiRoutes:
async def scan_loras(self, request: web.Request) -> web.Response: async def scan_loras(self, request: web.Request) -> web.Response:
"""Force a rescan of LoRA files""" """Force a rescan of LoRA files"""
try: try:
await self.scanner.get_cached_data(force_refresh=True) # Get full_rebuild parameter from query string, default to false
full_rebuild = request.query.get('full_rebuild', 'false').lower() == 'true'
await self.scanner.get_cached_data(force_refresh=True, rebuild_cache=full_rebuild)
return web.json_response({"status": "success", "message": "LoRA scan completed"}) return web.json_response({"status": "success", "message": "LoRA scan completed"})
except Exception as e: except Exception as e:
logger.error(f"Error in scan_loras: {e}", exc_info=True) logger.error(f"Error in scan_loras: {e}", exc_info=True)

View File

@@ -420,7 +420,10 @@ class CheckpointsRoutes:
async def scan_checkpoints(self, request): async def scan_checkpoints(self, request):
"""Force a rescan of checkpoint files""" """Force a rescan of checkpoint files"""
try: try:
await self.scanner.get_cached_data(force_refresh=True) # Get the full_rebuild parameter and convert to bool, default to False
full_rebuild = request.query.get('full_rebuild', 'false').lower() == 'true'
await self.scanner.get_cached_data(force_refresh=True, rebuild_cache=full_rebuild)
return web.json_response({"status": "success", "message": "Checkpoint scan completed"}) return web.json_response({"status": "success", "message": "Checkpoint scan completed"})
except Exception as e: except Exception as e:
logger.error(f"Error in scan_checkpoints: {e}", exc_info=True) logger.error(f"Error in scan_checkpoints: {e}", exc_info=True)

View File

@@ -333,6 +333,66 @@
user-select: none; user-select: none;
} }
/* Dropdown Button Styling */
.dropdown-group {
position: relative;
display: flex;
}
.dropdown-main {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
.dropdown-toggle {
width: 24px !important;
min-width: unset !important;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
padding: 0 !important;
}
.dropdown-menu {
position: absolute;
top: 100%;
left: 0;
z-index: 1000;
display: none;
min-width: 200px;
padding: 5px 0;
margin: 2px 0 0;
font-size: 0.85em;
background-color: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: var(--border-radius-xs);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
}
.dropdown-group.active .dropdown-menu {
display: block;
}
.dropdown-item {
display: block;
padding: 6px 15px;
clear: both;
font-weight: 400;
color: var(--text-color);
cursor: pointer;
transition: background-color 0.2s ease;
}
.dropdown-item:hover {
background-color: oklch(var(--lora-accent) / 0.1);
}
.dropdown-item i {
margin-right: 8px;
width: 16px;
text-align: center;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.actions { .actions {
flex-wrap: wrap; flex-wrap: wrap;
@@ -378,4 +438,9 @@
.back-to-top { .back-to-top {
bottom: 60px; /* Give some extra space from bottom on mobile */ bottom: 60px; /* Give some extra space from bottom on mobile */
} }
.dropdown-menu {
left: auto;
right: 0; /* Align to right on mobile */
}
} }

View File

@@ -499,13 +499,18 @@ export async function refreshModels(options = {}) {
const { const {
modelType = 'lora', modelType = 'lora',
scanEndpoint = '/api/loras/scan', scanEndpoint = '/api/loras/scan',
resetAndReloadFunction resetAndReloadFunction,
fullRebuild = false // New parameter with default value false
} = options; } = options;
try { try {
state.loadingManager.showSimpleLoading(`Refreshing ${modelType}s...`); state.loadingManager.showSimpleLoading(`${fullRebuild ? 'Full rebuild' : 'Refreshing'} ${modelType}s...`);
const response = await fetch(scanEndpoint); // Add fullRebuild parameter to the request
const url = new URL(scanEndpoint, window.location.origin);
url.searchParams.append('full_rebuild', fullRebuild);
const response = await fetch(url);
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to refresh ${modelType}s: ${response.status} ${response.statusText}`); throw new Error(`Failed to refresh ${modelType}s: ${response.status} ${response.statusText}`);
@@ -515,10 +520,10 @@ export async function refreshModels(options = {}) {
await resetAndReloadFunction(true); // update folders await resetAndReloadFunction(true); // update folders
} }
showToast(`Refresh complete`, 'success'); showToast(`${fullRebuild ? 'Full rebuild' : 'Refresh'} complete`, 'success');
} catch (error) { } catch (error) {
console.error(`Refresh failed:`, error); console.error(`Refresh failed:`, error);
showToast(`Failed to refresh ${modelType}s`, 'error'); showToast(`Failed to ${fullRebuild ? 'rebuild' : 'refresh'} ${modelType}s`, 'error');
} finally { } finally {
state.loadingManager.hide(); state.loadingManager.hide();
state.loadingManager.restoreProgressBar(); state.loadingManager.restoreProgressBar();

View File

@@ -76,11 +76,12 @@ export async function resetAndReload(updateFolders = false) {
} }
// Refresh checkpoints // Refresh checkpoints
export async function refreshCheckpoints() { export async function refreshCheckpoints(fullRebuild = false) {
return baseRefreshModels({ return baseRefreshModels({
modelType: 'checkpoint', modelType: 'checkpoint',
scanEndpoint: '/api/checkpoints/scan', scanEndpoint: '/api/checkpoints/scan',
resetAndReloadFunction: resetAndReload resetAndReloadFunction: resetAndReload,
fullRebuild: fullRebuild
}); });
} }

View File

@@ -143,11 +143,12 @@ export async function resetAndReload(updateFolders = false) {
} }
} }
export async function refreshLoras() { export async function refreshLoras(fullRebuild = false) {
return baseRefreshModels({ return baseRefreshModels({
modelType: 'lora', modelType: 'lora',
scanEndpoint: '/api/loras/scan', scanEndpoint: '/api/loras/scan',
resetAndReloadFunction: resetAndReload resetAndReloadFunction: resetAndReload,
fullRebuild: fullRebuild
}); });
} }

View File

@@ -33,8 +33,8 @@ export class CheckpointsControls extends PageControls {
return await resetAndReload(updateFolders); return await resetAndReload(updateFolders);
}, },
refreshModels: async () => { refreshModels: async (fullRebuild = false) => {
return await refreshCheckpoints(); return await refreshCheckpoints(fullRebuild);
}, },
// Add fetch from Civitai functionality for checkpoints // Add fetch from Civitai functionality for checkpoints

View File

@@ -36,8 +36,8 @@ export class LorasControls extends PageControls {
return await resetAndReload(updateFolders); return await resetAndReload(updateFolders);
}, },
refreshModels: async () => { refreshModels: async (fullRebuild = false) => {
return await refreshLoras(); return await refreshLoras(fullRebuild);
}, },
// LoRA-specific API functions // LoRA-specific API functions

View File

@@ -83,9 +83,12 @@ export class PageControls {
// Refresh button handler // Refresh button handler
const refreshBtn = document.querySelector('[data-action="refresh"]'); const refreshBtn = document.querySelector('[data-action="refresh"]');
if (refreshBtn) { if (refreshBtn) {
refreshBtn.addEventListener('click', () => this.refreshModels()); refreshBtn.addEventListener('click', () => this.refreshModels(false)); // Regular refresh (incremental)
} }
// Initialize dropdown functionality
this.initDropdowns();
// Toggle folders button // Toggle folders button
const toggleFoldersBtn = document.querySelector('.toggle-folders-btn'); const toggleFoldersBtn = document.querySelector('.toggle-folders-btn');
if (toggleFoldersBtn) { if (toggleFoldersBtn) {
@@ -102,6 +105,61 @@ export class PageControls {
this.initPageSpecificListeners(); this.initPageSpecificListeners();
} }
/**
* Initialize dropdown functionality
*/
initDropdowns() {
// Handle dropdown toggles
const dropdownToggles = document.querySelectorAll('.dropdown-toggle');
dropdownToggles.forEach(toggle => {
toggle.addEventListener('click', (e) => {
e.stopPropagation(); // Prevent triggering parent button
const dropdownGroup = toggle.closest('.dropdown-group');
// Close all other open dropdowns first
document.querySelectorAll('.dropdown-group.active').forEach(group => {
if (group !== dropdownGroup) {
group.classList.remove('active');
}
});
// Toggle current dropdown
dropdownGroup.classList.toggle('active');
});
});
// Handle quick refresh option
const quickRefreshOption = document.querySelector('[data-action="quick-refresh"]');
if (quickRefreshOption) {
quickRefreshOption.addEventListener('click', (e) => {
e.stopPropagation();
this.refreshModels(false);
// Close the dropdown
document.querySelector('.dropdown-group.active')?.classList.remove('active');
});
}
// Handle full rebuild option
const fullRebuildOption = document.querySelector('[data-action="full-rebuild"]');
if (fullRebuildOption) {
fullRebuildOption.addEventListener('click', (e) => {
e.stopPropagation();
this.refreshModels(true);
// Close the dropdown
document.querySelector('.dropdown-group.active')?.classList.remove('active');
});
}
// Close dropdowns when clicking outside
document.addEventListener('click', (e) => {
if (!e.target.closest('.dropdown-group')) {
document.querySelectorAll('.dropdown-group.active').forEach(group => {
group.classList.remove('active');
});
}
});
}
/** /**
* Initialize page-specific event listeners * Initialize page-specific event listeners
*/ */
@@ -327,18 +385,19 @@ export class PageControls {
/** /**
* Refresh models list * Refresh models list
* @param {boolean} fullRebuild - Whether to perform a full rebuild
*/ */
async refreshModels() { async refreshModels(fullRebuild = false) {
if (!this.api) { if (!this.api) {
console.error('API methods not registered'); console.error('API methods not registered');
return; return;
} }
try { try {
await this.api.refreshModels(); await this.api.refreshModels(fullRebuild);
} catch (error) { } catch (error) {
console.error(`Error refreshing ${this.pageType}:`, error); console.error(`Error ${fullRebuild ? 'rebuilding' : 'refreshing'} ${this.pageType}:`, error);
showToast(`Failed to refresh ${this.pageType}: ${error.message}`, 'error'); showToast(`Failed to ${fullRebuild ? 'rebuild' : 'refresh'} ${this.pageType}: ${error.message}`, 'error');
} }
} }

View File

@@ -15,8 +15,19 @@
<option value="date">Date</option> <option value="date">Date</option>
</select> </select>
</div> </div>
<div title="Refresh model list" class="control-group"> <div title="Refresh model list" class="control-group dropdown-group">
<button data-action="refresh"><i class="fas fa-sync"></i> Refresh</button> <button data-action="refresh" class="dropdown-main"><i class="fas fa-sync"></i> Refresh</button>
<button class="dropdown-toggle" aria-label="Show refresh options">
<i class="fas fa-caret-down"></i>
</button>
<div class="dropdown-menu">
<div class="dropdown-item" data-action="quick-refresh">
<i class="fas fa-bolt"></i> Quick Refresh (incremental)
</div>
<div class="dropdown-item" data-action="full-rebuild">
<i class="fas fa-tools"></i> Full Rebuild (complete)
</div>
</div>
</div> </div>
<div class="control-group"> <div class="control-group">