mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat: enhance model version download with progress tracking
- Set refresh to true when fetching model update versions to ensure latest data - Refactor handleDownloadVersion to be async and accept button parameter - Add progress tracking and WebSocket integration for download operations - Implement button state management during download process - Add error handling and cleanup for download operations - Update download action to await async download handler
This commit is contained in:
@@ -391,7 +391,7 @@ export function initVersionsTab({
|
|||||||
try {
|
try {
|
||||||
const client = ensureClient();
|
const client = ensureClient();
|
||||||
const response = await client.fetchModelUpdateVersions(modelId, {
|
const response = await client.fetchModelUpdateVersions(modelId, {
|
||||||
refresh: false,
|
refresh: true,
|
||||||
});
|
});
|
||||||
if (!response?.success) {
|
if (!response?.success) {
|
||||||
throw new Error(response?.error || 'Request failed');
|
throw new Error(response?.error || 'Request failed');
|
||||||
@@ -509,11 +509,34 @@ export function initVersionsTab({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDownloadVersion(versionId) {
|
async function handleDownloadVersion(button, versionId) {
|
||||||
if (!controller.record) {
|
if (!controller.record) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
downloadManager.openForModelVersion(modelType, modelId, versionId);
|
|
||||||
|
const version = controller.record.versions.find(item => item.versionId === versionId);
|
||||||
|
if (!version) {
|
||||||
|
console.warn('Target version missing from record for download:', versionId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.disabled = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const success = await downloadManager.downloadVersionWithDefaults(modelType, modelId, versionId, {
|
||||||
|
versionName: version.name || `#${version.versionId}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
await refresh();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to start direct download for version:', error);
|
||||||
|
} finally {
|
||||||
|
if (document.body.contains(button)) {
|
||||||
|
button.disabled = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
container.addEventListener('click', async event => {
|
container.addEventListener('click', async event => {
|
||||||
@@ -541,7 +564,7 @@ export function initVersionsTab({
|
|||||||
switch (action) {
|
switch (action) {
|
||||||
case 'download':
|
case 'download':
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
handleDownloadVersion(versionId);
|
await handleDownloadVersion(actionButton, versionId);
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|||||||
@@ -428,6 +428,125 @@ export class DownloadManager {
|
|||||||
this.updateTargetPath();
|
this.updateTargetPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async executeDownloadWithProgress({
|
||||||
|
modelId,
|
||||||
|
versionId,
|
||||||
|
versionName = '',
|
||||||
|
modelRoot = '',
|
||||||
|
targetFolder = '',
|
||||||
|
useDefaultPaths = false,
|
||||||
|
source = null,
|
||||||
|
closeModal = false,
|
||||||
|
}) {
|
||||||
|
const config = this.apiClient?.apiConfig?.config;
|
||||||
|
|
||||||
|
if (!this.apiClient || !config) {
|
||||||
|
throw new Error('Download manager is not initialized with an API client');
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayName = versionName || `#${versionId}`;
|
||||||
|
let ws = null;
|
||||||
|
let updateProgress = () => {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.loadingManager.restoreProgressBar();
|
||||||
|
updateProgress = this.loadingManager.showDownloadProgress(1);
|
||||||
|
updateProgress(0, 0, displayName);
|
||||||
|
|
||||||
|
const downloadId = Date.now().toString();
|
||||||
|
const wsProtocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
||||||
|
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, displayName, 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);
|
||||||
|
};
|
||||||
|
|
||||||
|
await this.apiClient.downloadModel(
|
||||||
|
modelId,
|
||||||
|
versionId,
|
||||||
|
modelRoot,
|
||||||
|
targetFolder,
|
||||||
|
useDefaultPaths,
|
||||||
|
downloadId,
|
||||||
|
source
|
||||||
|
);
|
||||||
|
|
||||||
|
showToast('toast.loras.downloadCompleted', {}, 'success');
|
||||||
|
|
||||||
|
if (closeModal) {
|
||||||
|
modalManager.closeModal('downloadModal');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||||
|
ws.close();
|
||||||
|
ws = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageState = this.apiClient.getPageState();
|
||||||
|
|
||||||
|
if (!useDefaultPaths && targetFolder) {
|
||||||
|
pageState.activeFolder = targetFolder;
|
||||||
|
setStorageItem(`${this.apiClient.modelType}_activeFolder`, targetFolder);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to download model version:', error);
|
||||||
|
showToast('toast.downloads.downloadError', { message: error?.message }, 'error');
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) {
|
||||||
|
ws.close();
|
||||||
|
}
|
||||||
|
} catch (closeError) {
|
||||||
|
console.debug('Failed to close download progress socket:', closeError);
|
||||||
|
}
|
||||||
|
this.loadingManager.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updatePathSelectionUI() {
|
updatePathSelectionUI() {
|
||||||
const manualSelection = document.getElementById('manualPathSelection');
|
const manualSelection = document.getElementById('manualPathSelection');
|
||||||
|
|
||||||
@@ -489,92 +608,38 @@ export class DownloadManager {
|
|||||||
} else {
|
} else {
|
||||||
targetFolder = this.folderTreeManager.getSelectedPath();
|
targetFolder = this.folderTreeManager.getSelectedPath();
|
||||||
}
|
}
|
||||||
|
return this.executeDownloadWithProgress({
|
||||||
|
modelId: this.modelId,
|
||||||
|
versionId: this.currentVersion.id,
|
||||||
|
versionName: this.currentVersion.name,
|
||||||
|
modelRoot,
|
||||||
|
targetFolder,
|
||||||
|
useDefaultPaths,
|
||||||
|
source: this.source,
|
||||||
|
closeModal: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async downloadVersionWithDefaults(modelType, modelId, versionId, { versionName = '', source = null } = {}) {
|
||||||
try {
|
try {
|
||||||
const updateProgress = this.loadingManager.showDownloadProgress(1);
|
this.apiClient = getModelApiClient(modelType);
|
||||||
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) {
|
} catch (error) {
|
||||||
showToast('toast.downloads.downloadError', { message: error.message }, 'error');
|
this.apiClient = getModelApiClient();
|
||||||
} finally {
|
|
||||||
this.loadingManager.hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.modelId = modelId ? modelId.toString() : null;
|
||||||
|
this.source = source;
|
||||||
|
|
||||||
|
return this.executeDownloadWithProgress({
|
||||||
|
modelId,
|
||||||
|
versionId,
|
||||||
|
versionName,
|
||||||
|
modelRoot: '',
|
||||||
|
targetFolder: '',
|
||||||
|
useDefaultPaths: true,
|
||||||
|
source,
|
||||||
|
closeModal: false,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async initializeFolderTree() {
|
async initializeFolderTree() {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ export class LoadingManager {
|
|||||||
this.setProgress(0);
|
this.setProgress(0);
|
||||||
this.setStatus('');
|
this.setStatus('');
|
||||||
this.removeDetailsContainer();
|
this.removeDetailsContainer();
|
||||||
|
this.progressBar.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a details container for enhanced progress display
|
// Create a details container for enhanced progress display
|
||||||
@@ -69,6 +70,7 @@ export class LoadingManager {
|
|||||||
// Show enhanced progress for downloads
|
// Show enhanced progress for downloads
|
||||||
showDownloadProgress(totalItems = 1) {
|
showDownloadProgress(totalItems = 1) {
|
||||||
this.show(translate('modals.download.status.preparing', {}, 'Preparing download...'), 0);
|
this.show(translate('modals.download.status.preparing', {}, 'Preparing download...'), 0);
|
||||||
|
this.progressBar.style.display = 'none';
|
||||||
|
|
||||||
// Create details container
|
// Create details container
|
||||||
const detailsContainer = this.createDetailsContainer();
|
const detailsContainer = this.createDetailsContainer();
|
||||||
|
|||||||
Reference in New Issue
Block a user