mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
- Rename `preview_overrides` to `version_context` to better reflect expanded purpose - Add file_path and file_name fields to version serialization - Update method names and parameter signatures for consistency - Include file metadata from cache in version context building - Maintain backward compatibility with existing preview URL functionality The changes provide more comprehensive version information including file details while maintaining existing preview override behavior.
682 lines
26 KiB
JavaScript
682 lines
26 KiB
JavaScript
import { modalManager } from './ModalManager.js';
|
|
import { showToast } from '../utils/uiHelpers.js';
|
|
import { state } from '../state/index.js';
|
|
import { LoadingManager } from './LoadingManager.js';
|
|
import { getModelApiClient, resetAndReload } from '../api/modelApiFactory.js';
|
|
import { getStorageItem, setStorageItem } from '../utils/storageHelpers.js';
|
|
import { FolderTreeManager } from '../components/FolderTreeManager.js';
|
|
import { translate } from '../utils/i18nHelpers.js';
|
|
|
|
export class DownloadManager {
|
|
constructor() {
|
|
this.currentVersion = null;
|
|
this.versions = [];
|
|
this.modelInfo = null;
|
|
this.modelVersionId = null;
|
|
this.modelId = null;
|
|
this.source = null;
|
|
|
|
this.initialized = false;
|
|
this.selectedFolder = '';
|
|
this.apiClient = null;
|
|
this.useDefaultPath = false;
|
|
|
|
this.loadingManager = new LoadingManager();
|
|
this.folderTreeManager = new FolderTreeManager();
|
|
this.folderClickHandler = null;
|
|
this.updateTargetPath = this.updateTargetPath.bind(this);
|
|
|
|
// Bound methods for event handling
|
|
this.handleValidateAndFetchVersions = this.validateAndFetchVersions.bind(this);
|
|
this.handleProceedToLocation = this.proceedToLocation.bind(this);
|
|
this.handleStartDownload = this.startDownload.bind(this);
|
|
this.handleBackToUrl = this.backToUrl.bind(this);
|
|
this.handleBackToVersions = this.backToVersions.bind(this);
|
|
this.handleCloseModal = this.closeModal.bind(this);
|
|
this.handleToggleDefaultPath = this.toggleDefaultPath.bind(this);
|
|
}
|
|
|
|
showDownloadModal() {
|
|
console.log('Showing unified download modal...');
|
|
|
|
// Get API client for current page type
|
|
this.apiClient = getModelApiClient();
|
|
const config = this.apiClient.apiConfig.config;
|
|
|
|
if (!this.initialized) {
|
|
const modal = document.getElementById('downloadModal');
|
|
if (!modal) {
|
|
console.error('Unified download modal element not found');
|
|
return;
|
|
}
|
|
this.initializeEventHandlers();
|
|
this.initialized = true;
|
|
}
|
|
|
|
// Update modal title and labels based on model type
|
|
this.updateModalLabels();
|
|
|
|
modalManager.showModal('downloadModal', null, () => {
|
|
this.cleanupFolderBrowser();
|
|
});
|
|
this.resetSteps();
|
|
|
|
// Auto-focus on the URL input
|
|
setTimeout(() => {
|
|
const urlInput = document.getElementById('modelUrl');
|
|
if (urlInput) {
|
|
urlInput.focus();
|
|
}
|
|
}, 100);
|
|
}
|
|
|
|
initializeEventHandlers() {
|
|
// Button event handlers
|
|
document.getElementById('nextFromUrl').addEventListener('click', this.handleValidateAndFetchVersions);
|
|
document.getElementById('nextFromVersion').addEventListener('click', this.handleProceedToLocation);
|
|
document.getElementById('startDownloadBtn').addEventListener('click', this.handleStartDownload);
|
|
document.getElementById('backToUrlBtn').addEventListener('click', this.handleBackToUrl);
|
|
document.getElementById('backToVersionsBtn').addEventListener('click', this.handleBackToVersions);
|
|
document.getElementById('closeDownloadModal').addEventListener('click', this.handleCloseModal);
|
|
|
|
// Default path toggle handler
|
|
document.getElementById('useDefaultPath').addEventListener('change', this.handleToggleDefaultPath);
|
|
}
|
|
|
|
updateModalLabels() {
|
|
const config = this.apiClient.apiConfig.config;
|
|
|
|
// Update modal title
|
|
document.getElementById('downloadModalTitle').textContent = translate('modals.download.titleWithType', { type: config.displayName });
|
|
|
|
// Update URL label
|
|
document.getElementById('modelUrlLabel').textContent = translate('modals.download.civitaiUrl');
|
|
|
|
// Update root selection label
|
|
document.getElementById('modelRootLabel').textContent = translate('modals.download.selectTypeRoot', { type: config.displayName });
|
|
|
|
// Update path preview labels
|
|
const pathLabels = document.querySelectorAll('.path-preview label');
|
|
pathLabels.forEach(label => {
|
|
if (label.textContent.includes('Location Preview')) {
|
|
label.textContent = translate('modals.download.locationPreview') + ':';
|
|
}
|
|
});
|
|
|
|
// Update initial path text
|
|
const pathText = document.querySelector('#targetPathDisplay .path-text');
|
|
if (pathText) {
|
|
pathText.textContent = translate('modals.download.selectTypeRoot', { type: config.displayName });
|
|
}
|
|
}
|
|
|
|
resetSteps() {
|
|
document.querySelectorAll('.download-step').forEach(step => step.style.display = 'none');
|
|
document.getElementById('urlStep').style.display = 'block';
|
|
document.getElementById('modelUrl').value = '';
|
|
document.getElementById('urlError').textContent = '';
|
|
|
|
// Clear folder path input
|
|
const folderPathInput = document.getElementById('folderPath');
|
|
if (folderPathInput) {
|
|
folderPathInput.value = '';
|
|
}
|
|
|
|
this.currentVersion = null;
|
|
this.versions = [];
|
|
this.modelInfo = null;
|
|
this.modelId = null;
|
|
this.modelVersionId = null;
|
|
this.source = null;
|
|
|
|
this.selectedFolder = '';
|
|
|
|
// Clear folder tree selection
|
|
if (this.folderTreeManager) {
|
|
this.folderTreeManager.clearSelection();
|
|
}
|
|
|
|
// Reset default path toggle
|
|
this.loadDefaultPathSetting();
|
|
}
|
|
|
|
async retrieveVersionsForModel(modelId, source = null) {
|
|
this.versions = await this.apiClient.fetchCivitaiVersions(modelId, source);
|
|
if (!this.versions || !this.versions.length) {
|
|
throw new Error(translate('modals.download.errors.noVersions'));
|
|
}
|
|
return this.versions;
|
|
}
|
|
|
|
async validateAndFetchVersions() {
|
|
const url = document.getElementById('modelUrl').value.trim();
|
|
const errorElement = document.getElementById('urlError');
|
|
|
|
try {
|
|
this.loadingManager.showSimpleLoading(translate('modals.download.fetchingVersions'));
|
|
|
|
this.modelId = this.extractModelId(url);
|
|
if (!this.modelId) {
|
|
throw new Error(translate('modals.download.errors.invalidUrl'));
|
|
}
|
|
|
|
await this.retrieveVersionsForModel(this.modelId, this.source);
|
|
|
|
// 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();
|
|
}
|
|
}
|
|
|
|
async fetchVersionsForCurrentModel() {
|
|
const errorElement = document.getElementById('urlError');
|
|
if (errorElement) {
|
|
errorElement.textContent = '';
|
|
}
|
|
try {
|
|
this.loadingManager.showSimpleLoading(translate('modals.download.fetchingVersions'));
|
|
await this.retrieveVersionsForModel(this.modelId, this.source);
|
|
if (this.modelVersionId) {
|
|
this.currentVersion = this.versions.find(v => v.id.toString() === this.modelVersionId);
|
|
}
|
|
this.showVersionStep();
|
|
} catch (error) {
|
|
if (errorElement) {
|
|
errorElement.textContent = error.message;
|
|
}
|
|
} finally {
|
|
this.loadingManager.hide();
|
|
}
|
|
}
|
|
|
|
extractModelId(url) {
|
|
const versionMatch = url.match(/modelVersionId=(\d+)/i);
|
|
this.modelVersionId = versionMatch ? versionMatch[1] : null;
|
|
|
|
const civarchiveMatch = url.match(/https?:\/\/(?:www\.)?(?:civitaiarchive|civarchive)\.com\/models\/(\d+)/i);
|
|
if (civarchiveMatch) {
|
|
this.source = 'civarchive';
|
|
return civarchiveMatch[1];
|
|
}
|
|
|
|
const civitaiMatch = url.match(/https?:\/\/(?:www\.)?civitai\.com\/models\/(\d+)/i);
|
|
if (civitaiMatch) {
|
|
this.source = null;
|
|
return civitaiMatch[1];
|
|
}
|
|
|
|
this.source = null;
|
|
return null;
|
|
}
|
|
|
|
async openForModelVersion(modelType, modelId, versionId = null) {
|
|
try {
|
|
this.apiClient = getModelApiClient(modelType);
|
|
} catch (error) {
|
|
this.apiClient = getModelApiClient();
|
|
}
|
|
|
|
this.showDownloadModal();
|
|
|
|
this.modelId = modelId ? modelId.toString() : null;
|
|
this.modelVersionId = versionId ? versionId.toString() : null;
|
|
this.source = null;
|
|
|
|
if (!this.modelId) {
|
|
return;
|
|
}
|
|
|
|
await this.fetchVersionsForCurrentModel();
|
|
}
|
|
|
|
showVersionStep() {
|
|
document.getElementById('urlStep').style.display = 'none';
|
|
document.getElementById('versionStep').style.display = 'block';
|
|
|
|
const versionList = document.getElementById('versionList');
|
|
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';
|
|
|
|
const fileSize = version.modelSizeKB ?
|
|
(version.modelSizeKB / 1024).toFixed(2) :
|
|
(version.files[0]?.sizeKB / 1024).toFixed(2);
|
|
|
|
const existsLocally = version.existsLocally;
|
|
const localPath = version.localPath;
|
|
const isEarlyAccess = version.availability === 'EarlyAccess';
|
|
|
|
let earlyAccessBadge = '';
|
|
if (isEarlyAccess) {
|
|
earlyAccessBadge = `
|
|
<div class="early-access-badge" title="${translate('modals.download.earlyAccessTooltip')}">
|
|
<i class="fas fa-clock"></i> ${translate('modals.download.earlyAccess')}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
const localStatus = existsLocally ?
|
|
`<div class="local-badge">
|
|
<i class="fas fa-check"></i> ${translate('modals.download.inLibrary')}
|
|
<div class="local-path">${localPath || ''}</div>
|
|
</div>` : '';
|
|
|
|
return `
|
|
<div class="version-item ${this.currentVersion?.id === version.id ? 'selected' : ''}
|
|
${existsLocally ? 'exists-locally' : ''}
|
|
${isEarlyAccess ? 'is-early-access' : ''}"
|
|
data-version-id="${version.id}">
|
|
<div class="version-thumbnail">
|
|
<img src="${thumbnailUrl}" alt="${translate('modals.download.versionPreview')}">
|
|
</div>
|
|
<div class="version-content">
|
|
<div class="version-header">
|
|
<h3>${version.name}</h3>
|
|
${localStatus}
|
|
</div>
|
|
<div class="version-info">
|
|
${version.baseModel ? `<div class="base-model">${version.baseModel}</div>` : ''}
|
|
${earlyAccessBadge}
|
|
</div>
|
|
<div class="version-meta">
|
|
<span><i class="fas fa-calendar"></i> ${new Date(version.createdAt).toLocaleDateString()}</span>
|
|
<span><i class="fas fa-file-archive"></i> ${fileSize} MB</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
// Add click handlers for version selection
|
|
versionList.addEventListener('click', (event) => {
|
|
const versionItem = event.target.closest('.version-item');
|
|
if (versionItem) {
|
|
this.selectVersion(versionItem.dataset.versionId);
|
|
}
|
|
});
|
|
|
|
// Auto-select the version if there's only one
|
|
if (this.versions.length === 1 && !this.currentVersion) {
|
|
this.selectVersion(this.versions[0].id.toString());
|
|
}
|
|
|
|
this.updateNextButtonState();
|
|
}
|
|
|
|
selectVersion(versionId) {
|
|
this.currentVersion = this.versions.find(v => v.id.toString() === versionId.toString());
|
|
if (!this.currentVersion) return;
|
|
|
|
document.querySelectorAll('.version-item').forEach(item => {
|
|
item.classList.toggle('selected', item.dataset.versionId === versionId);
|
|
});
|
|
|
|
this.updateNextButtonState();
|
|
}
|
|
|
|
updateNextButtonState() {
|
|
const nextButton = document.getElementById('nextFromVersion');
|
|
if (!nextButton) return;
|
|
|
|
const existsLocally = this.currentVersion?.existsLocally;
|
|
|
|
if (existsLocally) {
|
|
nextButton.disabled = true;
|
|
nextButton.classList.add('disabled');
|
|
nextButton.textContent = translate('modals.download.alreadyInLibrary');
|
|
} else {
|
|
nextButton.disabled = false;
|
|
nextButton.classList.remove('disabled');
|
|
nextButton.textContent = translate('common.actions.next');
|
|
}
|
|
}
|
|
|
|
async proceedToLocation() {
|
|
if (!this.currentVersion) {
|
|
showToast('toast.loras.pleaseSelectVersion', {}, 'error');
|
|
return;
|
|
}
|
|
|
|
const existsLocally = this.currentVersion.existsLocally;
|
|
if (existsLocally) {
|
|
showToast('toast.loras.versionExists', {}, 'info');
|
|
return;
|
|
}
|
|
|
|
document.getElementById('versionStep').style.display = 'none';
|
|
document.getElementById('locationStep').style.display = 'block';
|
|
|
|
try {
|
|
// Fetch model roots
|
|
const rootsData = await this.apiClient.fetchModelRoots();
|
|
const modelRoot = document.getElementById('modelRoot');
|
|
modelRoot.innerHTML = rootsData.roots.map(root =>
|
|
`<option value="${root}">${root}</option>`
|
|
).join('');
|
|
|
|
// Set default root if available
|
|
const singularType = this.apiClient.modelType.replace(/s$/, '');
|
|
const defaultRootKey = `default_${singularType}_root`;
|
|
const defaultRoot = state.global.settings[defaultRootKey];
|
|
console.log(`Default root for ${this.apiClient.modelType}:`, defaultRoot);
|
|
console.log('Available roots:', rootsData.roots);
|
|
if (defaultRoot && rootsData.roots.includes(defaultRoot)) {
|
|
console.log(`Setting default root: ${defaultRoot}`);
|
|
modelRoot.value = defaultRoot;
|
|
}
|
|
|
|
// Set autocomplete="off" on folderPath input
|
|
const folderPathInput = document.getElementById('folderPath');
|
|
if (folderPathInput) {
|
|
folderPathInput.setAttribute('autocomplete', 'off');
|
|
}
|
|
|
|
// Initialize folder tree
|
|
await this.initializeFolderTree();
|
|
|
|
// Setup folder tree manager
|
|
this.folderTreeManager.init({
|
|
onPathChange: (path) => {
|
|
this.selectedFolder = path;
|
|
this.updateTargetPath();
|
|
}
|
|
});
|
|
|
|
// Setup model root change handler
|
|
modelRoot.addEventListener('change', async () => {
|
|
await this.initializeFolderTree();
|
|
this.updateTargetPath();
|
|
});
|
|
|
|
// Load default path setting for current model type
|
|
this.loadDefaultPathSetting();
|
|
|
|
this.updateTargetPath();
|
|
} catch (error) {
|
|
showToast('toast.downloads.loadError', { message: error.message }, 'error');
|
|
}
|
|
}
|
|
|
|
loadDefaultPathSetting() {
|
|
const modelType = this.apiClient.modelType;
|
|
const storageKey = `use_default_path_${modelType}`;
|
|
this.useDefaultPath = getStorageItem(storageKey, false);
|
|
|
|
const toggleInput = document.getElementById('useDefaultPath');
|
|
if (toggleInput) {
|
|
toggleInput.checked = this.useDefaultPath;
|
|
this.updatePathSelectionUI();
|
|
}
|
|
}
|
|
|
|
toggleDefaultPath(event) {
|
|
this.useDefaultPath = event.target.checked;
|
|
|
|
// Save to localStorage per model type
|
|
const modelType = this.apiClient.modelType;
|
|
const storageKey = `use_default_path_${modelType}`;
|
|
setStorageItem(storageKey, this.useDefaultPath);
|
|
|
|
this.updatePathSelectionUI();
|
|
this.updateTargetPath();
|
|
}
|
|
|
|
updatePathSelectionUI() {
|
|
const manualSelection = document.getElementById('manualPathSelection');
|
|
|
|
// Always show manual path selection, but disable/enable based on useDefaultPath
|
|
manualSelection.style.display = 'block';
|
|
if (this.useDefaultPath) {
|
|
manualSelection.classList.add('disabled');
|
|
// Disable all inputs and buttons inside manualSelection
|
|
manualSelection.querySelectorAll('input, select, button').forEach(el => {
|
|
el.disabled = true;
|
|
el.tabIndex = -1;
|
|
});
|
|
} else {
|
|
manualSelection.classList.remove('disabled');
|
|
manualSelection.querySelectorAll('input, select, button').forEach(el => {
|
|
el.disabled = false;
|
|
el.tabIndex = 0;
|
|
});
|
|
}
|
|
|
|
// Always update the main path display
|
|
this.updateTargetPath();
|
|
}
|
|
|
|
backToUrl() {
|
|
document.getElementById('versionStep').style.display = 'none';
|
|
document.getElementById('urlStep').style.display = 'block';
|
|
}
|
|
|
|
backToVersions() {
|
|
document.getElementById('locationStep').style.display = 'none';
|
|
document.getElementById('versionStep').style.display = 'block';
|
|
}
|
|
|
|
closeModal() {
|
|
// Clean up folder tree manager
|
|
if (this.folderTreeManager) {
|
|
this.folderTreeManager.destroy();
|
|
}
|
|
modalManager.closeModal('downloadModal');
|
|
}
|
|
|
|
async startDownload() {
|
|
const modelRoot = document.getElementById('modelRoot').value;
|
|
const config = this.apiClient.apiConfig.config;
|
|
|
|
if (!modelRoot) {
|
|
showToast('toast.models.pleaseSelectRoot', { type: config.displayName }, 'error');
|
|
return;
|
|
}
|
|
|
|
// Determine target folder and use_default_paths parameter
|
|
let targetFolder = '';
|
|
let useDefaultPaths = false;
|
|
|
|
if (this.useDefaultPath) {
|
|
useDefaultPaths = true;
|
|
targetFolder = ''; // Not needed when using default paths
|
|
} else {
|
|
targetFolder = this.folderTreeManager.getSelectedPath();
|
|
}
|
|
|
|
try {
|
|
const updateProgress = this.loadingManager.showDownloadProgress(1);
|
|
updateProgress(0, 0, this.currentVersion.name);
|
|
|
|
const downloadId = Date.now().toString();
|
|
|
|
// Setup WebSocket for progress updates
|
|
const wsProtocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
|
const ws = new WebSocket(`${wsProtocol}${window.location.host}/ws/download-progress?id=${downloadId}`);
|
|
|
|
ws.onmessage = (event) => {
|
|
const data = JSON.parse(event.data);
|
|
|
|
if (data.type === 'download_id') {
|
|
console.log(`Connected to download progress with ID: ${data.download_id}`);
|
|
return;
|
|
}
|
|
|
|
if (data.status === 'progress' && data.download_id === downloadId) {
|
|
const metrics = {
|
|
bytesDownloaded: data.bytes_downloaded,
|
|
totalBytes: data.total_bytes,
|
|
bytesPerSecond: data.bytes_per_second
|
|
};
|
|
|
|
updateProgress(data.progress, 0, this.currentVersion.name, metrics);
|
|
|
|
if (data.progress < 3) {
|
|
this.loadingManager.setStatus(translate('modals.download.status.preparing'));
|
|
} else if (data.progress === 3) {
|
|
this.loadingManager.setStatus(translate('modals.download.status.downloadedPreview'));
|
|
} else if (data.progress > 3 && data.progress < 100) {
|
|
this.loadingManager.setStatus(translate('modals.download.status.downloadingFile', { type: config.singularName }));
|
|
} else {
|
|
this.loadingManager.setStatus(translate('modals.download.status.finalizing'));
|
|
}
|
|
}
|
|
};
|
|
|
|
ws.onerror = (error) => {
|
|
console.error('WebSocket error:', error);
|
|
};
|
|
|
|
// Start download with use_default_paths parameter
|
|
await this.apiClient.downloadModel(
|
|
this.modelId,
|
|
this.currentVersion.id,
|
|
modelRoot,
|
|
targetFolder,
|
|
useDefaultPaths,
|
|
downloadId,
|
|
this.source
|
|
);
|
|
|
|
showToast('toast.loras.downloadCompleted', {}, 'success');
|
|
modalManager.closeModal('downloadModal');
|
|
|
|
ws.close();
|
|
|
|
// Update state and trigger reload
|
|
const pageState = this.apiClient.getPageState();
|
|
|
|
if (!useDefaultPaths) {
|
|
pageState.activeFolder = targetFolder;
|
|
|
|
// Save the active folder preference
|
|
setStorageItem(`${this.apiClient.modelType}_activeFolder`, targetFolder);
|
|
|
|
// Update UI folder selection
|
|
document.querySelectorAll('.folder-tags .tag').forEach(tag => {
|
|
const isActive = tag.dataset.folder === targetFolder;
|
|
tag.classList.toggle('active', isActive);
|
|
if (isActive && !tag.parentNode.classList.contains('collapsed')) {
|
|
tag.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
}
|
|
});
|
|
}
|
|
|
|
await resetAndReload(true);
|
|
|
|
} catch (error) {
|
|
showToast('toast.downloads.downloadError', { message: error.message }, 'error');
|
|
} finally {
|
|
this.loadingManager.hide();
|
|
}
|
|
}
|
|
|
|
async initializeFolderTree() {
|
|
try {
|
|
// Fetch unified folder tree
|
|
const treeData = await this.apiClient.fetchUnifiedFolderTree();
|
|
|
|
if (treeData.success) {
|
|
// Load tree data into folder tree manager
|
|
await this.folderTreeManager.loadTree(treeData.tree);
|
|
} else {
|
|
console.error('Failed to fetch folder tree:', treeData.error);
|
|
showToast('toast.import.folderTreeFailed', {}, 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error initializing folder tree:', error);
|
|
showToast('toast.import.folderTreeError', {}, 'error');
|
|
}
|
|
}
|
|
|
|
initializeFolderBrowser() {
|
|
const folderBrowser = document.getElementById('folderBrowser');
|
|
if (!folderBrowser) return;
|
|
|
|
this.cleanupFolderBrowser();
|
|
|
|
this.folderClickHandler = (event) => {
|
|
const folderItem = event.target.closest('.folder-item');
|
|
if (!folderItem) return;
|
|
|
|
if (folderItem.classList.contains('selected')) {
|
|
folderItem.classList.remove('selected');
|
|
this.selectedFolder = '';
|
|
} else {
|
|
folderBrowser.querySelectorAll('.folder-item').forEach(f =>
|
|
f.classList.remove('selected'));
|
|
folderItem.classList.add('selected');
|
|
this.selectedFolder = folderItem.dataset.folder;
|
|
}
|
|
|
|
this.updateTargetPath();
|
|
};
|
|
|
|
folderBrowser.addEventListener('click', this.folderClickHandler);
|
|
|
|
const modelRoot = document.getElementById('modelRoot');
|
|
const newFolder = document.getElementById('newFolder');
|
|
|
|
modelRoot.addEventListener('change', this.updateTargetPath);
|
|
newFolder.addEventListener('input', this.updateTargetPath);
|
|
|
|
this.updateTargetPath();
|
|
}
|
|
|
|
cleanupFolderBrowser() {
|
|
if (this.folderClickHandler) {
|
|
const folderBrowser = document.getElementById('folderBrowser');
|
|
if (folderBrowser) {
|
|
folderBrowser.removeEventListener('click', this.folderClickHandler);
|
|
this.folderClickHandler = null;
|
|
}
|
|
}
|
|
|
|
const modelRoot = document.getElementById('modelRoot');
|
|
const newFolder = document.getElementById('newFolder');
|
|
|
|
if (modelRoot) modelRoot.removeEventListener('change', this.updateTargetPath);
|
|
if (newFolder) newFolder.removeEventListener('input', this.updateTargetPath);
|
|
}
|
|
|
|
updateTargetPath() {
|
|
const pathDisplay = document.getElementById('targetPathDisplay');
|
|
const modelRoot = document.getElementById('modelRoot').value;
|
|
const config = this.apiClient.apiConfig.config;
|
|
|
|
let fullPath = modelRoot || translate('modals.download.selectTypeRoot', { type: config.displayName });
|
|
|
|
if (modelRoot) {
|
|
if (this.useDefaultPath) {
|
|
// Show actual template path
|
|
try {
|
|
const singularType = this.apiClient.modelType.replace(/s$/, '');
|
|
const templates = state.global.settings.download_path_templates;
|
|
const template = templates[singularType];
|
|
fullPath += `/${template}`;
|
|
} catch (error) {
|
|
console.error('Failed to fetch template:', error);
|
|
fullPath += '/' + translate('modals.download.autoOrganizedPath');
|
|
}
|
|
} else {
|
|
// Show manual path selection
|
|
const selectedPath = this.folderTreeManager ? this.folderTreeManager.getSelectedPath() : '';
|
|
if (selectedPath) {
|
|
fullPath += '/' + selectedPath;
|
|
}
|
|
}
|
|
}
|
|
|
|
pathDisplay.innerHTML = `<span class="path-text">${fullPath}</span>`;
|
|
}
|
|
}
|
|
|
|
// Create global instance
|
|
export const downloadManager = new DownloadManager();
|