import { modalManager } from './ModalManager.js'; import { showToast } from '../utils/uiHelpers.js'; import { LoadingManager } from './LoadingManager.js'; import { state } from '../state/index.js'; import { resetAndReload } from '../api/checkpointApi.js'; import { getStorageItem, setStorageItem } from '../utils/storageHelpers.js'; export class CheckpointDownloadManager { constructor() { this.currentVersion = null; this.versions = []; this.modelInfo = null; this.modelVersionId = null; this.initialized = false; this.selectedFolder = ''; this.loadingManager = new LoadingManager(); this.folderClickHandler = null; this.updateTargetPath = this.updateTargetPath.bind(this); } showDownloadModal() { console.log('Showing checkpoint download modal...'); if (!this.initialized) { const modal = document.getElementById('checkpointDownloadModal'); if (!modal) { console.error('Checkpoint download modal element not found'); return; } this.initialized = true; } modalManager.showModal('checkpointDownloadModal', null, () => { // Cleanup handler when modal closes this.cleanupFolderBrowser(); }); this.resetSteps(); // Auto-focus on the URL input setTimeout(() => { const urlInput = document.getElementById('checkpointUrl'); if (urlInput) { urlInput.focus(); } }, 100); // Small delay to ensure the modal is fully displayed } resetSteps() { document.querySelectorAll('#checkpointDownloadModal .download-step').forEach(step => step.style.display = 'none'); document.getElementById('cpUrlStep').style.display = 'block'; document.getElementById('checkpointUrl').value = ''; document.getElementById('cpUrlError').textContent = ''; // Clear new folder input const newFolderInput = document.getElementById('cpNewFolder'); if (newFolderInput) { newFolderInput.value = ''; } this.currentVersion = null; this.versions = []; this.modelInfo = null; this.modelId = null; this.modelVersionId = null; // Clear selected folder and remove selection from UI this.selectedFolder = ''; const folderBrowser = document.getElementById('cpFolderBrowser'); if (folderBrowser) { folderBrowser.querySelectorAll('.folder-item').forEach(f => f.classList.remove('selected')); } } async validateAndFetchVersions() { const url = document.getElementById('checkpointUrl').value.trim(); const errorElement = document.getElementById('cpUrlError'); try { this.loadingManager.showSimpleLoading('Fetching model versions...'); this.modelId = this.extractModelId(url); if (!this.modelId) { throw new Error('Invalid Civitai URL format'); } const response = await fetch(`/api/checkpoints/civitai/versions/${this.modelId}`); if (!response.ok) { const errorData = await response.json().catch(() => ({})); if (errorData && errorData.error && errorData.error.includes('Model type mismatch')) { throw new Error('This model is not a Checkpoint. Please switch to the LoRAs page to download LoRA models.'); } throw new Error('Failed to fetch model versions'); } this.versions = await response.json(); if (!this.versions.length) { throw new Error('No versions available for this model'); } // If we have a version ID from URL, pre-select it if (this.modelVersionId) { this.currentVersion = this.versions.find(v => v.id.toString() === this.modelVersionId); } this.showVersionStep(); } catch (error) { errorElement.textContent = error.message; } finally { this.loadingManager.hide(); } } extractModelId(url) { const modelMatch = url.match(/civitai\.com\/models\/(\d+)/); const versionMatch = url.match(/modelVersionId=(\d+)/); if (modelMatch) { this.modelVersionId = versionMatch ? versionMatch[1] : null; return modelMatch[1]; } return null; } showVersionStep() { document.getElementById('cpUrlStep').style.display = 'none'; document.getElementById('cpVersionStep').style.display = 'block'; const versionList = document.getElementById('cpVersionList'); versionList.innerHTML = this.versions.map(version => { const firstImage = version.images?.find(img => !img.url.endsWith('.mp4')); const thumbnailUrl = firstImage ? firstImage.url : '/loras_static/images/no-preview.png'; // Use version-level size or fallback to first file const fileSize = version.modelSizeKB ? (version.modelSizeKB / 1024).toFixed(2) : (version.files[0]?.sizeKB / 1024).toFixed(2); // Use version-level existsLocally flag const existsLocally = version.existsLocally; const localPath = version.localPath; // Check if this is an early access version const isEarlyAccess = version.availability === 'EarlyAccess'; // Create early access badge if needed let earlyAccessBadge = ''; if (isEarlyAccess) { earlyAccessBadge = `