feat(tagger): real byte progress for model download instead of a spinner
The 1.3 GB model.onnx downloaded via hf-hub's repo.get() and the ONNX runtime DLLs via a read_to_end — neither reported bytes, so the user saw only an indeterminate spinner on a multi-minute download. - tagger model files now download via repo.download_with_progress with a HubProgress adapter over hf-hub's Progress trait (throttled 200ms events carrying downloaded/total bytes) - captioner's nuget DLL download streams in 64 KB chunks, parsing content-length, and reports per-chunk byte progress - TaggerModelProgress carries downloaded_bytes/total_bytes; onboarding and Settings show a determinate bar plus 'X MB / Y MB' for the current file
This commit is contained in:
@@ -9,6 +9,12 @@ const SECTIONS: { id: SettingsSection; label: string; detail: string }[] = [
|
||||
{ id: "general", label: "General", detail: "App data and diagnostics" },
|
||||
];
|
||||
|
||||
function formatBytesShort(bytes: number): string {
|
||||
if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
||||
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(0)} MB`;
|
||||
return `${(bytes / 1024).toFixed(0)} KB`;
|
||||
}
|
||||
|
||||
function StatusPill({ children, tone }: { children: React.ReactNode; tone: "ready" | "muted" | "busy" }) {
|
||||
const className =
|
||||
tone === "ready"
|
||||
@@ -235,16 +241,24 @@ export function SettingsModal() {
|
||||
: "no folders selected";
|
||||
const thresholdDisplay = taggerThresholdDraft ?? String(taggerThreshold);
|
||||
const batchSizeDisplay = taggerBatchSizeDraft ?? String(taggerBatchSize);
|
||||
const taggerDownloadLabel = taggerModelProgress
|
||||
? `Downloading ${taggerModelProgress.completed_files}/${taggerModelProgress.total_files}`
|
||||
: taggerModelPreparing
|
||||
? "Preparing WD Tagger..."
|
||||
: taggerReady
|
||||
? "Installed"
|
||||
: "Install model";
|
||||
const taggerDownloadPercent = taggerModelProgress
|
||||
? Math.round((taggerModelProgress.completed_files / Math.max(taggerModelProgress.total_files, 1)) * 100)
|
||||
: 0;
|
||||
const taggerBytesKnown =
|
||||
taggerModelProgress?.downloaded_bytes != null &&
|
||||
taggerModelProgress.total_bytes != null &&
|
||||
taggerModelProgress.total_bytes > 0;
|
||||
const taggerDownloadLabel = taggerBytesKnown
|
||||
? `Downloading ${formatBytesShort(taggerModelProgress!.downloaded_bytes!)} / ${formatBytesShort(taggerModelProgress!.total_bytes!)}`
|
||||
: taggerModelProgress
|
||||
? `Downloading ${taggerModelProgress.completed_files}/${taggerModelProgress.total_files}`
|
||||
: taggerModelPreparing
|
||||
? "Preparing WD Tagger..."
|
||||
: taggerReady
|
||||
? "Installed"
|
||||
: "Install model";
|
||||
const taggerDownloadPercent = taggerBytesKnown
|
||||
? Math.round((taggerModelProgress!.downloaded_bytes! / taggerModelProgress!.total_bytes!) * 100)
|
||||
: taggerModelProgress
|
||||
? Math.round((taggerModelProgress.completed_files / Math.max(taggerModelProgress.total_files, 1)) * 100)
|
||||
: 0;
|
||||
|
||||
const runQueueAction = (action: "queue" | "clear") => {
|
||||
const selectedIds = taggingQueueFolderIds;
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
import { useEffect } from "react";
|
||||
import { useGalleryStore } from "../../store";
|
||||
import { FakeProgressBar } from "./fakes";
|
||||
import { TaggerModelProgress, useGalleryStore } from "../../store";
|
||||
import { FakeProgressBar, formatBytes } from "./fakes";
|
||||
|
||||
const FAKE_TAGS = ["landscape", "sunset", "outdoors", "no_humans", "ocean", "cloudy_sky"];
|
||||
|
||||
// Prefer the current file's byte fraction (the 1.3 GB model dominates); fall
|
||||
// back to the coarse step count; null renders an indeterminate bar.
|
||||
function taggerDownloadFraction(progress: TaggerModelProgress | null): number | null {
|
||||
if (!progress) return null;
|
||||
if (progress.downloaded_bytes != null && progress.total_bytes != null && progress.total_bytes > 0) {
|
||||
return progress.downloaded_bytes / progress.total_bytes;
|
||||
}
|
||||
if (progress.total_files > 0) return progress.completed_files / progress.total_files;
|
||||
return null;
|
||||
}
|
||||
|
||||
export function StepAiFeatures() {
|
||||
const taggerModelStatus = useGalleryStore((s) => s.taggerModelStatus);
|
||||
const taggerModelPreparing = useGalleryStore((s) => s.taggerModelPreparing);
|
||||
@@ -61,16 +72,15 @@ export function StepAiFeatures() {
|
||||
</div>
|
||||
{taggerModelPreparing ? (
|
||||
<div className="mt-3">
|
||||
<FakeProgressBar
|
||||
fraction={
|
||||
taggerModelProgress && taggerModelProgress.total_files > 0
|
||||
? taggerModelProgress.completed_files / taggerModelProgress.total_files
|
||||
: null
|
||||
}
|
||||
/>
|
||||
{taggerModelProgress?.current_file ? (
|
||||
<p className="mt-1.5 truncate text-[11px] text-gray-600">{taggerModelProgress.current_file}</p>
|
||||
) : null}
|
||||
<FakeProgressBar fraction={taggerDownloadFraction(taggerModelProgress)} />
|
||||
<div className="mt-1.5 flex items-center justify-between gap-3 text-[11px] text-gray-600">
|
||||
<span className="truncate">{taggerModelProgress?.current_file ?? "Preparing..."}</span>
|
||||
{taggerModelProgress?.downloaded_bytes != null && taggerModelProgress.total_bytes != null ? (
|
||||
<span className="shrink-0 tabular-nums">
|
||||
{formatBytes(taggerModelProgress.downloaded_bytes)} / {formatBytes(taggerModelProgress.total_bytes)}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
{!taggerModelPreparing && taggerModelError ? (
|
||||
|
||||
@@ -112,6 +112,8 @@ export interface TaggerModelProgress {
|
||||
total_files: number;
|
||||
completed_files: number;
|
||||
current_file: string | null;
|
||||
downloaded_bytes: number | null;
|
||||
total_bytes: number | null;
|
||||
done: boolean;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user