diff --git a/static/js/api/apiConfig.js b/static/js/api/apiConfig.js index 6f02033f..53b3f842 100644 --- a/static/js/api/apiConfig.js +++ b/static/js/api/apiConfig.js @@ -103,13 +103,12 @@ export const MODEL_SPECIFIC_ENDPOINTS = { moveBulk: `/api/${MODEL_TYPES.LORA}/move_models_bulk`, getTriggerWordsPost: `/api/${MODEL_TYPES.LORA}/get_trigger_words`, civitaiModelByVersion: `/api/${MODEL_TYPES.LORA}/civitai/model/version`, - civitaiModelByHash: `/api/${MODEL_TYPES.LORA}/civitai/model/hash` + civitaiModelByHash: `/api/${MODEL_TYPES.LORA}/civitai/model/hash`, }, [MODEL_TYPES.CHECKPOINT]: { - info: `/api/${MODEL_TYPES.CHECKPOINT}/info` + info: `/api/${MODEL_TYPES.CHECKPOINT}/info`, }, [MODEL_TYPES.EMBEDDING]: { - // Future embedding-specific endpoints } }; diff --git a/static/js/api/baseModelApi.js b/static/js/api/baseModelApi.js index 692a83db..042871cd 100644 --- a/static/js/api/baseModelApi.js +++ b/static/js/api/baseModelApi.js @@ -581,6 +581,86 @@ class ModelApiClient { return successFilePaths; } + /** + * Fetch Civitai model versions + */ + async fetchCivitaiVersions(modelId) { + try { + const response = await fetch(`${this.apiConfig.endpoints.civitaiVersions}/${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 ${this.apiConfig.config.displayName}. Please switch to the appropriate page to download this model type.`); + } + throw new Error('Failed to fetch model versions'); + } + return await response.json(); + } catch (error) { + console.error('Error fetching Civitai versions:', error); + throw error; + } + } + + /** + * Fetch model roots + */ + async fetchModelRoots() { + try { + const response = await fetch(this.apiConfig.endpoints.roots); + if (!response.ok) { + throw new Error(`Failed to fetch ${this.apiConfig.config.displayName} roots`); + } + return await response.json(); + } catch (error) { + console.error('Error fetching model roots:', error); + throw error; + } + } + + /** + * Fetch model folders + */ + async fetchModelFolders() { + try { + const response = await fetch(this.apiConfig.endpoints.folders); + if (!response.ok) { + throw new Error(`Failed to fetch ${this.apiConfig.config.displayName} folders`); + } + return await response.json(); + } catch (error) { + console.error('Error fetching model folders:', error); + throw error; + } + } + + /** + * Download a model + */ + async downloadModel(modelId, versionId, modelRoot, relativePath, downloadId) { + try { + const response = await fetch(DOWNLOAD_ENDPOINTS.download, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + model_id: modelId, + model_version_id: versionId, + model_root: modelRoot, + relative_path: relativePath, + download_id: downloadId + }) + }); + + if (!response.ok) { + throw new Error(await response.text()); + } + + return await response.json(); + } catch (error) { + console.error('Error downloading model:', error); + throw error; + } + } + /** * Build query parameters for API requests */ diff --git a/static/js/checkpoints.js b/static/js/checkpoints.js index 69ab6224..cbc02ace 100644 --- a/static/js/checkpoints.js +++ b/static/js/checkpoints.js @@ -2,7 +2,6 @@ import { appCore } from './core.js'; import { confirmDelete, closeDeleteModal, confirmExclude, closeExcludeModal } from './utils/modalUtils.js'; import { createPageControls } from './components/controls/index.js'; import { loadMoreCheckpoints } from './api/checkpointApi.js'; -import { CheckpointDownloadManager } from './managers/CheckpointDownloadManager.js'; import { CheckpointContextMenu } from './components/ContextMenu/index.js'; import { ModelDuplicatesManager } from './components/ModelDuplicatesManager.js'; import { MODEL_TYPES } from './api/apiConfig.js'; @@ -13,9 +12,6 @@ class CheckpointsPageManager { // Initialize page controls this.pageControls = createPageControls(MODEL_TYPES.CHECKPOINT); - // Initialize checkpoint download manager - window.checkpointDownloadManager = new CheckpointDownloadManager(); - // Initialize the ModelDuplicatesManager this.duplicatesManager = new ModelDuplicatesManager(this, MODEL_TYPES.CHECKPOINT); diff --git a/static/js/components/ContextMenu/CheckpointContextMenu.js b/static/js/components/ContextMenu/CheckpointContextMenu.js index 8e589b51..b7be4b9f 100644 --- a/static/js/components/ContextMenu/CheckpointContextMenu.js +++ b/static/js/components/ContextMenu/CheckpointContextMenu.js @@ -3,7 +3,7 @@ import { ModelContextMenuMixin } from './ModelContextMenuMixin.js'; import { resetAndReload } from '../../api/checkpointApi.js'; import { getModelApiClient } from '../../api/baseModelApi.js'; import { showToast } from '../../utils/uiHelpers.js'; -import { showExcludeModal } from '../../utils/modalUtils.js'; +import { showDeleteModal, showExcludeModal } from '../../utils/modalUtils.js'; export class CheckpointContextMenu extends BaseContextMenu { constructor() { @@ -42,10 +42,7 @@ export class CheckpointContextMenu extends BaseContextMenu { apiClient.replaceModelPreview(this.currentCard.dataset.filepath); break; case 'delete': - // Delete checkpoint - if (this.currentCard.querySelector('.fa-trash')) { - this.currentCard.querySelector('.fa-trash').click(); - } + showDeleteModal(this.currentCard.dataset.filepath); break; case 'copyname': // Copy checkpoint name diff --git a/static/js/components/controls/CheckpointsControls.js b/static/js/components/controls/CheckpointsControls.js index f5af38a8..6528a8a5 100644 --- a/static/js/components/controls/CheckpointsControls.js +++ b/static/js/components/controls/CheckpointsControls.js @@ -2,7 +2,7 @@ import { PageControls } from './PageControls.js'; import { loadMoreCheckpoints, resetAndReload, refreshCheckpoints, fetchCivitai } from '../../api/checkpointApi.js'; import { showToast } from '../../utils/uiHelpers.js'; -import { CheckpointDownloadManager } from '../../managers/CheckpointDownloadManager.js'; +import { downloadManager } from '../../managers/DownloadManager.js'; /** * CheckpointsControls class - Extends PageControls for Checkpoint-specific functionality @@ -12,9 +12,6 @@ export class CheckpointsControls extends PageControls { // Initialize with 'checkpoints' page type super('checkpoints'); - // Initialize checkpoint download manager - this.downloadManager = new CheckpointDownloadManager(); - // Register API methods specific to the Checkpoints page this.registerCheckpointsAPI(); } @@ -44,7 +41,7 @@ export class CheckpointsControls extends PageControls { // Add show download modal functionality showDownloadModal: () => { - this.downloadManager.showDownloadModal(); + downloadManager.showDownloadModal(); }, // No clearCustomFilter implementation is needed for checkpoints diff --git a/static/js/components/controls/LorasControls.js b/static/js/components/controls/LorasControls.js index 22f768d4..dee5e128 100644 --- a/static/js/components/controls/LorasControls.js +++ b/static/js/components/controls/LorasControls.js @@ -3,6 +3,7 @@ import { PageControls } from './PageControls.js'; import { loadMoreLoras, fetchCivitai, resetAndReload, refreshLoras } from '../../api/loraApi.js'; import { getSessionItem, removeSessionItem } from '../../utils/storageHelpers.js'; import { createAlphabetBar } from '../alphabet/index.js'; +import { downloadManager } from '../../managers/DownloadManager.js'; /** * LorasControls class - Extends PageControls for LoRA-specific functionality @@ -46,11 +47,7 @@ export class LorasControls extends PageControls { }, showDownloadModal: () => { - if (window.downloadManager) { - window.downloadManager.showDownloadModal(); - } else { - console.error('Download manager not available'); - } + downloadManager.showDownloadModal(); }, toggleBulkMode: () => { diff --git a/static/js/loras.js b/static/js/loras.js index c5e2f77a..537ed628 100644 --- a/static/js/loras.js +++ b/static/js/loras.js @@ -3,7 +3,6 @@ import { state } from './state/index.js'; import { loadMoreLoras } from './api/loraApi.js'; import { updateCardsForBulkMode } from './components/LoraCard.js'; import { bulkManager } from './managers/BulkManager.js'; -import { DownloadManager } from './managers/DownloadManager.js'; import { moveManager } from './managers/MoveManager.js'; import { LoraContextMenu } from './components/ContextMenu/index.js'; import { createPageControls } from './components/controls/index.js'; @@ -17,9 +16,6 @@ class LoraPageManager { state.bulkMode = false; state.selectedLoras = new Set(); - // Initialize managers - this.downloadManager = new DownloadManager(); - // Initialize page controls this.pageControls = createPageControls('loras'); @@ -39,7 +35,6 @@ class LoraPageManager { window.closeDeleteModal = closeDeleteModal; window.confirmExclude = confirmExclude; window.closeExcludeModal = closeExcludeModal; - window.downloadManager = this.downloadManager; window.moveManager = moveManager; // Bulk operations diff --git a/static/js/managers/CheckpointDownloadManager.js b/static/js/managers/CheckpointDownloadManager.js deleted file mode 100644 index 7545e617..00000000 --- a/static/js/managers/CheckpointDownloadManager.js +++ /dev/null @@ -1,463 +0,0 @@ -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 = ` -