disclosure-bureau/web/components/batch-progress-banner.tsx

92 lines
3.1 KiB
TypeScript

/**
* BatchProgressBanner — slim banner showing live batch rebuild progress.
* Renders on the homepage only when a rebuild is actively in progress
* (completed < queue_total).
*/
"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
interface BatchPayload {
status: string;
queue_total: number;
completed: number;
successes: number;
failures: number;
progress_pct: number;
quota_state?: "ok" | "throttled";
quota_resume_eta_minutes?: number | null;
}
export function BatchProgressBanner() {
const [data, setData] = useState<BatchPayload | null>(null);
useEffect(() => {
let alive = true;
async function tick() {
try {
const r = await fetch("/api/admin/batch");
if (!r.ok) return;
const j = (await r.json()) as BatchPayload;
if (alive && j.status === "ok") setData(j);
} catch {
/* ignore */
}
}
tick();
const i = setInterval(tick, 60_000);
return () => {
alive = false;
clearInterval(i);
};
}, []);
if (!data || data.completed >= data.queue_total) return null;
const throttled = data.quota_state === "throttled";
return (
<Link
href="/admin/batch"
className={`block mb-6 px-4 py-3 border rounded transition ${
throttled
? "border-[rgba(255,165,0,0.40)] bg-[rgba(255,165,0,0.08)] hover:bg-[rgba(255,165,0,0.14)]"
: "border-[rgba(0,255,156,0.30)] bg-[rgba(0,255,156,0.05)] hover:bg-[rgba(0,255,156,0.10)]"
}`}
>
<div className="flex items-center gap-3">
<span className="text-xl">{throttled ? "💸" : "⚙️"}</span>
<div className="flex-1">
<div className="flex items-center gap-2 font-mono text-xs mb-1.5 flex-wrap">
<span className={`font-bold ${throttled ? "text-[#ffa500]" : "text-[#00ff9c]"}`}>
{throttled ? "Anthropic quota throttled" : "Chunks rebuild em progresso"}
</span>
<span className="text-[#8896aa]">
{data.completed}/{data.queue_total} docs · {data.progress_pct}%
</span>
{throttled && data.quota_resume_eta_minutes != null && (
<span className="text-[#ffa500]">
· reset em{" "}
{data.quota_resume_eta_minutes >= 60
? `${(data.quota_resume_eta_minutes / 60).toFixed(1)}h`
: `${data.quota_resume_eta_minutes}min`}
</span>
)}
{data.failures > 0 && (
<span className="text-[#ff6b6b]">· {data.failures} falhas</span>
)}
<span className="ml-auto text-[#7fdbff]">ver detalhes </span>
</div>
<div className="w-full h-1.5 bg-[#0a121e] border border-[rgba(0,255,156,0.20)] rounded overflow-hidden">
<div
className={`h-full transition-all ${
throttled
? "bg-gradient-to-r from-[#ffa500] to-[#ff6b6b]"
: "bg-gradient-to-r from-[#00ff9c] to-[#7fdbff]"
}`}
style={{ width: `${data.progress_pct}%` }}
/>
</div>
</div>
</div>
</Link>
);
}