← back to Homesonspec
apps/admin/src/app/engine/EngineLive.tsx
80 lines
"use client";
import { useEffect, useRef, useState } from "react";
type Resp = { ts: string; ok: boolean; error?: string; lines: string[] };
function lineClass(l: string): string {
if (/SWEEP/i.test(l)) return "text-cyan-300 font-semibold";
if (/"errors":\s*\[\s*\]/.test(l)) return "text-neutral-600"; // empty errors array = NO errors, don't flag red
if (/Prisma|throw|ECONN|blocked|disallow|\b403\b|\b429\b|"errors":\s*\[[^\]]|\bError\b/.test(l)) return "text-red-400";
if (/"published":\s*(?!0\b)\d+/.test(l)) return "text-emerald-300";
if (/fanning out|enrich|sleeping/i.test(l)) return "text-amber-300";
if (/^\s*(drh|plt|len|dw|kb|tri|tol)\b/i.test(l.replace(/^[0-9T:\-]+:?\s*/, ""))) return "text-neutral-300";
return "text-neutral-400";
}
export default function EngineLive() {
const [r, setR] = useState<Resp | null>(null);
const [err, setErr] = useState(false);
const [live, setLive] = useState(true);
const boxRef = useRef<HTMLDivElement>(null);
const stick = useRef(true);
useEffect(() => {
let alive = true;
const load = async () => {
try {
const res = await fetch("/api/engine-log", { cache: "no-store" });
const j: Resp = await res.json();
if (!alive) return;
setR(j); setErr(!j.ok);
} catch { if (alive) setErr(true); }
};
load();
const id = setInterval(() => live && load(), 2500);
return () => { alive = false; clearInterval(id); };
}, [live]);
// auto-scroll to bottom unless the user scrolled up
useEffect(() => {
const el = boxRef.current;
if (el && stick.current) el.scrollTop = el.scrollHeight;
}, [r]);
const onScroll = () => {
const el = boxRef.current;
if (el) stick.current = el.scrollHeight - el.scrollTop - el.clientHeight < 40;
};
const lines = r?.lines ?? [];
const newest = lines[lines.length - 1] ?? "";
return (
<div className="mt-4">
<div className="mb-2 flex items-center gap-2 text-sm">
<span className={`inline-block h-2.5 w-2.5 rounded-full ${err ? "bg-red-500" : "bg-emerald-500 animate-pulse"}`} />
<span className={err ? "text-red-700" : "text-emerald-700"}>{err ? "engine log unavailable" : "engine live"}</span>
<span className="text-neutral-400">· {lines.length} lines · updated {r ? new Date(r.ts).toLocaleTimeString() : "…"}</span>
<button
onClick={() => setLive((v) => !v)}
className={`ml-auto rounded-md border px-2 py-0.5 text-xs ${live ? "border-emerald-300 text-emerald-700" : "border-neutral-300 text-neutral-500"}`}
>
{live ? "⏸ pause" : "▶ resume"}
</button>
</div>
<div
ref={boxRef}
onScroll={onScroll}
className="h-[560px] overflow-auto rounded-xl border border-neutral-800 bg-neutral-950 p-3 font-mono text-xs leading-relaxed shadow-inner"
>
{err && <div className="text-red-400">{r?.error ?? "could not read the engine log"}</div>}
{lines.map((l, i) => (
<div key={i} className={`whitespace-pre-wrap break-all ${lineClass(l)}`}>{l}</div>
))}
</div>
<p className="mt-2 text-xs text-neutral-400 truncate">newest: <span className="font-mono">{newest}</span></p>
</div>
);
}