mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-25 15:15:44 -03:00
Add full rebuild option to model refresh functionality and enhance dropdown controls
This commit is contained in:
@@ -107,7 +107,10 @@ 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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user