Enhance Checkpoints Manager: Implement API integration for checkpoints, add filtering and sorting options, and improve UI components for better user experience

This commit is contained in:
Will Miao
2025-04-10 16:04:08 +08:00
parent 048d486fa6
commit 252e90a633
10 changed files with 877 additions and 41 deletions

View File

@@ -0,0 +1,247 @@
import { state, getCurrentPageState } from '../state/index.js';
import { showToast } from '../utils/uiHelpers.js';
import { confirmDelete } from '../utils/modalUtils.js';
import { createCheckpointCard } from '../components/CheckpointCard.js';
// Load more checkpoints with pagination
export async function loadMoreCheckpoints(resetPagination = true) {
try {
const pageState = getCurrentPageState();
// Don't load if we're already loading or there are no more items
if (pageState.isLoading || (!resetPagination && !pageState.hasMore)) {
return;
}
// Set loading state
pageState.isLoading = true;
document.body.classList.add('loading');
// Reset pagination if requested
if (resetPagination) {
pageState.currentPage = 1;
const grid = document.getElementById('checkpointGrid');
if (grid) grid.innerHTML = '';
}
// Build API URL with parameters
const params = new URLSearchParams({
page: pageState.currentPage,
page_size: pageState.pageSize || 20,
sort: pageState.sortBy || 'name'
});
// Add folder filter if active
if (pageState.activeFolder) {
params.append('folder', pageState.activeFolder);
}
// Add search if available
if (pageState.filters && pageState.filters.search) {
params.append('search', pageState.filters.search);
// Add search options
if (pageState.searchOptions) {
params.append('search_filename', pageState.searchOptions.filename.toString());
params.append('search_modelname', pageState.searchOptions.modelname.toString());
params.append('recursive', pageState.searchOptions.recursive.toString());
}
}
// Add base model filters
if (pageState.filters && pageState.filters.baseModel && pageState.filters.baseModel.length > 0) {
pageState.filters.baseModel.forEach(model => {
params.append('base_model', model);
});
}
// Add tags filters
if (pageState.filters && pageState.filters.tags && pageState.filters.tags.length > 0) {
pageState.filters.tags.forEach(tag => {
params.append('tag', tag);
});
}
// Execute fetch
const response = await fetch(`/api/checkpoints?${params.toString()}`);
if (!response.ok) {
throw new Error(`Failed to load checkpoints: ${response.status} ${response.statusText}`);
}
const data = await response.json();
// Update state with response data
pageState.hasMore = data.page < data.total_pages;
// Update UI with checkpoints
const grid = document.getElementById('checkpointGrid');
if (!grid) {
return;
}
// Clear grid if this is the first page
if (resetPagination) {
grid.innerHTML = '';
}
// Check for empty result
if (data.items.length === 0 && resetPagination) {
grid.innerHTML = `
<div class="placeholder-message">
<p>No checkpoints found</p>
<p>Add checkpoints to your models folders to see them here.</p>
</div>
`;
return;
}
// Render checkpoint cards
data.items.forEach(checkpoint => {
const card = createCheckpointCard(checkpoint);
grid.appendChild(card);
});
} catch (error) {
console.error('Error loading checkpoints:', error);
showToast('Failed to load checkpoints', 'error');
} finally {
// Clear loading state
const pageState = getCurrentPageState();
pageState.isLoading = false;
document.body.classList.remove('loading');
}
}
// Reset and reload checkpoints
export async function resetAndReload() {
const pageState = getCurrentPageState();
pageState.currentPage = 1;
pageState.hasMore = true;
await loadMoreCheckpoints(true);
}
// Refresh checkpoints
export async function refreshCheckpoints() {
try {
showToast('Scanning for checkpoints...', 'info');
const response = await fetch('/api/checkpoints/scan');
if (!response.ok) {
throw new Error(`Failed to scan checkpoints: ${response.status} ${response.statusText}`);
}
await resetAndReload();
showToast('Checkpoints refreshed successfully', 'success');
} catch (error) {
console.error('Error refreshing checkpoints:', error);
showToast('Failed to refresh checkpoints', 'error');
}
}
// Delete a checkpoint
export function deleteCheckpoint(filePath) {
confirmDelete('Are you sure you want to delete this checkpoint?', () => {
_performDelete(filePath);
});
}
// Private function to perform the delete operation
async function _performDelete(filePath) {
try {
showToast('Deleting checkpoint...', 'info');
const response = await fetch('/api/model/delete', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
file_path: filePath,
model_type: 'checkpoint'
})
});
if (!response.ok) {
throw new Error(`Failed to delete checkpoint: ${response.status} ${response.statusText}`);
}
const data = await response.json();
if (data.success) {
// Remove the card from UI
const card = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
if (card) {
card.remove();
}
showToast('Checkpoint deleted successfully', 'success');
} else {
throw new Error(data.error || 'Failed to delete checkpoint');
}
} catch (error) {
console.error('Error deleting checkpoint:', error);
showToast(`Failed to delete checkpoint: ${error.message}`, 'error');
}
}
// Replace checkpoint preview
export function replaceCheckpointPreview(filePath) {
// Open file picker
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.onchange = async (e) => {
if (!e.target.files.length) return;
const file = e.target.files[0];
await _uploadPreview(filePath, file);
};
input.click();
}
// Upload a preview image
async function _uploadPreview(filePath, file) {
try {
showToast('Uploading preview...', 'info');
const formData = new FormData();
formData.append('file', file);
formData.append('file_path', filePath);
formData.append('model_type', 'checkpoint');
const response = await fetch('/api/model/preview', {
method: 'POST',
body: formData
});
if (!response.ok) {
throw new Error(`Failed to upload preview: ${response.status} ${response.statusText}`);
}
const data = await response.json();
if (data.success) {
// Update the preview in UI
const card = document.querySelector(`.lora-card[data-filepath="${filePath}"]`);
if (card) {
const img = card.querySelector('.card-preview img');
if (img) {
// Add timestamp to prevent caching
const timestamp = new Date().getTime();
if (data.preview_url) {
img.src = `${data.preview_url}?t=${timestamp}`;
} else {
img.src = `/api/model/preview_image?path=${encodeURIComponent(filePath)}&t=${timestamp}`;
}
}
}
showToast('Preview updated successfully', 'success');
} else {
throw new Error(data.error || 'Failed to update preview');
}
} catch (error) {
console.error('Error updating preview:', error);
showToast(`Failed to update preview: ${error.message}`, 'error');
}
}