feat: add trash button and backend endpoint to delete corrupted .badsha downloads

This commit is contained in:
Dariusz L
2026-11-01 16:49:35 +01:00
parent d0d706f1cf
commit 3ece5d7bbf
12 changed files with 263 additions and 2 deletions
+14
View File
@@ -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);
+5
View File
@@ -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);
+3 -1
View File
@@ -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>
`;
},