feat(onboarding): guided first-run tour with background FFmpeg provisioning
Phase 5 of the 0.1.0 release prep: - FFmpeg no longer blocks startup (or crashes the app offline): provisioning runs in a background thread emitting throttled ffmpeg-progress events, with installed/downloading/failed state queryable via get_ffmpeg_status and a retry command - video jobs are invisible to claiming and tier-priority checks until FFmpeg is ready, so they wait as pending without failing — and without starving image embeddings/tagging (include_videos gating in db.rs) - 7-step show-don't-tell onboarding wizard: welcome + live FFmpeg progress, real first-folder picker, faked animating pipeline bar, placeholder gallery tiles, cycling search-syntax demo, views overview, and an AI features step with a real opt-in tagger download - skippable at every point (Escape included), persisted via settings/onboarding_completed.txt, re-runnable from Settings > General, which also gains an FFmpeg status/retry row
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import { useEffect } from "react";
|
||||
import { useGalleryStore } from "../../store";
|
||||
import { FakeProgressBar } from "./fakes";
|
||||
|
||||
const FAKE_TAGS = ["landscape", "sunset", "outdoors", "no_humans", "ocean", "cloudy_sky"];
|
||||
|
||||
export function StepAiFeatures() {
|
||||
const taggerModelStatus = useGalleryStore((s) => s.taggerModelStatus);
|
||||
const taggerModelPreparing = useGalleryStore((s) => s.taggerModelPreparing);
|
||||
const taggerModelProgress = useGalleryStore((s) => s.taggerModelProgress);
|
||||
const prepareTaggerModel = useGalleryStore((s) => s.prepareTaggerModel);
|
||||
const loadTaggerModelStatus = useGalleryStore((s) => s.loadTaggerModelStatus);
|
||||
|
||||
useEffect(() => {
|
||||
void loadTaggerModelStatus();
|
||||
}, [loadTaggerModelStatus]);
|
||||
|
||||
const taggerReady = taggerModelStatus?.ready ?? false;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className="text-sm leading-relaxed text-gray-300">
|
||||
Two optional AI features run entirely on this machine — nothing is sent anywhere. Both are
|
||||
one-time downloads you can also start later from Settings.
|
||||
</p>
|
||||
|
||||
<h4 className="mt-6 text-[12px] font-semibold uppercase tracking-[0.08em] text-gray-400">AI tagging</h4>
|
||||
<div className="mt-1 divide-y divide-white/[0.05]">
|
||||
<div className="py-4">
|
||||
<div className="flex items-start justify-between gap-6">
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm text-white">Automatic tags for every image</p>
|
||||
<p className="mt-1 text-xs leading-relaxed text-gray-500">
|
||||
The WD tagger model (~1.3 GB download) labels images so you can search with{" "}
|
||||
<code className="rounded bg-white/[0.07] px-1 py-0.5 text-[11px] text-gray-200">/t</code> — tags look like:
|
||||
</p>
|
||||
<span className="mt-2 flex flex-wrap gap-1.5">
|
||||
{FAKE_TAGS.map((tag) => (
|
||||
<span key={tag} className="rounded-md border border-white/10 bg-white/[0.04] px-2 py-0.5 text-[11px] text-gray-400">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
{taggerReady ? (
|
||||
<span className="inline-flex rounded-md border border-emerald-400/25 bg-emerald-500/10 px-2 py-0.5 text-[11px] font-medium text-emerald-300">
|
||||
Installed
|
||||
</span>
|
||||
) : (
|
||||
<button
|
||||
className="rounded-md border border-white/10 bg-white/[0.055] px-3 py-1.5 text-xs text-gray-300 transition-colors hover:bg-white/10 hover:text-white disabled:cursor-not-allowed disabled:opacity-45"
|
||||
onClick={() => void prepareTaggerModel()}
|
||||
disabled={taggerModelPreparing}
|
||||
>
|
||||
{taggerModelPreparing ? "Downloading..." : "Download now"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</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}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 className="mt-6 text-[12px] font-semibold uppercase tracking-[0.08em] text-gray-400">Semantic search & similarity</h4>
|
||||
<div className="mt-1 divide-y divide-white/[0.05]">
|
||||
<div className="py-4">
|
||||
<p className="text-sm text-white">Search by meaning, find look-alikes</p>
|
||||
<p className="mt-1 text-xs leading-relaxed text-gray-500">
|
||||
Powers <code className="rounded bg-white/[0.07] px-1 py-0.5 text-[11px] text-gray-200">/s</code> search and
|
||||
"find similar". The CLIP model (~330 MB) downloads automatically the first time embeddings are
|
||||
generated — no action needed; you'll see it in the background-tasks bar.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="mt-5 text-xs leading-relaxed text-gray-500">
|
||||
That's the tour. Add folders from the sidebar, and revisit any of this from Settings — including
|
||||
re-running this tour.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user