mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-24 06:32:12 -03:00
Implement virtual scrolling for model loading and checkpoint management
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
// filepath: d:\Workspace\ComfyUI\custom_nodes\ComfyUI-Lora-Manager\static\js\api\baseModelApi.js
|
// filepath: d:\Workspace\ComfyUI\custom_nodes\ComfyUI-Lora-Manager\static\js\api\baseModelApi.js
|
||||||
import { state, getCurrentPageState } from '../state/index.js';
|
import { state, getCurrentPageState } from '../state/index.js';
|
||||||
import { showToast } from '../utils/uiHelpers.js';
|
import { showToast } from '../utils/uiHelpers.js';
|
||||||
import { showDeleteModal, confirmDelete } from '../utils/modalUtils.js';
|
|
||||||
import { getSessionItem, saveMapToStorage } from '../utils/storageHelpers.js';
|
import { getSessionItem, saveMapToStorage } from '../utils/storageHelpers.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -279,6 +278,112 @@ export async function fetchModelsPage(options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset and reload models using virtual scrolling
|
||||||
|
* @param {Object} options - Operation options
|
||||||
|
* @returns {Promise<Object>} The fetch result
|
||||||
|
*/
|
||||||
|
export async function resetAndReloadWithVirtualScroll(options = {}) {
|
||||||
|
const {
|
||||||
|
modelType = 'lora',
|
||||||
|
updateFolders = false,
|
||||||
|
fetchPageFunction
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const pageState = getCurrentPageState();
|
||||||
|
|
||||||
|
try {
|
||||||
|
pageState.isLoading = true;
|
||||||
|
document.body.classList.add('loading');
|
||||||
|
|
||||||
|
// Reset page counter
|
||||||
|
pageState.currentPage = 1;
|
||||||
|
|
||||||
|
// Fetch the first page
|
||||||
|
const result = await fetchPageFunction(1, pageState.pageSize || 50);
|
||||||
|
|
||||||
|
// Update the virtual scroller
|
||||||
|
state.virtualScroller.refreshWithData(
|
||||||
|
result.items,
|
||||||
|
result.totalItems,
|
||||||
|
result.hasMore
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
pageState.hasMore = result.hasMore;
|
||||||
|
pageState.currentPage = 2; // Next page will be 2
|
||||||
|
|
||||||
|
// Update folders if needed
|
||||||
|
if (updateFolders && result.folders) {
|
||||||
|
updateFolderTags(result.folders);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error reloading ${modelType}s:`, error);
|
||||||
|
showToast(`Failed to reload ${modelType}s: ${error.message}`, 'error');
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
pageState.isLoading = false;
|
||||||
|
document.body.classList.remove('loading');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load more models using virtual scrolling
|
||||||
|
* @param {Object} options - Operation options
|
||||||
|
* @returns {Promise<Object>} The fetch result
|
||||||
|
*/
|
||||||
|
export async function loadMoreWithVirtualScroll(options = {}) {
|
||||||
|
const {
|
||||||
|
modelType = 'lora',
|
||||||
|
resetPage = false,
|
||||||
|
updateFolders = false,
|
||||||
|
fetchPageFunction
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const pageState = getCurrentPageState();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Start loading state
|
||||||
|
pageState.isLoading = true;
|
||||||
|
document.body.classList.add('loading');
|
||||||
|
|
||||||
|
// Reset to first page if requested
|
||||||
|
if (resetPage) {
|
||||||
|
pageState.currentPage = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the first page of data
|
||||||
|
const result = await fetchPageFunction(pageState.currentPage, pageState.pageSize || 50);
|
||||||
|
|
||||||
|
// Update virtual scroller with the new data
|
||||||
|
state.virtualScroller.refreshWithData(
|
||||||
|
result.items,
|
||||||
|
result.totalItems,
|
||||||
|
result.hasMore
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
pageState.hasMore = result.hasMore;
|
||||||
|
pageState.currentPage = 2; // Next page to load would be 2
|
||||||
|
|
||||||
|
// Update folders if needed
|
||||||
|
if (updateFolders && result.folders) {
|
||||||
|
updateFolderTags(result.folders);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error loading ${modelType}s:`, error);
|
||||||
|
showToast(`Failed to load ${modelType}s: ${error.message}`, 'error');
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
pageState.isLoading = false;
|
||||||
|
document.body.classList.remove('loading');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update folder tags in the UI
|
// Update folder tags in the UI
|
||||||
export function updateFolderTags(folders) {
|
export function updateFolderTags(folders) {
|
||||||
const folderTagsContainer = document.querySelector('.folder-tags');
|
const folderTagsContainer = document.querySelector('.folder-tags');
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { createCheckpointCard } from '../components/CheckpointCard.js';
|
import { createCheckpointCard } from '../components/CheckpointCard.js';
|
||||||
import {
|
import {
|
||||||
loadMoreModels,
|
loadMoreModels,
|
||||||
|
fetchModelsPage,
|
||||||
resetAndReload as baseResetAndReload,
|
resetAndReload as baseResetAndReload,
|
||||||
|
resetAndReloadWithVirtualScroll,
|
||||||
|
loadMoreWithVirtualScroll,
|
||||||
refreshModels as baseRefreshModels,
|
refreshModels as baseRefreshModels,
|
||||||
deleteModel as baseDeleteModel,
|
deleteModel as baseDeleteModel,
|
||||||
replaceModelPreview,
|
replaceModelPreview,
|
||||||
@@ -9,25 +12,67 @@ import {
|
|||||||
refreshSingleModelMetadata,
|
refreshSingleModelMetadata,
|
||||||
excludeModel as baseExcludeModel
|
excludeModel as baseExcludeModel
|
||||||
} from './baseModelApi.js';
|
} from './baseModelApi.js';
|
||||||
|
import { state } from '../state/index.js';
|
||||||
|
|
||||||
// Load more checkpoints with pagination
|
/**
|
||||||
export async function loadMoreCheckpoints(resetPagination = true) {
|
* Fetch checkpoints with pagination for virtual scrolling
|
||||||
return loadMoreModels({
|
* @param {number} page - Page number to fetch
|
||||||
resetPage: resetPagination,
|
* @param {number} pageSize - Number of items per page
|
||||||
updateFolders: true,
|
* @returns {Promise<Object>} Object containing items, total count, and pagination info
|
||||||
|
*/
|
||||||
|
export async function fetchCheckpointsPage(page = 1, pageSize = 100) {
|
||||||
|
return fetchModelsPage({
|
||||||
modelType: 'checkpoint',
|
modelType: 'checkpoint',
|
||||||
createCardFunction: createCheckpointCard,
|
page,
|
||||||
|
pageSize,
|
||||||
endpoint: '/api/checkpoints'
|
endpoint: '/api/checkpoints'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load more checkpoints with pagination - updated to work with VirtualScroller
|
||||||
|
* @param {boolean} resetPage - Whether to reset to the first page
|
||||||
|
* @param {boolean} updateFolders - Whether to update folder tags
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
export async function loadMoreCheckpoints(resetPage = false, updateFolders = false) {
|
||||||
|
// Check if virtual scroller is available
|
||||||
|
if (state.virtualScroller) {
|
||||||
|
return loadMoreWithVirtualScroll({
|
||||||
|
modelType: 'checkpoint',
|
||||||
|
resetPage,
|
||||||
|
updateFolders,
|
||||||
|
fetchPageFunction: fetchCheckpointsPage
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fall back to the original implementation if virtual scroller isn't available
|
||||||
|
return loadMoreModels({
|
||||||
|
resetPage,
|
||||||
|
updateFolders,
|
||||||
|
modelType: 'checkpoint',
|
||||||
|
createCardFunction: createCheckpointCard,
|
||||||
|
endpoint: '/api/checkpoints'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Reset and reload checkpoints
|
// Reset and reload checkpoints
|
||||||
export async function resetAndReload() {
|
export async function resetAndReload(updateFolders = false) {
|
||||||
return baseResetAndReload({
|
// Check if virtual scroller is available
|
||||||
updateFolders: true,
|
if (state.virtualScroller) {
|
||||||
modelType: 'checkpoint',
|
return resetAndReloadWithVirtualScroll({
|
||||||
loadMoreFunction: loadMoreCheckpoints
|
modelType: 'checkpoint',
|
||||||
});
|
updateFolders,
|
||||||
|
fetchPageFunction: fetchCheckpointsPage
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Fall back to original implementation
|
||||||
|
return baseResetAndReload({
|
||||||
|
updateFolders,
|
||||||
|
modelType: 'checkpoint',
|
||||||
|
loadMoreFunction: loadMoreCheckpoints
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refresh checkpoints
|
// Refresh checkpoints
|
||||||
@@ -60,7 +105,11 @@ export async function fetchCivitai() {
|
|||||||
|
|
||||||
// Refresh single checkpoint metadata
|
// Refresh single checkpoint metadata
|
||||||
export async function refreshSingleCheckpointMetadata(filePath) {
|
export async function refreshSingleCheckpointMetadata(filePath) {
|
||||||
return refreshSingleModelMetadata(filePath, 'checkpoint');
|
const success = await refreshSingleModelMetadata(filePath, 'checkpoint');
|
||||||
|
if (success) {
|
||||||
|
// Reload the current view to show updated data
|
||||||
|
await resetAndReload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import {
|
|||||||
loadMoreModels,
|
loadMoreModels,
|
||||||
fetchModelsPage,
|
fetchModelsPage,
|
||||||
resetAndReload as baseResetAndReload,
|
resetAndReload as baseResetAndReload,
|
||||||
|
resetAndReloadWithVirtualScroll,
|
||||||
|
loadMoreWithVirtualScroll,
|
||||||
refreshModels as baseRefreshModels,
|
refreshModels as baseRefreshModels,
|
||||||
deleteModel as baseDeleteModel,
|
deleteModel as baseDeleteModel,
|
||||||
replaceModelPreview,
|
replaceModelPreview,
|
||||||
@@ -58,45 +60,12 @@ export async function loadMoreLoras(resetPage = false, updateFolders = false) {
|
|||||||
|
|
||||||
// Check if virtual scroller is available
|
// Check if virtual scroller is available
|
||||||
if (state.virtualScroller) {
|
if (state.virtualScroller) {
|
||||||
try {
|
return loadMoreWithVirtualScroll({
|
||||||
// Start loading state
|
modelType: 'lora',
|
||||||
pageState.isLoading = true;
|
resetPage,
|
||||||
document.body.classList.add('loading');
|
updateFolders,
|
||||||
|
fetchPageFunction: fetchLorasPage
|
||||||
// Reset to first page if requested
|
});
|
||||||
if (resetPage) {
|
|
||||||
pageState.currentPage = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch the first page of data
|
|
||||||
const result = await fetchLorasPage(pageState.currentPage, pageState.pageSize || 50);
|
|
||||||
|
|
||||||
// Update virtual scroller with the new data
|
|
||||||
state.virtualScroller.refreshWithData(
|
|
||||||
result.items,
|
|
||||||
result.totalItems,
|
|
||||||
result.hasMore
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update state
|
|
||||||
pageState.hasMore = result.hasMore;
|
|
||||||
pageState.currentPage = 2; // Next page to load would be 2
|
|
||||||
|
|
||||||
// Update folders if needed
|
|
||||||
if (updateFolders && result.folders) {
|
|
||||||
// Import function dynamically to avoid circular dependencies
|
|
||||||
const { updateFolderTags } = await import('./baseModelApi.js');
|
|
||||||
updateFolderTags(result.folders);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading loras:', error);
|
|
||||||
showToast(`Failed to load loras: ${error.message}`, 'error');
|
|
||||||
} finally {
|
|
||||||
pageState.isLoading = false;
|
|
||||||
document.body.classList.remove('loading');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Fall back to the original implementation if virtual scroller isn't available
|
// Fall back to the original implementation if virtual scroller isn't available
|
||||||
return loadMoreModels({
|
return loadMoreModels({
|
||||||
@@ -115,7 +84,7 @@ export async function loadMoreLoras(resetPage = false, updateFolders = false) {
|
|||||||
* @param {number} pageSize - Number of items per page
|
* @param {number} pageSize - Number of items per page
|
||||||
* @returns {Promise<Object>} Object containing items, total count, and pagination info
|
* @returns {Promise<Object>} Object containing items, total count, and pagination info
|
||||||
*/
|
*/
|
||||||
export async function fetchLorasPage(page = 1, pageSize = 50) {
|
export async function fetchLorasPage(page = 1, pageSize = 100) {
|
||||||
return fetchModelsPage({
|
return fetchModelsPage({
|
||||||
modelType: 'lora',
|
modelType: 'lora',
|
||||||
page,
|
page,
|
||||||
@@ -160,42 +129,11 @@ export async function resetAndReload(updateFolders = false) {
|
|||||||
|
|
||||||
// Check if virtual scroller is available
|
// Check if virtual scroller is available
|
||||||
if (state.virtualScroller) {
|
if (state.virtualScroller) {
|
||||||
try {
|
return resetAndReloadWithVirtualScroll({
|
||||||
pageState.isLoading = true;
|
modelType: 'lora',
|
||||||
document.body.classList.add('loading');
|
updateFolders,
|
||||||
|
fetchPageFunction: fetchLorasPage
|
||||||
// Reset page counter
|
});
|
||||||
pageState.currentPage = 1;
|
|
||||||
|
|
||||||
// Fetch the first page
|
|
||||||
const result = await fetchLorasPage(1, pageState.pageSize || 50);
|
|
||||||
|
|
||||||
// Update the virtual scroller
|
|
||||||
state.virtualScroller.refreshWithData(
|
|
||||||
result.items,
|
|
||||||
result.totalItems,
|
|
||||||
result.hasMore
|
|
||||||
);
|
|
||||||
|
|
||||||
// Update state
|
|
||||||
pageState.hasMore = result.hasMore;
|
|
||||||
pageState.currentPage = 2; // Next page will be 2
|
|
||||||
|
|
||||||
// Update folders if needed
|
|
||||||
if (updateFolders && result.folders) {
|
|
||||||
// Import function dynamically to avoid circular dependencies
|
|
||||||
const { updateFolderTags } = await import('./baseModelApi.js');
|
|
||||||
updateFolderTags(result.folders);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error reloading loras:', error);
|
|
||||||
showToast(`Failed to reload loras: ${error.message}`, 'error');
|
|
||||||
} finally {
|
|
||||||
pageState.isLoading = false;
|
|
||||||
document.body.classList.remove('loading');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Fall back to original implementation
|
// Fall back to original implementation
|
||||||
return baseResetAndReload({
|
return baseResetAndReload({
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
import { state, getCurrentPageState } from '../state/index.js';
|
import { state, getCurrentPageState } from '../state/index.js';
|
||||||
import { debounce } from './debounce.js';
|
|
||||||
import { VirtualScroller } from './VirtualScroller.js';
|
import { VirtualScroller } from './VirtualScroller.js';
|
||||||
import { createLoraCard, setupLoraCardEventDelegation } from '../components/LoraCard.js';
|
import { createLoraCard, setupLoraCardEventDelegation } from '../components/LoraCard.js';
|
||||||
|
import { createCheckpointCard } from '../components/CheckpointCard.js';
|
||||||
import { fetchLorasPage } from '../api/loraApi.js';
|
import { fetchLorasPage } from '../api/loraApi.js';
|
||||||
|
import { fetchCheckpointsPage } from '../api/checkpointApi.js';
|
||||||
import { showToast } from './uiHelpers.js';
|
import { showToast } from './uiHelpers.js';
|
||||||
|
|
||||||
// Function to dynamically import the appropriate card creator based on page type
|
// Function to dynamically import the appropriate card creator based on page type
|
||||||
@@ -18,13 +19,7 @@ async function getCardCreator(pageType) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else if (pageType === 'checkpoints') {
|
} else if (pageType === 'checkpoints') {
|
||||||
try {
|
return createCheckpointCard;
|
||||||
const { createCheckpointCard } = await import('../components/CheckpointCard.js');
|
|
||||||
return createCheckpointCard;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to load checkpoint card creator:', err);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -42,13 +37,7 @@ async function getDataFetcher(pageType) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
} else if (pageType === 'checkpoints') {
|
} else if (pageType === 'checkpoints') {
|
||||||
try {
|
return fetchCheckpointsPage;
|
||||||
const { fetchCheckpointsPage } = await import('../api/checkpointApi.js');
|
|
||||||
return fetchCheckpointsPage;
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Failed to load checkpoint data fetcher:', err);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -105,11 +94,11 @@ async function initializeVirtualScroll(pageType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Change this line to get the actual scrolling container
|
// Change this line to get the actual scrolling container
|
||||||
const pageContainer = document.querySelector('.page-content');
|
const scrollContainer = document.querySelector('.page-content');
|
||||||
const pageContent = pageContainer.querySelector('.container');
|
const gridContainer = scrollContainer.querySelector('.container');
|
||||||
|
|
||||||
if (!pageContent) {
|
if (!gridContainer) {
|
||||||
console.warn('Page content element not found for virtual scroll');
|
console.warn('Grid container element not found for virtual scroll');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,15 +111,15 @@ async function initializeVirtualScroll(pageType) {
|
|||||||
throw new Error(`Required components not available for ${pageType} page`);
|
throw new Error(`Required components not available for ${pageType} page`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass the correct scrolling container
|
// Initialize virtual scroller with renamed container elements
|
||||||
state.virtualScroller = new VirtualScroller({
|
state.virtualScroller = new VirtualScroller({
|
||||||
gridElement: grid,
|
gridElement: grid,
|
||||||
containerElement: pageContent,
|
containerElement: gridContainer,
|
||||||
scrollContainer: pageContainer, // Add this new parameter
|
scrollContainer: scrollContainer,
|
||||||
createItemFn: createCardFn,
|
createItemFn: createCardFn,
|
||||||
fetchItemsFn: fetchDataFn,
|
fetchItemsFn: fetchDataFn,
|
||||||
pageSize: 100,
|
pageSize: 100,
|
||||||
rowGap: 20 // Add consistent vertical spacing between rows
|
rowGap: 20
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initialize the virtual scroller
|
// Initialize the virtual scroller
|
||||||
|
|||||||
Reference in New Issue
Block a user