93 lines
3.1 KiB
TypeScript
93 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>
|
||
|
|
);
|
||
|
|
}
|