feat: add trash button and backend endpoint to delete corrupted .badsha downloads
This commit is contained in:
@@ -979,6 +979,20 @@
|
||||
color: var(--mr-color-danger-bright);
|
||||
}
|
||||
|
||||
#model-resolver-modal .mr-download-queue-delete-badsha {
|
||||
color: var(--mr-color-danger-bright, #ff4d4f);
|
||||
}
|
||||
|
||||
#model-resolver-modal .mr-download-queue-delete-badsha:hover {
|
||||
color: #ff7875;
|
||||
background: rgba(239, 68, 68, 0.16);
|
||||
}
|
||||
|
||||
.mr-status .mr-delete-badsha-btn {
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.mr-download-queue-progress {
|
||||
gap: var(--mr-spacing-8);
|
||||
margin-top: var(--mr-spacing-3);
|
||||
|
||||
@@ -1161,6 +1161,7 @@ export const queueMethods = {
|
||||
pause: '.mr-download-queue-pause',
|
||||
resume: '.mr-download-queue-resume',
|
||||
retry: '.mr-download-queue-retry',
|
||||
deleteBadsha: '.mr-download-queue-delete-badsha',
|
||||
openFolder: '.mr-download-queue-open-folder',
|
||||
switchWorkflow: '.mr-download-queue-switch-workflow',
|
||||
more: '.mr-download-queue-more',
|
||||
@@ -1171,6 +1172,7 @@ export const queueMethods = {
|
||||
onPause: downloadId => this.pauseDownload(downloadId),
|
||||
onResume: downloadId => this.resumeDownload(downloadId),
|
||||
onRetry: downloadId => this.retryDownload?.(downloadId),
|
||||
onDeleteBadsha: downloadId => this.deleteBadDownload?.(downloadId),
|
||||
onOpenFolder: context => this.openContainingFolder?.(context),
|
||||
onSwitchWorkflow: context => this.switchToDownloadWorkflow?.(context),
|
||||
moreBinding: 'click',
|
||||
@@ -1293,6 +1295,9 @@ export const queueMethods = {
|
||||
html += `<div class="mr-download-queue-actions" role="group" aria-label="Download actions">`;
|
||||
if (isError) {
|
||||
html += actionButton('mr-download-queue-retry', 'Retry download', 'play');
|
||||
if (isShaMismatch || Boolean(progress.bad_path)) {
|
||||
html += actionButton('mr-download-queue-delete-badsha', 'Delete corrupted (.badsha) file', 'trash2');
|
||||
}
|
||||
} else {
|
||||
html += actionButton(
|
||||
isPaused ? 'mr-download-queue-resume' : 'mr-download-queue-pause',
|
||||
|
||||
@@ -392,9 +392,19 @@ export const resolveDownloadMethods = {
|
||||
|
||||
renderDownloadStatusMessage(message, type, progress = {}, info = {}) {
|
||||
const contextMenuModel = this.getDownloadFolderContext(progress, info);
|
||||
const errorMessage = String(progress.error || info.statusSnapshot?.message || message || '').toLowerCase();
|
||||
const hasBadsha = Boolean(progress.bad_path || errorMessage.includes('sha256') || errorMessage.includes('.badsha'));
|
||||
const downloadId = info.downloadId || progress.download_id || '';
|
||||
let extraHtml = '';
|
||||
if (type === 'error' && hasBadsha && downloadId) {
|
||||
const safeDownloadId = this.escapeHtml(downloadId);
|
||||
const deleteLabel = 'Delete corrupted (.badsha) file';
|
||||
extraHtml = `<button type="button" class="mr-download-queue-action mr-delete-badsha-btn mr-download-queue-delete-badsha" data-download-id="${safeDownloadId}" data-tooltip="${deleteLabel}" aria-label="${deleteLabel}">${getSvgIcon('trash2', 'currentColor', 'mr-download-queue-action-icon')}</button>`;
|
||||
}
|
||||
return this.renderStatusMessage(message, type, {
|
||||
contextMenuModel,
|
||||
contextMenuTooltip: 'Right-click to open download folder'
|
||||
contextMenuTooltip: 'Right-click to open download folder',
|
||||
extraHtml,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1112,6 +1122,47 @@ export const resolveDownloadMethods = {
|
||||
return { progressDiv, downloadBtn };
|
||||
},
|
||||
|
||||
async deleteBadDownload(downloadId) {
|
||||
if (!downloadId) return;
|
||||
const info = this.activeDownloads?.[downloadId];
|
||||
const badPath = info?.lastProgress?.bad_path || info?.statusSnapshot?.progress?.bad_path || '';
|
||||
try {
|
||||
const res = await this.fetchJson(
|
||||
`/model_resolver/delete_bad_download/${encodeURIComponent(downloadId)}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path: badPath })
|
||||
},
|
||||
'Delete corrupted download'
|
||||
);
|
||||
if (res && res.success) {
|
||||
this.showNotification('Corrupted (.badsha) file deleted', 'info');
|
||||
delete this.activeDownloads?.[downloadId];
|
||||
this.removeActiveDownloadRecovery?.(downloadId);
|
||||
this.updateDownloadAllButtonState?.();
|
||||
this.updateQueuePanel?.();
|
||||
if (info?.missing) {
|
||||
const elements = this.resolveDownloadUiElements(info);
|
||||
if (elements.progressDiv) {
|
||||
elements.progressDiv.classList.add('mr-is-hidden');
|
||||
elements.progressDiv.classList.remove('mr-is-visible');
|
||||
elements.progressDiv.innerHTML = '';
|
||||
}
|
||||
if (elements.downloadBtn) {
|
||||
this.restoreDownloadButtonReadyState(elements.downloadBtn);
|
||||
}
|
||||
this.refreshLocalMatchesUiForMissing?.(info.missing);
|
||||
}
|
||||
} else {
|
||||
this.showNotification(res?.error || 'Failed to delete corrupted file', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Model Resolver: Failed to delete corrupted download:', err);
|
||||
this.showNotification('Failed to delete corrupted file: ' + err.message, 'error');
|
||||
}
|
||||
},
|
||||
|
||||
attachDownloadActionHandlers(progressDiv, downloadId) {
|
||||
if (!progressDiv) return;
|
||||
const getDownloadContext = (button) => {
|
||||
@@ -1133,6 +1184,7 @@ export const resolveDownloadMethods = {
|
||||
pause: '.pause-download-btn, .mr-download-queue-pause',
|
||||
resume: '.resume-download-btn, .mr-download-queue-resume',
|
||||
retry: '.retry-download-btn, .mr-download-queue-retry',
|
||||
deleteBadsha: '.mr-download-queue-delete-badsha, .mr-delete-badsha-btn',
|
||||
openFolder: '.mr-download-queue-open-folder',
|
||||
switchWorkflow: '.mr-download-queue-switch-workflow',
|
||||
more: '.mr-download-queue-more',
|
||||
@@ -1142,6 +1194,7 @@ export const resolveDownloadMethods = {
|
||||
onPause: targetDownloadId => this.pauseDownload(targetDownloadId),
|
||||
onResume: targetDownloadId => this.resumeDownload(targetDownloadId),
|
||||
onRetry: targetDownloadId => this.retryDownload?.(targetDownloadId),
|
||||
onDeleteBadsha: targetDownloadId => this.deleteBadDownload?.(targetDownloadId),
|
||||
onOpenFolder: context => this.openContainingFolder?.(context),
|
||||
onSwitchWorkflow: context => this.switchToDownloadWorkflow?.(context),
|
||||
onMore: (event, button) => {
|
||||
|
||||
@@ -28,6 +28,7 @@ export function bindDownloadActionHandlers(root, {
|
||||
onPause = null,
|
||||
onResume = null,
|
||||
onRetry = null,
|
||||
onDeleteBadsha = null,
|
||||
onOpenFolder = null,
|
||||
onSwitchWorkflow = null,
|
||||
onMore = null,
|
||||
@@ -49,6 +50,7 @@ export function bindDownloadActionHandlers(root, {
|
||||
bindIdAction(selectors.pause, onPause);
|
||||
bindIdAction(selectors.resume, onResume);
|
||||
bindIdAction(selectors.retry, onRetry);
|
||||
bindIdAction(selectors.deleteBadsha, onDeleteBadsha);
|
||||
|
||||
bindActionButtons(root, selectors.openFolder, (button) => {
|
||||
const context = resolveContext(button);
|
||||
|
||||
@@ -141,11 +141,13 @@ export const renderFormatMethods = {
|
||||
const className = contextMenuAttrs
|
||||
? `mr-status mr-status-${type} mr-download-folder-context`
|
||||
: `mr-status mr-status-${type}`;
|
||||
const extraHtml = options?.extraHtml ? `<span class="mr-status-extra">${options.extraHtml}</span>` : '';
|
||||
|
||||
return `
|
||||
<div class="${className}"${contextMenuAttrs}>
|
||||
<span class="mr-status-icon">${icon}</span>
|
||||
<span>${message}</span>
|
||||
<span class="mr-status-text">${message}</span>
|
||||
${extraHtml}
|
||||
</div>
|
||||
`;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user