download job: collapse to one phase (no metadata prefetch)
The job's two phases were a misread — the "count" phase is the client-side
playlist expansion (for the inline-vs-job decision, already done in the panel).
The job itself is just one download request per item.
Dropped the in-job metadata pass entirely:
- reclip-client: reclipDownloadOne no longer prefetches /api/info for a title —
ReClip names the file from the video title itself, so it's a single request
per item.
- execute-download: one phase — expand the playlist, then /api/download each url,
skip failures. Progress is a single { done, failed, total, current } counter
(no meta/dl split); ~2× faster and downloads start right after expansion.
- UI (DownloadJobDetail + panel JobView): one "Downloaded" bar instead of two.
Verified: every item is attempted directly (no /api/info gate), skip-on-error
counts correct.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,11 +7,11 @@ import { toast } from 'sonner';
|
||||
// Detail view for a `download` job — a compact two-phase progress readout (metadata → download), polled
|
||||
// from the job's persisted progress (the executor emits counter snapshots, not per-item events).
|
||||
|
||||
type Counts = { done: number; failed: number; total: number };
|
||||
type DownloadProgress = {
|
||||
phase: 'expanding' | 'metadata' | 'download' | 'done';
|
||||
meta: Counts;
|
||||
dl: Counts;
|
||||
phase: 'expanding' | 'download' | 'done';
|
||||
done: number;
|
||||
failed: number;
|
||||
total: number;
|
||||
current?: string;
|
||||
};
|
||||
type DownloadJob = {
|
||||
@@ -54,46 +54,24 @@ const statusBadge = (status: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
// A labelled progress bar: fill = processed/total; the caption states saved vs skipped/failed.
|
||||
const PhaseBar = ({
|
||||
label,
|
||||
c,
|
||||
active,
|
||||
savedLabel,
|
||||
failedLabel,
|
||||
}: {
|
||||
label: string;
|
||||
c: Counts;
|
||||
active: boolean;
|
||||
savedLabel: string;
|
||||
failedLabel: string;
|
||||
}) => {
|
||||
const processed = c.done + c.failed;
|
||||
const pct = c.total ? (processed / c.total) * 100 : 0;
|
||||
// The download progress bar: fill = processed/total; caption states saved vs skipped.
|
||||
const DownloadBar = ({ p }: { p: DownloadProgress }) => {
|
||||
const processed = p.done + p.failed;
|
||||
const pct = p.total ? (processed / p.total) * 100 : 0;
|
||||
return (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<div className="flex items-center justify-between text-xs">
|
||||
<span
|
||||
className={`font-medium ${active ? 'text-duck-dark dark:text-foreground' : 'text-duck-dark/50 dark:text-foreground/50'}`}
|
||||
>
|
||||
{label}
|
||||
</span>
|
||||
<span className="font-medium text-duck-dark dark:text-foreground">Downloaded</span>
|
||||
<span className="tabular-nums text-duck-dark/50 dark:text-foreground/50">
|
||||
{processed}/{c.total || '—'}
|
||||
{processed}/{p.total || '—'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="h-2 overflow-hidden rounded-full bg-duck-dark/10 dark:bg-foreground/10">
|
||||
<div className="h-2.5 overflow-hidden rounded-full bg-duck-dark/10 dark:bg-foreground/10">
|
||||
<div className="h-full rounded-full bg-duck-teal transition-all duration-300" style={{ width: `${pct}%` }} />
|
||||
</div>
|
||||
<div className="flex gap-3 text-[11px] text-duck-dark/50 dark:text-foreground/50">
|
||||
<span className="text-duck-teal">
|
||||
{c.done} {savedLabel}
|
||||
</span>
|
||||
{c.failed > 0 && (
|
||||
<span className="text-red-500/80">
|
||||
{c.failed} {failedLabel}
|
||||
</span>
|
||||
)}
|
||||
<span className="text-duck-teal">{p.done} saved</span>
|
||||
{p.failed > 0 && <span className="text-red-500/80">{p.failed} skipped</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -161,13 +139,7 @@ export const DownloadJobDetail = () => {
|
||||
const p = job.progress;
|
||||
const isAudio = job.inputs?.format !== 'video';
|
||||
const phaseLabel =
|
||||
!p || p.phase === 'expanding'
|
||||
? 'Preparing…'
|
||||
: p.phase === 'metadata'
|
||||
? 'Fetching metadata'
|
||||
: p.phase === 'download'
|
||||
? 'Downloading'
|
||||
: 'Done';
|
||||
!p || p.phase === 'expanding' ? 'Expanding playlist…' : p.phase === 'download' ? 'Downloading' : 'Done';
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col min-h-0">
|
||||
@@ -205,20 +177,7 @@ export const DownloadJobDetail = () => {
|
||||
<div className="flex-1 min-h-0 overflow-y-auto p-5">
|
||||
<div className="mx-auto flex max-w-md flex-col gap-5">
|
||||
<div className="text-sm text-duck-dark/60 dark:text-foreground/60">{phaseLabel}</div>
|
||||
<PhaseBar
|
||||
label="Metadata"
|
||||
c={p?.meta ?? { done: 0, failed: 0, total: 0 }}
|
||||
active={p?.phase === 'metadata'}
|
||||
savedLabel="found"
|
||||
failedLabel="skipped"
|
||||
/>
|
||||
<PhaseBar
|
||||
label="Download"
|
||||
c={p?.dl ?? { done: 0, failed: 0, total: 0 }}
|
||||
active={p?.phase === 'download'}
|
||||
savedLabel="saved"
|
||||
failedLabel="failed"
|
||||
/>
|
||||
<DownloadBar p={p ?? { phase: 'expanding', done: 0, failed: 0, total: 0 }} />
|
||||
{running && p?.phase === 'download' && p.current && (
|
||||
<div className="truncate text-xs text-duck-dark/50 dark:text-foreground/50" title={p.current}>
|
||||
<Loader2 className="mr-1.5 inline h-3 w-3 animate-spin" />
|
||||
|
||||
Reference in New Issue
Block a user