import { useEffect, useState } from "react"; import { FakeProgressBar, FakeStageTag, ReplayButton } from "./fakes"; const STAGES = ["Thumbnails", "Metadata", "Embeddings", "Tags"] as const; const STAGE_TOTAL = 128; const TICK_MS = 80; const STEP_PER_TICK = 8; /// A one-shot fake of the background-tasks bar: each stage drains in order, /// then it stops at "all done" with a replay control. export function StepPipeline() { const [stageIndex, setStageIndex] = useState(0); const [filled, setFilled] = useState(0); const finished = stageIndex >= STAGES.length; useEffect(() => { if (finished) return; const timer = setTimeout(() => { // Read from the closure and call setters directly โ€” nesting one setter // inside another's updater double-advances under React StrictMode. if (filled + STEP_PER_TICK < STAGE_TOTAL) { setFilled(filled + STEP_PER_TICK); } else { setStageIndex(stageIndex + 1); setFilled(0); } }, TICK_MS); return () => clearTimeout(timer); }, [filled, stageIndex, finished]); const replay = () => { setStageIndex(0); setFilled(0); }; const remaining = STAGE_TOTAL - filled; return (

After indexing, Phokus works through a strict pipeline: thumbnails first, then video metadata, then visual embeddings (for similarity and semantic search), then AI tags. One stage at a time, so each runs at full speed.

While it's working you'll see this bar above the gallery โ€” here's a preview:

{/* Fake BackgroundTasks slim bar */}
{!finished ? ( ) : null} Holiday Photos
{STAGES.map((stage, i) => ( ))}

It's all interruptible. Close the app whenever you like โ€” the queue picks up where it left off next launch.

Per-folder control. Right-click a folder in the sidebar to pause its background work, and click the bar itself to expand a detailed per-folder view.

{finished ? : null}
); }