feat(downloads): Unverified review filter + visible retry progress
- '⚠ Unverified' filter pill on the Downloads page lists completed downloads
whose verification status is unverified/force_imported (review queue)
- the quarantine-retry engine's attempt counter (already tracked internally)
is now surfaced: task.retry_info ('2/5') shows next to Searching/Downloading
in the modal and as 🔁 on the Downloads page rows, with the trigger in the
tooltip
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
2a11dc961a
commit
8dcad2be4e
6 changed files with 26 additions and 3 deletions
|
|
@ -250,6 +250,11 @@ def requeue_quarantined_task_for_retry(task_id, batch_id, trigger):
|
|||
task.pop('quarantine_entry_id', None)
|
||||
task['status'] = 'searching'
|
||||
task['status_change_time'] = time.time()
|
||||
# Surface the retry progress to the UI ("attempt 2/5" next to the
|
||||
# status while the task goes around again). Cleared implicitly on
|
||||
# completion (UI only renders it for active/queued states).
|
||||
task['retry_info'] = attempt_desc
|
||||
task['retry_trigger'] = trigger
|
||||
|
||||
logger.info(
|
||||
f"[Retry:{trigger}] Re-queuing task {task_id} for next-best candidate "
|
||||
|
|
|
|||
|
|
@ -343,6 +343,9 @@ def build_batch_status_data(batch_id: str, batch: dict, live_transfers_lookup: d
|
|||
# 'verified' / 'unverified' / 'force_imported' — set by the
|
||||
# import pipeline once post-processing finishes.
|
||||
'verification_status': task.get('verification_status'),
|
||||
# "2/5" while the quarantine-retry engine walks candidates.
|
||||
'retry_info': task.get('retry_info'),
|
||||
'retry_trigger': task.get('retry_trigger'),
|
||||
}
|
||||
_ti = task.get('track_info') if isinstance(task.get('track_info'), dict) else {}
|
||||
task_filename = task.get('filename') or _ti.get('filename')
|
||||
|
|
@ -742,6 +745,8 @@ def build_unified_downloads_response(limit: int, deps: StatusDeps) -> dict:
|
|||
'progress': progress,
|
||||
'error': task.get('error_message'),
|
||||
'verification_status': task.get('verification_status'),
|
||||
'retry_info': task.get('retry_info'),
|
||||
'retry_trigger': task.get('retry_trigger'),
|
||||
'batch_id': batch_id,
|
||||
'batch_name': batch.get('playlist_name') or batch.get('album_name') or '',
|
||||
'batch_source': batch.get('source_page') or batch.get('initiated_from') or '',
|
||||
|
|
|
|||
|
|
@ -2390,6 +2390,7 @@
|
|||
<button class="adl-pill" data-filter="queued" onclick="adlSetFilter('queued')">Queued</button>
|
||||
<button class="adl-pill" data-filter="completed" onclick="adlSetFilter('completed')">Completed</button>
|
||||
<button class="adl-pill" data-filter="failed" onclick="adlSetFilter('failed')">Failed</button>
|
||||
<button class="adl-pill" data-filter="unverified" onclick="adlSetFilter('unverified')" title="Completed downloads that were imported without hard AcoustID confirmation (unverified) or force-imported after repeated mismatches — review these.">⚠ Unverified</button>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:10px;">
|
||||
<span class="adl-count" id="adl-count"></span>
|
||||
|
|
|
|||
|
|
@ -3665,8 +3665,16 @@ function processModalStatusUpdate(playlistId, data) {
|
|||
} else {
|
||||
switch (task.status) {
|
||||
case 'pending': statusText = '⏸️ Pending'; break;
|
||||
case 'searching': statusText = '🔍 Searching...'; break;
|
||||
case 'downloading': statusText = `⏬ Downloading... ${Math.round(task.progress || 0)}%`; break;
|
||||
case 'searching':
|
||||
statusText = '🔍 Searching...';
|
||||
// Quarantine-retry engine: show which attempt we're on
|
||||
// ("retry 2/5") while it walks the next-best candidates.
|
||||
if (task.retry_info) statusText += ` 🔁 retry ${task.retry_info}`;
|
||||
break;
|
||||
case 'downloading':
|
||||
statusText = `⏬ Downloading... ${Math.round(task.progress || 0)}%`;
|
||||
if (task.retry_info) statusText += ` 🔁 retry ${task.retry_info}`;
|
||||
break;
|
||||
case 'post_processing': statusText = '⌛ Processing...'; break;
|
||||
case 'completed': {
|
||||
statusText = '✅ Completed';
|
||||
|
|
|
|||
|
|
@ -2384,6 +2384,9 @@ function _adlRender() {
|
|||
if (_adlFilter === 'active') filtered = filtered.filter(d => activeStatuses.includes(d.status));
|
||||
else if (_adlFilter === 'queued') filtered = filtered.filter(d => queuedStatuses.includes(d.status));
|
||||
else if (_adlFilter === 'completed') filtered = filtered.filter(d => completedStatuses.includes(d.status));
|
||||
else if (_adlFilter === 'unverified') filtered = filtered.filter(d =>
|
||||
completedStatuses.includes(d.status) &&
|
||||
(d.verification_status === 'unverified' || d.verification_status === 'force_imported'));
|
||||
else if (_adlFilter === 'failed') filtered = filtered.filter(d => failedStatuses.includes(d.status));
|
||||
|
||||
const completedN = _adlData.filter(d =>
|
||||
|
|
@ -2509,7 +2512,7 @@ function _adlRender() {
|
|||
</div>
|
||||
<div class="adl-row-status ${statusClass}">
|
||||
<span class="adl-status-dot ${statusClass}"></span>
|
||||
${statusLabel}${_adlVerifBadge(dl)}
|
||||
${statusLabel}${_adlVerifBadge(dl)}${dl.retry_info && (statusClass === 'active' || statusClass === 'queued') ? ` <span class="adl-retry-info" title="Retry engine: trying the next-best candidate (attempt ${_adlEsc(String(dl.retry_info))}${dl.retry_trigger ? ', triggered by ' + _adlEsc(dl.retry_trigger) : ''})">🔁 ${_adlEsc(String(dl.retry_info))}</span>` : ''}
|
||||
</div>
|
||||
${cancelBtnHtml}
|
||||
</div>`;
|
||||
|
|
|
|||
|
|
@ -67898,3 +67898,4 @@ body.em-scroll-lock { overflow: hidden; }
|
|||
.verif-badge.verif-ok { color: #2ecc71; background: rgba(46,204,113,0.12); }
|
||||
.verif-badge.verif-unverified { color: #f1c40f; background: rgba(241,196,15,0.14); }
|
||||
.verif-badge.verif-force { color: #e67e22; background: rgba(230,126,34,0.16); }
|
||||
.adl-retry-info { margin-left: 6px; font-size: 11px; color: #e67e22; cursor: help; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue