Classify yt-dlp failures into actionable buckets (2.3)
When a job fails, today the user sees a raw stderr line — often something like "ERROR: [youtube] dQw4w9WgXcQ: Sign in to confirm you're not a bot" that doesn't tell a non-expert what to do. New src/error_class.rs scans the failed job's log buffer for the nine well-known yt-dlp fingerprints and returns: - an ErrorClass enum (rate-limited, members-only, geo-blocked, not-found, codec-missing, disk-full, network-error, bad-cookies, other) - a one-line human-readable suggested action paired with each class Auto-populated in Job::drain when state transitions to Failed. Exposed on JobSnapshot as error_class + error_hint. Rendered in the web UI Downloads modal (red badge + hint box) and the desktop downloads panel (colored label + hint text). Other (no fingerprint matched) is filtered out of the badge so the existing raw last_line keeps doing the talking for unknown failures. Includes 10 unit tests covering each pattern + the walks-from-end priority case. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
a8b14f250a
commit
d9a7007f34
6 changed files with 365 additions and 8 deletions
|
|
@ -113,9 +113,15 @@
|
|||
#dl-btn.has-active{color:var(--accent)}
|
||||
#dl-badge{position:absolute;top:-4px;right:-4px;background:var(--accent);color:#000;font-size:10px;font-weight:700;border-radius:9px;padding:1px 5px;min-width:16px;text-align:center;line-height:1.2;display:none}
|
||||
#dl-btn.has-active #dl-badge{display:inline-block}
|
||||
.job{display:flex;align-items:center;gap:8px;padding:5px 14px;font-size:12px;border-bottom:1px solid var(--border);flex-wrap:wrap;min-width:0}
|
||||
.job-row{border-bottom:1px solid var(--border)}
|
||||
.job{display:flex;align-items:center;gap:8px;padding:5px 14px;font-size:12px;flex-wrap:wrap;min-width:0}
|
||||
.job-row .job{border-bottom:none}
|
||||
.badge{font-weight:700;min-width:48px;flex-shrink:0}
|
||||
.badge.running{color:#facc15}.badge.done{color:#4ade80}.badge.failed{color:#f87171}
|
||||
/* Error-class badge sits next to the state badge. Subtle so the actual
|
||||
hint below carries the user's attention. */
|
||||
.err-class{font-size:10px;background:#7f1d1d;color:#fecaca;border-radius:3px;padding:1px 6px;text-transform:uppercase;letter-spacing:.4px;flex-shrink:0}
|
||||
.job-hint{font-size:11px;color:#fecaca;background:rgba(127,29,29,.18);border-left:2px solid #f87171;padding:6px 14px 6px 12px;margin:0 14px 6px}
|
||||
progress{flex:1;height:5px;accent-color:var(--accent);min-width:40px}
|
||||
footer{background:var(--panel);border-top:1px solid var(--border);padding:8px 12px;display:flex;gap:8px;align-items:center;flex-shrink:0}
|
||||
footer input{flex:1;min-width:0;background:var(--bg);border:1px solid var(--border);color:var(--text);padding:5px 9px;border-radius:4px;font-size:13px}
|
||||
|
|
@ -1639,12 +1645,24 @@ function renderJobs(jobs,queued,maxConcurrent){
|
|||
const limitNote=maxConcurrent>0?`<div style="font-size:11px;color:var(--muted);padding:6px 14px">max ${maxConcurrent} concurrent</div>`:'';
|
||||
body.innerHTML=hdr+jobs.map((j,i)=>{
|
||||
const dismiss=j.state!=='running'?`<button onclick="removeJob(${i})" style="font-size:11px;padding:1px 6px">✕</button>`:'';
|
||||
return `<div class="job">
|
||||
<span class="badge ${j.state}">${j.state}</span>
|
||||
<span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(j.label)} — ${esc(j.url)}</span>
|
||||
${j.state==='running'?`<progress value="${j.progress}" max="1"></progress>`:''}
|
||||
<span class="last-line" style="font-size:11px;color:var(--muted);max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(j.last_line)}</span>
|
||||
${dismiss}
|
||||
// Failed-job hint: when the classifier matched a known pattern, show
|
||||
// the suggested action below the row in a subtle box. Unclassified
|
||||
// failures (error_class === undefined) fall back to just the raw
|
||||
// last_line we already render in the badge row.
|
||||
const errBadge=j.state==='failed'&&j.error_class
|
||||
? `<span class="err-class" title="${esc(j.error_hint||'')}">${esc(j.error_class)}</span>` : '';
|
||||
const errHint=j.state==='failed'&&j.error_hint
|
||||
? `<div class="job-hint">${esc(j.error_hint)}</div>` : '';
|
||||
return `<div class="job-row">
|
||||
<div class="job">
|
||||
<span class="badge ${j.state}">${j.state}</span>
|
||||
${errBadge}
|
||||
<span style="flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(j.label)} — ${esc(j.url)}</span>
|
||||
${j.state==='running'?`<progress value="${j.progress}" max="1"></progress>`:''}
|
||||
<span class="last-line" style="font-size:11px;color:var(--muted);max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${esc(j.last_line)}</span>
|
||||
${dismiss}
|
||||
</div>
|
||||
${errHint}
|
||||
</div>`;
|
||||
}).join('')+queuedHtml+limitNote;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue