Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,6 @@ soak-results/

# LSP originality-check reference cache (scripts/check-lsp-originality.sh)
.lsp-refs/

# Local npm cache
graph-ui/.npm-cache-local/
46 changes: 41 additions & 5 deletions graph-ui/src/components/StatsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -274,19 +274,35 @@ function CreateIndexModal({ onClose, onCreated }: { onClose: () => void; onCreat
/* ── Index Progress ─────────────────────────────────────── */

function IndexProgress({ onDone }: { onDone: () => void }) {
const [jobs, setJobs] = useState<{ slot: number; status: string; path: string }[]>([]);
const [jobs, setJobs] = useState<{ slot: number; status: string; path: string; error?: string }[]>([]);
const [hasActive, setHasActive] = useState(true);

useEffect(() => {
if (!hasActive) return;
const poll = setInterval(async () => {
try {
const data = await (await fetch("/api/index-status")).json();
setJobs(data);
if (data.length > 0 && data.every((j: { status: string }) => j.status !== "indexing")) onDone();
} catch { /* */ }
const stillIndexing = data.some((j: { status: string }) => j.status === "indexing");
if (!stillIndexing) {
setHasActive(false);
const hasErrors = data.some((j: { status: string }) => j.status === "error");
if (!hasErrors) {
onDone();
}
}
} catch (error) {
console.error("[IndexProgress] Poll failed:", error);
}
}, 2000);
return () => clearInterval(poll);
}, [onDone]);
}, [onDone, hasActive]);

const active = jobs.filter((j) => j.status === "indexing");
if (active.length === 0) return null;
const errors = jobs.filter((j) => j.status === "error");

if (active.length === 0 && errors.length === 0) return null;

return (
<div className="rounded-xl border border-primary/20 bg-primary/5 p-4 mb-6">
{active.map((j) => (
Expand All @@ -298,6 +314,26 @@ function IndexProgress({ onDone }: { onDone: () => void }) {
</div>
</div>
))}
{errors.map((j) => (
<div key={j.slot} className="flex items-start gap-3 mt-3 first:mt-0 p-3 rounded-lg border border-destructive/20 bg-destructive/5 text-destructive">
<span className="text-[14px]">⚠️</span>
<div className="flex-1 min-w-0">
<p className="text-[12px] font-semibold">Indexing Failed</p>
<p className="text-[11px] font-mono truncate">{j.path}</p>
{j.error && <p className="text-[10px] opacity-75 mt-1 font-mono">{j.error}</p>}
</div>
</div>
))}
{errors.length > 0 && (
<div className="flex justify-end mt-3">
<button
onClick={onDone}
className="px-3 py-1 rounded bg-destructive/10 hover:bg-destructive/20 text-destructive text-[11px] font-medium transition-all"
>
Dismiss
</button>
</div>
)}
</div>
);
}
Expand Down
Loading