UI kit
IngestStatus
IngestionReact · Next.jsHits your /health endpoint and shows whether the service is online and whether the index has been built, with optional polling. A quick way to surface backend readiness in your app.
<IngestStatus />Live preview
Checking…
Installation
No npm dependencies — just React + Tailwind CSS. There are two ways to use it:
- React — copy the source below; it calls your pipeline directly from the browser.
- Next.js — switch to the Next.js tab in the source below; it keeps your pipeline URL + key server-side behind a Route Handler (adds a couple of
.env.localvars).
Usage
usage.tsx
import { IngestStatus } from "@/components/IngestStatus";
export default function Example() {
return (
<IngestStatus endpoint="https://api.example.com/health" pollMs={5000} />
);
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
endpointrequired | string | — | Health URL. GET → { status, pipeline_loaded }. |
pollMs | number | 0 | Poll interval in ms (0 = fetch once). |
fetcher | typeof fetch | — | Override the transport. Defaults to window.fetch. |
Component source copy-paste ready
IngestStatus.tsx
"use client";
import { useEffect, useState } from "react";
interface HealthData {
status?: string;
pipeline_loaded?: boolean;
[k: string]: unknown;
}
interface IngestStatusProps {
/** Health endpoint. GET → { status, pipeline_loaded, … }. */
endpoint: string;
/** Poll interval in ms (0 = fetch once). Default: 0. */
pollMs?: number;
/** Override the transport (e.g. for testing). Defaults to window.fetch. */
fetcher?: typeof fetch;
className?: string;
}
/**
* IngestStatus — a live status pill for your pipeline. Hits the /health
* endpoint and shows whether the service is online and whether the index has
* been built, with optional polling.
*/
export function IngestStatus({ endpoint, pollMs = 0, fetcher, className = "" }: IngestStatusProps) {
const [state, setState] = useState<"loading" | "ok" | "down">("loading");
const [data, setData] = useState<HealthData | null>(null);
useEffect(() => {
let alive = true;
const f = fetcher ?? fetch;
async function check() {
try {
const res = await f(endpoint);
const json = (await res.json()) as HealthData;
if (!alive) return;
setData(json);
setState(res.ok && json.status !== "error" ? "ok" : "down");
} catch {
if (alive) setState("down");
}
}
check();
const id = pollMs > 0 ? setInterval(check, pollMs) : undefined;
return () => {
alive = false;
if (id) clearInterval(id);
};
}, [endpoint, pollMs, fetcher]);
const dot = state === "ok" ? "#10b981" : state === "down" ? "#ef4444" : "#f59e0b";
const label = state === "ok" ? "Pipeline online" : state === "down" ? "Pipeline unreachable" : "Checking…";
return (
<div className={`inline-flex items-center gap-2.5 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm dark:border-zinc-800 dark:bg-zinc-900 ${className}`}>
<span className="relative flex h-2.5 w-2.5">
{state === "loading" && (
<span className="absolute inline-flex h-full w-full animate-ping rounded-full opacity-60" style={{ background: dot }} />
)}
<span className="relative inline-flex h-2.5 w-2.5 rounded-full" style={{ background: dot }} />
</span>
<span className="font-medium text-zinc-700 dark:text-zinc-200">{label}</span>
{state === "ok" && (
<span className="text-xs text-zinc-400">{data?.pipeline_loaded ? "index ready" : "awaiting ingest"}</span>
)}
</div>
);
}
1 · Add to .env.local
RAG_ENDPOINT=https://your-pipeline.onrender.com/query RAG_API_KEY=
RAG_ENDPOINT— Health is derived as /health from this, or set RAG_HEALTH_ENDPOINTRAG_API_KEY— Optional — only if your serve.py sets API_KEY
The status check is proxied so the pipeline URL stays server-side.
2 · Component
components/IngestStatus.tsx
"use client";
import { useEffect, useState } from "react";
interface HealthData {
status?: string;
pipeline_loaded?: boolean;
[k: string]: unknown;
}
interface IngestStatusProps {
/** Local route handler that proxies /health. Default: "/api/health". */
route?: string;
/** Poll interval in ms (0 = fetch once). Default: 0. */
pollMs?: number;
className?: string;
}
/**
* IngestStatus (Next.js) — a live status pill that polls a local Route Handler
* (app/api/health/route.ts), keeping the pipeline URL server-side.
*/
export function IngestStatus({ route = "/api/health", pollMs = 0, className = "" }: IngestStatusProps) {
const [state, setState] = useState<"loading" | "ok" | "down">("loading");
const [data, setData] = useState<HealthData | null>(null);
useEffect(() => {
let alive = true;
async function check() {
try {
const res = await fetch(route, { cache: "no-store" });
const json = (await res.json()) as HealthData;
if (!alive) return;
setData(json);
setState(res.ok && json.status !== "error" ? "ok" : "down");
} catch {
if (alive) setState("down");
}
}
check();
const id = pollMs > 0 ? setInterval(check, pollMs) : undefined;
return () => {
alive = false;
if (id) clearInterval(id);
};
}, [route, pollMs]);
const dot = state === "ok" ? "#10b981" : state === "down" ? "#ef4444" : "#f59e0b";
const label = state === "ok" ? "Pipeline online" : state === "down" ? "Pipeline unreachable" : "Checking…";
return (
<div className={`inline-flex items-center gap-2.5 rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm dark:border-zinc-800 dark:bg-zinc-900 ${className}`}>
<span className="relative flex h-2.5 w-2.5">
{state === "loading" && <span className="absolute inline-flex h-full w-full animate-ping rounded-full opacity-60" style={{ background: dot }} />}
<span className="relative inline-flex h-2.5 w-2.5 rounded-full" style={{ background: dot }} />
</span>
<span className="font-medium text-zinc-700 dark:text-zinc-200">{label}</span>
{state === "ok" && <span className="text-xs text-zinc-400">{data?.pipeline_loaded ? "index ready" : "awaiting ingest"}</span>}
</div>
);
}
3 · Route handler
app/api/health/route.ts
// app/api/health/route.ts
//
// Proxies the pipeline's /health check so the endpoint stays server-side.
// Reads RAG_HEALTH_ENDPOINT, or derives /health from RAG_ENDPOINT.
export const dynamic = "force-dynamic";
export async function GET() {
const endpoint =
process.env.RAG_HEALTH_ENDPOINT ??
(process.env.RAG_ENDPOINT ? process.env.RAG_ENDPOINT.replace(/\/query\/?$/, "/health") : undefined);
if (!endpoint) {
return Response.json({ status: "error", pipeline_loaded: false }, { status: 500 });
}
try {
const upstream = await fetch(endpoint, {
cache: "no-store",
headers: { ...(process.env.RAG_API_KEY ? { "X-API-Key": process.env.RAG_API_KEY } : {}) },
});
return Response.json(await upstream.json(), { status: upstream.status });
} catch {
return Response.json({ status: "error", pipeline_loaded: false }, { status: 502 });
}
}